rley 0.5.01 → 0.5.02

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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