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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/examples/data_formats/JSON/cli_options.rb +25 -9
- data/examples/data_formats/JSON/json_ast_builder.rb +152 -0
- data/examples/data_formats/JSON/json_ast_nodes.rb +141 -0
- data/examples/data_formats/JSON/json_demo.rb +24 -8
- data/examples/general/calc_iter1/calc_ast_builder.rb +142 -0
- data/examples/general/calc_iter1/calc_ast_nodes.rb +151 -0
- data/examples/general/calc_iter1/calc_demo.rb +38 -0
- data/examples/general/calc_iter1/calc_grammar.rb +25 -0
- data/examples/general/calc_iter1/calc_lexer.rb +81 -0
- data/examples/general/{calc → calc_iter1}/calc_parser.rb +0 -0
- data/examples/general/calc_iter1/spec/calculator_spec.rb +73 -0
- data/examples/general/calc_iter2/calc_ast_builder.rb +186 -0
- data/examples/general/calc_iter2/calc_ast_nodes.rb +151 -0
- data/examples/general/{calc → calc_iter2}/calc_demo.rb +3 -2
- data/examples/general/{calc → calc_iter2}/calc_grammar.rb +0 -0
- data/examples/general/calc_iter2/calc_lexer.rb +81 -0
- data/examples/general/calc_iter2/calc_parser.rb +24 -0
- data/lib/rley.rb +1 -0
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/parser/cst_builder.rb +5 -225
- data/lib/rley/parser/gfg_parsing.rb +2 -2
- data/lib/rley/parser/parse_forest_factory.rb +1 -1
- data/lib/rley/parser/parse_rep_creator.rb +2 -2
- data/lib/rley/parser/parse_tree_builder.rb +161 -104
- data/lib/rley/parser/parse_tree_factory.rb +6 -2
- data/spec/rley/parser/ast_builder_spec.rb +395 -0
- data/spec/rley/support/grammar_arr_int_helper.rb +21 -11
- metadata +20 -9
- data/examples/general/calc/calc_lexer.rb +0 -90
- 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
|
-
#
|
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
|
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
|
-
@
|
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
|
-
|
56
|
-
|
57
|
-
|
67
|
+
protected
|
68
|
+
|
69
|
+
# Return the stack
|
70
|
+
def stack()
|
71
|
+
return @stack
|
58
72
|
end
|
59
73
|
|
60
74
|
private
|
61
75
|
|
62
|
-
|
63
|
-
|
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
|
75
|
-
|
76
|
-
|
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
|
-
|
86
|
-
|
87
|
-
#
|
88
|
-
if
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
108
|
-
|
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
|
-
|
122
|
-
|
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
|
127
|
-
|
128
|
-
|
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
|
-
#
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
-
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
158
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|