rley 0.5.01 → 0.5.02

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/examples/data_formats/JSON/cli_options.rb +25 -9
  4. data/examples/data_formats/JSON/json_ast_builder.rb +152 -0
  5. data/examples/data_formats/JSON/json_ast_nodes.rb +141 -0
  6. data/examples/data_formats/JSON/json_demo.rb +24 -8
  7. data/examples/general/calc_iter1/calc_ast_builder.rb +142 -0
  8. data/examples/general/calc_iter1/calc_ast_nodes.rb +151 -0
  9. data/examples/general/calc_iter1/calc_demo.rb +38 -0
  10. data/examples/general/calc_iter1/calc_grammar.rb +25 -0
  11. data/examples/general/calc_iter1/calc_lexer.rb +81 -0
  12. data/examples/general/{calc → calc_iter1}/calc_parser.rb +0 -0
  13. data/examples/general/calc_iter1/spec/calculator_spec.rb +73 -0
  14. data/examples/general/calc_iter2/calc_ast_builder.rb +186 -0
  15. data/examples/general/calc_iter2/calc_ast_nodes.rb +151 -0
  16. data/examples/general/{calc → calc_iter2}/calc_demo.rb +3 -2
  17. data/examples/general/{calc → calc_iter2}/calc_grammar.rb +0 -0
  18. data/examples/general/calc_iter2/calc_lexer.rb +81 -0
  19. data/examples/general/calc_iter2/calc_parser.rb +24 -0
  20. data/lib/rley.rb +1 -0
  21. data/lib/rley/constants.rb +1 -1
  22. data/lib/rley/parser/cst_builder.rb +5 -225
  23. data/lib/rley/parser/gfg_parsing.rb +2 -2
  24. data/lib/rley/parser/parse_forest_factory.rb +1 -1
  25. data/lib/rley/parser/parse_rep_creator.rb +2 -2
  26. data/lib/rley/parser/parse_tree_builder.rb +161 -104
  27. data/lib/rley/parser/parse_tree_factory.rb +6 -2
  28. data/spec/rley/parser/ast_builder_spec.rb +395 -0
  29. data/spec/rley/support/grammar_arr_int_helper.rb +21 -11
  30. metadata +20 -9
  31. data/examples/general/calc/calc_lexer.rb +0 -90
  32. data/spec/rley/parser/parse_tree_builder_spec.rb +0 -249
@@ -151,10 +151,10 @@ module Rley # This module is used as a namespace
151
151
 
152
152
  # Factory method. Builds a ParseTree from the parse result.
153
153
  # @return [ParseTree]
154
- def parse_tree()
154
+ def parse_tree(aBuilder = nil)
155
155
  factory = ParseTreeFactory.new(self)
156
156
 
157
- return factory.create
157
+ return factory.create(aBuilder)
158
158
  end
159
159
 
160
160
  # Retrieve the very first parse entry added to the chart.
@@ -11,7 +11,7 @@ module Rley # This module is used as a namespace
11
11
 
12
12
  # Create a Builder, that is, an object
13
13
  # that will create piece by piece the forest
14
- def builder(aParseResult)
14
+ def builder(aParseResult, aBuilder = nil)
15
15
  ParseForestBuilder.new(aParseResult.tokens)
16
16
  end
17
17
  end # class
@@ -16,9 +16,9 @@ module Rley # This module is used as a namespace
16
16
 
17
17
  # Factory method that produces the representation of the parse.
18
18
  # @return [ParseTree] The parse representation.
19
- def create()
19
+ def create(aBuilder = nil)
20
20
  a_walker = walker(parsing)
21
- a_builder = builder(parsing)
21
+ a_builder = builder(parsing, aBuilder)
22
22
 
23
23
  begin
24
24
  loop do
@@ -1,3 +1,4 @@
1
+ require_relative '../tokens/token_range'
1
2
  require_relative '../syntax/terminal'
2
3
  require_relative '../syntax/non_terminal'
3
4
  require_relative '../gfg/end_vertex'
@@ -9,7 +10,21 @@ require_relative '../ptree/parse_tree'
9
10
 
10
11
  module Rley # This module is used as a namespace
11
12
  module Parser # This module is used as a namespace
