+
+ # 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
+
+ # Is `self` a token or a pure-structural production like `AQId`?
+ fun is_structural: Bool do return false
+
+ # Write the subtree on stdout.
+ #
+ # Visit the subtree and display it with additional and useful information.
+ #
+ # By default, this displays all kind of nodes and the corresponding lines of codes.
+ #
+ # See `ASTDump` for details.
+ fun dump_tree(display_structural, display_line: nullable Bool)
+ do
+ var d = new ASTDump(display_structural or else true, display_line or else true)
+ d.enter_visit(self)
+ d.write_to(sys.stdout)
+ end
+
+ # Information to display on a node
+ #
+ # Refine this method to add additional information on each node type.
+ fun dump_info(v: ASTDump): String do return ""
+
+ # 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
+
+ # Display tokens and structural production?
+ #
+ # Should tokens (and structural production like AQId) be displayed?
+ var display_structural: Bool
+
+ # Display lines?
+ #
+ # Should each new line be displayed (numbered and in gray)?
+ var display_line: Bool
+
+ # Current line number (when printing lines)
+ private var line = 0
+
+ redef fun visit(n)
+ do
+ if not display_structural and n.is_structural then return
+ var p = last_parent
+ add(p, n)
+ last_parent = n
+ n.visit_all(self)
+ last_parent = p
+ end
+
+ redef fun write_line(o, n, p)
+ do
+ if display_line then
+ var ls = n.location.line_start
+ var file = n.location.file
+ var line = self.line
+ if ls > line and file != null then
+ if line == 0 then line = ls - 1
+ while line < ls do
+ line += 1
+ o.write "{line}\t{file.get_line(line)}\n".light_gray
+ end
+ self.line = ls
+ end
+ end
+
+ super
+ end
+
+ redef fun display(n)
+ do
+ return "{n.class_name} {n.dump_info(self)} @{n.location}"
+ end
+
+ # `s` as yellow
+ fun yellow(s: String): String do return s.yellow
+
+ # `s` as red
+ fun red(s: String): String do return s.red
+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 reverse_iterator do return items.reverse_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