+ # Location is set during AST building. Once built, location cannon be null.
+ # However, manual instantiated nodes may need more care.
+ var location: Location is writable, noinit
+
+ # The location of the important part of the node (identifier or whatever)
+ fun hot_location: Location do return location
+
+ # Display a message for the colored location of the node
+ fun debug(message: String)
+ do
+ sys.stderr.write "{hot_location} {self.class_name}: {message}\n{hot_location.colored_line("0;32")}\n"
+ end
+
+ # Write the subtree on stdout.
+ # See `ASTDump`
+ fun dump_tree
+ do
+ var d = new ASTDump
+ d.enter_visit(self)
+ d.write_to(sys.stdout)
+ end
+
+ # Parent of the node in the AST
+ var parent: nullable ANode = null
+
+ # The topmost ancestor of the element
+ # This just apply `parent` until the first one
+ fun root: ANode
+ do
+ var res = self
+ loop
+ var p = res.parent
+ if p == null then return res
+ res = p
+ end
+ end
+
+ # The most specific common parent between `self` and `other`
+ # Return null if the two node are unrelated (distinct root)
+ fun common_parent(other: ANode): nullable ANode
+ do
+ # First, get the same depth
+ var s: nullable ANode = self
+ var o: nullable ANode = other
+ var d = s.depth - o.depth
+ while d > 0 do
+ s = s.parent
+ d -= 1
+ end
+ while d < 0 do
+ o = o.parent
+ d += 1
+ end
+ assert o.depth == s.depth
+ # Second, go up until same in found
+ while s != o do
+ s = s.parent
+ o = o.parent
+ end
+ return s
+ end
+
+ # Number of nodes between `self` and the `root` of the AST
+ # ENSURE `self == self.root implies result == 0 `
+ # ENSURE `self != self.root implies result == self.parent.depth + 1`
+ fun depth: Int
+ do
+ var n = self
+ var res = 0
+ loop
+ var p = n.parent
+ if p == null then return res
+ n = p
+ res += 1
+ end
+ end
+
+ # Replace a child with an other node in the AST
+ private fun replace_child(old_child: ANode, new_child: nullable ANode) is abstract
+
+ # Detach a node from its parent
+ # Aborts if the node is not detachable. use `replace_with` instead
+ # REQUIRE: parent != null
+ # REQUIRE: is_detachable
+ # ENDURE: parent == null
+ fun detach
+ do
+ assert parent != null
+ parent.replace_child(self, null)
+ parent = null
+ end
+
+ # Replace itself with an other node in the AST
+ # REQUIRE: parent != null
+ # ENSURE: node.parent == old(parent)
+ # ENSURE: parent == null
+ fun replace_with(node: ANode)
+ do
+ assert parent != null
+ parent.replace_child(self, node)
+ parent = null
+ end
+
+ # Visit all nodes in order.
+ # Thus, call `v.enter_visit(e)` for each child `e`
+ fun visit_all(v: Visitor) is abstract
+
+ # Do a deep search and return an array of tokens that match a given text
+ fun collect_tokens_by_text(text: String): Array[Token]
+ do
+ var v = new CollectTokensByTextVisitor(text)
+ v.enter_visit(self)
+ return v.result
+ end
+
+ # Do a deep search and return an array of node that are annotated
+ # The attached node can be retrieved by two invocations of parent
+ fun collect_annotations_by_name(name: String): Array[AAnnotation]
+ do
+ var v = new CollectAnnotationsByNameVisitor(name)
+ v.enter_visit(self)
+ return v.result
+ end
+end
+
+private class CollectTokensByTextVisitor
+ super Visitor
+ var text: String
+ var result = new Array[Token]
+ redef fun visit(node)
+ do
+ node.visit_all(self)
+ if node isa Token and node.text == text then result.add(node)
+ end
+end
+
+private class CollectAnnotationsByNameVisitor
+ super Visitor
+ var name: String
+ var result = new Array[AAnnotation]
+ redef fun visit(node)
+ do
+ node.visit_all(self)
+ if node isa AAnnotation and node.n_atid.n_id.text == name then result.add(node)
+ end
+end
+
+# A helper class to handle (print) Nit AST as an OrderedTree
+class ASTDump
+ super Visitor
+ super OrderedTree[ANode]
+
+ # Reference to the last parent in the Ordered Tree
+ # Is used to handle the initial node parent and workaround possible inconsistent `ANode::parent`
+ private var last_parent: nullable ANode = null
+
+ redef fun visit(n)
+ do
+ var p = last_parent
+ add(p, n)
+ last_parent = n
+ n.visit_all(self)
+ last_parent = p
+ end
+
+ redef fun display(n)
+ do
+ if n isa Token then
+ return "{n.class_name} \"{n.text.escape_to_c}\" @{n.location}"
+ else
+ return "{n.class_name} @{n.location}"
+ end
+ end
+end
+
+# A sequence of nodes
+# It is a specific class (instead of using a Array) to track the parent/child relation when nodes are added or removed
+class ANodes[E: ANode]
+ super Sequence[E]
+ private var parent: ANode
+ private var items = new Array[E]
+ redef fun iterator do return items.iterator
+ redef fun length do return items.length
+ redef fun is_empty do return items.is_empty
+ redef fun push(e)
+ do
+ hook_add(e)
+ items.push(e)
+ end
+ redef fun pop
+ do
+ var res = items.pop
+ hook_remove(res)
+ return res
+ end
+ redef fun unshift(e)
+ do
+ hook_add(e)
+ items.unshift(e)
+ end
+ redef fun shift
+ do
+ var res = items.shift
+ hook_remove(res)
+ return res
+ end
+ redef fun has(e)
+ do
+ return items.has(e)
+ end
+ redef fun [](index)
+ do
+ return items[index]
+ end
+ redef fun []=(index, e)
+ do
+ hook_remove(self[index])
+ hook_add(e)
+ items[index]=e
+ end
+ redef fun remove_at(index)
+ do
+ hook_remove(items[index])
+ items.remove_at(index)
+ end
+ private fun hook_add(e: E)
+ do
+ #assert e.parent == null
+ e.parent = parent
+ end
+ private fun hook_remove(e: E)
+ do
+ assert e.parent == parent
+ e.parent = null
+ end
+
+ # Used in parent constructor to fill elements
+ private fun unsafe_add_all(nodes: Collection[Object])
+ do
+ var parent = self.parent
+ for n in nodes do
+ assert n isa E
+ add n
+ n.parent = parent
+ end
+ end
+
+ private fun replace_child(old_child: ANode, new_child: nullable ANode): Bool
+ do
+ var parent = self.parent
+ for i in [0..length[ do
+ if self[i] == old_child then
+ if new_child != null then
+ assert new_child isa E
+ self[i] = new_child
+ new_child.parent = parent
+ else
+ self.remove_at(i)
+ end
+ return true
+ end
+ end
+ return false
+ end
+
+ private fun visit_all(v: Visitor)
+ do
+ for n in self do v.enter_visit(n)
+ end