rley 0.4.08 → 0.5.00

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0ce91c79f9088da09449654c9b6e1b6e0581cf1c
4
- data.tar.gz: 3b1e3cc1ce21d3b031b7b09cdbb7606e2eb5a747
3
+ metadata.gz: a0a5683c3a64ffa38c57d916ecbca45fa2f8f0b2
4
+ data.tar.gz: 77c4d6c99c3836c37e129cce40a9457fe74fbb8e
5
5
  SHA512:
6
- metadata.gz: 7bd55bc558c857bab67f080169c1f9d72705fb66fda177c63032fdb0e8e6674efd1119f9fafba3681593dbc9b027beb05081bfe3f38fb99f46068f94b2563282
7
- data.tar.gz: 54186ced4b193fa133b48ecdb2cddad7df0960cd2499b2ae48bc586a657cc7a2e49466b42570d78e8be4d0d123b6c821f5c9ab75b98d2e3c3d04d4876fd61f0e
6
+ metadata.gz: 7f4d20ab683694d83fb8f16933bd3cf7dccfa39e1166d0ae2571503f576074c53d13ad26006016ea4518627e3d94596ec91af527394e33d71d825f12c64611dc
7
+ data.tar.gz: 620870b9f7ddb191fb1899a13ae08dd97feb01ef33e96d6b40e752ac4e01d47b5bfd0f09107a017babe00fec08cb44579d45fae0d8583a4d8d801dd1f76de216
@@ -1,3 +1,8 @@
1
+ ### 0.5.00 / 2017-08-20
2
+ * [CHANGE] Concrete Syntax tree generation re-designed in prevision of customized tree generation.
3
+ * [NEW] Class `Parser::CSTBuilder` Builder class that creates Concrete Syntax (parse) Tree.
4
+
5
+
1
6
  ### 0.4.08 / 2017-08-06
2
7
  * [FIX] File `/spec/spec_helper.rb` replaced deprecated syntax for `SimpleCov::Formatter::MultiFormatter` construction
3
8
  * [NEW] File `examples/data_formats/JSON/json_minifier.rb` Added a working JSON minifier to the demo app.
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Rley # Module used as a namespace
5
5
  # The version number of the gem.
6
- Version = '0.4.08'.freeze
6
+ Version = '0.5.00'.freeze
7
7
 
8
8
  # Brief description of the gem.
9
9
  Description = "Ruby implementation of the Earley's parsing algorithm".freeze
@@ -14,6 +14,7 @@ module Rley # This module is used as a namespace
14
14
  # Build a visitor for the given ptree.
15
15
  # @param aParseTree [ParseTree] the parse tree to visit.
16
16
  def initialize(aParseTree, aTraversalStrategy = :post_order)
17
+ raise StandardError if aParseTree.nil?
17
18
  @ptree = aParseTree
18
19
  @subscribers = []
19
20
  @traversal = aTraversalStrategy
