online_ide: update to the new API of the loader
[nit.git] / lib / nitcc_runtime.nit
index 5778641..bfd137d 100644 (file)
@@ -72,21 +72,22 @@ abstract class Parser
        fun parse_error
        do
                var token = peek_token
-               print "* parse error in state {state} on token {token}"
-               print "  expected: {state.error_msg}"
-               print "  node_stack={node_stack.join(", ")}"
-               print "  state_stack={state_stack.join(", ")}"
-               var error: Node
+               #print "* parse error in state {state} on token {token}"
+               #print "  expected: {state.error_msg}"
+               #print "  node_stack={node_stack.join(", ")}"
+               #print "  state_stack={state_stack.join(", ")}"
+               node_stack.add(token)
+               var error: NError
                if token isa NLexerError then
                        error = token
-                       token.error_tree.items.add_all(node_stack)
                else
                        error = new NParserError
                        error.position = token.position
                        error.text = token.text
                        error.token = token
-                       error.error_tree.items.add_all(node_stack)
                end
+               error.error_tree.children.add_all(node_stack)
+               error.expected = state.error_msg
                node_stack.clear
                node_stack.add error
                stop = true
@@ -98,7 +99,7 @@ abstract class Parser
 
        # The current state
        # Used by generated parsers
-       var state: LRState
+       var state: LRState is noinit
 
        init
        do
@@ -115,7 +116,7 @@ abstract class Parser
 
        # Should the parser stop
        # Used by generated parsers
-       var stop writable = true
+       var stop = true is writable
 
        # Parse a full sequence of tokens and return a complete syntactic tree
        fun parse: Node
@@ -185,27 +186,31 @@ abstract class Lexer
                                last_state = state
                        end
                        var c
+                       var next
                        if pos >= length then
                                c = '\0'
+                               next = null
                        else
-                               c = text[pos]
+                               c = text.chars[pos]
+                               next = state.trans(c)
                        end
-                       var next = state.trans(c)
                        if next == null then
-                               if last_state == null then
-                                       var token = new NLexerError
-                                       var position = new Position(pos_start, pos, line_start, line, col_start, col)
-                                       token.position = position
-                                       token.text = text.substring(pos_start, pos-pos_start+1)
-                                       res.add token
-                                       break
+                               if pos_start < length then
+                                       if last_state == null then
+                                               var token = new NLexerError
+                                               var position = new Position(pos_start, pos, line_start, line, col_start, col)
+                                               token.position = position
+                                               token.text = text.substring(pos_start, pos-pos_start+1)
+                                               res.add token
+                                               break
+                                       end
+                                       var position = new Position(pos_start, pos_end, line_start, line_end, col_start, col_end)
+                                       var token = last_state.make_token(position, text.substring(pos_start, pos_end-pos_start+1))
+                                       if token != null then res.add(token)
                                end
-                               var position = new Position(pos_start, pos_end, line_start, line_end, col_start, col_end)
-                               var token = last_state.make_token(position, text.substring(pos_start, pos_end-pos_start+1))
-                               if token != null then res.add(token)
                                if pos >= length then
-                                       token = new NEof
-                                       position = new Position(pos, pos, line, line, col, col)
+                                       var token = new NEof
+                                       var position = new Position(pos, pos, line, line, col, col)
                                        token.position = position
                                        token.text = ""
                                        res.add token
@@ -269,9 +274,8 @@ end
 # Print a node (using to_s) on a line and recustively each children indented (with two spaces)
 class TreePrinterVisitor
        super Visitor
-       var writer: OStream
+       var writer: Writer
        private var indent = 0
