--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Automatic derivable implementation of standard basic methods.
+#
+# This module introduce `Derivable` as the main interface to implement (or auto-implement) and
+# provides additional mixin-interfaces with specific default behavior of standard basic methods based
+# on the services of this interface.
+#
+# The name *deriving* is inspired from the deriving mechanism of Haskell.
+#
+# This module also introduce a new annotation `auto_derive`. See `Derivable` for details.
+module deriving is
+ new_annotation auto_derive
+end
+
+# Interface of objects that expose some kind of internal representation in a very unreliable way.
+#
+# The point of this interface is to allow objects to give a basic representation of
+# themselves within a simple key-value dictionary.
+# The specific semantic of each key and value is let unspecified.
+#
+# Moreover the class annotation `auto_derive` will automatically implements the
+# interface with the attributes locally defined in the class.
+#
+# ~~~
+# class A
+# auto_derive
+# var an_int: Int
+# var a_string: String
+# end
+#
+# var a = new A(5, "five")
+# var map = a.derive_to_map
+# assert map.length == 2
+# assert map["an_int"] == 5
+# assert map["a_string"] == "five"
+# ~~~
+interface Derivable
+ # Returns a map that loosely represents the object `self`.
+ #
+ # Warning: by default the method returns an empty Map.
+ # It is done this way so that subclasses can just call `super` and add their own attributes.
+ #
+ # Forgetting to redefine `derive_to_map` will broke the expectation of the user of the class
+ # Since an empty map is not POLA.
+ #
+ # Note that the semantic of keys and values is let unspecified.
+ # Moreover, there is no specification nor mechanism to avoid key collision.
+ fun derive_to_map: Map[String, nullable Object]
+ do
+ return new HashMap[String, nullable Object]
+ end
+end
+
+# Implementation of `to_s` for `Derivable` objects.
+#
+# The implementation uses `to_s` for each value of `attributes_to_map`.
+#
+# ~~~
+# class A
+# auto_derive
+# super DeriveToS
+# var an_int: Int
+# var a_string: String
+# end
+#
+# var a = new A(5, "five")
+# assert a.to_s == "an_int:5; a_string:five"
+# ~~~
+#
+# Warning: the method may go in an infinite recursion if there is a circuit in
+# the implementation of `to_s`.
+interface DeriveToS
+ super Derivable
+ redef fun to_s do return derive_to_map.join("; ", ":")
+end
+
+# Implementation of `==` and `hash` for `Derivable` objects.
+#
+# The implementations just call `==` and `hash` on `derive_to_map`.
+#
+# ~~~
+# class A
+# auto_derive
+# super DeriveEqual
+# var an_int: Int
+# var a_string: String
+# end
+#
+# var a = new A(5, "five")
+# var b = new A(5, "five")
+# var c = new A(6, "six")
+# assert a == b
+# assert a.hash == b.hash
+# assert a != c
+# ~~~
+#
+# Warning: the method may go in an infinite recursion if there is a circuit in
+# the implementation of `==` or `hash`.
+interface DeriveEqual
+ super Derivable
+ redef fun ==(other) do
+ if not other isa Derivable then return false
+ return derive_to_map == other.derive_to_map
+ end
+ redef fun hash do
+ return derive_to_map.hash
+ end
+end
generate_inspect_method(nclassdef)
end
end
+
+ if nat.name == "auto_derive" then
+ if not nclassdef isa AStdClassdef then
+ toolcontext.error(nclassdef.location, "Syntax error: only a concrete class can be `{nat.name}`.")
+ else
+ generate_derive_to_map_method(nclassdef, nat)
+ end
+ end
end
fun generate_inspect_method(nclassdef: AClassdef)
# Create method Node and add it to the AST
npropdefs.push(toolcontext.parse_propdef(code.join("\n")))
end
+
+ fun generate_derive_to_map_method(nclassdef: AClassdef, nat: AAnnotation)
+ do
+ var npropdefs = nclassdef.n_propdefs
+
+ var sc = toolcontext.parse_superclass("Derivable")
+ sc.location = nat.location
+ nclassdef.n_propdefs.add sc
+
+ var code = new Array[String]
+ code.add "redef fun derive_to_map"
+ code.add "do"
+ code.add " var res = super"
+
+ for attribute in npropdefs do if attribute isa AAttrPropdef then
+ var name = attribute.n_id2.text
+ code.add """ res["{{{name}}}"] = self.{{{name}}}"""
+ end
+
+ code.add " return res"
+ code.add "end"
+
+ # Create method Node and add it to the AST
+ npropdefs.push(toolcontext.parse_propdef(code.join("\n")))
+ end
end