rley 0.4.08 → 0.5.00

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.
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