-       init(writer: OStream) do self.writer = writer
        redef fun visit(n)
        do
                for i in [0..indent[ do writer.write("  ")
@@ -292,7 +296,42 @@ class Position
        var line_end: Int
        var col_start: Int
        var col_end: Int
+
        redef fun to_s do return "{line_start}:{col_start}-{line_end}:{col_end}"
+
+       # Get the lines covered by `self` and underline the target columns.
+       #
+       # This is useful for pretty printing errors or debug the output
+       #
+       # ~~~
+       # var src = "var Foo = new Array[Int]"
+       # var pos = new Position(0,0, 1, 1, 5, 8)
+       #
+       # assert pos.underline(src) == """
+       # var Foo = new Array[Int]
+       #     ^^^"""
+       # ~~~
+       fun underline(source: Text): String
+       do
+               var res = new FlatBuffer
+
+               # All the concerned lines
+               var lines = source.split("\n")
+               for line in [line_start..line_end] do
+                       res.append lines[line-1]
+                       res.append "\n"
+               end
+
+               # Cover all columns, no matter their lines
+               var col_start = col_start.min(col_end)
+               var col_end = self.col_start.max(col_end)
+
+               # "           ^^^^"
+               var ptr = " "*(col_start-1).max(0) + "^"*(col_end-col_start)
+               res.append ptr
+
+               return res.to_s
+       end
 end
 
 # A node of a syntactic tree
@@ -300,9 +339,12 @@ abstract class Node
        # The name of the node (as used in the grammar file)
        fun node_name: String do return class_name
 
-       # A point of view on the direct childrens of the node
+       # A point of view on the direct children of the node
        fun children: SequenceRead[nullable Node] is abstract
 
+       # A point of view of a depth-first visit of all non-null children
+       var depth: Collection[Node] = new DephCollection(self)
+
        # Visit all the children of the node with the visitor `v`
        protected fun visit_children(v: Visitor)
        do
@@ -310,12 +352,12 @@ abstract class Node
        end
 
        # The position of the node in the input stream
-       var position: nullable Position writable = null
+       var position: nullable Position = null is writable
 
        # Produce a graphiz file for the syntaxtic tree rooted at `self`.
        fun to_dot(filepath: String)
        do
-               var f = new OFStream.open(filepath)
+               var f = new FileWriter.open(filepath)
                f.write("digraph g \{\n")
                f.write("rankdir=BT;\n")
 
@@ -339,7 +381,7 @@ abstract class Node
                f.close
        end
 
-       private fun to_dot_visitor(f: OStream, a: Array[NToken])
+       private fun to_dot_visitor(f: Writer, a: Array[NToken])
        do
                f.write("n{object_id} [label=\"{node_name}\"];\n")
                for x in children do
@@ -359,6 +401,42 @@ abstract class Node
        end
 end
 
+private class DephCollection
+       super Collection[Node]
+       var node: Node
+       redef fun iterator do return new DephIterator([node].iterator)
+end
+
+private class DephIterator
+       super Iterator[Node]
+
+       var stack = new List[Iterator[nullable Node]]
+
+       init(i: Iterator[nullable Node]) is old_style_init do
+               stack.add i
+       end
+
+       redef fun is_ok do return not stack.is_empty
+       redef fun item do return stack.last.item.as(not null)
+       redef fun next
+       do
+               var i = stack.last
+               stack.push i.item.children.iterator
+               i.next
+               while is_ok do
+                       if not stack.last.is_ok then
+                               stack.pop
+                               continue
+                       end
+                       if stack.last.item == null then
+                               stack.last.next
+                               continue
+                       end
+                       return
+               end
+       end
+end
+
 # A token produced by the lexer and used in a syntactic tree
 abstract class NToken
        super Node
@@ -379,7 +457,7 @@ abstract class NToken
        end
 
        # The text associated with the token
-       var text: String writable = ""
+       var text: String = "" is writable
 
        redef fun to_s do
                var res = super
@@ -402,29 +480,56 @@ abstract class NError
 
        # All the partially built tree during parsing (aka the node_stack)
        var error_tree = new Nodes[Node]
+
+       # The things unexpected
+       fun unexpected: String is abstract
+
+       # The things expected (if any)
+       var expected: nullable String = null
+
+       # The error message,using `expected` and `unexpected`
+       fun message: String
+       do
+               var exp = expected
+               var res = "Unexpected {unexpected}"
+               if exp != null then res += "; is acceptable instead: {exp}"
+               return res
+       end
 end
 
 # A lexer error as a token for the unexpected characted
 class NLexerError
        super NError
+
+       redef fun unexpected do return "character '{text.chars.first}'"
 end
 
 # A parser error linked to a unexpected token
 class NParserError
        super NError
+
        # The unexpected token
-       var token: nullable NToken
+       var token: nullable NToken = null
+
+       redef fun unexpected
+       do
+               var res = token.node_name
+               var text = token.text
+               if not text.is_empty and res != "'{text}'" then
+                       res += " '{text.escape_to_c}'"
+               end
+               return res
+       end
 end
 
 # A hogeneous sequence of node, used to represent unbounded lists (and + modifier)
 class Nodes[T: Node]
        super Node
-       redef fun children do return items
-       var items = new Array[T]
+       redef var children: Array[T] = new Array[T]
 end
 
-# A production with a specific, named and statically typed childrens
-class NProd
+# A production with a specific, named and statically typed children
+abstract class NProd
        super Node
        redef var children: SequenceRead[nullable Node] = new NProdChildren(self)
 
@@ -480,7 +585,7 @@ abstract class TestParser
                var filepath = args.shift
                var text
                if filepath == "-" then
-                       text = stdin.read_all
+                       text = sys.stdin.read_all
                else if filepath == "-e" then
                        if args.is_empty then
                                print "Error: -e need a text"
@@ -488,7 +593,7 @@ abstract class TestParser
                        end
                        text = args.shift
                else
-                       var f = new IFStream.open(filepath)
+                       var f = new FileReader.open(filepath)
                        text = f.read_all
                        f.close
                end
@@ -512,7 +617,7 @@ abstract class TestParser
                var tokout = "{name}.tokens.out"
                print "TOKEN: {tokens.length} tokens (see {tokout})"
 
-               var f = new OFStream.open(tokout)
+               var f = new FileWriter.open(tokout)
                for t in tokens do
                        f.write "{t.to_s}\n"
                end
@@ -524,15 +629,16 @@ abstract class TestParser
                var n = p.parse
 
                var astout = "{name}.ast.out"
-               f = new OFStream.open(astout)
+               f = new FileWriter.open(astout)
                var tpv = new TreePrinterVisitor(f)
                var astdotout = "{name}.ast.dot"
                if n isa NError then
+                       print "Syntax error: {n.message}"
                        print "ERROR: {n} (see {astout} and {astdotout})"
                        tpv.enter_visit(n)
                        n = n.error_tree
                else
-                       print "ROOT: {n} (see {astout} and {astdotout})"
+                       print "ROOT: {n}; {n.depth.length} nodes (see {astout} and {astdotout})"
                end
                tpv.enter_visit(n)
                n.to_dot(astdotout)