12
- # Builder GoF pattern.
13
+ # Structure used internally by ParseTreeBuilder class.
14
+ CSTRawNode = Struct.new(:range, :symbol, :children) do
15
+ def initialize(aRange, aSymbol)
16
+ super
17
+ self.range = aRange
18
+ self.symbol = aSymbol
19
+ self.children = nil
20
+ end
21
+ end # Struct
22
+
23
+
24
+ # The purpose of a ParseTreeBuilder is to build piece by piece a CST
25
+ # (Concrete Syntax Tree) from a sequence of input tokens and
26
+ # visit events produced by walking over a GFGParsing object.
27
+ # Uses the Builder GoF pattern.
13
28
  # The Builder pattern creates a complex object
14
29
  # (say, a parse tree) from simpler objects (terminal and non-terminal
15
30
  # nodes) and using a step by step approach.
@@ -17,33 +32,30 @@ module Rley # This module is used as a namespace
17
32
  # @return [Array<Token>] The sequence of input tokens
18
33
  attr_reader(:tokens)
19
34
 
20
- # Link to tree object (being) built
35
+ # Link to CST object (being) built.
21
36
  attr_reader(:result)
22
37
 
23
- # Link to current path
24
- attr_reader(:curr_path)
25
-
26
- # The last parse entry visited
27
- attr_reader(:last_visitee)
28
-
29
- # A hash with pairs of the form: visited parse entry => tree node
30
- attr_reader(:entry2node)
31
38
 
32
39
  # Create a new builder instance.
33
40
  # @param theTokens [Array<Token>] The sequence of input tokens.
34
41
  def initialize(theTokens)
35
42
  @tokens = theTokens
36
- @curr_path = []
37
- @entry2node = {}
43
+ @stack = []
38
44
  end
39
45
 
46
+ # Receive events resulting from a visit of GFGParsing object.
47
+ # These events are produced by a specialized Enumerator created
48
+ # with a ParseWalkerFactory instance.
49
+ # @param anEvent [Symbol] Kind of visit event. Should be: :visit
50
+ # @param anEntry [ParseEntry] The entry being visited
51
+ # @param anIndex [anIndex] The token index associated with anEntry
40
52
  def receive_event(anEvent, anEntry, anIndex)
41
53
  # puts "Event: #{anEvent} #{anEntry} #{anIndex}"
42
- if anEntry.dotted_entry?
54
+ if anEntry.dotted_entry? # A N => alpha . beta pattern?
43
55
  process_item_entry(anEvent, anEntry, anIndex)
44
- elsif anEntry.start_entry?
56
+ elsif anEntry.start_entry? # A .N pattern?
45
57
  process_start_entry(anEvent, anEntry, anIndex)
46
- elsif anEntry.end_entry?
58
+ elsif anEntry.end_entry? # A N. pattern?
47
59
  process_end_entry(anEvent, anEntry, anIndex)
48
60
  else
49
61
  raise NotImplementedError
@@ -52,133 +64,178 @@ module Rley # This module is used as a namespace
52
64
  @last_visitee = anEntry
53
65
  end
54
66
 
55
- # Return the current_parent node
56
- def curr_parent()
57
- return curr_path.last
67
+ protected
68
+
69
+ # Return the stack
70
+ def stack()
71
+ return @stack
58
72
  end
59
73
 
60
74
  private
61
75
 
62
- def process_start_entry(_anEvent, _anEntry, _anIndex)
63
- curr_path.pop
76
+ # Return the top of stack element.
77
+ def tos()
78
+ return @stack.last
64
79
  end
65
80
 
81
+ # Handler for visit events for ParseEntry matching N. pattern
82
+ # @param anEvent [Symbol] Kind of visit event. Should be: :visit
83
+ # @param anEntry [ParseEntry] The entry being visited
84
+ # @param anIndex [anIndex] The token index at end of anEntry
66
85
  def process_end_entry(anEvent, anEntry, anIndex)
67
86
  case anEvent
68
87
  when :visit
69
- # create a node with the non-terminal
70
- # with same right extent as curr_entry_set_index
71
- # add the new node as first child of current_parent
72
- # append the new node to the curr_path
73
88
  range = { low: anEntry.origin, high: anIndex }
74
- non_terminal = anEntry.vertex.non_terminal
75
- create_non_terminal_node(anEntry, range, non_terminal)
76
- @result = create_tree(curr_parent) unless @last_visitee
89
+ non_terminal = entry2nonterm(anEntry)
90
+ # Create raw node and push onto stack
91
+ push_raw_node(range, non_terminal)
77
92
  else
78
93
  raise NotImplementedError
79
94
  end
80
95
  end
81
96
 