@@ -0,0 +1,271 @@
1
+ require_relative '../tokens/token_range'
2
+ require_relative '../syntax/terminal'
3
+ require_relative '../syntax/non_terminal'
4
+ require_relative '../gfg/end_vertex'
5
+ require_relative '../gfg/item_vertex'
6
+ require_relative '../gfg/start_vertex'
7
+ require_relative '../ptree/non_terminal_node'
8
+ require_relative '../ptree/terminal_node'
9
+ require_relative '../ptree/parse_tree'
10
+
11
+ module Rley # This module is used as a namespace
12
+ module Parser # This module is used as a namespace
13
+ # Structure used internally by CSTBuilder 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 CSTBuilder 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.
28
+ # The Builder pattern creates a complex object
29
+ # (say, a parse tree) from simpler objects (terminal and non-terminal
30
+ # nodes) and using a step by step approach.
31
+ class CSTBuilder
32
+ # @return [Array<Token>] The sequence of input tokens
33
+ attr_reader(:tokens)
34
+
35
+ # Link to CST object (being) built.
36
+ attr_reader(:result)
37
+
38
+
39
+ # Create a new builder instance.
40
+ # @param theTokens [Array<Token>] The sequence of input tokens.
41
+ def initialize(theTokens)
42
+ @tokens = theTokens
43
+ @stack = []
44
+ end
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
52
+ def receive_event(anEvent, anEntry, anIndex)
53
+ # puts "Event: #{anEvent} #{anEntry} #{anIndex}"
54
+ if anEntry.dotted_entry? # A N => alpha . beta pattern?
55
+ process_item_entry(anEvent, anEntry, anIndex)
56
+ elsif anEntry.start_entry? # A .N pattern?
57
+ process_start_entry(anEvent, anEntry, anIndex)
58
+ elsif anEntry.end_entry? # A N. pattern?
59
+ process_end_entry(anEvent, anEntry, anIndex)
60
+ else
61
+ raise NotImplementedError
62
+ end
63
+
64
+ @last_visitee = anEntry
65
+ end
66
+
67
+ protected
68
+
69
+ # Return the stack
70
+ def stack()
71
+ return @stack
72
+ end
73
+
74
+ private
75
+
76
+ # Return the top of stack element.
77
+ def tos()
78
+ return @stack.last
79
+ end
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
85
+ def process_end_entry(anEvent, anEntry, anIndex)
86
+ case anEvent
87
+ when :visit
88
+ range = { low: anEntry.origin, high: anIndex }
89
+ non_terminal = entry2nonterm(anEntry)
90
+ # Create raw node and push onto stack
91
+ push_raw_node(range, non_terminal)
92
+ else
93
+ raise NotImplementedError
94
+ end
95
+ end
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
109
+ def process_item_entry(anEvent, anEntry, anIndex)
110
+ # TODO: what if rhs is empty?
111
+ case anEvent
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)
123
+ end
124
+ else
125
+ $stderr.puts "waiko '#{anEvent}'"
126
+ raise NotImplementedError
127
+ end
128
+ end
129
+
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
138
+
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
144
+
145
+
146
+
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)
163
+ end
164
+ end
165
+
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)
171
+ end
172
+
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)
177
+ end
178
+
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?
185
+ end
186
+
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
195
+ token_position = anIndex - 1
196
+ token = tokens[token_position]
197
+ term_node = new_leaf_node(term_symbol, token_position, token)
198
+
199
+ # Second make it a child of TOS...
200
+ pos = anEntry.vertex.dotted_item.prev_position # position in rhs of rule
201
+ place_TOS_child(term_node, pos)
202
+ end
203
+
204
+ # Method to override
205
+ # Create a parse tree object with given
206
+ # node as root node.
207
+ def create_tree(aRootNode)
208
+ return Rley::PTree::ParseTree.new(aRootNode)
209
+ end
210
+
211
+ # Method to override
212
+ # Factory method for creating a node object for the given
213
+ # input token.
214
+ # @param _terminal [Terminal] Terminal symbol associated with the token
215
+ # @param aTokenPosition [Integer] Position of token in the input stream
216
+ # @param aToken [Token] The input token
217
+ def new_leaf_node(_terminal, aTokenPosition, aToken)
218
+ PTree::TerminalNode.new(aToken, aTokenPosition)
219
+ end
220
+
221
+ # Method to override.
222
+ # Factory method for creating a parent node object.
223
+ # @param aProduction [Production] Production rule
224
+ # @param aRange [Range] Range of tokens matched by the rule
225
+ # @param theTokens [Array] The input tokens
226
+ # @param theChildren [Array] Children nodes (one per rhs symbol)
227
+ def new_parent_node(aProduction, aRange, theTokens, theChildren)
228
+ node = Rley::PTree::NonTerminalNode.new(aProduction.lhs, aRange)
229
+ theChildren.reverse_each { |child| node.add_subnode(child) }
230
+ return node
231
+ end
232
+
233
+ # Place the given node object as one of the children of the TOS
234
+ # (TOS = Top Of Stack).
235
+ # Each child has a position that is dictated by the position of the
236
+ # related grammar symbol in the right-handed side (RHS) of the grammar
237
+ # rule.
238
+ # @param aNode [TerminalNode, NonTerminalNode] Node object to be placed
239
+ # @param aRHSPos [Integer, NilClass] Position in RHS of rule.
240
+ # If the position is provided, then the node will placed in the children
241
+ # array at that position.
242
+ # If the position is nil, then the node will be placed at the position of
243
+ # the rightmost nil element in children array.
244
+ def place_TOS_child(aNode, aRHSPos)
245
+ if aRHSPos.nil?
246
+ # Retrieve index of most rightmost nil child...
247
+ pos = tos.children.rindex { |child| child.nil? }
248
+ raise StandardError, 'Internal error' if pos.nil?
249
+ else
250
+ pos = aRHSPos
251
+ end
252
+
253
+ tos.children[pos] = aNode
254
+ end
255
+
256
+ # Retrieve non-terminal symbol of given parse entry
257
+ def entry2nonterm(anEntry)
258
+ case anEntry.vertex
259
+ when GFG::StartVertex, GFG::EndVertex
260
+ non_terminal = anEntry.vertex.non_terminal
261
+ when GFG::ItemVertex
262
+ non_terminal = anEntry.vertex.lhs
263
+ end
264
+
265
+ return non_terminal
266
+ end
267
+ end # class
268
+ end # module
269
+ end # module
270
+
271
+ # End of file
@@ -1,5 +1,6 @@
1
1
  require_relative 'parse_rep_creator'
