# State of the parser automata as stored in the parser stack.
private class State
# The internal state number
- readable writable var _state: Int
+ var state: Int
# The node stored with the state in the stack
- readable writable var _nodes: nullable Object
-
- init(state: Int, nodes: nullable Object)
- do
- _state = state
- _nodes = nodes
- end
+ var nodes: nullable Object
end
+# The parser of the Nit language.
class Parser
super TablesCapable
# Associated lexer
- var _lexer: Lexer
+ var lexer: Lexer
# Stack of pushed states and productions
- var _stack: Array[State]
+ private var stack = new Array[State]
# Position in the stack
- var _stack_pos: Int
+ private var stack_pos: Int = -1
- # Create a new parser based on a given lexer
- init(lexer: Lexer)
+ init
do
- _lexer = lexer
- _stack = new Array[State]
- _stack_pos = -1
- build_reduce_table
+ self.reduce_table = once build_reduce_table
end
# Do a transition in the automata
_stack_pos = pos
if pos < _stack.length then
var state = _stack[pos]
- state.state = numstate
- state.nodes = list_node
+ state._state = numstate
+ state._nodes = list_node
else
_stack.push(new State(numstate, list_node))
end
# The current state
private fun state: Int
do
- return _stack[_stack_pos].state
+ return _stack[_stack_pos]._state
end
# Pop something from the stack state
private fun pop: nullable Object
do
- var res = _stack[_stack_pos].nodes
+ var res = _stack[_stack_pos]._nodes
_stack_pos = _stack_pos -1
return res
end
var node1 = pop
assert node1 isa AModule
var node = new Start(node1, node2)
- (new ComputeProdLocationVisitor).enter_visit(node)
+ node2.parent = node
+ (new ComputeProdLocationVisitor(lexer.file.first_token)).enter_visit(node)
return node
else if action_type == 3 then # ERROR
# skip injected tokens
- while token._location == null do token = lexer.next
- var node2 = new AParserError.init_parser_error("Syntax error: unexpected {token}.", token.location, token)
+ while not isset token._location do token = lexer.next
+ var node2 = new AParserError.init_parser_error("Syntax Error: unexpected {token}.", token.location, token)
var node = new Start(null, node2)
return node
end
end
end
- var _reduce_table: Array[ReduceAction]
- private fun build_reduce_table is abstract
+ private var reduce_table: Array[ReduceAction] is noinit
+ private fun build_reduce_table: Array[ReduceAction] is abstract
end
redef class Prod
# Location on the first token after the start of a production
- # So outside the production for epilon production
- var _first_location: nullable Location
+ # So outside the production for epsilon production
+ var first_location: nullable Location
+
+ # Join the text of all visited tokens
+ fun collect_text: String
+ do
+ var v = new TextCollectorVisitor
+ v.enter_visit(self)
+ assert v.text != ""
+ return v.text
+ end
end
# Find location of production nodes
# Uses existing token locations to infer location of productions.
private class ComputeProdLocationVisitor
super Visitor
- # Currenlty visited productions that need a first token
- var _need_first_prods: Array[Prod] = new Array[Prod]
+
+ # The current (or starting) cursor on the token sequence used to collect loose tokens
+ var token: nullable Token
+
+ # Currently visited productions that need a first token
+ var need_first_prods = new Array[Prod]
# Already visited epsilon productions that waits something after them
- var _need_after_epsilons: Array[Prod] = new Array[Prod]
+ var need_after_epsilons = new Array[Prod]
- # Location of the last visited token in the current production
- var _last_location: nullable Location = null
+ # The last visited token in the current production
+ var last_token: nullable Token = null
redef fun visit(n: ANode)
do
if n isa Token then
+ # Skip injected tokens
+ if not isset n._location then return
+
+ # Collect loose tokens (not in the AST) and attach them to token in the AST
+ var cursor = token
+ if n != cursor then
+ var lt = last_token
+ # In order, we have the tokens:
+ # * `lt` the previous visited token in the AST (if any)
+ # * then `cursor` the loose tokens to attach
+ # * then `n` the current visited token in the AST
+
+ # In the following, we advance `cursor` to add them to `lt.next_looses` or to `n.prev_looses`.
+ if lt != null then
+ var ltl = lt.location.line_end
+ # floating tokens on the same line of a AST-token follows it
+ while cursor != null and cursor != n and ltl == cursor.location.line_start do
+ cursor.is_loose = true
+ lt.next_looses.add cursor
+ cursor = cursor.next_token
+ end
+ end
+ # other loose tokens precede the next AST-token
+ while cursor != null and cursor != n do
+ cursor.is_loose = true
+ n.prev_looses.add cursor
+ cursor = cursor.next_token
+ end
+ end
+ token = n.next_token
+
var loc = n._location
- if loc == null then return
- _last_location = loc
+ _last_token = n
# Add a first token to productions that need one
if not _need_first_prods.is_empty then
var startl = n._first_location
if startl != null then
# Non-epsilon production
- var endl = _last_location
- assert endl != null
+ var endl = _last_token.location
- n.location = new Location(startl.file, startl.line_start, endl.line_end, startl.column_start, endl.column_end)
+ if startl == endl then
+ n.location = startl
+ else
+ n.location = new Location(startl.file, startl.line_start, endl.line_end, startl.column_start, endl.column_end)
+ end
if not _need_after_epsilons.is_empty then
var loc = new Location(endl.file, endl.line_end, endl.line_end, endl.column_end, endl.column_end)
end
end
end
+end
- init do end
+private class TextCollectorVisitor
+ super Visitor
+ var text: String = ""
+ redef fun visit(n)
+ do
+ if n isa Token then text += n.text
+ n.visit_all(self)
+ end
end
-# Each reduca action has its own class, this one is the root of the hierarchy.
+
+# Each reduce action has its own class, this one is the root of the hierarchy.
private abstract class ReduceAction
fun action(p: Parser) is abstract
fun concat(l1, l2 : Array[Object]): Array[Object]
l1.append(l2)
return l1
end
- var _goto: Int
- init(g: Int) do _goto = g
+ var goto: Int
+end
+
+redef class AExpr
+
+ # Get `self` as a single identifier.
+ # Return null if not a single identifier.
+ fun as_id: nullable String
+ do
+ if self isa AMethidExpr then
+ return self.collect_text
+ end
+ if not self isa ACallExpr then return null
+ if not self.n_expr isa AImplicitSelfExpr then return null
+ if not self.n_args.n_exprs.is_empty then return null
+ return self.n_qid.n_id.text
+ end
end