97
+ # Handler for visit events for ParseEntry matching .N pattern
98
+ # @param anEvent [Symbol] Kind of visit event. Should be: :visit
99
+ # @param anEntry [ParseEntry] The entry being visited
100
+ # @param anIndex [anIndex] The token index at end of anEntry
101
+ def process_start_entry(anEvent, anEntry, anIndex)
102
+ raise NotImplementedError unless [:visit, :revisit].include?(anEvent)
103
+ end
104
+
105
+ # Handler for visit events for ParseEntry matching N => alpha* . beta*
106
+ # @param anEvent [Symbol] Kind of visit event. Should be: :visit
107
+ # @param anEntry [ParseEntry] The entry being visited
108
+ # @param anIndex [anIndex] The token index at end of anEntry
82
109
  def process_item_entry(anEvent, anEntry, anIndex)
110
+ # TODO: what if rhs is empty?
83
111
  case anEvent
84
- when :visit
85
- if anEntry.exit_entry?
86
- # Previous entry was an end entry (X. pattern)
87
- # Does the previous entry have multiple antecedent?
88
- if last_visitee.end_entry? && last_visitee.antecedents.size > 1
89
- # Store current path for later backtracking
90
- # puts "Store backtrack context #{last_visitee}"
91
- # puts "path [#{curr_path.map{|e|e.to_string(0)}.join(', ')}]"
92
- entry2path_to_alt[last_visitee] = curr_path.dup
93
- curr_parent.refinement = :or
94
-
95
- create_alternative_node(anEntry)
96
- end
112
+ when :visit, :revisit
113
+ dot_pos = anEntry.vertex.dotted_item.position
114
+ if dot_pos.zero? || dot_pos < 0
115
+ # Check for pattern: N => alpha* .
116
+ process_exit_entry(anEntry, anIndex) if anEntry.exit_entry?
117
+
118
+ # Check for pattern: N => . alpha*
119
+ process_entry_entry(anEntry, anIndex) if anEntry.entry_entry?
120
+ else
121
+ # (pattern: N => alpha+ . beta+)
122
+ process_middle_entry(anEntry, anIndex)
97
123
  end
124
+ else
125
+ $stderr.puts "waiko '#{anEvent}'"
126
+ raise NotImplementedError
127
+ end
128
+ end
98
129
 
99
- # Does this entry have multiple antecedent?
100
- if anEntry.antecedents.size > 1
101
- # Store current path for later backtracking
102
- # puts "Store backtrack context #{anEntry}"
103
- # puts "path [#{curr_path.map{|e|e.to_string(0)}.join(', ')}]"
104
- entry2path_to_alt[anEntry] = curr_path.dup
105
- # curr_parent.refinement = :or
130
+ # @param anEntry [ParseEntry] Entry matching (pattern: N => alpha* .)
131
+ # @param anIndex [anIndex] The token index at end of anEntry
132
+ def process_exit_entry(anEntry, anIndex)
133
+ production = anEntry.vertex.dotted_item.production
134
+ count_rhs = production.rhs.members.size
135
+ init_TOS_children(count_rhs) # Create placeholders for children
136
+ build_terminal(anEntry, anIndex) if terminal_before_dot?(anEntry)
137
+ end
106
138
 
107
- create_alternative_node(anEntry)
108
- end
139
+ # @param anEntry [ParseEntry] Entry matching pattern: N => alpha+ . beta+
140
+ # @param anIndex [anIndex] The token index at end of anEntry
141
+ def process_middle_entry(anEntry, anIndex)
142
+ build_terminal(anEntry, anIndex) if terminal_before_dot?(anEntry)
143
+ end
109
144
 
110
- # Retrieve the grammar symbol before the dot (if any)
111
- prev_symbol = anEntry.prev_symbol
112
- case prev_symbol
113
- when Syntax::Terminal
114
- # Add node without changing current path
115
- create_token_node(anEntry, anIndex)
116
145
 
117
- when NilClass # Dot at the beginning of production
118
- curr_path.pop if curr_parent.kind_of?(SPPF::AlternativeNode)
119
- end
120
146
 
121
- # when :backtrack
122
- # when :revisit
147
+ # @param anEntry [ParseEntry] Entry matching (pattern: N => . alpha)
148
+ # @param anIndex [anIndex] The token index at end of anEntry
149
+ def process_entry_entry(anEntry, anIndex)
150
+ dotted_item = anEntry.vertex.dotted_item
151
+ rule = dotted_item.production
152
+ previous_tos = stack.pop
153
+ non_terminal = entry2nonterm(anEntry)
154
+ # For debugging purposes
155
+ raise StandardError if previous_tos.symbol != non_terminal
156
+
157
+ new_node = new_parent_node(rule, previous_tos.range,
158
+ tokens, previous_tos.children)
159
+ if stack.empty?
160
+ @result = create_tree(new_node)
161
+ else
162
+ place_TOS_child(new_node, nil)
123
163
  end