2
- require_relative 'parse_tree_builder'
2
+ # require_relative 'parse_tree_builder' # TODO remove this line
3
+ require_relative 'cst_builder'
3
4
 
4
5
  module Rley # This module is used as a namespace
5
6
  module Parser # This module is used as a namespace
@@ -12,7 +13,7 @@ module Rley # This module is used as a namespace
12
13
  # Create a Builder, that is, an object
13
14
  # that will create piece by piece the forest
14
15
  def builder(aParseResult)
15
- ParseTreeBuilder.new(aParseResult.tokens)
16
+ CSTBuilder.new(aParseResult.tokens)
16
17
  end
17
18
  end # class
18
19
  end # module
@@ -124,9 +124,13 @@ module Rley # This module is used as a namespace
124
124
  event = [:revisit, anEntry, index]
125
125
 
126
126
  when GFG::StartVertex
127
+ # Even for non-ambiguous parse, can be caused by
128
+ # left recursive rule e.g. (S => S A)
127
129
  event = [:revisit, anEntry, index]
128
130
 
129
131
  when GFG::ItemVertex
132
+ # Even for non-ambiguous parse, can be caused by
133
+ # left recursive rule e.g. (S => S A)
130
134
  # Skip item entries while revisiting
131
135
  event = [:revisit, anEntry, index]
132
136
  else
@@ -29,6 +29,8 @@ module Rley # This module is used as a namespace
29
29
  result = low == other[:low] && high == other[:high]
30
30
  when TokenRange
31
31
  result = low == other.low && high == other.high
32
+ when Range
33
+ result = low == other.first && high == other.last
32
34
  when Array
33
35
  result = low == other[0] && high == other[1]
34
36
  end
