lrama 0.1.0
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 +7 -0
- data/.github/workflows/test.yaml +72 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/Gemfile +8 -0
- data/LEGAL.md +26 -0
- data/MIT +21 -0
- data/README.md +32 -0
- data/Rakefile +1 -0
- data/doc/TODO.md +50 -0
- data/exe/lrama +7 -0
- data/lib/lrama/command.rb +129 -0
- data/lib/lrama/context.rb +510 -0
- data/lib/lrama/grammar.rb +850 -0
- data/lib/lrama/lexer.rb +349 -0
- data/lib/lrama/output.rb +268 -0
- data/lib/lrama/parser.rb +321 -0
- data/lib/lrama/report.rb +35 -0
- data/lib/lrama/states.rb +1124 -0
- data/lib/lrama/version.rb +3 -0
- data/lib/lrama.rb +9 -0
- data/lrama.gemspec +22 -0
- data/template/bison/yacc.c +1750 -0
- data/template/bison/yacc.h +112 -0
- metadata +67 -0
@@ -0,0 +1,510 @@
|
|
1
|
+
require "lrama/report"
|
2
|
+
|
3
|
+
module Lrama
|
4
|
+
# This is passed to a template
|
5
|
+
class Context
|
6
|
+
include Report::Duration
|
7
|
+
|
8
|
+
ErrorActionNumber = -Float::INFINITY
|
9
|
+
BaseMin = -Float::INFINITY
|
10
|
+
|
11
|
+
# TODO: It might be better to pass `states` to Output directly?
|
12
|
+
attr_reader :states
|
13
|
+
|
14
|
+
def initialize(states)
|
15
|
+
@states = states
|
16
|
+
@yydefact = nil
|
17
|
+
@yydefgoto = nil
|
18
|
+
# Array of array
|
19
|
+
@_actions = []
|
20
|
+
|
21
|
+
report_duration(:compute_tables) { compute_tables }
|
22
|
+
end
|
23
|
+
|
24
|
+
# enum yytokentype
|
25
|
+
def yytokentype
|
26
|
+
@states.terms.reject do |term|
|
27
|
+
0 < term.token_id && term.token_id < 128
|
28
|
+
end.map do |term|
|
29
|
+
[term.id.s_value, term.token_id, term.display_name]
|
30
|
+
end.unshift(["YYEMPTY", -2, nil])
|
31
|
+
end
|
32
|
+
|
33
|
+
# enum yysymbol_kind_t
|
34
|
+
def yysymbol_kind_t
|
35
|
+
@states.symbols.map do |sym|
|
36
|
+
[sym.enum_name, sym.number, sym.comment]
|
37
|
+
end.unshift(["YYSYMBOL_YYEMPTY", -2, nil])
|
38
|
+
end
|
39
|
+
|
40
|
+
# State number of final (accepted) state
|
41
|
+
def yyfinal
|
42
|
+
@states.states.find do |state|
|
43
|
+
state.items.find do |item|
|
44
|
+
item.rule.lhs.id.s_value == "$accept" && item.end_of_rule?
|
45
|
+
end
|
46
|
+
end.id
|
47
|
+
end
|
48
|
+
|
49
|
+
def yylast
|
50
|
+
@yylast
|
51
|
+
end
|
52
|
+
|
53
|
+
# Number of terms
|
54
|
+
def yyntokens
|
55
|
+
@states.terms.count
|
56
|
+
end
|
57
|
+
|
58
|
+
# Number of nterms
|
59
|
+
def yynnts
|
60
|
+
@states.nterms.count
|
61
|
+
end
|
62
|
+
|
63
|
+
# Number of rules
|
64
|
+
def yynrules
|
65
|
+
@states.rules.count
|
66
|
+
end
|
67
|
+
|
68
|
+
# Number of states
|
69
|
+
def yynstates
|
70
|
+
@states.states.count
|
71
|
+
end
|
72
|
+
|
73
|
+
# Last token number
|
74
|
+
def yymaxutok
|
75
|
+
@states.terms.map(&:token_id).max
|
76
|
+
end
|
77
|
+
|
78
|
+
# YYTRANSLATE
|
79
|
+
#
|
80
|
+
# yytranslate is a mapping from token id to symbol number
|
81
|
+
def yytranslate
|
82
|
+
# 2 is YYSYMBOL_YYUNDEF
|
83
|
+
a = Array.new(yymaxutok, 2)
|
84
|
+
|
85
|
+
@states.terms.each do |term|
|
86
|
+
a[term.token_id] = term.number
|
87
|
+
end
|
88
|
+
|
89
|
+
return a
|
90
|
+
end
|
91
|
+
|
92
|
+
# Mapping from rule number to line number of the rule is defined.
|
93
|
+
# Dummy rule is appended as the first element whose value is 0
|
94
|
+
# because 0 means error in yydefact.
|
95
|
+
def yyrline
|
96
|
+
a = [0]
|
97
|
+
|
98
|
+
@states.rules.each do |rule|
|
99
|
+
a << rule.lineno
|
100
|
+
end
|
101
|
+
|
102
|
+
return a
|
103
|
+
end
|
104
|
+
|
105
|
+
# Mapping from symbol number to its name
|
106
|
+
def yytname
|
107
|
+
@states.symbols.sort_by(&:number).map do |sym|
|
108
|
+
sym.display_name
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def yypact_ninf
|
113
|
+
@yypact_ninf
|
114
|
+
end
|
115
|
+
|
116
|
+
def yytable_ninf
|
117
|
+
@yytable_ninf
|
118
|
+
end
|
119
|
+
|
120
|
+
def yypact
|
121
|
+
@base[0...yynstates]
|
122
|
+
end
|
123
|
+
|
124
|
+
def yydefact
|
125
|
+
@yydefact
|
126
|
+
end
|
127
|
+
|
128
|
+
def yypgoto
|
129
|
+
@base[yynstates..-1]
|
130
|
+
end
|
131
|
+
|
132
|
+
def yydefgoto
|
133
|
+
@yydefgoto
|
134
|
+
end
|
135
|
+
|
136
|
+
def yytable
|
137
|
+
@table
|
138
|
+
end
|
139
|
+
|
140
|
+
def yycheck
|
141
|
+
@check
|
142
|
+
end
|
143
|
+
|
144
|
+
def yystos
|
145
|
+
@states.states.map do |state|
|
146
|
+
state.accessing_symbol.number
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Mapping from rule number to symbol number of LHS.
|
151
|
+
# Dummy rule is appended as the first element whose value is 0
|
152
|
+
# because 0 means error in yydefact.
|
153
|
+
def yyr1
|
154
|
+
a = [0]
|
155
|
+
|
156
|
+
@states.rules.each do |rule|
|
157
|
+
a << rule.lhs.number
|
158
|
+
end
|
159
|
+
|
160
|
+
return a
|
161
|
+
end
|
162
|
+
|
163
|
+
# Mapping from rule number to lenght of RHS.
|
164
|
+
# Dummy rule is appended as the first element whose value is 0
|
165
|
+
# because 0 means error in yydefact.
|
166
|
+
def yyr2
|
167
|
+
a = [0]
|
168
|
+
|
169
|
+
@states.rules.each do |rule|
|
170
|
+
a << rule.rhs.count
|
171
|
+
end
|
172
|
+
|
173
|
+
return a
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
# Compute these
|
179
|
+
#
|
180
|
+
# See also: "src/tables.c" of Bison.
|
181
|
+
#
|
182
|
+
# * yydefact
|
183
|
+
# * yydefgoto
|
184
|
+
# * yypact and yypgoto
|
185
|
+
# * yytable
|
186
|
+
# * yycheck
|
187
|
+
# * yypact_ninf
|
188
|
+
# * yytable_ninf
|
189
|
+
def compute_tables
|
190
|
+
compute_yydefact
|
191
|
+
compute_yydefgoto
|
192
|
+
sort_actions
|
193
|
+
# debug_sorted_actions
|
194
|
+
compute_packed_table
|
195
|
+
end
|
196
|
+
|
197
|
+
def vectors_count
|
198
|
+
@states.states.count + @states.nterms.count
|
199
|
+
end
|
200
|
+
|
201
|
+
# In compressed table, rule 0 is appended as an error case
|
202
|
+
# and reduce is represented as minus number.
|
203
|
+
def rule_id_to_action_number(rule_id)
|
204
|
+
(rule_id + 1) * -1
|
205
|
+
end
|
206
|
+
|
207
|
+
# Symbol number is assinged to term first then nterm.
|
208
|
+
# This method calculates sequence_number for nterm.
|
209
|
+
def nterm_number_to_sequence_number(nterm_number)
|
210
|
+
nterm_number - @states.terms.count
|
211
|
+
end
|
212
|
+
|
213
|
+
# Vector is states + nterms
|
214
|
+
def nterm_number_to_vector_number(nterm_number)
|
215
|
+
@states.states.count + (nterm_number - @states.terms.count)
|
216
|
+
end
|
217
|
+
|
218
|
+
def compute_yydefact
|
219
|
+
# Default action (shift/reduce/error) for each state.
|
220
|
+
# Index is state id, value is `rule id + 1` of a default reduction.
|
221
|
+
@yydefact = Array.new(@states.states.count, 0)
|
222
|
+
|
223
|
+
@states.states.each do |state|
|
224
|
+
# Action number means
|
225
|
+
#
|
226
|
+
# * number = 0, default action
|
227
|
+
# * number = -Float::INFINITY, error by %nonassoc
|
228
|
+
# * number > 0, shift then move to state "number"
|
229
|
+
# * number < 0, reduce by "-number" rule. Rule "number" is already added by 1.
|
230
|
+
actions = Array.new(@states.terms.count, 0)
|
231
|
+
|
232
|
+
if state.reduces.map(&:selected_look_ahead).any? {|la| !la.empty? }
|
233
|
+
# Iterate reduces with reverse order so that first rule is used.
|
234
|
+
state.reduces.reverse.each do |reduce|
|
235
|
+
reduce.look_ahead.each do |term|
|
236
|
+
actions[term.number] = rule_id_to_action_number(reduce.rule.id)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Shift is selected when S/R conflict exists.
|
242
|
+
state.selected_term_transitions.each do |shift, next_state|
|
243
|
+
actions[shift.next_sym.number] = next_state.id
|
244
|
+
end
|
245
|
+
|
246
|
+
state.resolved_conflicts.select do |conflict|
|
247
|
+
conflict.which == :error
|
248
|
+
end.each do |conflict|
|
249
|
+
actions[conflict.symbol.number] = ErrorActionNumber
|
250
|
+
end
|
251
|
+
|
252
|
+
# If default_reduction_rule, replase default_reduction_rule in
|
253
|
+
# actions with zero.
|
254
|
+
if state.default_reduction_rule
|
255
|
+
actions.map! do |e|
|
256
|
+
if e == rule_id_to_action_number(state.default_reduction_rule.id)
|
257
|
+
0
|
258
|
+
else
|
259
|
+
e
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# If no default_reduction_rule, default behavior is an
|
265
|
+
# error then replase ErrorActionNumber with zero.
|
266
|
+
if !state.default_reduction_rule
|
267
|
+
actions.map! do |e|
|
268
|
+
if e == ErrorActionNumber
|
269
|
+
0
|
270
|
+
else
|
271
|
+
e
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
s = actions.each_with_index.map do |n, i|
|
277
|
+
[i, n]
|
278
|
+
end.select do |i, n|
|
279
|
+
# Remove default_reduction_rule entries
|
280
|
+
n != 0
|
281
|
+
end
|
282
|
+
|
283
|
+
if s.count != 0
|
284
|
+
# Entry of @_actions is an array of
|
285
|
+
#
|
286
|
+
# * State id
|
287
|
+
# * Array of tuple, [from, to] where from is term number and to is action.
|
288
|
+
# * The number of "Array of tuple" used by sort_actions
|
289
|
+
# * "width" used by sort_actions
|
290
|
+
@_actions << [state.id, s, s.count, s.last[0] - s.first[0] + 1]
|
291
|
+
end
|
292
|
+
|
293
|
+
@yydefact[state.id] = state.default_reduction_rule ? state.default_reduction_rule.id + 1 : 0
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def compute_yydefgoto
|
298
|
+
# Default GOTO (nterm transition) for each nterm.
|
299
|
+
# Index is sequence number of nterm, value is state id
|
300
|
+
# of a default nterm transition destination.
|
301
|
+
@yydefgoto = Array.new(@states.nterms.count, 0)
|
302
|
+
h = {}
|
303
|
+
# Mapping from nterm to next_states
|
304
|
+
nterm_to_next_states = {}
|
305
|
+
terms_count = @states.terms.count
|
306
|
+
|
307
|
+
@states.states.each do |state|
|
308
|
+
state.nterm_transitions.each do |shift, next_state|
|
309
|
+
key = shift.next_sym
|
310
|
+
nterm_to_next_states[key] ||= []
|
311
|
+
nterm_to_next_states[key] << [state, next_state] # [from_state, to_state]
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
@states.nterms.each do |nterm|
|
316
|
+
if !(states = nterm_to_next_states[nterm])
|
317
|
+
default_goto = 0
|
318
|
+
not_default_gotos = []
|
319
|
+
else
|
320
|
+
default_state = states.map(&:last).group_by {|s| s }.max_by {|_, v| v.count }.first
|
321
|
+
default_goto = default_state.id
|
322
|
+
not_default_gotos = []
|
323
|
+
states.each do |from_state, to_state|
|
324
|
+
next if to_state.id == default_goto
|
325
|
+
not_default_gotos << [from_state.id, to_state.id]
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
k = nterm_number_to_sequence_number(nterm.number)
|
330
|
+
@yydefgoto[k] = default_goto
|
331
|
+
|
332
|
+
if not_default_gotos.count != 0
|
333
|
+
v = nterm_number_to_vector_number(nterm.number)
|
334
|
+
|
335
|
+
# Entry of @_actions is an array of
|
336
|
+
#
|
337
|
+
# * Nterm number as vector number
|
338
|
+
# * Array of tuple, [from, to] where from is state number and to is state number.
|
339
|
+
# * The number of "Array of tuple" used by sort_actions
|
340
|
+
# * "width" used by sort_actions
|
341
|
+
@_actions << [v, not_default_gotos, not_default_gotos.count, not_default_gotos.last[0] - not_default_gotos.first[0] + 1]
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def sort_actions
|
347
|
+
# This is not same with #sort_actions
|
348
|
+
#
|
349
|
+
# @sorted_actions = @_actions.sort_by do |_, _, count, width|
|
350
|
+
# [-width, -count]
|
351
|
+
# end
|
352
|
+
|
353
|
+
@sorted_actions = []
|
354
|
+
|
355
|
+
@_actions.each do |action|
|
356
|
+
if @sorted_actions.empty?
|
357
|
+
@sorted_actions << action
|
358
|
+
next
|
359
|
+
end
|
360
|
+
|
361
|
+
j = @sorted_actions.count - 1
|
362
|
+
state_id, froms_and_tos, count, width = action
|
363
|
+
|
364
|
+
while (j >= 0) do
|
365
|
+
case
|
366
|
+
when @sorted_actions[j][3] < width
|
367
|
+
j -= 1
|
368
|
+
when @sorted_actions[j][3] == width && @sorted_actions[j][2] < count
|
369
|
+
j -= 1
|
370
|
+
else
|
371
|
+
break
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
@sorted_actions.insert(j + 1, action)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
def debug_sorted_actions
|
380
|
+
ary = Array.new
|
381
|
+
@sorted_actions.each do |state_id, froms_and_tos, count, width|
|
382
|
+
ary[state_id] = [state_id, froms_and_tos, count, width]
|
383
|
+
end
|
384
|
+
|
385
|
+
print sprintf("table_print:\n\n")
|
386
|
+
|
387
|
+
print sprintf("order [\n")
|
388
|
+
vectors_count.times do |i|
|
389
|
+
print sprintf("%d, ", @sorted_actions[i] ? @sorted_actions[i][0] : 0)
|
390
|
+
print "\n" if i % 10 == 9
|
391
|
+
end
|
392
|
+
print sprintf("]\n\n")
|
393
|
+
|
394
|
+
|
395
|
+
print sprintf("width [\n")
|
396
|
+
vectors_count.times do |i|
|
397
|
+
print sprintf("%d, ", ary[i] ? ary[i][3] : 0)
|
398
|
+
print "\n" if i % 10 == 9
|
399
|
+
end
|
400
|
+
print sprintf("]\n\n")
|
401
|
+
|
402
|
+
|
403
|
+
print sprintf("tally [\n")
|
404
|
+
vectors_count.times do |i|
|
405
|
+
print sprintf("%d, ", ary[i] ? ary[i][2] : 0)
|
406
|
+
print "\n" if i % 10 == 9
|
407
|
+
end
|
408
|
+
print sprintf("]\n\n")
|
409
|
+
end
|
410
|
+
|
411
|
+
def compute_packed_table
|
412
|
+
# yypact and yypgoto
|
413
|
+
@base = Array.new(vectors_count, BaseMin)
|
414
|
+
# yytable
|
415
|
+
@table = []
|
416
|
+
# yycheck
|
417
|
+
@check = []
|
418
|
+
# Key is froms_and_tos, value is index position
|
419
|
+
pushed = {}
|
420
|
+
userd_res = {}
|
421
|
+
lowzero = 0
|
422
|
+
high = 0
|
423
|
+
|
424
|
+
@sorted_actions.each do |state_id, froms_and_tos, _, _|
|
425
|
+
if (res = pushed[froms_and_tos])
|
426
|
+
@base[state_id] = res
|
427
|
+
next
|
428
|
+
end
|
429
|
+
|
430
|
+
res = lowzero - froms_and_tos.first[0]
|
431
|
+
|
432
|
+
while true do
|
433
|
+
ok = true
|
434
|
+
|
435
|
+
froms_and_tos.each do |from, to|
|
436
|
+
loc = res + from
|
437
|
+
|
438
|
+
if @table[loc]
|
439
|
+
# If the cell of table is set, can not use the cell.
|
440
|
+
ok = false
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
if userd_res[res]
|
445
|
+
ok = false
|
446
|
+
end
|
447
|
+
|
448
|
+
if ok
|
449
|
+
break
|
450
|
+
else
|
451
|
+
res += 1
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
loc = 0
|
456
|
+
|
457
|
+
froms_and_tos.each do |from, to|
|
458
|
+
loc = res + from
|
459
|
+
|
460
|
+
@table[loc] = to
|
461
|
+
@check[loc] = from
|
462
|
+
end
|
463
|
+
|
464
|
+
while (@table[lowzero]) do
|
465
|
+
lowzero += 1
|
466
|
+
end
|
467
|
+
|
468
|
+
high = loc if high < loc
|
469
|
+
|
470
|
+
@base[state_id] = res
|
471
|
+
pushed[froms_and_tos] = res
|
472
|
+
userd_res[res] = true
|
473
|
+
end
|
474
|
+
|
475
|
+
@yylast = high
|
476
|
+
|
477
|
+
# replace_ninf
|
478
|
+
@yypact_ninf = (@base.select {|i| i != BaseMin } + [0]).min - 1
|
479
|
+
@base.map! do |i|
|
480
|
+
case i
|
481
|
+
when BaseMin
|
482
|
+
@yypact_ninf
|
483
|
+
else
|
484
|
+
i
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
@yytable_ninf = (@table.compact.select {|i| i != ErrorActionNumber } + [0]).min - 1
|
489
|
+
@table.map! do |i|
|
490
|
+
case i
|
491
|
+
when nil
|
492
|
+
0
|
493
|
+
when ErrorActionNumber
|
494
|
+
@yytable_ninf
|
495
|
+
else
|
496
|
+
i
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
@check.map! do |i|
|
501
|
+
case i
|
502
|
+
when nil
|
503
|
+
-1
|
504
|
+
else
|
505
|
+
i
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|