124
164
  end
125
165
 
126
- # Create an empty parse tree
127
- def create_tree(aRootNode)
128
- return Rley::PTree::ParseTree.new(aRootNode)
166
+ # Create a raw node with given range
167
+ # and push it on top of stack.
168
+ def push_raw_node(aRange, aSymbol)
169
+ raw_node = CSTRawNode.new(Tokens::TokenRange.new(aRange), aSymbol)
170
+ stack.push(raw_node)
129
171
  end
130
172
 
131
- # Factory method. Build and return an PTree non-terminal node.
132
- def create_non_terminal_node(anEntry, aRange, nonTSymb = nil)
133
- non_terminal = nonTSymb.nil? ? anEntry.vertex.non_terminal : nonTSymb
134
- new_node = Rley::PTree::NonTerminalNode.new(non_terminal, aRange)
135
- entry2node[anEntry] = new_node
136
- add_subnode(new_node)
137
- # puts "FOREST ADD #{curr_parent.key if curr_parent}/#{new_node.key}"
138
-
139
- return new_node
173
+ # Initialize children array of TOS with nil placeholders.
174
+ # The number of elements equals the number of symbols at rhs.
175
+ def init_TOS_children(aCount)
176
+ tos.children = Array.new(aCount)
140
177
  end
141
178
 
142
- # Add an alternative node to the tree
143
- def create_alternative_node(anEntry)
144
- vertex = anEntry.vertex
145
- range = curr_parent.range
146
- alternative = Rley::PTree::AlternativeNode.new(vertex, range)
147
- add_subnode(alternative)
148
- result.is_ambiguous = true
149
- # puts "FOREST ADD #{alternative.key}"
150
-
151
- return alternative
179
+ # Does the position on the left side of the dot correspond
180
+ # a terminal symbol?
181
+ # @param anEntry [ParseEntry] The entry being visited
182
+ def terminal_before_dot?(anEntry)
183
+ prev_symbol = anEntry.prev_symbol
184
+ return prev_symbol && prev_symbol.terminal?
152
185
  end
153
186
 
154
- # create a token node,
155
- # with same origin as token,
156
- # with same right extent = origin + 1
157
- # add the new node as first child of current_parent
158
- def create_token_node(anEntry, anIndex)
187
+ # A terminal symbol was detected at left of dot.
188
+ # Build a raw node for that terminal and make it
189
+ # a child of TOS.
190
+ # @param anEntry [ParseEntry] The entry being visited
191
+ # @param anIndex [anIndex] The token index at end of anEntry
192
+ def build_terminal(anEntry, anIndex)
193
+ # First, build node for terminal...
194
+ term_symbol = anEntry.prev_symbol
159
195
  token_position = anIndex - 1
160
- curr_token = tokens[token_position]
161
- new_node = PTree::TerminalNode.new(curr_token, token_position)
162
- candidate = add_node_to_tree(new_node)
163
- entry2node[anEntry] = candidate
164
-
165
- return candidate
196
+ token = tokens[token_position]
197
+ prod = anEntry.vertex.dotted_item.production
198
+ term_node = new_leaf_node(prod, term_symbol, token_position, token)
199
+
200
+ # Second make it a child of TOS...
201
+ pos = anEntry.vertex.dotted_item.prev_position # pos. in rhs of rule
202
+ place_TOS_child(term_node, pos)
166
203
  end
167
204
 
168
- # Add the given node if not yet present in parse tree
169
- def add_node_to_tree(aNode)
170
- new_node = aNode
171
- # puts "FOREST ADD #{key_node}"
172
- add_subnode(new_node, false)
173
205
 
174
- return new_node
206
+ # Place the given node object as one of the children of the TOS
207
+ # (TOS = Top Of Stack).
208
+ # Each child has a position that is dictated by the position of the
209
+ # related grammar symbol in the right-handed side (RHS) of the grammar
210
+ # rule.
211
+ # @param aNode [TerminalNode, NonTerminalNode] Node object to be placed
212
+ # @param aRHSPos [Integer, NilClass] Position in RHS of rule.
213
+ # If the position is provided, then the node will placed in the children
214
+ # array at that position.
215
+ # If the position is nil, then the node will be placed at the position of
216
+ # the rightmost nil element in children array.
217
+ def place_TOS_child(aNode, aRHSPos)
218
+ if aRHSPos.nil?
219
+ # Retrieve index of most rightmost nil child...
220
+ pos = tos.children.rindex { |child| child.nil? }
221
+ raise StandardError, 'Internal error' if pos.nil?
222
+ else
223
+ pos = aRHSPos
224
+ end
225
+
226
+ tos.children[pos] = aNode
175
227
  end