@@ -0,0 +1,439 @@
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
+
6
+ require_relative '../support/expectation_helper'
7
+ require_relative '../support/grammar_b_expr_helper'
8
+ require_relative '../support/grammar_arr_int_helper'
9
+
10
+ # Load the class under test
11
+ require_relative '../../../lib/rley/parser/cst_builder'
12
+
13
+ module Rley # Open this namespace to avoid module qualifier prefixes
14
+ module Parser
15
+ describe CSTBuilder do
16
+ include ExpectationHelper # Mix-in with expectation on parse entry sets
17
+ include GrammarBExprHelper # Mix-in for basic arithmetic language
18
+ include GrammarArrIntHelper # Mix-in for array of integers language
19
+
20
+ let(:sample_grammar) do
21
+ builder = grammar_expr_builder
22
+ builder.grammar
23
+ end
24
+
25
+ let(:sample_tokens) do
26
+ expr_tokenizer('2 + 3 * 4', sample_grammar)
27
+ end
28
+
29
+ subject { CSTBuilder.new(sample_tokens) }
30
+
31
+ def init_walker(theParser, theTokens)
32
+ result = theParser.parse(theTokens)
33
+ factory = ParseWalkerFactory.new
34
+ accept_entry = result.accepting_entry
35
+ accept_index = result.chart.last_index
36
+ @walker = factory.build_walker(accept_entry, accept_index)
37
+ end
38
+
39
+ def skip_events(count)
40
+ count.times do
41
+ event = @walker.next
42
+ subject.receive_event(*event)
43
+ end
44
+ end
45
+
46
+ def get_stack(aBuilder)
47
+ return aBuilder.send(:stack)
48
+ end
49
+
50
+ def create_range(low, high)
51
+ return Tokens::TokenRange.new({low: low, high: high })
52
+ end
53
+
54
+ context 'Initialization:' do
55
+ it 'should be created with a sequence of tokens' do
56
+ expect { CSTBuilder.new(sample_tokens) }.not_to raise_error
57
+ end
58
+
59
+ it 'should know the input tokens' do
60
+ expect(subject.tokens).to eq(sample_tokens)
61
+ end
62
+
63
+ it "shouldn't know the result yet" do
64
+ expect(subject.result).to be_nil
65
+ end
66
+
67
+ it 'should have an empty stack' do
68
+ expect(subject.send(:stack)).to be_empty
69
+ end
70
+ end # context
71
+
72
+
73
+
74
+ context 'Parse tree construction (no null symbol):' do
75
+ before(:each) do
76
+ parser = Parser::GFGEarleyParser.new(sample_grammar)
77
+ init_walker(parser, sample_tokens)
78
+ end
79
+
80
+ # Event: visit P. | 0 5
81
+ # Event: visit P => S . | 0 5
82
+ # Event: visit S. | 0 5
83
+ # Event: visit S => S + M . | 0 5
84
+ # Event: visit M. | 2 5
85
+ # Event: visit M => M * T . | 2 5
86
+ # Event: visit T. | 4 5
87
+ # Event: visit T => integer . | 4 5
88
+ # Event: visit T => . integer | 4 4
89
+ # Event: visit .T | 4 4
90
+ # Event: visit M => M * . T | 2 4
91
+ # Event: visit M => M . * T | 2 3
92
+ # Event: visit M. | 2 3
93
+ # Event: visit M => T . | 2 3
94
+ # Event: visit T. | 2 3
95
+ # Event: visit T => integer . | 2 3
96
+ # Event: visit T => . integer | 2 2
97
+ # Event: visit .T | 2 2
98
+ # Event: visit M => . T | 2 2
99
+ # Event: visit .M | 2 2
100
+ # Event: visit M => . M * T | 2 2
101
+ # Event: revisit .M | 2 2 <= revisit because of left recursive rule
102
+ # Event: visit S => S + . M | 0 2
103
+ # Event: visit S => S . + M | 0 1
104
+ # Event: visit S. | 0 1
105
+ # Event: visit S => M . | 0 1
106
+ # Event: visit M. | 0 1
107
+ # Event: visit M => T . | 0 1
108
+ # Event: visit T. | 0 1
109
+ # Event: visit T => integer . | 0 1
110
+ # Event: visit T => . integer | 0 0
111
+ # Event: visit .T | 0 0
112
+ # Event: visit M => . T | 0 0
113
+ # Event: visit .M | 0 0
114
+ # Event: visit S => . M | 0 0
115
+ # Event: visit .S | 0 0
116
+ # Event: visit S => . S + M | 0 0
117
+ # Event: revisit .S | 0 0
118
+ # Event: visit P => . S | 0 0
119
+ # Event: visit .P | 0 0
120
+
121
+ it 'should react to a first end event' do
122
+ event = @walker.next
123
+ expect { subject.receive_event(*event) }.not_to raise_error
124
+ stack = get_stack(subject)
125
+ expect(stack.size).to eq(1)
126
+ expect(stack.last.range).to eq(create_range(0, 5))
127
+ expect(stack.last.children).to be_nil
128
+ end
129
+
130
+ it 'should react to a first exit event' do
131
+ skip_events(1)
132
+ event = @walker.next
133
+ expect { subject.receive_event(*event) }.not_to raise_error
134
+ stack = get_stack(subject)
135
+ expect(stack.size).to eq(1)
136
+ expect(stack.last.children).to eq([nil])
137
+ end
138
+
139
+ it 'should react to a second end event' do
140
+ skip_events(2)
141
+ event = @walker.next
142
+ expect { subject.receive_event(*event) }.not_to raise_error
143
+ stack = get_stack(subject)
144
+ expect(stack.size).to eq(2)
145
+ expect(stack.last.range).to eq(create_range(0, 5))
146
+ expect(stack.last.children).to be_nil
147
+ end
148
+
149
+ it 'should react to a second exit event' do
150
+ skip_events(3)
151
+ event = @walker.next
152
+ expect { subject.receive_event(*event) }.not_to raise_error
153
+ stack = get_stack(subject)
154
+ expect(stack.size).to eq(2)
155
+ expect(stack.last.children).to eq([nil, nil, nil])
156
+ end
157
+
158
+ it 'should react to an exit event that creates a terminal node' do
159
+ skip_events(7)
160
+ event = @walker.next
161
+ expect { subject.receive_event(*event) }.not_to raise_error
162
+ stack = get_stack(subject)
163
+ expect(stack.size).to eq(4)
164
+ expect(stack.last.children.size).to eq(1)
165
+ child = stack.last.children[-1]
166
+ expect(child).to be_kind_of(PTree::TerminalNode)
167
+ expect(child.to_s).to eq("integer[4, 5]: '4'")
168
+ end
169
+
170
+
171
+ it 'should react to a first entry event' do
172
+ skip_events(8)
173
+ event = @walker.next
174
+ expect { subject.receive_event(*event) }.not_to raise_error
175
+ stack = get_stack(subject)
176
+ expect(stack.size).to eq(3) # Element popped
177
+ expect(stack.last.children.size).to eq(3)
178
+ child = stack.last.children[-1]
179
+ expect(child).to be_kind_of(PTree::NonTerminalNode)
180
+ expect(child.to_s).to eq('T[4, 5]')
181
+ end
182
+
183
+ it 'should react to a first start event' do
184
+ skip_events(9)
185
+ event = @walker.next
186
+ expect { subject.receive_event(*event) }.not_to raise_error
187
+ stack = get_stack(subject)
188
+ expect(stack.size).to eq(3)
189
+ end
190
+
191
+ it 'should react to an middle event that creates a terminal node' do
192
+ skip_events(10)
193
+ event = @walker.next
194
+ expect { subject.receive_event(*event) }.not_to raise_error
195
+ stack = get_stack(subject)
196
+ expect(stack.size).to eq(3)
197
+ expect(stack.last.children.size).to eq(3)
198
+ child = stack.last.children[1]
199
+ expect(child).to be_kind_of(PTree::TerminalNode)
200
+ expect(child.to_s).to eq("*[3, 4]: '*'")
201
+ end
202
+
203
+ it 'should react to an exit event that creates a terminal node' do
204
+ skip_events(15)
205
+ event = @walker.next
206
+ expect { subject.receive_event(*event) }.not_to raise_error
207
+ stack = get_stack(subject)
208
+ expect(stack.size).to eq(5)
209
+ expect(stack.last.children.size).to eq(1)
210
+ child = stack.last.children[-1]
211
+ expect(child).to be_kind_of(PTree::TerminalNode)
212
+ expect(child.to_s).to eq("integer[2, 3]: '3'")
213
+ end
214
+
215
+ it 'should ignore to a revisit event' do
216
+ skip_events(21)
217
+ event = @walker.next
218
+ expect { subject.receive_event(*event) }.not_to raise_error
219
+ stack = get_stack(subject)
220
+ expect(stack.size).to eq(2)
221
+ expect(stack.last.children.size).to eq(3)
222
+ child = stack.last.children[-1]
223
+ expect(child).to be_kind_of(PTree::NonTerminalNode)
224
+ expect(child.to_s).to eq('M[2, 5]')
225
+ end
226
+
227
+ it 'should react to a 2nd middle event that creates a terminal node' do
228
+ skip_events(22)
229
+ event = @walker.next
230
+ expect { subject.receive_event(*event) }.not_to raise_error
231
+ stack = get_stack(subject)
232
+ expect(stack.size).to eq(2)
233
+ expect(stack.last.children.size).to eq(3)
234
+ child = stack.last.children[1]
235
+ expect(child).to be_kind_of(PTree::TerminalNode)
236
+ expect(child.to_s).to eq("+[1, 2]: '+'")
237
+ end
238
+
239
+ it 'should react to a exit event that creates a terminal node' do
240
+ skip_events(29)
241
+ event = @walker.next
242
+ expect { subject.receive_event(*event) }.not_to raise_error
243
+ stack = get_stack(subject)
244
+ expect(stack.size).to eq(5)
245
+ expect(stack.last.children.size).to eq(1)
246
+ child = stack.last.children[-1]
247
+ expect(child).to be_kind_of(PTree::TerminalNode)
248
+ expect(child.to_s).to eq("integer[0, 1]: '2'")
249
+ end
250
+
251
+
252
+ it 'should react to entry event 31' do
253
+ skip_events(30)
254
+ event = @walker.next
255
+ expect { subject.receive_event(*event) }.not_to raise_error
256
+ stack = get_stack(subject)
257
+ expect(stack.size).to eq(4)
258
+ expect(stack.last.children.size).to eq(1)
259
+ child = stack.last.children[-1]
260
+ expect(child).to be_kind_of(PTree::NonTerminalNode)
261
+ expect(child.to_s).to eq('T[0, 1]')
262
+ end
263
+
264
+ it 'should react to entry event 33' do
265
+ skip_events(32)
266
+ event = @walker.next
267
+ expect { subject.receive_event(*event) }.not_to raise_error
268
+ stack = get_stack(subject)
269
+ expect(stack.size).to eq(3)
270
+ expect(stack.last.children.size).to eq(1)
271
+ child = stack.last.children[-1]
272
+ expect(child).to be_kind_of(PTree::NonTerminalNode)
273
+ expect(child.to_s).to eq('M[0, 1]')
274
+ end
275
+
276
+ it 'should react to entry event 35' do
277
+ skip_events(34)
278
+ event = @walker.next
279
+ expect { subject.receive_event(*event) }.not_to raise_error
280
+ stack = get_stack(subject)
281
+ expect(stack.size).to eq(2)
282
+ expect(stack.last.children.size).to eq(3)
283
+ child = stack.last.children[0]
284
+ expect(child).to be_kind_of(PTree::NonTerminalNode)
285
+ expect(child.to_s).to eq('S[0, 1]')
286
+ end
287
+
288
+ it 'should react to entry event 37' do
289
+ skip_events(36)
290
+ event = @walker.next
291
+ expect { subject.receive_event(*event) }.not_to raise_error
292
+ stack = get_stack(subject)
293
+ expect(stack.size).to eq(1)
294
+ expect(stack.last.children.size).to eq(1)
295
+ child = stack.last.children[0]
296
+ expect(child).to be_kind_of(PTree::NonTerminalNode)
297
+ expect(child.to_s).to eq('S[0, 5]')
298
+ end
299
+
300
+ it 'should react to entry event that creates the tree' do
301
+ skip_events(38)
302
+ event = @walker.next
303
+ expect { subject.receive_event(*event) }.not_to raise_error
304
+ stack = get_stack(subject)
305
+ expect(stack).to be_empty
306
+ expect(subject.result).to be_kind_of(PTree::ParseTree)
307
+
308
+ # Lightweight sanity check
309
+ expect(subject.result.root.to_s).to eq('P[0, 5]')
310
+ expect(subject.result.root.subnodes.size).to eq(1)
311
+ child_node = subject.result.root.subnodes[0]
312
+ expect(child_node.to_s).to eq('S[0, 5]')
313
+ expect(child_node.subnodes.size).to eq(3)
314
+ first_grandchild = child_node.subnodes[0]
315
+ expect(first_grandchild.to_s).to eq('S[0, 1]')
316
+ second_grandchild = child_node.subnodes[1]
317
+ expect(second_grandchild.to_s).to eq("+[1, 2]: '+'")
318
+ third_grandchild = child_node.subnodes[2]
319
+ expect(third_grandchild.to_s).to eq('M[2, 5]')
320
+ end
321
+ end # context
322
+
323
+ context 'Parse tree construction with null symbol:' do
324
+ def next_event(expectation)
325
+ event = @walker.next
326
+ (ev_type, entry, index) = event
327
+ actual = "#{ev_type} #{entry} #{index}"
328
+ expect(actual).to eq(expectation)
329
+ @instance.receive_event(*event)
330
+ end
331
+
332
+ let(:array_grammar) do
333
+ builder = grammar_arr_int_builder
334
+ builder.grammar
335
+ end
336
+
337
+ before(:each) do
338
+ @parser = Parser::GFGEarleyParser.new(array_grammar)
339
+ end
340
+
341
+ # The visit events were generated with the following snippets:
342
+ # 13.times do
343
+ # event = @walker.next
344
+ # subject.receive_event(*event)
345
+ # end
346
+ # The events are:
347
+ # Event: visit P. | 0 2
348
+ # Event: visit P => arr . | 0 2
349
+ # Event: visit arr. | 0 2
350
+ # Event: visit arr => [ sequence ] . | 0 2
351
+ # Event: visit arr => [ sequence . ] | 0 1
352
+ # Event: visit sequence. | 1 1
353
+ # Event: visit sequence => . | 1 1
354
+ # Event: visit .sequence | 1 1
355
+ # Event: visit arr => [ . sequence ] | 0 1
356
+ # Event: visit arr => . [ sequence ] | 0 0
357
+ # Event: visit .arr | 0 0
358
+ # Event: visit P => . arr | 0 0
359
+ # Event: visit .P | 0 0
360
+ it 'should build a tree for an empty array' do
361
+ empty_arr_tokens = arr_int_tokenizer('[ ]', array_grammar)
362
+ @instance = CSTBuilder.new(empty_arr_tokens)
363
+ init_walker(@parser, empty_arr_tokens)
364
+ stack = get_stack(@instance)
365
+
366
+ next_event('visit P. | 0 2')
367
+ expect(stack.size).to eq(1)
368
+ # stack: [P[0, 2]]
369
+ expect(stack.last.range).to eq(create_range(0, 2))
370
+ expect(stack.last.children).to be_nil
371
+
372
+ next_event('visit P => arr . | 0 2')
373
+ expect(stack.last.children).to eq([nil])
374
+
375
+ next_event('visit arr. | 0 2')
376
+ expect(stack.size).to eq(2)
377
+ # stack: [arr[0, 2], P[0, 2]]
378
+ expect(stack.last.range).to eq(create_range(0, 2))
379
+ expect(stack.last.children).to be_nil
380
+
381
+ next_event('visit arr => [ sequence ] . | 0 2')
382
+ expect(stack.size).to eq(2)
383
+ expect(stack.last.range).to eq(create_range(0, 2))
384
+ expect(stack.last.children.size).to eq(3)
385
+ child = stack.last.children.last
386
+ expect(child.to_s).to eq("][1, 2]: ']'")
387
+
388
+ next_event('visit arr => [ sequence . ] | 0 1')
389
+ expect(stack.size).to eq(2)
390
+
391
+ next_event('visit sequence. | 1 1')
392
+ expect(stack.size).to eq(3)
393
+ # stack: [sequence[1, 1], arr[0, 2], P[0, 2]]
394
+ expect(stack.last.range).to eq(create_range(1, 1))
395
+ expect(stack.last.children).to be_nil
396
+
397
+ next_event('visit sequence => . | 1 1')
398
+ expect(stack.size).to eq(2)
399
+ # stack: [arr[0, 2], P[0, 2]]
400
+ expect(stack.last.range).to eq(create_range(0, 2))
401
+ sequence = stack.last.children[1]
402
+ expect(sequence).to be_kind_of(PTree::NonTerminalNode)
403
+ expect(sequence.subnodes).to be_empty
404
+ expect(sequence.to_s).to eq('sequence[1, 1]')
405
+
406
+ next_event('visit .sequence | 1 1')
407
+ expect(stack.size).to eq(2)
408
+
409
+ next_event('visit arr => [ . sequence ] | 0 1')
410
+ expect(stack.size).to eq(2)
411
+ expect(stack.last.range).to eq(create_range(0, 2))
412
+ sequence = stack.last.children[0]
413
+ expect(sequence).to be_kind_of(PTree::TerminalNode)
414
+ expect(sequence.to_s).to eq("[[0, 1]: '['")
415
+
416
+ next_event('visit arr => . [ sequence ] | 0 0')
417
+ expect(stack.size).to eq(1)
418
+ # stack: [P[0, 2]]
419
+ expect(stack.last.range).to eq(create_range(0, 2))
420
+ expect(stack.last.children.size).to eq(1)
421
+ expect(stack.last.children[0]).to be_kind_of(PTree::NonTerminalNode)
422
+ expect(stack.last.children[0].to_s).to eq('arr[0, 2]')
423
+
424
+ next_event('visit .arr | 0 0')
425
+ expect(stack.size).to eq(1)
426
+
427
+ next_event('visit P => . arr | 0 0')
428
+ expect(stack).to be_empty
429
+ expect(@instance.result).not_to be_nil
430
+
431
+ next_event('visit .P | 0 0')
432
+ expect(stack).to be_empty
433
+ expect(@instance.result).not_to be_nil
434
+ end
435
+ end # context
436
+ end # describe
437
+ end # module
438
+ end # module
439
+ # End of file
@@ -0,0 +1,41 @@
1
+ # Load the builder class
2
+ require_relative '../../../lib/rley/syntax/grammar_builder'
3
+ require_relative '../../../lib/rley/tokens/token'
4
+
5
+
6
+ module GrammarArrIntHelper
7
+ # Factory method. Creates a grammar builder for a grammar of
8
+ # array of integers.
9
+ # (based on the article about Earley's algorithm in Wikipedia)
10
+ def grammar_arr_int_builder()
11
+ builder = Rley::Syntax::GrammarBuilder.new do
12
+ add_terminals('[', ']', ',', 'integer')
13
+ rule 'P' => 'arr'
14
+ rule 'arr' => %w( [ sequence ] )
15
+ rule 'sequence' => ['list']
16
+ rule 'sequence' => []
17
+ rule 'list' => %w[list , integer]
18
+ rule 'list' => 'integer'
19
+ end
20
+ builder
21
+ end
22
+
23
+ # Basic tokenizer for array of integers
24
+ def arr_int_tokenizer(aText, aGrammar)
25
+ tokens = aText.scan(/\S+/).map do |lexeme|
26
+ case lexeme
27
+ when '[', ']', ','
28
+ terminal = aGrammar.name2symbol[lexeme]
29
+ when /^[-+]?\d+$/
30
+ terminal = aGrammar.name2symbol['integer']
31
+ else
32
+ msg = "Unknown input text '#{lexeme}'"
33
+ raise StandardError, msg
34
+ end
35
+ Rley::Tokens::Token.new(lexeme, terminal)
36
+ end
37
+
38
+ return tokens
39
+ end
40
+ end # module
41
+ # End of file
@@ -55,6 +55,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
55
55
  expect(subject == me).to eq(true)
