rley 0.8.11 → 0.8.13
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/.rubocop.yml +2 -2
- data/.yardopts +2 -2
- data/CHANGELOG.md +10 -0
- data/README.md +3 -3
- data/examples/general/calc_iter2/calc_demo.rb +1 -1
- data/examples/general/recursive_right.rb +601 -0
- data/examples/tokenizer/loxxy_raw_scanner.rex +8 -2
- data/examples/tokenizer/loxxy_raw_scanner.rex.rb +9 -6
- data/examples/tokenizer/loxxy_tokenizer.rb +1 -1
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/engine.rb +5 -3
- data/lib/rley/rgn/grammar_builder.rb +0 -35
- data/spec/rley/parser/dangling_else_spec.rb +1 -1
- data/spec/rley/parser/gfg_earley_parser_spec.rb +2 -2
- data/spec/spec_helper.rb +0 -1
- metadata +20 -22
- data/.travis.yml +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1ad581d39b568b76013e43a7c1a04177845226c8d6a65cf49314a4897ad1e59
|
4
|
+
data.tar.gz: 6b626ec3a1f32e886a057075d90fc42fb07ddc0abe3a9d6abe357ccf934902dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9185b89a40e09b17648415df3ade7d9a2948eee552e938d3452b0abc06082914cc48a4bd67ba389b36d9bf20c09ad42e3bf132487c8ce8d402687a2fa302d1d5
|
7
|
+
data.tar.gz: 5b2b5e1b4abf2f233e21edae90e2ee7a197b5bc9cedbaee5b2690b57562201e331dfff21b4115ffea48d5bd7086b9673cac2985aa9d7942fdac4d199ec1f43d9
|
data/.rubocop.yml
CHANGED
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
### 0.8.13 / 2025-02-16
|
2
|
+
- Tested against MRI Ruby 3.4.1.
|
3
|
+
- [FIX] Fixed most "offenses" reported by Rubocop 1.72.0
|
4
|
+
|
5
|
+
### 0.8.11 / 2022-04-17
|
6
|
+
- Moved dependency on `prime` to gem.
|
7
|
+
|
8
|
+
* [CHANGE] File `rley.gemspec` added runtime dependency on `prime` gem (rationale: was part of stdlib, from 3.1 it is demoted to a bundled gem)
|
9
|
+
* [CHANGE] File `rgn\tokenizer.rb` minor style refactoring to please Rubocop 1.27
|
10
|
+
|
1
11
|
### 0.8.10 / 2022-04-08
|
2
12
|
- Refactoring of `RGN::Tokenizer` class.
|
3
13
|
* [CHANGE] Class `RGN::Tokenizer` changes.
|
data/README.md
CHANGED
@@ -72,5 +72,5 @@ print_tree('Abstract Syntax Tree (AST)', ast_ptree)
|
|
72
72
|
# Now perform the computation of math expression
|
73
73
|
root = ast_ptree.root
|
74
74
|
print_title('Result:')
|
75
|
-
puts root.interpret
|
75
|
+
puts root.interpret # Output the expression result
|
76
76
|
# End of file
|
@@ -0,0 +1,601 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rley'
|
4
|
+
|
5
|
+
# Simple right recursive grammar
|
6
|
+
# based on example in D. Grune, C. Jacobs "Parsing Techniques" book
|
7
|
+
# pp. 224 et sq.
|
8
|
+
# S => a S;
|
9
|
+
# S => ;
|
10
|
+
# This grammar requires a time that is quadratic in the number of
|
11
|
+
# input tokens
|
12
|
+
# Similar grammar is also considered here: https://loup-vaillant.fr/tutorials/earley-parsing/right-recursion
|
13
|
+
builder = Rley::grammar_builder do
|
14
|
+
# Define first the terminal symbols...
|
15
|
+
add_terminals('a')
|
16
|
+
|
17
|
+
# ... then with syntax rules
|
18
|
+
# First found rule is considered to be the top-level rule
|
19
|
+
rule('S' => 'a S')
|
20
|
+
rule('S' => [])
|
21
|
+
end
|
22
|
+
|
23
|
+
# Highly simplified tokenizer implementation.
|
24
|
+
def tokenizer(aText, aGrammar)
|
25
|
+
index = 0
|
26
|
+
tokens = aText.scan(/a/).map do |letter_a|
|
27
|
+
terminal = aGrammar.name2symbol['a']
|
28
|
+
index += 1
|
29
|
+
pos = Rley::Lexical::Position.new(1, index)
|
30
|
+
Rley::Lexical::Token.new(letter_a, terminal, pos)
|
31
|
+
end
|
32
|
+
|
33
|
+
return tokens
|
34
|
+
end
|
35
|
+
|
36
|
+
right_recursive_grammar = builder.grammar.freeze
|
37
|
+
|
38
|
+
|
39
|
+
input_to_parse = 'a' * 5
|
40
|
+
|
41
|
+
parser = Rley::Parser::GFGEarleyParser.new(right_recursive_grammar)
|
42
|
+
tokens = tokenizer(input_to_parse, right_recursive_grammar)
|
43
|
+
result = parser.parse(tokens)
|
44
|
+
|
45
|
+
# p result.chart.to_s
|
46
|
+
|
47
|
+
|
48
|
+
# If we remove the .X and X. entries, then we have exactly the same output than Loup Vaillant
|
49
|
+
|
50
|
+
=begin
|
51
|
+
State[0]
|
52
|
+
.S | 0
|
53
|
+
S => . a S | 0
|
54
|
+
S => . | 0
|
55
|
+
S. | 0
|
56
|
+
State[1]
|
57
|
+
S => a . S | 0
|
58
|
+
.S | 1
|
59
|
+
S => . a S | 1
|
60
|
+
S => . | 1
|
61
|
+
S. | 1
|
62
|
+
S => a S . | 0
|
63
|
+
S. | 0
|
64
|
+
State[2]
|
65
|
+
S => a . S | 1
|
66
|
+
.S | 2
|
67
|
+
S => . a S | 2
|
68
|
+
S => . | 2
|
69
|
+
S. | 2
|
70
|
+
S => a S . | 1
|
71
|
+
S. | 1
|
72
|
+
S => a S . | 0
|
73
|
+
S. | 0
|
74
|
+
State[3]
|
75
|
+
S => a . S | 2
|
76
|
+
.S | 3
|
77
|
+
S => . a S | 3
|
78
|
+
S => . | 3
|
79
|
+
S. | 3
|
80
|
+
S => a S . | 2
|
81
|
+
S. | 2
|
82
|
+
S => a S . | 1
|
83
|
+
S. | 1
|
84
|
+
S => a S . | 0
|
85
|
+
S. | 0
|
86
|
+
State[4]
|
87
|
+
S => a . S | 3
|
88
|
+
.S | 4
|
89
|
+
S => . a S | 4
|
90
|
+
S => . | 4
|
91
|
+
S. | 4
|
92
|
+
S => a S . | 3
|
93
|
+
S. | 3
|
94
|
+
S => a S . | 2
|
95
|
+
S. | 2
|
96
|
+
S => a S . | 1
|
97
|
+
S. | 1
|
98
|
+
S => a S . | 0
|
99
|
+
S. | 0
|
100
|
+
State[5]
|
101
|
+
S => a . S | 4
|
102
|
+
.S | 5
|
103
|
+
S => . a S | 5
|
104
|
+
S => . | 5
|
105
|
+
S. | 5
|
106
|
+
S => a S . | 4
|
107
|
+
S. | 4
|
108
|
+
S => a S . | 3
|
109
|
+
S. | 3
|
110
|
+
S => a S . | 2
|
111
|
+
S. | 2
|
112
|
+
S => a S . | 1
|
113
|
+
S. | 1
|
114
|
+
S => a S . | 0
|
115
|
+
S. | 0
|
116
|
+
=end
|
117
|
+
|
118
|
+
=begin
|
119
|
+
from the right recursive grammar:
|
120
|
+
Terminals: a
|
121
|
+
Rules:
|
122
|
+
S => a S
|
123
|
+
S => []
|
124
|
+
|
125
|
+
... the following table is generated:
|
126
|
+
| | .S | S. | (S -> .a S) |
|
127
|
+
|:-------------:|:--:|:--:|-------------|
|
128
|
+
| .S start | | Ta | Tb |
|
129
|
+
| S. end | | | |
|
130
|
+
| (S -> .a S) | | Tc | Td |
|
131
|
+
|
132
|
+
Two first rows are start and end nodes
|
133
|
+
Remaining rows are for nodes with outgoing scan edge
|
134
|
+
|
135
|
+
Algorithm to build the table
|
136
|
+
start node
|
137
|
+
end node
|
138
|
+
find every scan edge
|
139
|
+
|
140
|
+
=end
|
141
|
+
|
142
|
+
|
143
|
+
def build_gfg(aGrammar)
|
144
|
+
items_builder = Object.new.extend(Rley::Base::GrmItemsBuilder)
|
145
|
+
items = items_builder.build_dotted_items(aGrammar)
|
146
|
+
gfg = Rley::GFG::GrmFlowGraph.new(items)
|
147
|
+
end
|
148
|
+
|
149
|
+
graph = build_gfg(right_recursive_grammar)
|
150
|
+
|
151
|
+
=begin
|
152
|
+
Execution simulation table construction
|
153
|
+
Do a df traversal from start vertex
|
154
|
+
current_from: .S
|
155
|
+
|
156
|
+
Visit .S ; stack = [.S]
|
157
|
+
|
158
|
+
Visit S => .a S ; stack = [.S, S => . a S]
|
159
|
+
scan edge detected
|
160
|
+
table[.S] = {(S => . a S) => [[S => . a S]]}
|
161
|
+
current_from: (S => . a S)
|
162
|
+
|
163
|
+
|
164
|
+
Visit S => a . S; stack = [.S, S => . a S, S => a . S]
|
165
|
+
call edge to .S
|
166
|
+
current_from: (S => . a S)
|
167
|
+
prov_table[(S => . a S)] => { .S => [S => a . S] }
|
168
|
+
|
169
|
+
Visit .S; [.S, S => . a S, S => a . S, .S]
|
170
|
+
.S is a table heading
|
171
|
+
prov_table[(S => . a S)] => { .S => [S => a . S, .S] }
|
172
|
+
current_from: .S
|
173
|
+
|
174
|
+
Visit S => . ; stack = [.S, S => . a S, S => a . S, .S, S => .]
|
175
|
+
table[.S] = {..., S. => [[S => .]]}
|
176
|
+
current_from: .S
|
177
|
+
|
178
|
+
Visit S. ; stack = [.S, S => . a S, S => a . S, .S, S => ., S.]
|
179
|
+
S. is a table heading
|
180
|
+
table[.S] = {..., S. => [[S => ., S.]]}
|
181
|
+
current_from: S.
|
182
|
+
|
183
|
+
Visit S => a S .; stack = [.S, S => . a S, S => a . S, .S, S => ., S., S => a S .]
|
184
|
+
return edge to .S detected
|
185
|
+
table[S.] = { S. => [[S => a S ., S.]]}
|
186
|
+
|
187
|
+
Visit S.; stack = [.S, S => . a S, S => a . S, .S, S => ., S., S => a S ., S.]
|
188
|
+
S. fully visited; pop it from stack
|
189
|
+
|
190
|
+
Visit S => a S .; stack = [.S, S => . a S, S => a . S, .S, S => ., S., S => a S .]
|
191
|
+
S => a S . fully visited; pop it from stack
|
192
|
+
|
193
|
+
Visit S.; stack = [.S, S => . a S, S => a . S, .S, S => ., S.]
|
194
|
+
S. fully visited; pop it from stack
|
195
|
+
|
196
|
+
Visit S => .; stack = [.S, S => . a S, S => a . S, .S, S => .]
|
197
|
+
S => . fully visited; pop it from stack
|
198
|
+
|
199
|
+
Visit .S; stack = [.S, S => . a S, S => a . S, .S]
|
200
|
+
.S fully visited; pop it from stack
|
201
|
+
|
202
|
+
Visit S => a . S; stack = [.S, S => . a S, S => a . S]
|
203
|
+
S => a . S fully visited; pop it from stack
|
204
|
+
|
205
|
+
Visit S => . a S; stack = [.S, S => . a S]
|
206
|
+
S => . a Sfully visited; pop it from stack
|
207
|
+
|
208
|
+
Visit .S; stack = [.S]
|
209
|
+
.S fully visited; pop it from stack
|
210
|
+
|
211
|
+
Stack contains couples of the type (node, node-from)
|
212
|
+
Stack empty; table is complete
|
213
|
+
table[.S] = {(S => . a S) => [[S => . a S, .S], S. => [[S => ., S.]]}
|
214
|
+
table[S.] = { S. => [[S => a S ., S.]]}
|
215
|
+
prov_table[(S => . a S)] = { .S => [S => a . S] }
|
216
|
+
table[(S => . a S] = { (S => . a S) => [S => a . S, .S, S => . a S], S. =>[[S => a S ., S., S => ., S.]] }
|
217
|
+
=end
|
218
|
+
|
219
|
+
module GraphMixin
|
220
|
+
def build_table
|
221
|
+
table = { start_vertex => {} }
|
222
|
+
end_vertex = end_vertex_for[start_vertex.non_terminal]
|
223
|
+
table[end_vertex] = {}
|
224
|
+
term2scan_edge = {}
|
225
|
+
|
226
|
+
vertices.each do |vx|
|
227
|
+
next if vx.kind_of?(Rley::GFG::NonTerminalVertex)
|
228
|
+
|
229
|
+
edge = vx.edges[0]
|
230
|
+
next unless edge.kind_of?(Rley::GFG::ScanEdge)
|
231
|
+
|
232
|
+
table[edge] = {}
|
233
|
+
terminal = edge.terminal
|
234
|
+
term2scan_edge[terminal] = [] unless term2scan_edge.include? terminal
|
235
|
+
term2scan_edge[terminal] << edge
|
236
|
+
end
|
237
|
+
|
238
|
+
table
|
239
|
+
end
|
240
|
+
|
241
|
+
def build_raw_table
|
242
|
+
table = { start_vertex => {} }
|
243
|
+
end_vertex = end_vertex_for[start_vertex.non_terminal]
|
244
|
+
table[end_vertex] = {} # table: vertex => { to_vertex => [path to_vertex] }
|
245
|
+
visit = [start_vertex] # visit: stack of vertex to visit
|
246
|
+
from_indices = [0] # Array of indices. Each index refers to one visitee
|
247
|
+
vtx2count = { start_vertex => 0 }
|
248
|
+
|
249
|
+
loop do
|
250
|
+
break if visit.empty?
|
251
|
+
|
252
|
+
current_from = visit[from_indices.last]
|
253
|
+
visitee = visit.last
|
254
|
+
visit_count = vtx2count[visitee]
|
255
|
+
if visit_count < visitee.edges.size
|
256
|
+
# puts visitee.label
|
257
|
+
vtx2count[visitee] = visit_count + 1
|
258
|
+
edge = visitee.edges[visit_count]
|
259
|
+
# Do processing
|
260
|
+
if edge.kind_of?(Rley::GFG::ScanEdge) || ((visitee == start_vertex) && visit_count.positive?)
|
261
|
+
table[visitee] = {} unless table.include? visitee
|
262
|
+
current_row = table[current_from]
|
263
|
+
current_row[visitee] = [] unless current_row.include? visitee
|
264
|
+
current_row[visitee].concat(visit[from_indices.last..(visit.size - 1)])
|
265
|
+
from_indices << (visit.size - 1)
|
266
|
+
elsif (visitee == end_vertex) && visit_count.zero?
|
267
|
+
current_row = table[current_from]
|
268
|
+
current_row[visitee] = [] unless current_row.include? visitee
|
269
|
+
current_row[visitee].concat(visit[from_indices.last..(visit.size - 1)])
|
270
|
+
from_indices << (visit.size - 1)
|
271
|
+
end
|
272
|
+
successor = edge.successor
|
273
|
+
visit << successor
|
274
|
+
vtx2count[successor] = 0 unless vtx2count.include?(successor)
|
275
|
+
else
|
276
|
+
if (visitee == end_vertex) && visit_count == visitee.edges.size
|
277
|
+
current_row = table[current_from]
|
278
|
+
current_row[visitee] = [] unless current_row.include? visitee
|
279
|
+
slice = visit[from_indices.last..(visit.size - 1)]
|
280
|
+
if slice.size > 1
|
281
|
+
current_row[visitee].concat(slice)
|
282
|
+
from_indices << (visit.size - 1)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
visit.pop
|
286
|
+
from_indices.pop if from_indices.last >= visit.size
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
table
|
291
|
+
end
|
292
|
+
|
293
|
+
def build_table2
|
294
|
+
raw_table = build_raw_table
|
295
|
+
refined_table = {}
|
296
|
+
|
297
|
+
# To get table right
|
298
|
+
# for every non start or end vertex:
|
299
|
+
# Check if there is an entry for start_vertex, if yes
|
300
|
+
# prefix path = raw_table[curr_vertex][start_vertex]
|
301
|
+
# remove last vertex in prefix path (it's start vertex)
|
302
|
+
# then for each entry from raw_table[start_vertex]:
|
303
|
+
# create a keyval pair: key, value = prefix + original value
|
304
|
+
end_vertex = end_vertex_for[start_vertex.non_terminal]
|
305
|
+
raw_table.each_pair do |key, val|
|
306
|
+
if key == start_vertex || key == end_vertex
|
307
|
+
refined_table[key] = val.dup
|
308
|
+
next
|
309
|
+
end
|
310
|
+
|
311
|
+
if val.include? start_vertex
|
312
|
+
prefix_path = val[start_vertex]
|
313
|
+
prefix_path.pop
|
314
|
+
start_row = raw_table[start_vertex]
|
315
|
+
new_row = val.dup
|
316
|
+
new_row.delete(start_vertex)
|
317
|
+
start_row.each_pair do |to_vertex, to_path|
|
318
|
+
new_row[to_vertex] = prefix_path + to_path
|
319
|
+
end
|
320
|
+
refined_table[key] = new_row
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
refined_table
|
325
|
+
end
|
326
|
+
|
327
|
+
# visit_action takes two arguments: a vertex and an edge
|
328
|
+
# return true/false if false stop traversal
|
329
|
+
def df_traversal(aVertex, &visit_action)
|
330
|
+
visit = [aVertex]
|
331
|
+
vtx2count = { aVertex => 0 }
|
332
|
+
|
333
|
+
loop do
|
334
|
+
break if visit.empty?
|
335
|
+
|
336
|
+
visitee = visit.last
|
337
|
+
visit_count = vtx2count[visitee]
|
338
|
+
if visit_count < visitee.edges.size
|
339
|
+
# puts visitee.label
|
340
|
+
vtx2count[visitee] = visit_count + 1
|
341
|
+
edge = visitee.edges[visit_count]
|
342
|
+
resume = block_given? ? yield(visitee, edge) : true
|
343
|
+
if resume
|
344
|
+
successor = edge.successor
|
345
|
+
visit << successor
|
346
|
+
vtx2count[successor] = 0 unless vtx2count.include?(successor)
|
347
|
+
end
|
348
|
+
else
|
349
|
+
visit.pop
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def row_to_s(aRow)
|
355
|
+
result = +''
|
356
|
+
aRow.each_pair do |to_vertex, to_path|
|
357
|
+
path_text = to_path.map(&:label).join(', ')
|
358
|
+
result << "#{to_vertex.label} => [#{path_text}]\n"
|
359
|
+
end
|
360
|
+
|
361
|
+
result
|
362
|
+
end
|
363
|
+
end # module
|
364
|
+
|
365
|
+
graph.extend(GraphMixin)
|
366
|
+
# graph.df_traversal(graph.start_vertex) { |n, _edge| puts n.label; true }
|
367
|
+
# p graph.build_table.size
|
368
|
+
raw_table = graph.build_raw_table
|
369
|
+
p raw_table[graph.start_vertex]
|
370
|
+
# (S. => .a S) => [.S, S => .a S], S. => [.S, S => ., S.]
|
371
|
+
|
372
|
+
p raw_table[raw_table.keys[1]]
|
373
|
+
# S. => [S., S=> a S., S.]
|
374
|
+
|
375
|
+
p raw_table[raw_table.keys[2]]
|
376
|
+
# .S => [S => .a S, S => a .S, .S]
|
377
|
+
|
378
|
+
# raw_table[.S] = { (S. => .a S) => [.S, S => .a S], S. => [.S, S => ., S.] }
|
379
|
+
# raw_table[.S] = { S. => [S., S=> a S., S.] }
|
380
|
+
# raw_table[S => .a S] = { .S => [S => .a S, S => a .S, .S] }
|
381
|
+
|
382
|
+
refined_table = graph.build_table2
|
383
|
+
puts '=='
|
384
|
+
puts graph.row_to_s(refined_table[graph.start_vertex])
|
385
|
+
# refined_table[start_vertex] = { (S => .a S) => [.S, S => .a S], S. => [S., S => ., S.] }
|
386
|
+
puts '=='
|
387
|
+
puts graph.row_to_s(refined_table[refined_table.keys[1]])
|
388
|
+
# refined_table[S.] = { S. => [S., S => a S., S.] }
|
389
|
+
puts '=='
|
390
|
+
puts graph.row_to_s(refined_table[refined_table.keys[2]])
|
391
|
+
# refined_table[S => .a S] = { (S => .a S) => [S => .a S, S => a .S, .S, S -> .a S],
|
392
|
+
# S. => [S => .a S, S => a .S, .S, S => ., S.] }
|
393
|
+
|
394
|
+
|
395
|
+
# | | .S | S. | (S -> .a S) |
|
396
|
+
# |:-------------:|:--:|:--:|-------------|
|
397
|
+
# | .S start | | Ta | Tb |
|
398
|
+
# | S. end | | ?? | |
|
399
|
+
# | (S -> .a S) | | Tc | Td |
|
400
|
+
|
401
|
+
|
402
|
+
class TransitionTable
|
403
|
+
attr_reader(:table)
|
404
|
+
|
405
|
+
def initialize(aTable)
|
406
|
+
@table = aTable
|
407
|
+
end
|
408
|
+
|
409
|
+
def paths_from_start(aTerminalName)
|
410
|
+
destinations = table.values.first
|
411
|
+
result = []
|
412
|
+
destinations.each_pair do |to_vx, path|
|
413
|
+
next unless Rley::GFG::ItemVertex
|
414
|
+
|
415
|
+
result << path if to_vx.next_symbol&.name == aTerminalName
|
416
|
+
end
|
417
|
+
|
418
|
+
result
|
419
|
+
end
|
420
|
+
|
421
|
+
def paths_from(from_terminal, to_terminal)
|
422
|
+
from_vertices = table.keys.select do |vx|
|
423
|
+
vx.kind_of?(Rley::GFG::ItemVertex) && vx.next_symbol&.name == from_terminal
|
424
|
+
end
|
425
|
+
|
426
|
+
result = []
|
427
|
+
from_vertices.each do |fr_vx|
|
428
|
+
destinations = table[fr_vx]
|
429
|
+
destinations.each_pair do |to_vx, path|
|
430
|
+
next unless Rley::GFG::ItemVertex
|
431
|
+
|
432
|
+
result << path if to_vx.next_symbol&.name == to_terminal
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
result
|
437
|
+
end
|
438
|
+
|
439
|
+
def paths_to_end(from_terminal)
|
440
|
+
from_vertices = table.keys.select do |vx|
|
441
|
+
vx.kind_of?(Rley::GFG::ItemVertex) && vx.next_symbol&.name == from_terminal
|
442
|
+
end
|
443
|
+
|
444
|
+
result = []
|
445
|
+
from_vertices.each do |fr_vx|
|
446
|
+
destinations = table[fr_vx]
|
447
|
+
destinations.each_pair do |to_vx, path|
|
448
|
+
result << path if to_vx.kind_of?(Rley::GFG::EndVertex)
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
result
|
453
|
+
end
|
454
|
+
end # class
|
455
|
+
|
456
|
+
=begin
|
457
|
+
Simulation of recognizer algorithm
|
458
|
+
input: aaaaa
|
459
|
+
Reminder:
|
460
|
+
# refined_table[start_vertex] = { (S => .a S) => [.S, S => .a S], S. => [S., S => ., S.] }
|
461
|
+
# refined_table[S.] = { S. => [S., S => a S., S.] }
|
462
|
+
# refined_table[S => .a S] = { (S => .a S) => [S => .a S, S => a .S, .S, S -> .a S],
|
463
|
+
# S. => [S => .a S, S => a .S, .S, S => ., S.] }
|
464
|
+
|
465
|
+
Maybe one needs to transform the above table into terminal => terminal table
|
466
|
+
|
467
|
+
Computing state 0
|
468
|
+
What is next terminal? a
|
469
|
+
We push P1 == (S => .a S) => [.S, S => .a S]
|
470
|
+
|
471
|
+
Computing state 1
|
472
|
+
What is next terminal? a
|
473
|
+
We push P2 == (S => .a S) => [S => .a S, S => a .S, .S, S -> .a S]
|
474
|
+
|
475
|
+
Computing state 2
|
476
|
+
What is next terminal? a
|
477
|
+
We push P2 == (S => .a S) => [S => .a S, S => a .S, .S, S -> .a S]
|
478
|
+
|
479
|
+
Computing state 3
|
480
|
+
What is next terminal? a
|
481
|
+
We push P2 == (S => .a S) => [S => .a S, S => a .S, .S, S -> .a S]
|
482
|
+
|
483
|
+
Computing state 4
|
484
|
+
What is next terminal? a
|
485
|
+
We push P2 == (S => .a S) => [S => .a S, S => a .S, .S, S -> .a S]
|
486
|
+
|
487
|
+
Computing state 5
|
488
|
+
What is next terminal? EOS
|
489
|
+
We push P3 == S. => [S => .a S, S => a .S, .S, S => ., S.]
|
490
|
+
|
491
|
+
Recognizer terminates here.
|
492
|
+
=end
|
493
|
+
|
494
|
+
class Recognizer
|
495
|
+
attr_reader(:table)
|
496
|
+
|
497
|
+
def initialize(aTable)
|
498
|
+
@table = TransitionTable.new(aTable)
|
499
|
+
end
|
500
|
+
|
501
|
+
# @param enumerator [Enumerator] Enumerator that yields Terminal symbols
|
502
|
+
def run(enum_tokens)
|
503
|
+
states = []
|
504
|
+
prev_terminal = nil
|
505
|
+
paths = nil
|
506
|
+
enum_tokens.each_with_index do |token, index|
|
507
|
+
terminal_name = token.terminal.name
|
508
|
+
if index.zero?
|
509
|
+
paths = table.paths_from_start(terminal_name)
|
510
|
+
else
|
511
|
+
paths = table.paths_from(prev_terminal, terminal_name)
|
512
|
+
end
|
513
|
+
prev_terminal = terminal_name
|
514
|
+
raise StandardError, "Error at position #{index} with #{token.lexeme}." if paths.empty?
|
515
|
+
|
516
|
+
states << paths
|
517
|
+
end
|
518
|
+
|
519
|
+
paths = table.paths_to_end(prev_terminal)
|
520
|
+
raise StandardError, "Error at position #{index} with #{token.lexeme}." if paths.empty?
|
521
|
+
|
522
|
+
states << paths
|
523
|
+
|
524
|
+
states
|
525
|
+
end
|
526
|
+
end # class
|
527
|
+
|
528
|
+
|
529
|
+
recog = Recognizer.new(refined_table)
|
530
|
+
states = recog.run(tokens)
|
531
|
+
puts 'Done!'
|
532
|
+
p states[5]
|
533
|
+
# states[0] = [[.S, S => .a S]]
|
534
|
+
# states[1] = [[S => .a S, S => a .S, .S, S => .a S]]
|
535
|
+
# states[4] = [[S => .a S, S => a .S, .S, S => .a S]]
|
536
|
+
# states[5] = [[S => .a S, S => a .S, .S, S => a S., S.]]
|
537
|
+
|
538
|
+
# Simulation for second recognizer pass
|
539
|
+
# Every entry edge has an identifier
|
540
|
+
# Corresponding exit edge has same identifier negated
|
541
|
+
# Call and return edges are ambiguous when a non-terminal is lhs of multiple productions
|
542
|
+
# Creating statechart[0]:
|
543
|
+
# for each path do
|
544
|
+
# for each item vertex do
|
545
|
+
# push on current statechart rank, the item vertices
|
546
|
+
# statechart[0] # next token is 'a'. There is only one path from .S to S => .a S
|
547
|
+
# push S => .a S @ 0
|
548
|
+
# stack:
|
549
|
+
# S => .a S @ 0
|
550
|
+
|
551
|
+
# statechart[1] # next to token is 'a'. There is only one path from S => a .S to S => .a S
|
552
|
+
# Remove vertex from previous state
|
553
|
+
# push S => a . S @ 0
|
554
|
+
# push S => .a S @ 1 # Start vertex discarded
|
555
|
+
# stack:
|
556
|
+
# S => .a S @ 1
|
557
|
+
# S => .a S @ 0
|
558
|
+
#
|
559
|
+
# statechart[2] # next to token is 'a'. There is only one path from S => a .S to S => .a S
|
560
|
+
# Remove vertex from previous state
|
561
|
+
# push S => a . S @ 1
|
562
|
+
# push S => .a S @ 2 # Start vertex discarded
|
563
|
+
# stack:
|
564
|
+
# S => .a S @ 2
|
565
|
+
# S => .a S @ 1
|
566
|
+
# S => .a S @ 0
|
567
|
+
#
|
568
|
+
# statechart[3] # next to token is 'a'. There is only one path from S => a .S to S => .a S
|
569
|
+
# Remove vertex from previous state
|
570
|
+
# push S => a . S @ 2
|
571
|
+
# push S => .a S @ 3 # Start vertex discarded
|
572
|
+
# stack:
|
573
|
+
# S => .a S @ 3
|
574
|
+
# S => .a S @ 2
|
575
|
+
# S => .a S @ 1
|
576
|
+
# S => .a S @ 0
|
577
|
+
#
|
578
|
+
# statechart[4] # next to token is 'a'. There is only one path from S => a .S to S => .a S
|
579
|
+
# Remove vertex from previous state
|
580
|
+
# push S => a . S @ 3
|
581
|
+
# push S => .a S @ 4 # Start vertex discarded
|
582
|
+
# stack:
|
583
|
+
# S => .a S @ 4
|
584
|
+
# S => .a S @ 3
|
585
|
+
# S => .a S @ 2
|
586
|
+
# S => .a S @ 1
|
587
|
+
# S => .a S @ 0
|
588
|
+
#
|
589
|
+
# statechart[5] # next to token is EOS. There is only one path from S => a .S to S.
|
590
|
+
# Remove vertex from previous state
|
591
|
+
# push S => a . S @ 4
|
592
|
+
# push .S @ 5 ?
|
593
|
+
# push S => . @5
|
594
|
+
# push S. possibly add 0..n events S => a S. @ xx
|
595
|
+
# stack:
|
596
|
+
# S => .a S @ 4 expecting S => a S @ 4 event
|
597
|
+
# S => .a S @ 3
|
598
|
+
# S => .a S @ 2
|
599
|
+
# S => .a S @ 1
|
600
|
+
# S => .a S @ 0
|
601
|
+
#
|
@@ -1,14 +1,17 @@
|
|
1
|
-
# As Rubocop shouts about "offences" in the generated code,
|
1
|
+
# As Rubocop shouts about "offences" in the generated code,
|
2
2
|
# we disable the detection of most of them...
|
3
3
|
# rubocop: disable Style/MutableConstant
|
4
4
|
# rubocop: disable Layout/SpaceBeforeSemicolon
|
5
5
|
# rubocop: disable Style/Alias
|
6
6
|
# rubocop: disable Style/AndOr
|
7
7
|
# rubocop: disable Style/MultilineIfModifier
|
8
|
+
# rubocop: disable Style/MultilineIfThen
|
8
9
|
# rubocop: disable Style/StringLiterals
|
9
10
|
# rubocop: disable Style/MethodDefParentheses
|
10
|
-
# rubocop: disable
|
11
|
+
# rubocop: disable Style/RedundantParentheses
|
12
|
+
# rubocop: disable Style/SlicingWithRange
|
11
13
|
# rubocop: disable Style/TrailingCommaInArrayLiteral
|
14
|
+
# rubocop: disable Security/Open#
|
12
15
|
# rubocop: disable Layout/EmptyLinesAroundMethodBody
|
13
16
|
# rubocop: disable Style/WhileUntilDo
|
14
17
|
# rubocop: disable Style/MultilineWhenThen
|
@@ -94,6 +97,9 @@ end
|
|
94
97
|
# rubocop: enable Style/MultilineIfModifier
|
95
98
|
# rubocop: enable Style/StringLiterals
|
96
99
|
# rubocop: enable Style/MethodDefParentheses
|
100
|
+
# rubocop: enable Style/MultilineIfThen:
|
101
|
+
# rubocop: enable Style/RedundantParentheses
|
102
|
+
# rubocop: enable Style/SlicingWithRange
|
97
103
|
# rubocop: enable Security/Open
|
98
104
|
# rubocop: enable Style/TrailingCommaInArrayLiteral
|
99
105
|
# rubocop: enable Layout/EmptyLinesAroundMethodBody
|
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# encoding: UTF-8
|
4
|
-
|
5
3
|
#--
|
6
4
|
# This file is automatically generated. Do not modify it.
|
7
|
-
# Generated by: oedipus_lex version 2.
|
5
|
+
# Generated by: oedipus_lex version 2.6.2.
|
8
6
|
# Source: loxxy_raw_scanner.rex
|
9
7
|
#++
|
10
8
|
|
@@ -15,10 +13,13 @@
|
|
15
13
|
# rubocop: disable Style/Alias
|
16
14
|
# rubocop: disable Style/AndOr
|
17
15
|
# rubocop: disable Style/MultilineIfModifier
|
16
|
+
# rubocop: disable Style/MultilineIfThen
|
18
17
|
# rubocop: disable Style/StringLiterals
|
19
18
|
# rubocop: disable Style/MethodDefParentheses
|
20
|
-
# rubocop: disable
|
19
|
+
# rubocop: disable Style/RedundantParentheses
|
20
|
+
# rubocop: disable Style/SlicingWithRange
|
21
21
|
# rubocop: disable Style/TrailingCommaInArrayLiteral
|
22
|
+
# rubocop: disable Security/Open#
|
22
23
|
# rubocop: disable Layout/EmptyLinesAroundMethodBody
|
23
24
|
# rubocop: disable Style/WhileUntilDo
|
24
25
|
# rubocop: disable Style/MultilineWhenThen
|
@@ -104,7 +105,6 @@ class LoxxyRawScanner
|
|
104
105
|
old_pos - start_of_current_line_pos
|
105
106
|
end
|
106
107
|
|
107
|
-
|
108
108
|
##
|
109
109
|
# The current scanner class. Must be overridden in subclasses.
|
110
110
|
|
@@ -153,7 +153,7 @@ class LoxxyRawScanner
|
|
153
153
|
token = nil
|
154
154
|
|
155
155
|
until ss.eos? or token do
|
156
|
-
if ss.
|
156
|
+
if ss.check(/\n/) then
|
157
157
|
self.lineno += 1
|
158
158
|
# line starts 1 position after the newline
|
159
159
|
self.start_of_current_line_pos = ss.pos + 1
|
@@ -246,6 +246,9 @@ end # class
|
|
246
246
|
# rubocop: enable Style/MultilineIfModifier
|
247
247
|
# rubocop: enable Style/StringLiterals
|
248
248
|
# rubocop: enable Style/MethodDefParentheses
|
249
|
+
# rubocop: enable Style/MultilineIfThen:
|
250
|
+
# rubocop: enable Style/RedundantParentheses
|
251
|
+
# rubocop: enable Style/SlicingWithRange
|
249
252
|
# rubocop: enable Security/Open
|
250
253
|
# rubocop: enable Style/TrailingCommaInArrayLiteral
|
251
254
|
# rubocop: enable Layout/EmptyLinesAroundMethodBody
|
data/lib/rley/constants.rb
CHANGED
data/lib/rley/engine.rb
CHANGED
@@ -80,9 +80,11 @@ module Rley # This module is used as a namespace
|
|
80
80
|
aTokenizer.each do |a_token|
|
81
81
|
next unless a_token
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
if a_token.terminal.kind_of?(String)
|
84
|
+
term_name = a_token.terminal
|
85
|
+
term_symb = grammar.name2symbol[term_name]
|
86
|
+
a_token.instance_variable_set(:@terminal, term_symb)
|
87
|
+
end
|
86
88
|
tokens << a_token
|
87
89
|
end
|
88
90
|
parser = build_parser(grammar)
|
@@ -386,23 +386,6 @@ module Rley # This module is used as a namespace
|
|
386
386
|
end
|
387
387
|
end
|
388
388
|
|
389
|
-
# def sequence_name(aSequenceNode)
|
390
|
-
# subnode_names = +''
|
391
|
-
# aSequenceNode.subnodes.each do |subn|
|
392
|
-
# case subn
|
393
|
-
# when SymbolNode
|
394
|
-
# subnode_names << "_#{subn.name}"
|
395
|
-
# when SequenceNode
|
396
|
-
# subnode_names << "_#{sequence_name(subn)}"
|
397
|
-
# when RepetitionNode
|
398
|
-
# suffix = repetition2suffix(subn.repetition)
|
399
|
-
# subnode_names << suffix
|
400
|
-
# end
|
401
|
-
# end
|
402
|
-
#
|
403
|
-
# "seq#{subnode_names}"
|
404
|
-
# end
|
405
|
-
|
406
389
|
def node_base_name(aNode)
|
407
390
|
if aNode.kind_of?(SymbolNode)
|
408
391
|
aNode.name
|
@@ -418,24 +401,6 @@ module Rley # This module is used as a namespace
|
|
418
401
|
"#{base_name}#{suffix}"
|
419
402
|
end
|
420
403
|
|
421
|
-
# def serialize_sequence(aSequenceNode)
|
422
|
-
# text = +''
|
423
|
-
# aSequenceNode.subnodes.each do |sn|
|
424
|
-
# text << ' '
|
425
|
-
# case sn
|
426
|
-
# when SymbolNode
|
427
|
-
# text << sn.name
|
428
|
-
# when SequenceNode
|
429
|
-
# text << sequence_name(sn)
|
430
|
-
# when RepetitionNode
|
431
|
-
# suffix = repetition2suffix(sn.repetition)
|
432
|
-
# text << suffix
|
433
|
-
# end
|
434
|
-
# end
|
435
|
-
#
|
436
|
-
# text.strip
|
437
|
-
# end
|
438
|
-
|
439
404
|
def add_raw_rule(aSymbol, aRHS, aTag, simplified = false, constraints = [])
|
440
405
|
raw_rule = RawRule.new(aSymbol, aRHS, aTag, simplified, constraints)
|
441
406
|
if synthetized.include?(aSymbol)
|
@@ -49,7 +49,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
49
49
|
term_name = 'INTEGER'
|
50
50
|
else
|
51
51
|
err_msg = "Unknown token '#{lexeme}'"
|
52
|
-
raise StandardError,
|
52
|
+
raise StandardError, err_msg
|
53
53
|
end
|
54
54
|
end
|
55
55
|
pos = Rley::Lexical::Position.new(1, curr_pos + 1)
|
@@ -104,9 +104,9 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
104
104
|
context 'Parsing: ' do
|
105
105
|
# rubocop: disable Naming/VariableNumber
|
106
106
|
it 'should parse a valid simple input' do
|
107
|
-
parse_result = subject.parse(
|
107
|
+
parse_result = subject.parse(build_token_sequence(%w[a a b c c], grammar_abc))
|
108
108
|
expect(parse_result.success?).to eq(true)
|
109
|
-
|
109
|
+
expect(parse_result.ambiguous?).to eq(false)
|
110
110
|
######################
|
111
111
|
# Expectation chart[0]:
|
112
112
|
expected = [
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rley
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitri Geshef
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-02-16 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: prime
|
@@ -16,74 +15,74 @@ dependencies:
|
|
16
15
|
requirements:
|
17
16
|
- - "~>"
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.1.
|
18
|
+
version: 0.1.2
|
20
19
|
type: :runtime
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
23
|
- - "~>"
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.1.
|
25
|
+
version: 0.1.2
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
27
|
name: rake
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
30
|
- - "~>"
|
32
31
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
32
|
+
version: 13.1.0
|
34
33
|
- - ">="
|
35
34
|
- !ruby/object:Gem::Version
|
36
|
-
version: 13.
|
35
|
+
version: 13.1.0
|
37
36
|
type: :development
|
38
37
|
prerelease: false
|
39
38
|
version_requirements: !ruby/object:Gem::Requirement
|
40
39
|
requirements:
|
41
40
|
- - "~>"
|
42
41
|
- !ruby/object:Gem::Version
|
43
|
-
version:
|
42
|
+
version: 13.1.0
|
44
43
|
- - ">="
|
45
44
|
- !ruby/object:Gem::Version
|
46
|
-
version: 13.
|
45
|
+
version: 13.1.0
|
47
46
|
- !ruby/object:Gem::Dependency
|
48
47
|
name: rspec
|
49
48
|
requirement: !ruby/object:Gem::Requirement
|
50
49
|
requirements:
|
51
50
|
- - "~>"
|
52
51
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
52
|
+
version: 3.12.0
|
54
53
|
- - ">="
|
55
54
|
- !ruby/object:Gem::Version
|
56
|
-
version: 3.
|
55
|
+
version: 3.12.0
|
57
56
|
type: :development
|
58
57
|
prerelease: false
|
59
58
|
version_requirements: !ruby/object:Gem::Requirement
|
60
59
|
requirements:
|
61
60
|
- - "~>"
|
62
61
|
- !ruby/object:Gem::Version
|
63
|
-
version:
|
62
|
+
version: 3.12.0
|
64
63
|
- - ">="
|
65
64
|
- !ruby/object:Gem::Version
|
66
|
-
version: 3.
|
65
|
+
version: 3.12.0
|
67
66
|
- !ruby/object:Gem::Dependency
|
68
|
-
name:
|
67
|
+
name: yard
|
69
68
|
requirement: !ruby/object:Gem::Requirement
|
70
69
|
requirements:
|
71
70
|
- - "~>"
|
72
71
|
- !ruby/object:Gem::Version
|
73
|
-
version:
|
72
|
+
version: 0.9.34
|
74
73
|
- - ">="
|
75
74
|
- !ruby/object:Gem::Version
|
76
|
-
version:
|
75
|
+
version: 0.9.34
|
77
76
|
type: :development
|
78
77
|
prerelease: false
|
79
78
|
version_requirements: !ruby/object:Gem::Requirement
|
80
79
|
requirements:
|
81
80
|
- - "~>"
|
82
81
|
- !ruby/object:Gem::Version
|
83
|
-
version:
|
82
|
+
version: 0.9.34
|
84
83
|
- - ">="
|
85
84
|
- !ruby/object:Gem::Version
|
86
|
-
version:
|
85
|
+
version: 0.9.34
|
87
86
|
description: A general parser using the Earley algorithm.
|
88
87
|
email: famished.tiger@yahoo.com
|
89
88
|
executables: []
|
@@ -94,7 +93,6 @@ files:
|
|
94
93
|
- ".rspec"
|
95
94
|
- ".rubocop.yml"
|
96
95
|
- ".ruby-gemset"
|
97
|
-
- ".travis.yml"
|
98
96
|
- ".yardopts"
|
99
97
|
- CHANGELOG.md
|
100
98
|
- LICENSE.txt
|
@@ -136,6 +134,7 @@ files:
|
|
136
134
|
- examples/general/calc_iter2/spec/calculator_spec.rb
|
137
135
|
- examples/general/general_examples.md
|
138
136
|
- examples/general/left.rb
|
137
|
+
- examples/general/recursive_right.rb
|
139
138
|
- examples/general/right.rb
|
140
139
|
- examples/tokenizer/README.md
|
141
140
|
- examples/tokenizer/loxxy_raw_scanner.rex
|
@@ -310,15 +309,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
310
309
|
requirements:
|
311
310
|
- - ">="
|
312
311
|
- !ruby/object:Gem::Version
|
313
|
-
version: 2.
|
312
|
+
version: 3.2.0
|
314
313
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
315
314
|
requirements:
|
316
315
|
- - ">="
|
317
316
|
- !ruby/object:Gem::Version
|
318
317
|
version: '0'
|
319
318
|
requirements: []
|
320
|
-
rubygems_version: 3.
|
321
|
-
signing_key:
|
319
|
+
rubygems_version: 3.6.2
|
322
320
|
specification_version: 4
|
323
321
|
summary: Ruby implementation of the Earley's parsing algorithm
|
324
322
|
test_files:
|
data/.travis.yml
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
dist: trusty
|
3
|
-
|
4
|
-
before_install:
|
5
|
-
- gem update --system
|
6
|
-
- gem install bundler
|
7
|
-
|
8
|
-
script:
|
9
|
-
- bundle exec rake
|
10
|
-
|
11
|
-
rvm:
|
12
|
-
- 2.7.1
|
13
|
-
- 2.6.6
|
14
|
-
- 2.5.8
|
15
|
-
- 2.4.10
|
16
|
-
- ruby-head
|
17
|
-
- jruby-head
|
18
|
-
before_install: gem install bundler -v 2.0.2
|
19
|
-
|
20
|
-
matrix:
|
21
|
-
allow_failures:
|
22
|
-
- rvm: ruby-head
|
23
|
-
- rvm: jruby-head
|
24
|
-
|
25
|
-
|
26
|
-
# whitelist
|
27
|
-
branches:
|
28
|
-
only:
|
29
|
-
- master
|