228
+
229
+ # Retrieve non-terminal symbol of given parse entry
230
+ def entry2nonterm(anEntry)
231
+ case anEntry.vertex
232
+ when GFG::StartVertex, GFG::EndVertex
233
+ non_terminal = anEntry.vertex.non_terminal
234
+ when GFG::ItemVertex
235
+ non_terminal = anEntry.vertex.lhs
236
+ end
176
237
 
177
- # Add the given node as sub-node of current parent node
178
- # Optionally add the node to the current path
179
- def add_subnode(aNode, addToPath = true)
180
- curr_parent.add_subnode(aNode) unless curr_path.empty?
181
- curr_path << aNode if addToPath
238
+ return non_terminal
182
239
  end
183
240
  end # class
184
241
  end # module
@@ -12,8 +12,12 @@ module Rley # This module is used as a namespace
12
12
 
13
13
  # Create a Builder, that is, an object
14
14
  # that will create piece by piece the forest
15
- def builder(aParseResult)
16
- CSTBuilder.new(aParseResult.tokens)
15
+ def builder(aParseResult, aBuilder = nil)
16
+ if aBuilder
17
+ aBuilder.new(aParseResult.tokens)
18
+ else
19
+ CSTBuilder.new(aParseResult.tokens)
20
+ end
17
21
  end
18
22
  end # class
19
23
  end # module