56
56
  equal = TokenRange.new(low: 0, high: 5)
57
57
  expect(subject == equal).to eq(true)
58
+ # expect(subject == [0..5]).to eq(true)
58
59
  expect(subject == [0, 5]).to eq(true)
59
60
  end
60
61
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rley
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.08
4
+ version: 0.5.00
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-06 00:00:00.000000000 Z
11
+ date: 2017-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: coveralls
@@ -163,6 +163,7 @@ files:
163
163
  - lib/rley/parse_forest_visitor.rb
164
164
  - lib/rley/parse_tree_visitor.rb
165
165
  - lib/rley/parser/base_parser.rb
166
+ - lib/rley/parser/cst_builder.rb
166
167
  - lib/rley/parser/dotted_item.rb
167
168
  - lib/rley/parser/error_reason.rb
168
169
  - lib/rley/parser/gfg_chart.rb
@@ -225,6 +226,7 @@ files:
225
226
  - spec/rley/parse_forest_visitor_spec.rb
226
227
  - spec/rley/parse_tree_visitor_spec.rb
227
228
  - spec/rley/parser/ambiguous_parse_spec.rb
229
+ - spec/rley/parser/cst_builder_spec.rb
228
230
  - spec/rley/parser/dotted_item_spec.rb
229
231
  - spec/rley/parser/error_reason_spec.rb
230
232
  - spec/rley/parser/gfg_chart_spec.rb
@@ -252,6 +254,7 @@ files:
252
254
  - spec/rley/support/expectation_helper.rb
253
255
  - spec/rley/support/grammar_abc_helper.rb
254
256
  - spec/rley/support/grammar_ambig01_helper.rb
257
+ - spec/rley/support/grammar_arr_int_helper.rb
255
258
  - spec/rley/support/grammar_b_expr_helper.rb
256
259
  - spec/rley/support/grammar_helper.rb
257
260
  - spec/rley/support/grammar_l0_helper.rb
@@ -315,6 +318,7 @@ test_files:
315
318
  - spec/rley/gfg/start_vertex_spec.rb
316
319
  - spec/rley/gfg/vertex_spec.rb
317
320
  - spec/rley/parser/ambiguous_parse_spec.rb
321
+ - spec/rley/parser/cst_builder_spec.rb
318
322
  - spec/rley/parser/dotted_item_spec.rb
319
323
  - spec/rley/parser/error_reason_spec.rb
320
324
  - spec/rley/parser/gfg_chart_spec.rb