lrama 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,850 @@
1
+ require "forwardable"
2
+ require "lrama/lexer"
3
+
4
+ module Lrama
5
+ Rule = Struct.new(:id, :lhs, :rhs, :code, :nullable, :precedence_sym, :lineno, keyword_init: true) do
6
+ # TODO: Change this to display_name
7
+ def to_s
8
+ l = lhs.id.s_value
9
+ r = rhs.empty? ? "ε" : rhs.map {|r| r.id.s_value }.join(", ")
10
+
11
+ "#{l} -> #{r}"
12
+ end
13
+
14
+ # Used by #user_actions
15
+ def as_comment
16
+ l = lhs.id.s_value
17
+ r = rhs.empty? ? "%empty" : rhs.map {|r| r.display_name }.join(" ")
18
+
19
+ "#{l}: #{r}"
20
+ end
21
+
22
+ def precedence
23
+ precedence_sym && precedence_sym.precedence
24
+ end
25
+
26
+ def initial_rule?
27
+ id == 0
28
+ end
29
+
30
+ def translated_code
31
+ if code
32
+ code.translated_code
33
+ else
34
+ nil
35
+ end
36
+ end
37
+ end
38
+
39
+ # Symbol is both of nterm and term
40
+ # `number` is both for nterm and term
41
+ # `token_id` is tokentype for term, internal sequence number for nterm
42
+ #
43
+ # TODO: Add validation for ASCII code range for Token::Char
44
+ Symbol = Struct.new(:id, :alias_name, :number, :tag, :term, :token_id, :nullable, :precedence, :printer, keyword_init: true) do
45
+ attr_writer :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol
46
+
47
+ def term?
48
+ term
49
+ end
50
+
51
+ def nterm?
52
+ !term
53
+ end
54
+
55
+ def eof_symbol?
56
+ !!@eof_symbol
57
+ end
58
+
59
+ def error_symbol?
60
+ !!@error_symbol
61
+ end
62
+
63
+ def undef_symbol?
64
+ !!@undef_symbol
65
+ end
66
+
67
+ def accept_symbol?
68
+ !!@accept_symbol
69
+ end
70
+
71
+ def display_name
72
+ if alias_name
73
+ alias_name
74
+ else
75
+ id.s_value
76
+ end
77
+ end
78
+
79
+ # name for yysymbol_kind_t
80
+ #
81
+ # See: b4_symbol_kind_base
82
+ def enum_name
83
+ case
84
+ when accept_symbol?
85
+ name = "YYACCEPT"
86
+ when eof_symbol?
87
+ name = "YYEOF"
88
+ when term? && id.type == Token::Char
89
+ if alias_name
90
+ name = number.to_s + alias_name
91
+ else
92
+ name = number.to_s + id.s_value
93
+ end
94
+ when term? && id.type == Token::Ident
95
+ name = id.s_value
96
+ when nterm? && (id.s_value.include?("$") || id.s_value.include?("@"))
97
+ name = number.to_s + id.s_value
98
+ when nterm?
99
+ name = id.s_value
100
+ else
101
+ raise "Unexpected #{self}"
102
+ end
103
+
104
+ "YYSYMBOL_" + name.gsub(/[^a-zA-Z_0-9]+/, "_")
105
+ end
106
+
107
+ # comment for yysymbol_kind_t
108
+ def comment
109
+ case
110
+ when accept_symbol?
111
+ # YYSYMBOL_YYACCEPT
112
+ id.s_value
113
+ when eof_symbol?
114
+ # YYEOF
115
+ alias_name
116
+ when (term? && 0 < token_id && token_id < 128)
117
+ # YYSYMBOL_3_backslash_, YYSYMBOL_14_
118
+ alias_name || id.s_value
119
+ when id.s_value.include?("$") || id.s_value.include?("@")
120
+ # YYSYMBOL_21_1
121
+ id.s_value
122
+ else
123
+ # YYSYMBOL_keyword_class, YYSYMBOL_strings_1
124
+ alias_name || id.s_value
125
+ end
126
+ end
127
+ end
128
+
129
+ Type = Struct.new(:id, :tag, keyword_init: true)
130
+
131
+ Code = Struct.new(:type, :token_code, keyword_init: true) do
132
+ extend Forwardable
133
+
134
+ def_delegators "token_code", :s_value, :line, :column, :references
135
+
136
+ # $$, $n, @$, @n is translated to C code
137
+ def translated_code
138
+ case type
139
+ when :user_code
140
+ translated_user_code
141
+ when :initial_action
142
+ translated_initial_action_code
143
+ end
144
+ end
145
+
146
+ # * ($1) error
147
+ # * ($$) *yyvaluep
148
+ # * (@1) error
149
+ # * (@$) *yylocationp
150
+ def translated_printer_code(tag)
151
+ t_code = s_value.dup
152
+
153
+ references.reverse.each do |ref|
154
+ first_column = ref.first_column
155
+ last_column = ref.last_column
156
+
157
+ case
158
+ when ref.number == "$" && ref.type == :dollar # $$
159
+ # Omit "<>"
160
+ member = tag.s_value[1..-2]
161
+ str = "((*yyvaluep).#{member})"
162
+ when ref.number == "$" && ref.type == :at # @$
163
+ str = "(*yylocationp)"
164
+ when ref.type == :dollar # $n
165
+ raise "$#{ref.number} can not be used in %printer."
166
+ when ref.type == :at # @n
167
+ raise "@#{ref.number} can not be used in %printer."
168
+ else
169
+ raise "Unexpected. #{code}, #{ref}"
170
+ end
171
+
172
+ t_code[first_column..last_column] = str
173
+ end
174
+
175
+ return t_code
176
+ end
177
+
178
+
179
+ private
180
+
181
+ # * ($1) yyvsp[i]
182
+ # * ($$) yyval
183
+ # * (@1) yylsp[i]
184
+ # * (@$) yyloc
185
+ def translated_user_code
186
+ t_code = s_value.dup
187
+
188
+ references.reverse.each do |ref|
189
+ first_column = ref.first_column
190
+ last_column = ref.last_column
191
+
192
+ case
193
+ when ref.number == "$" && ref.type == :dollar # $$
194
+ # Omit "<>"
195
+ member = ref.tag.s_value[1..-2]
196
+ str = "(yyval.#{member})"
197
+ when ref.number == "$" && ref.type == :at # @$
198
+ str = "(yyloc)"
199
+ when ref.type == :dollar # $n
200
+ i = -ref.position_in_rhs + ref.number
201
+ # Omit "<>"
202
+ member = ref.tag.s_value[1..-2]
203
+ str = "(yyvsp[#{i}].#{member})"
204
+ when ref.type == :at # @n
205
+ i = -ref.position_in_rhs + ref.number
206
+ str = "(yylsp[#{i}])"
207
+ else
208
+ raise "Unexpected. #{code}, #{ref}"
209
+ end
210
+
211
+ t_code[first_column..last_column] = str
212
+ end
213
+
214
+ return t_code
215
+ end
216
+
217
+ # * ($1) error
218
+ # * ($$) yylval
219
+ # * (@1) error
220
+ # * (@$) yylloc
221
+ def translated_initial_action_code
222
+ t_code = s_value.dup
223
+
224
+ references.reverse.each do |ref|
225
+ first_column = ref.first_column
226
+ last_column = ref.last_column
227
+
228
+ case
229
+ when ref.number == "$" && ref.type == :dollar # $$
230
+ str = "yylval"
231
+ when ref.number == "$" && ref.type == :at # @$
232
+ str = "yylloc"
233
+ when ref.type == :dollar # $n
234
+ raise "$#{ref.number} can not be used in initial_action."
235
+ when ref.type == :at # @n
236
+ raise "@#{ref.number} can not be used in initial_action."
237
+ else
238
+ raise "Unexpected. #{code}, #{ref}"
239
+ end
240
+
241
+ t_code[first_column..last_column] = str
242
+ end
243
+
244
+ return t_code
245
+ end
246
+ end
247
+
248
+ # type: :dollar or :at
249
+ # ex_tag: "$<tag>1" (Optional)
250
+ Reference = Struct.new(:type, :number, :ex_tag, :first_column, :last_column, :referring_symbol, :position_in_rhs, keyword_init: true) do
251
+ def tag
252
+ if ex_tag
253
+ ex_tag
254
+ else
255
+ referring_symbol.tag
256
+ end
257
+ end
258
+ end
259
+
260
+ Precedence = Struct.new(:type, :precedence, keyword_init: true) do
261
+ include Comparable
262
+
263
+ def <=>(other)
264
+ self.precedence <=> other.precedence
265
+ end
266
+ end
267
+
268
+ Printer = Struct.new(:ident_or_tags, :code, :lineno, keyword_init: true) do
269
+ def translated_code(member)
270
+ code.translated_printer_code(member)
271
+ end
272
+ end
273
+
274
+ Union = Struct.new(:code, :lineno, keyword_init: true) do
275
+ def braces_less_code
276
+ # Remove braces
277
+ code.s_value[1..-2]
278
+ end
279
+ end
280
+
281
+ Token = Lrama::Lexer::Token
282
+
283
+ # Grammar is the result of parsing an input grammar file
284
+ class Grammar
285
+ # Grammar file information not used by States but by Output
286
+ Aux = Struct.new(:prologue_first_lineno, :prologue, :epilogue_first_lineno, :epilogue, keyword_init: true)
287
+
288
+ attr_reader :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux
289
+ attr_accessor :union, :expect,
290
+ :printers,
291
+ :lex_param, :parse_param, :initial_action,
292
+ :symbols, :types,
293
+ :rules, :_rules,
294
+ :sym_to_rules
295
+
296
+ def initialize
297
+ @printers = []
298
+ @symbols = []
299
+ @types = []
300
+ @_rules = []
301
+ @rules = []
302
+ @sym_to_rules = {}
303
+ @empty_symbol = nil
304
+ @eof_symbol = nil
305
+ @error_symbol = nil
306
+ @undef_symbol = nil
307
+ @accept_symbol = nil
308
+ @aux = Aux.new
309
+
310
+ append_special_symbols
311
+ end
312
+
313
+ def add_printer(ident_or_tags:, code:, lineno:)
314
+ @printers << Printer.new(ident_or_tags: ident_or_tags, code: code, lineno: lineno)
315
+ end
316
+
317
+ def add_term(id:, alias_name: nil, tag: nil, token_id: nil, replace: false)
318
+ if token_id && (sym = @symbols.find {|s| s.token_id == token_id })
319
+ if replace
320
+ sym.id = id
321
+ sym.alias_name = alias_name
322
+ sym.tag = tag
323
+ end
324
+
325
+ return sym
326
+ end
327
+
328
+ if sym = @symbols.find {|s| s.id == id }
329
+ return sym
330
+ end
331
+
332
+ sym = Symbol.new(
333
+ id: id, alias_name: alias_name, number: nil, tag: tag,
334
+ term: true, token_id: token_id, nullable: false
335
+ )
336
+ @symbols << sym
337
+ @terms = nil
338
+
339
+ return sym
340
+ end
341
+
342
+ def add_nterm(id:, alias_name: nil, tag: nil)
343
+ return if @symbols.find {|s| s.id == id }
344
+
345
+ sym = Symbol.new(
346
+ id: id, alias_name: alias_name, number: nil, tag: tag,
347
+ term: false, token_id: nil, nullable: nil,
348
+ )
349
+ @symbols << sym
350
+ @nterms = nil
351
+
352
+ return sym
353
+ end
354
+
355
+ def add_type(id:, tag:)
356
+ @types << Type.new(id: id, tag: tag)
357
+ end
358
+
359
+ def add_nonassoc(sym, precedence)
360
+ set_precedence(sym, Precedence.new(type: :nonassoc, precedence: precedence))
361
+ end
362
+
363
+ def add_left(sym, precedence)
364
+ set_precedence(sym, Precedence.new(type: :left, precedence: precedence))
365
+ end
366
+
367
+ def add_right(sym, precedence)
368
+ set_precedence(sym, Precedence.new(type: :right, precedence: precedence))
369
+ end
370
+
371
+ def set_precedence(sym, precedence)
372
+ raise "" if sym.nterm?
373
+ sym.precedence = precedence
374
+ end
375
+
376
+ def set_union(code, lineno)
377
+ @union = Union.new(code: code, lineno: lineno)
378
+ end
379
+
380
+ def add_rule(lhs:, rhs:, lineno:)
381
+ @_rules << [lhs, rhs, lineno]
382
+ end
383
+
384
+ def build_references(token_code)
385
+ token_code.references.map! do |type, number, tag, first_column, last_column|
386
+ Reference.new(type: type, number: number, ex_tag: tag, first_column: first_column, last_column: last_column)
387
+ end
388
+
389
+ token_code
390
+ end
391
+
392
+ def build_code(type, token_code)
393
+ build_references(token_code)
394
+ Code.new(type: type, token_code: token_code)
395
+ end
396
+
397
+ def prologue_first_lineno=(prologue_first_lineno)
398
+ @aux.prologue_first_lineno = prologue_first_lineno
399
+ end
400
+
401
+ def prologue=(prologue)
402
+ @aux.prologue = prologue
403
+ end
404
+
405
+ def epilogue_first_lineno=(epilogue_first_lineno)
406
+ @aux.epilogue_first_lineno = epilogue_first_lineno
407
+ end
408
+
409
+ def epilogue=(epilogue)
410
+ @aux.epilogue = epilogue
411
+ end
412
+
413
+ def prepare
414
+ normalize_rules
415
+ collect_symbols
416
+ replace_token_with_symbol
417
+ fill_symbol_number
418
+ fill_default_precedence
419
+ fill_sym_to_rules
420
+ fill_nterm_type
421
+ fill_symbol_printer
422
+ @symbols.sort_by!(&:number)
423
+ end
424
+
425
+ # TODO: More validation methods
426
+ def validate!
427
+ validate_symbol_number_uniqueness!
428
+ end
429
+
430
+ def compute_nullable
431
+ @rules.each do |rule|
432
+ case
433
+ when rule.rhs.empty?
434
+ rule.nullable = true
435
+ when rule.rhs.any?(&:term)
436
+ rule.nullable = false
437
+ else
438
+ # noop
439
+ end
440
+ end
441
+
442
+ while true do
443
+ rs = @rules.select {|e| e.nullable.nil? }
444
+ nts = nterms.select {|e| e.nullable.nil? }
445
+ rule_count_1 = rs.count
446
+ nterm_count_1 = nts.count
447
+
448
+ rs.each do |rule|
449
+ if rule.rhs.all?(&:nullable)
450
+ rule.nullable = true
451
+ end
452
+ end
453
+
454
+ nts.each do |nterm|
455
+ find_rules_by_symbol!(nterm).each do |rule|
456
+ if rule.nullable
457
+ nterm.nullable = true
458
+ end
459
+ end
460
+ end
461
+
462
+ rule_count_2 = @rules.count {|e| e.nullable.nil? }
463
+ nterm_count_2 = nterms.count {|e| e.nullable.nil? }
464
+
465
+ if (rule_count_1 == rule_count_2) && (nterm_count_1 == nterm_count_2)
466
+ break
467
+ end
468
+ end
469
+
470
+ rules.select {|r| r.nullable.nil? }.each do |rule|
471
+ rule.nullable = false
472
+ end
473
+
474
+ nterms.select {|r| r.nullable.nil? }.each do |nterm|
475
+ nterm.nullable = false
476
+ end
477
+ end
478
+
479
+ def find_symbol_by_s_value(s_value)
480
+ @symbols.find do |sym|
481
+ sym.id.s_value == s_value
482
+ end
483
+ end
484
+
485
+ def find_symbol_by_s_value!(s_value)
486
+ find_symbol_by_s_value(s_value) || (raise "Symbol not found: #{s_value}")
487
+ end
488
+
489
+ def find_symbol_by_id(id)
490
+ @symbols.find do |sym|
491
+ # TODO: validate uniqueness of Token#s_value and Symbol#alias_name
492
+ sym.id == id || sym.alias_name == id.s_value
493
+ end
494
+ end
495
+
496
+ def find_symbol_by_id!(id)
497
+ find_symbol_by_id(id) || (raise "Symbol not found: #{id}")
498
+ end
499
+
500
+ def find_symbol_by_number!(number)
501
+ sym = @symbols[number]
502
+
503
+ raise "Symbol not found: #{number}" unless sym
504
+ raise "[BUG] Symbol number mismatch. #{number}, #{sym}" if sym.number != number
505
+
506
+ sym
507
+ end
508
+
509
+ def find_rules_by_symbol!(sym)
510
+ find_rules_by_symbol(sym) || (raise "Rules for #{sym} not found")
511
+ end
512
+
513
+ def find_rules_by_symbol(sym)
514
+ @sym_to_rules[sym.number]
515
+ end
516
+
517
+ def terms_count
518
+ terms.count
519
+ end
520
+
521
+ def terms
522
+ @terms ||= @symbols.select(&:term?)
523
+ end
524
+
525
+ def nterms_count
526
+ nterms.count
527
+ end
528
+
529
+ def nterms
530
+ @nterms ||= @symbols.select(&:nterm?)
531
+ end
532
+
533
+ private
534
+
535
+ def find_nterm_by_id!(id)
536
+ nterms.find do |nterm|
537
+ nterm.id == id
538
+ end || (raise "Nterm not found: #{id}")
539
+ end
540
+
541
+
542
+ def append_special_symbols
543
+ # YYEMPTY (token_id: -2, number: -2) is added when a template is evaluated
544
+ # term = add_term(id: Token.new(Token::Ident, "YYEMPTY"), token_id: -2)
545
+ # term.number = -2
546
+ # @empty_symbol = term
547
+
548
+ # YYEOF
549
+ term = add_term(id: Token.new(type: Token::Ident, s_value: "YYEOF"), alias_name: "\"end of file\"", token_id: 0)
550
+ term.number = 0
551
+ term.eof_symbol = true
552
+ @eof_symbol = term
553
+
554
+ # YYerror
555
+ term = add_term(id: Token.new(type: Token::Ident, s_value: "YYerror"), alias_name: "error")
556
+ term.number = 1
557
+ term.error_symbol = true
558
+ @error_symbol = term
559
+
560
+ # YYUNDEF
561
+ term = add_term(id: Token.new(type: Token::Ident, s_value: "YYUNDEF"), alias_name: "\"invalid token\"")
562
+ term.number = 2
563
+ term.undef_symbol = true
564
+ @undef_symbol = term
565
+
566
+ # $accept
567
+ term = add_nterm(id: Token.new(type: Token::Ident, s_value: "$accept"))
568
+ term.accept_symbol = true
569
+ @accept_symbol = term
570
+ end
571
+
572
+ # 1. Add $accept rule to the top of rules
573
+ # 2. Extract precedence and last action
574
+ # 3. Extract action in the middle of RHS into new Empty rule
575
+ # 4. Append id and extract action then create Rule
576
+ #
577
+ # Bison 3.8.2 uses different orders for symbol number and rule number
578
+ # when a rule has actions in the middle of a rule.
579
+ #
580
+ # For example,
581
+ #
582
+ # `program: $@1 top_compstmt`
583
+ #
584
+ # Rules are ordered like below,
585
+ #
586
+ # 1 $@1: ε
587
+ # 2 program: $@1 top_compstmt
588
+ #
589
+ # Symbols are ordered like below,
590
+ #
591
+ # 164 program
592
+ # 165 $@1
593
+ #
594
+ def normalize_rules
595
+ # 1. Add $accept rule to the top of rules
596
+ accept = find_symbol_by_s_value!("$accept")
597
+ eof = find_symbol_by_number!(0)
598
+ lineno = @_rules.first ? @_rules.first[2] : 0
599
+ @rules << Rule.new(id: @rules.count, lhs: accept, rhs: [@_rules.first[0], eof], code: nil, lineno: lineno)
600
+
601
+ extracted_action_number = 1 # @n as nterm
602
+
603
+ @_rules.each do |lhs, rhs, lineno|
604
+ a = []
605
+ rhs1 = []
606
+ code = nil
607
+ precedence_sym = nil
608
+
609
+ # 2. Extract precedence and last action
610
+ rhs.reverse.each do |r|
611
+ case
612
+ when r.is_a?(Symbol) # precedence_sym
613
+ precedence_sym = r
614
+ when (r.type == Token::User_code) && precedence_sym.nil? && code.nil? && rhs1.empty?
615
+ code = r
616
+ else
617
+ rhs1 << r
618
+ end
619
+ end
620
+ rhs1.reverse!
621
+
622
+ # Bison n'th component is 1-origin
623
+ (rhs1 + [code]).compact.each.with_index(1) do |token, i|
624
+ if token.type == Token::User_code
625
+ token.references.each do |ref|
626
+ # Need to keep position_in_rhs for actions in the middle of RHS
627
+ ref.position_in_rhs = i - 1
628
+ next if ref.type == :at
629
+ # $$, $n, @$, @n can be used in any actions
630
+ number = ref.number
631
+
632
+ if number == "$"
633
+ # TODO: Should be postponed after middle actions are extracted?
634
+ ref.referring_symbol = lhs
635
+ else
636
+ raise "Can not refer following component. #{number} >= #{i}. #{token}" if number >= i
637
+ rhs1[number - 1].referred = true
638
+ ref.referring_symbol = rhs1[number - 1]
639
+ end
640
+ end
641
+ end
642
+ end
643
+
644
+ rhs2 = rhs1.map do |token|
645
+ if token.type == Token::User_code
646
+ prefix = token.referred ? "@" : "$@"
647
+ new_token = Token.new(type: Token::Ident, s_value: prefix + extracted_action_number.to_s)
648
+ extracted_action_number += 1
649
+ a << [new_token, token]
650
+ new_token
651
+ else
652
+ token
653
+ end
654
+ end
655
+
656
+ # Extract actions in the middle of RHS
657
+ # into new rules.
658
+ a.each do |new_token, code|
659
+ @rules << Rule.new(id: @rules.count, lhs: new_token, rhs: [], code: Code.new(type: :user_code, token_code: code), lineno: code.line)
660
+ end
661
+
662
+ c = code ? Code.new(type: :user_code, token_code: code) : nil
663
+ @rules << Rule.new(id: @rules.count, lhs: lhs, rhs: rhs2, code: c, precedence_sym: precedence_sym, lineno: lineno)
664
+
665
+ add_nterm(id: lhs)
666
+ a.each do |new_token, _|
667
+ add_nterm(id: new_token)
668
+ end
669
+ end
670
+ end
671
+
672
+ # Collect symbols from rules
673
+ def collect_symbols
674
+ @rules.flat_map(&:rhs).each do |s|
675
+ case s
676
+ when Token
677
+ if s.type == Token::Char
678
+ add_term(id: s)
679
+ end
680
+ when Symbol
681
+ # skip
682
+ else
683
+ raise "Unknown class: #{s}"
684
+ end
685
+ end
686
+ end
687
+
688
+ # Fill #number and #token_id
689
+ def fill_symbol_number
690
+ # TODO: why start from 256
691
+ token_id = 256
692
+
693
+ # YYEMPTY = -2
694
+ # YYEOF = 0
695
+ # YYerror = 1
696
+ # YYUNDEF = 2
697
+ number = 3
698
+
699
+ nterm_token_id = 0
700
+ used_numbers = {}
701
+
702
+ @symbols.map(&:number).each do |n|
703
+ used_numbers[n] = true
704
+ end
705
+
706
+ (@symbols.select(&:term?) + @symbols.select(&:nterm?)).each do |sym|
707
+ while used_numbers[number] do
708
+ number += 1
709
+ end
710
+
711
+ if sym.number.nil?
712
+ sym.number = number
713
+ number += 1
714
+ end
715
+
716
+ # If id is Token::Char, it uses ASCII code
717
+ if sym.term? && sym.token_id.nil?
718
+ if sym.id.type == Token::Char
719
+ # Igonre ' on the both sides
720
+ case sym.id.s_value[1..-2]
721
+ when "\\b"
722
+ sym.token_id = 8
723
+ when "\\f"
724
+ sym.token_id = 12
725
+ when "\\n"
726
+ sym.token_id = 10
727
+ when "\\r"
728
+ sym.token_id = 13
729
+ when "\\t"
730
+ sym.token_id = 9
731
+ when "\\v"
732
+ sym.token_id = 11
733
+ when "\""
734
+ sym.token_id = 34
735
+ when "\'"
736
+ sym.token_id = 39
737
+ when "\\\\"
738
+ sym.token_id = 92
739
+ when /\A\\(\d+)\z/
740
+ sym.token_id = Integer($1, 8)
741
+ when /\A(.)\z/
742
+ sym.token_id = $1.bytes.first
743
+ else
744
+ raise "Unknown Char s_value #{sym}"
745
+ end
746
+ else
747
+ sym.token_id = token_id
748
+ token_id += 1
749
+ end
750
+ end
751
+
752
+ if sym.nterm? && sym.token_id.nil?
753
+ sym.token_id = nterm_token_id
754
+ nterm_token_id += 1
755
+ end
756
+ end
757
+ end
758
+
759
+ def replace_token_with_symbol
760
+ @rules.each do |rule|
761
+ rule.lhs = token_to_symbol(rule.lhs)
762
+
763
+ rule.rhs.map! do |t|
764
+ token_to_symbol(t)
765
+ end
766
+
767
+ if rule.code
768
+ rule.code.references.each do |ref|
769
+ next if ref.type == :at
770
+
771
+ if ref.referring_symbol.type != Token::User_code
772
+ ref.referring_symbol = token_to_symbol(ref.referring_symbol)
773
+ end
774
+ end
775
+ end
776
+ end
777
+ end
778
+
779
+ def token_to_symbol(token)
780
+ case token
781
+ when Token
782
+ find_symbol_by_id!(token)
783
+ when Symbol
784
+ token
785
+ else
786
+ raise "Unknown class: #{token}"
787
+ end
788
+ end
789
+
790
+ # Rule inherits precedence from the last term in RHS.
791
+ #
792
+ # https://www.gnu.org/software/bison/manual/html_node/How-Precedence.html
793
+ def fill_default_precedence
794
+ @rules.each do |rule|
795
+ # Explicitly specified precedence has the highest priority
796
+ next if rule.precedence_sym
797
+
798
+ precedence_sym = nil
799
+ rule.rhs.each do |sym|
800
+ precedence_sym = sym if sym.term?
801
+ end
802
+
803
+ rule.precedence_sym = precedence_sym
804
+ end
805
+ end
806
+
807
+ def fill_sym_to_rules
808
+ @rules.each do |rule|
809
+ key = rule.lhs.number
810
+ @sym_to_rules[key] ||= []
811
+ @sym_to_rules[key] << rule
812
+ end
813
+ end
814
+
815
+ # Fill nterm's tag defined by %type decl
816
+ def fill_nterm_type
817
+ @types.each do |type|
818
+ nterm = find_nterm_by_id!(type.id)
819
+ nterm.tag = type.tag
820
+ end
821
+ end
822
+
823
+ def fill_symbol_printer
824
+ @symbols.each do |sym|
825
+ @printers.each do |printer|
826
+ printer.ident_or_tags.each do |ident_or_tag|
827
+ case ident_or_tag.type
828
+ when Token::Ident
829
+ sym.printer = printer if sym.id == ident_or_tag
830
+ when Token::Tag
831
+ sym.printer = printer if sym.tag == ident_or_tag
832
+ else
833
+ raise "Unknown token type. #{printer}"
834
+ end
835
+ end
836
+ end
837
+ end
838
+ end
839
+
840
+ def validate_symbol_number_uniqueness!
841
+ invalid = @symbols.group_by(&:number).select do |number, syms|
842
+ syms.count > 1
843
+ end
844
+
845
+ return if invalid.empty?
846
+
847
+ raise "Symbol number is dupulicated. #{invalid}"
848
+ end
849
+ end
850
+ end