@@ -0,0 +1,395 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ require_relative '../../../lib/rley/parser/gfg_earley_parser'
4
+ require_relative '../../../lib/rley/parser/parse_walker_factory'
5
+ require_relative '../../../lib/rley/parser/parse_tree_builder'
6
+ require_relative '../../../lib/rley/ptree/parse_tree'
7
+
8
+ require_relative '../support/expectation_helper'
9
+ require_relative '../support/grammar_b_expr_helper'
10
+ require_relative '../support/grammar_arr_int_helper'
11
+
12
+
13
+ module Rley # This module is used as a namespace
14
+ module Parser # This module is used as a namespace
15
+
16
+ ArrayNode = Struct.new(:children) do
17
+ def initialize()
18
+ super
19
+ self.children = []
20
+ end
21
+
22
+ def interpret()
23
+ return children.map(&:interpret)
24
+ end
25
+ end
26
+
27
+ IntegerNode = Struct.new(:value, :position) do
28
+ def initialize(integerLiteral, aPosition)
29
+ super
30
+ self.value = integerLiteral.to_i
31
+ self.position = aPosition
32
+ end
33
+
34
+ def interpret()
35
+ self.value
36
+ end
37
+ end
38
+
39
+ # The purpose of a ASTBuilder is to build piece by piece an AST
40
+ # (Abstract Syntax Tree) from a sequence of input tokens and
41
+ # visit events produced by walking over a GFGParsing object.
42
+ # Uses the Builder GoF pattern.
43
+ # The Builder pattern creates a complex object
44
+ # (say, a parse tree) from simpler objects (terminal and non-terminal
45
+ # nodes) and using a step by step approach.
46
+ class ASTBuilder < ParseTreeBuilder
47
+
48
+ protected
49
+
50
+ # Method to override
51
+ # Create a parse tree object with given
52
+ # node as root node.
53
+ def create_tree(aRootNode)
54
+ return Rley::PTree::ParseTree.new(aRootNode)
55
+ end
56
+
57
+ # Method to override
58
+ # Factory method for creating a node object for the given
59
+ # input token.
60
+ # @param terminal [Terminal] Terminal symbol associated with the token
61
+ # @param aTokenPosition [Integer] Position of token in the input stream
62
+ # @param aToken [Token] The input token
63
+ def new_leaf_node(_production, terminal, aTokenPosition, aToken)
64
+ if terminal.name == 'integer'
65
+ IntegerNode.new(aToken.lexeme, aTokenPosition)
66
+ else
67
+ PTree::TerminalNode.new(aToken, aTokenPosition)
68
+ end
69
+ end
70
+
71
+ # Method to override.
72
+ # Factory method for creating a parent node object.
73
+ # @param aProduction [Production] Production rule
74
+ # @param aRange [Range] Range of tokens matched by the rule
75
+ # @param theTokens [Array] The input tokens
76
+ # @param theChildren [Array] Children nodes (one per rhs symbol)
77
+ def new_parent_node(aProduction, aRange, theTokens, theChildren)
78
+ node = case aProduction.name
79
+ when 'P[0]'
80
+ reduce_P_0(aRange, theTokens, theChildren)
81
+
82
+ when 'arr[0]'
83
+ reduce_arr_0(aRange, theTokens, theChildren)
84
+
85
+ when 'sequence[0]'
86
+ reduce_sequence_0(aRange, theTokens, theChildren)
87
+
88
+ when 'sequence[1]'
89
+ reduce_sequence_1(aRange, theTokens, theChildren)
90
+
91
+ when 'list[0]'
92
+ reduce_list_0(aRange, theTokens, theChildren)
93
+
94
+ when 'list[1]'
95
+ reduce_list_1(aRange, theTokens, theChildren)
96
+ else
97
+ raise StandardError, "Don't know production #{aProduction.name}"
98
+ end
99
+
100
+ return node
101
+ end
102
+
103
+ def reduce_P_0(aRange, theTokens, theChildren)
104
+ return theChildren[0]
105
+ end
106
+
107
+ def reduce_arr_0(aRange, theTokens, theChildren)
108
+ return theChildren[1]
109
+ end
110
+
111
+ def reduce_sequence_0(aRange, theTokens, theChildren)
112
+ return theChildren[0]
113
+ end
114
+
115
+ def reduce_sequence_1(aRange, theTokens, theChildren)
116
+ return ArrayNode.new
117
+ end
118
+
119
+ def reduce_list_0(aRange, theTokens, theChildren)
120
+ node = theChildren[0]
121
+ node.children << theChildren[2]
122
+ return node
123
+ end
124
+
125
+ def reduce_list_1(aRange, theTokens, theChildren)
126
+ node = ArrayNode.new
127
+ node.children << theChildren[0]
128
+ return node
129
+ end
130
+ end # class
131
+ end # module
132
+ end # module
133
+
134
+
135
+ module Rley # Open this namespace to avoid module qualifier prefixes
136
+ module Parser
137
+ describe ASTBuilder do
138
+ include ExpectationHelper # Mix-in with expectation on parse entry sets
139
+ include GrammarArrIntHelper # Mix-in for array of integers language
140
+
141
+ let(:sample_grammar) do
142
+ builder = grammar_arr_int_builder
143
+ builder.grammar
144
+ end
145
+
146
+ let(:sample_tokens) do
147
+ arr_int_tokenizer('[2 , 3, 5 ]', sample_grammar)
148
+ end
149
+
150
+ subject { ASTBuilder.new(sample_tokens) }
151
+
152
+ def init_walker(theParser, theTokens)
153
+ result = theParser.parse(theTokens)
154
+ factory = ParseWalkerFactory.new
155
+ accept_entry = result.accepting_entry
156
+ accept_index = result.chart.last_index
157
+ @walker = factory.build_walker(accept_entry, accept_index)
158
+ end
159
+
160
+ def skip_events(count)
161
+ count.times do
162
+ event = @walker.next
163
+ subject.receive_event(*event)
164
+ end
165
+ end
166
+ def get_stack(aBuilder)
167
+ return aBuilder.send(:stack)
168
+ end
169
+
170
+ def create_range(low, high)
171
+ return Tokens::TokenRange.new({low: low, high: high })
172
+ end
173
+
174
+ context 'Initialization:' do
175
+ it 'should be created with a sequence of tokens' do
176
+ expect { ASTBuilder.new(sample_tokens) }.not_to raise_error
177
+ end
178
+
179
+ it 'should know the input tokens' do
180
+ expect(subject.tokens).to eq(sample_tokens)
181
+ end
182
+
183
+ it "shouldn't know the result yet" do
184
+ expect(subject.result).to be_nil
185
+ end
186
+
187
+ it 'should have an empty stack' do
188
+ expect(subject.send(:stack)).to be_empty
189
+ end
190
+ end # context
191
+
192
+ context 'Parse tree construction (no null symbol):' do
193
+
194
+ def next_event(expectation)
195
+ event = @walker.next
196
+ (ev_type, entry, index) = event
197
+ actual = "#{ev_type} #{entry} #{index}"
198
+ expect(actual).to eq(expectation)
199
+ subject.receive_event(*event)
200
+ end
201
+
202
+
203
+ before(:each) do
204
+ @parser = Parser::GFGEarleyParser.new(sample_grammar)
205
+ init_walker(@parser, sample_tokens)
206
+ end
207
+
208
+ # List of visit events
209
+ # Event: visit P. | 0 7
210
+ # Event: visit P => arr . | 0 7
211
+ # Event: visit arr. | 0 7
212
+ # Event: visit arr => [ sequence ] . | 0 7
213
+ # Event: visit arr => [ sequence . ] | 0 6
214
+ # Event: visit sequence. | 1 6
215
+ # Event: visit sequence => list . | 1 6
216
+ # Event: visit list. | 1 6
217
+ # Event: visit list => list , integer . | 1 6
218
+ # Event: visit list => list , . integer | 1 5
219
+ # Event: visit list => list . , integer | 1 4
220
+ # Event: visit list. | 1 4
221
+ # Event: visit list => list , integer . | 1 4
222
+ # Event: visit list => list , . integer | 1 3
223
+ # Event: visit list => list . , integer | 1 2
224
+ # Event: visit list. | 1 2
225
+ # Event: visit list => integer . | 1 2
226
+ # Event: visit list => . integer | 1 1
227
+ # Event: visit .list | 1 1
228
+ # Event: visit list => . list , integer | 1 1
229
+ # Event: revisit .list | 1 1
230
+ # Event: revisit list => . list , integer | 1 1
231
+ # Event: revisit .list | 1 1
232
+ # Event: visit sequence => . list | 1 1
233
+ # Event: visit .sequence | 1 1
234
+ # Event: visit arr => [ . sequence ] | 0 1
235
+ # Event: visit arr => . [ sequence ] | 0 0
236
+ # Event: visit .arr | 0 0
237
+ # Event: visit P => . arr | 0 0
238
+ # Event: visit .P | 0 0
239
+
240
+ it 'should accept a first visit event' do
241
+ stack = get_stack(subject)
242
+
243
+ next_event('visit P. | 0 7')
244
+ expect(stack.size).to eq(1)
245
+ # stack: [P[0, 7]]
246
+ expect(stack.last.range).to eq(create_range(0, 7))
247
+ expect(stack.last.children).to be_nil
248
+ end
249
+
250
+ it 'should build a tree for an empty array' do
251
+ stack = get_stack(subject)
252
+
253
+ next_event('visit P. | 0 7')
254
+
255
+ next_event('visit P => arr . | 0 7')
256
+ # stack: [P[0, 7]]
257
+ expect(stack.last.children).to eq([nil])
258
+
259
+ next_event('visit arr. | 0 7')
260
+ expect(stack.size).to eq(2)
261
+ # stack: [P[0, 7], arr[0, 7]]
262
+ expect(stack.last.range).to eq(create_range(0, 7))
263
+ expect(stack.last.children).to be_nil
264
+
265
+ next_event('visit arr => [ sequence ] . | 0 7')
266
+ # stack: [P[0, 7], arr[0, 7]]
267
+ rbracket = stack.last.children[-1]
268
+ expect(rbracket).to be_kind_of(PTree::TerminalNode)
269
+ expect(rbracket.to_s).to eq("][6, 7]: ']'")
270
+
271
+ next_event('visit arr => [ sequence . ] | 0 6')
272
+
273
+ next_event('visit sequence. | 1 6')
274
+ expect(stack.size).to eq(3)
275
+ # stack: [P[0, 7], arr[0, 7], sequence[1, 6]]
276
+ expect(stack.last.range).to eq(create_range(1, 6))
277
+ expect(stack.last.children).to be_nil
278
+
279
+ next_event('visit sequence => list . | 1 6')
280
+ expect(stack.last.children).to eq([nil])
281
+
282
+ next_event('visit list. | 1 6')
283
+ expect(stack.size).to eq(4)
284
+ # stack: [P[0, 7], arr[0, 7], sequence[1, 6], list[1, 6]]
285
+ expect(stack.last.range).to eq(create_range(1, 6))
286
+ expect(stack.last.children).to be_nil
287
+
288
+ next_event('visit list => list , integer . | 1 6')
289
+ intval = stack.last.children[-1]
290
+ expect(intval).to be_kind_of(IntegerNode)
291
+ expect(intval.value).to eq(5)
292
+
293
+ next_event('visit list => list , . integer | 1 5')
294
+ comma = stack.last.children[-2]
295
+ expect(comma).to be_kind_of(PTree::TerminalNode)
296
+ expect(comma.to_s).to eq(",[4, 5]: ','")
297
+
298
+ next_event('visit list => list . , integer | 1 4')
299
+
300
+ next_event('visit list. | 1 4')
301
+ expect(stack.size).to eq(5)
302
+ # stack: [P[0, 7], arr[0, 7], sequence[1, 6], list[1, 6], list[1, 4]
303
+ expect(stack.last.range).to eq(create_range(1, 4))
304
+ expect(stack.last.children).to be_nil
305
+
306
+ next_event('visit list => list , integer . | 1 4')
307
+ intval = stack.last.children[-1]
308
+ expect(intval).to be_kind_of(IntegerNode)
309
+ expect(intval.value).to eq(3)
310
+
311
+ next_event('visit list => list , . integer | 1 3')
312
+ comma = stack.last.children[-2]
313
+ expect(comma).to be_kind_of(PTree::TerminalNode)
314
+ expect(comma.to_s).to eq(",[2, 3]: ','")
315
+
316
+ next_event('visit list => list . , integer | 1 2')
317
+
318
+ next_event('visit list. | 1 2')
319
+ expect(stack.size).to eq(6)
320
+ # stack: [P[0, 7], arr[0, 7], sequence[1, 6], list[1, 6], list[1, 4], list[1, 2]
321
+ expect(stack.last.range).to eq(create_range(1, 2))
322
+ expect(stack.last.children).to be_nil
323
+
324
+ next_event('visit list => integer . | 1 2')
325
+ intval = stack.last.children[-1]
326
+ expect(intval).to be_kind_of(IntegerNode)
327
+ expect(intval.value).to eq(2)
328
+
329
+ next_event('visit list => . integer | 1 1')
330
+ expect(stack.size).to eq(5)
331
+ # stack: [P[0, 7], arr[0, 7], sequence[1, 6], list[1, 6], list[1, 4]
332
+ list_node = stack.last.children[0]
333
+ expect(list_node).to be_kind_of(ArrayNode)
334
+ expect(list_node.children.size).to eq(1)
335
+ expect(list_node.children.last.value).to eq(2)
336
+
337
+ next_event('visit .list | 1 1')
338
+
339
+ next_event('visit list => . list , integer | 1 1')
340
+ expect(stack.size).to eq(4)
341
+ # stack: [P[0, 7], arr[0, 7], sequence[1, 6], list[1, 6]
342
+ list_node = stack.last.children[0]
343
+ expect(list_node).to be_kind_of(ArrayNode)
344
+ expect(list_node.children.size).to eq(2)
345
+ expect(list_node.children.last.value).to eq(3)
346
+
347
+ next_event('revisit .list | 1 1')
348
+
349
+ next_event('revisit list => . list , integer | 1 1')
350
+ # stack: [P[0, 7], arr[0, 7], sequence[1, 6]
351
+ list_node = stack.last.children.last
352
+ expect(list_node).to be_kind_of(ArrayNode)
353
+ expect(list_node.children.size).to eq(3)
354
+ expect(list_node.children.last.value).to eq(5)
355
+
356
+ next_event('revisit .list | 1 1')
357
+
358
+ next_event('visit sequence => . list | 1 1')
359
+ expect(stack.size).to eq(2)
360
+ # stack: [P[0, 7], arr[0, 7]
361
+ list_node = stack.last.children[1]
362
+ expect(list_node).to be_kind_of(ArrayNode)
363
+ expect(list_node.children.size).to eq(3)
364
+
365
+ next_event('visit .sequence | 1 1')
366
+
367
+ next_event('visit arr => [ . sequence ] | 0 1')
368
+ lbracket = stack.last.children[0]
369
+ expect(lbracket).to be_kind_of(PTree::TerminalNode)
370
+ expect(lbracket.to_s).to eq("[[0, 1]: '['")
371
+
372
+ next_event('visit arr => . [ sequence ] | 0 0')
373
+ expect(stack.size).to eq(1)
374
+ # stack: [P[0, 7]
375
+ array_node = stack.last.children[0]
376
+ expect(array_node).to be_kind_of(ArrayNode)
377
+ expect(array_node.children.size).to eq(3)
378
+
379
+
380
+ next_event('visit .arr | 0 0')
381
+
382
+ next_event('visit P => . arr | 0 0')
383
+ expect(stack.size).to eq(0)
384
+ expect(subject.result).not_to be_nil
385
+ root = subject.result.root
386
+ expect(root.interpret).to eq([2, 3, 5])
387
+
388
+ next_event('visit .P | 0 0')
389
+ end
390
+ end # context
391
+ end # describe
392
+ end # module
393
+ end # module
394
+
395
+ # End of file