ibex 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.
Files changed (118) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +43 -0
  3. data/CHANGELOG.md +30 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +194 -0
  6. data/Rakefile +25 -0
  7. data/Steepfile +10 -0
  8. data/docs/architecture.md +99 -0
  9. data/docs/compat-notes.md +37 -0
  10. data/docs/grammar-reference.md +127 -0
  11. data/docs/lexer-coverage.md +14 -0
  12. data/docs/phase10-extensions.md +27 -0
  13. data/docs/racc-migration.md +47 -0
  14. data/exe/ibex +6 -0
  15. data/gemfiles/Gemfile +7 -0
  16. data/gemfiles/Gemfile.lock +98 -0
  17. data/lib/ibex/analysis/sets.rb +194 -0
  18. data/lib/ibex/analysis.rb +8 -0
  19. data/lib/ibex/cli/counterexample_options.rb +38 -0
  20. data/lib/ibex/cli/outputs.rb +88 -0
  21. data/lib/ibex/cli.rb +333 -0
  22. data/lib/ibex/codegen/dot.rb +52 -0
  23. data/lib/ibex/codegen/html.rb +110 -0
  24. data/lib/ibex/codegen/rbs.rb +54 -0
  25. data/lib/ibex/codegen/report.rb +149 -0
  26. data/lib/ibex/codegen/ruby.rb +235 -0
  27. data/lib/ibex/codegen/symbol_labels.rb +18 -0
  28. data/lib/ibex/error.rb +7 -0
  29. data/lib/ibex/frontend/action_scanner.rb +266 -0
  30. data/lib/ibex/frontend/ast.rb +146 -0
  31. data/lib/ibex/frontend/bootstrap_parser.rb +163 -0
  32. data/lib/ibex/frontend/dsl.rb +196 -0
  33. data/lib/ibex/frontend/generated_parser.rb +434 -0
  34. data/lib/ibex/frontend/generated_parser_base.rb +273 -0
  35. data/lib/ibex/frontend/grammar.y +156 -0
  36. data/lib/ibex/frontend/lexer.rb +165 -0
  37. data/lib/ibex/frontend/parser/declarations.rb +163 -0
  38. data/lib/ibex/frontend/parser/rules.rb +154 -0
  39. data/lib/ibex/frontend/parser.rb +23 -0
  40. data/lib/ibex/frontend/regenerator.rb +41 -0
  41. data/lib/ibex/frontend/source_cursor.rb +94 -0
  42. data/lib/ibex/frontend/token_adapter/declaration_state.rb +248 -0
  43. data/lib/ibex/frontend/token_adapter/delimiter_tracker.rb +37 -0
  44. data/lib/ibex/frontend/token_adapter/rule_state.rb +162 -0
  45. data/lib/ibex/frontend/token_adapter.rb +108 -0
  46. data/lib/ibex/frontend.rb +28 -0
  47. data/lib/ibex/ir/automaton_ir.rb +109 -0
  48. data/lib/ibex/ir/grammar_ir.rb +192 -0
  49. data/lib/ibex/ir/serialize.rb +154 -0
  50. data/lib/ibex/ir.rb +53 -0
  51. data/lib/ibex/lalr/builder.rb +270 -0
  52. data/lib/ibex/lalr/conflict.rb +120 -0
  53. data/lib/ibex/lalr/conflict_search.rb +332 -0
  54. data/lib/ibex/lalr/conflict_search_limits.rb +39 -0
  55. data/lib/ibex/lalr/counterexample.rb +138 -0
  56. data/lib/ibex/lalr/default_reductions.rb +67 -0
  57. data/lib/ibex/lalr.rb +27 -0
  58. data/lib/ibex/normalize/declarations.rb +60 -0
  59. data/lib/ibex/normalize/diagnostics.rb +99 -0
  60. data/lib/ibex/normalize/expander.rb +218 -0
  61. data/lib/ibex/normalize/expression.rb +54 -0
  62. data/lib/ibex/normalize.rb +166 -0
  63. data/lib/ibex/runtime/parser.rb +360 -0
  64. data/lib/ibex/runtime.rb +8 -0
  65. data/lib/ibex/tables.rb +125 -0
  66. data/lib/ibex/version.rb +6 -0
  67. data/lib/ibex.rb +23 -0
  68. data/sig/ibex/analysis/sets.rbs +72 -0
  69. data/sig/ibex/analysis.rbs +6 -0
  70. data/sig/ibex/cli/counterexample_options.rbs +16 -0
  71. data/sig/ibex/cli/outputs.rbs +31 -0
  72. data/sig/ibex/cli.rbs +108 -0
  73. data/sig/ibex/codegen/dot.rbs +19 -0
  74. data/sig/ibex/codegen/html.rbs +35 -0
  75. data/sig/ibex/codegen/rbs.rbs +31 -0
  76. data/sig/ibex/codegen/report.rbs +39 -0
  77. data/sig/ibex/codegen/ruby.rbs +92 -0
  78. data/sig/ibex/codegen/symbol_labels.rbs +11 -0
  79. data/sig/ibex/error.rbs +7 -0
  80. data/sig/ibex/frontend/action_scanner.rbs +74 -0
  81. data/sig/ibex/frontend/ast.rbs +223 -0
  82. data/sig/ibex/frontend/bootstrap_parser.rbs +80 -0
  83. data/sig/ibex/frontend/dsl.rbs +124 -0
  84. data/sig/ibex/frontend/generated_parser.rbs +163 -0
  85. data/sig/ibex/frontend/generated_parser_base.rbs +114 -0
  86. data/sig/ibex/frontend/lexer.rbs +61 -0
  87. data/sig/ibex/frontend/parser/declarations.rbs +56 -0
  88. data/sig/ibex/frontend/parser/rules.rbs +48 -0
  89. data/sig/ibex/frontend/parser.rbs +16 -0
  90. data/sig/ibex/frontend/regenerator.rbs +16 -0
  91. data/sig/ibex/frontend/source_cursor.rbs +73 -0
  92. data/sig/ibex/frontend/token_adapter/declaration_state.rbs +97 -0
  93. data/sig/ibex/frontend/token_adapter/delimiter_tracker.rbs +25 -0
  94. data/sig/ibex/frontend/token_adapter/rule_state.rbs +77 -0
  95. data/sig/ibex/frontend/token_adapter.rbs +67 -0
  96. data/sig/ibex/frontend.rbs +15 -0
  97. data/sig/ibex/ir/automaton_ir.rbs +77 -0
  98. data/sig/ibex/ir/grammar_ir.rbs +143 -0
  99. data/sig/ibex/ir/serialize.rbs +50 -0
  100. data/sig/ibex/ir.rbs +43 -0
  101. data/sig/ibex/lalr/builder.rbs +85 -0
  102. data/sig/ibex/lalr/conflict.rbs +47 -0
  103. data/sig/ibex/lalr/conflict_search.rbs +131 -0
  104. data/sig/ibex/lalr/conflict_search_limits.rbs +29 -0
  105. data/sig/ibex/lalr/counterexample.rbs +53 -0
  106. data/sig/ibex/lalr/default_reductions.rbs +20 -0
  107. data/sig/ibex/lalr.rbs +23 -0
  108. data/sig/ibex/normalize/declarations.rbs +20 -0
  109. data/sig/ibex/normalize/diagnostics.rbs +32 -0
  110. data/sig/ibex/normalize/expander.rbs +66 -0
  111. data/sig/ibex/normalize/expression.rbs +21 -0
  112. data/sig/ibex/normalize.rbs +96 -0
  113. data/sig/ibex/runtime/parser.rbs +167 -0
  114. data/sig/ibex/runtime.rbs +6 -0
  115. data/sig/ibex/tables.rbs +50 -0
  116. data/sig/ibex/version.rbs +6 -0
  117. data/sig/ibex.rbs +6 -0
  118. metadata +161 -0
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ # Grammar frontend node types.
6
+ module AST
7
+ # @rbs!
8
+ # type declaration = Tokens | Precedence | Options | Expect | Start | Convert
9
+ # type item = SymbolReference | InlineAction | Optional | Star | Plus | Group | SeparatedList
10
+ # type user_code = Hash[String, Array[UserCode]]
11
+
12
+ # Adds deterministic, recursively serializable hashes to Struct nodes.
13
+ # @rbs module-self Struct[untyped]
14
+ module Node
15
+ def to_h
16
+ fields = each_pair.to_h { |name, value| [name, serialize(value)] }
17
+ { node: self.class.name.split("::").last }.merge(fields)
18
+ end
19
+
20
+ private
21
+
22
+ def serialize(value)
23
+ return value if value.nil?
24
+
25
+ case value
26
+ when Array then value.map { |item| serialize(item) }
27
+ when Hash then value.to_h { |key, item| [key, serialize(item)] }
28
+ else value.respond_to?(:to_h) ? value.to_h : value
29
+ end
30
+ end
31
+ end
32
+
33
+ Root = Struct.new(
34
+ :class_name, #: String
35
+ :superclass, #: String?
36
+ :declarations, #: Array[declaration]
37
+ :rules, #: Array[Rule]
38
+ :user_code, #: user_code
39
+ :loc, #: Location
40
+ keyword_init: true
41
+ ) do
42
+ include Node
43
+ end
44
+ Tokens = Struct.new(
45
+ :names, #: Array[String]
46
+ :loc, #: Location
47
+ keyword_init: true
48
+ ) { include Node }
49
+ Precedence = Struct.new(
50
+ :direction, #: Symbol
51
+ :levels, #: Array[PrecedenceLevel]
52
+ :loc, #: Location
53
+ keyword_init: true
54
+ ) { include Node }
55
+ PrecedenceLevel = Struct.new(
56
+ :associativity, #: Symbol
57
+ :symbols, #: Array[String]
58
+ :loc, #: Location
59
+ keyword_init: true
60
+ ) { include Node }
61
+ Options = Struct.new(
62
+ :names, #: Array[String]
63
+ :loc, #: Location
64
+ keyword_init: true
65
+ ) { include Node }
66
+ Expect = Struct.new(
67
+ :conflicts, #: Integer
68
+ :loc, #: Location
69
+ keyword_init: true
70
+ ) { include Node }
71
+ Start = Struct.new(
72
+ :name, #: String
73
+ :loc, #: Location
74
+ keyword_init: true
75
+ ) { include Node }
76
+ Convert = Struct.new(
77
+ :pairs, #: Array[Conversion]
78
+ :loc, #: Location
79
+ keyword_init: true
80
+ ) { include Node }
81
+ Conversion = Struct.new(
82
+ :name, #: String
83
+ :expression, #: String
84
+ :loc, #: Location
85
+ keyword_init: true
86
+ ) { include Node }
87
+ Rule = Struct.new(
88
+ :lhs, #: String
89
+ :alternatives, #: Array[Alternative]
90
+ :loc, #: Location
91
+ keyword_init: true
92
+ ) { include Node }
93
+ Alternative = Struct.new(
94
+ :items, #: Array[item]
95
+ :action, #: InlineAction?
96
+ :precedence, #: String?
97
+ :loc, #: Location
98
+ keyword_init: true
99
+ ) { include Node }
100
+ SymbolReference = Struct.new(
101
+ :name, #: String
102
+ :named_reference, #: String?
103
+ :loc, #: Location
104
+ keyword_init: true
105
+ ) { include Node }
106
+ InlineAction = Struct.new(
107
+ :code, #: String
108
+ :loc, #: Location
109
+ keyword_init: true
110
+ ) { include Node }
111
+ Optional = Struct.new(
112
+ :item, #: item
113
+ :loc, #: Location
114
+ keyword_init: true
115
+ ) { include Node }
116
+ Star = Struct.new(
117
+ :item, #: item
118
+ :loc, #: Location
119
+ keyword_init: true
120
+ ) { include Node }
121
+ Plus = Struct.new(
122
+ :item, #: item
123
+ :loc, #: Location
124
+ keyword_init: true
125
+ ) { include Node }
126
+ Group = Struct.new(
127
+ :alternatives, #: Array[Array[item]]
128
+ :loc, #: Location
129
+ keyword_init: true
130
+ ) { include Node }
131
+ SeparatedList = Struct.new(
132
+ :item, #: item
133
+ :separator, #: item
134
+ :nonempty, #: bool
135
+ :loc, #: Location
136
+ keyword_init: true
137
+ ) { include Node }
138
+ UserCode = Struct.new(
139
+ :name, #: String
140
+ :code, #: String
141
+ :loc, #: Location
142
+ keyword_init: true
143
+ ) { include Node }
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ # Handwritten parser used only to regenerate the self-hosted frontend.
6
+ class BootstrapParser
7
+ include BootstrapParserDeclarations
8
+ include BootstrapParserRules
9
+
10
+ # @rbs @tokens: Array[Token]
11
+ # @rbs @index: Integer
12
+ # @rbs @mode: Symbol
13
+
14
+ # @rbs (String | Array[Token] source, ?file: String, ?mode: Symbol) -> void
15
+ def initialize(source, file: "(grammar)", mode: :racc)
16
+ raise ArgumentError, "mode must be :racc or :extended" unless %i[racc extended].include?(mode)
17
+
18
+ @tokens = source.is_a?(Array) ? source : Lexer.new(source, file: file).tokenize
19
+ @index = 0
20
+ @mode = mode
21
+ end
22
+
23
+ # @rbs () -> AST::Root
24
+ def parse
25
+ location = expect_keyword("class").location
26
+ class_name = parse_constant_path
27
+ superclass = accept(:<) ? parse_constant_path : nil
28
+ parse_pragmas
29
+ declarations = parse_declarations
30
+ expect_keyword("rule")
31
+ rules = parse_rules
32
+ expect_keyword("end")
33
+ user_code = parse_user_code
34
+ expect(:eof)
35
+ AST::Root.new(class_name: class_name, superclass: superclass, declarations: declarations,
36
+ rules: rules, user_code: user_code, loc: location)
37
+ end
38
+
39
+ private
40
+
41
+ # @rbs () -> String
42
+ def parse_constant_path
43
+ parts = [token_string(expect(:identifier))]
44
+ parts << token_string(expect(:identifier)) while accept(:scope)
45
+ parts.join("::")
46
+ end
47
+
48
+ # @rbs () -> String
49
+ def parse_symbol_name
50
+ token_string(expect_symbol)
51
+ end
52
+
53
+ # @rbs () -> Token
54
+ def expect_symbol
55
+ return advance if %i[identifier literal].include?(current.type)
56
+
57
+ fail_expected("a grammar symbol")
58
+ end
59
+
60
+ # @rbs (Array[String] values) -> Token
61
+ def expect_one_of(values)
62
+ return advance if current.type == :identifier && values.include?(current.value)
63
+
64
+ fail_expected(values.join(" or "))
65
+ end
66
+
67
+ # @rbs (String value) -> bool
68
+ def keyword?(value)
69
+ current.type == :identifier && current.value == value
70
+ end
71
+
72
+ # @rbs (String value) -> Token
73
+ def expect_keyword(value)
74
+ return advance if keyword?(value)
75
+
76
+ fail_expected(value)
77
+ end
78
+
79
+ # @rbs (Symbol type) -> (Token | false)
80
+ def accept(type)
81
+ return false unless current.type == type
82
+
83
+ advance
84
+ end
85
+
86
+ # @rbs (Symbol type) -> Token
87
+ def expect(type)
88
+ return advance if current.type == type
89
+
90
+ fail_expected(type)
91
+ end
92
+
93
+ # @rbs () -> Token
94
+ def current
95
+ @tokens.fetch(@index)
96
+ end
97
+
98
+ # @rbs () -> Token
99
+ def lookahead
100
+ @tokens.fetch(@index + 1) { @tokens.fetch(-1) }
101
+ end
102
+
103
+ # @rbs () -> Token
104
+ def advance
105
+ token = current
106
+ @index += 1
107
+ token
108
+ end
109
+
110
+ # @rbs () -> AST::user_code
111
+ def parse_user_code
112
+ blocks = Hash.new { |hash, key| hash[key] = Array.new(0) } #: AST::user_code
113
+ while current.type == :user_code
114
+ token = advance
115
+ value = token_user_code(token)
116
+ blocks[value[:name]] << AST::UserCode.new(name: value[:name], code: value[:code], loc: token.location)
117
+ end
118
+ blocks
119
+ end
120
+
121
+ # @rbs (Location location, String feature) -> void
122
+ def extended_only!(location, feature)
123
+ return if @mode == :extended
124
+
125
+ fail_at(location, "#{feature} require extended mode")
126
+ end
127
+
128
+ # @rbs (String | Symbol expected) -> bot
129
+ def fail_expected(expected)
130
+ fail_at(current.location, "expected #{expected}, got #{current.value || current.type}")
131
+ end
132
+
133
+ # @rbs (Location location, String message) -> bot
134
+ def fail_at(location, message)
135
+ raise Ibex::Error, "#{location}: #{message}"
136
+ end
137
+
138
+ # @rbs (Token token) -> String
139
+ def token_string(token)
140
+ value = token.value
141
+ return value if value.is_a?(String)
142
+
143
+ fail_at(token.location, "expected text token")
144
+ end
145
+
146
+ # @rbs (Token token) -> Integer
147
+ def token_integer(token)
148
+ value = token.value
149
+ return value if value.is_a?(Integer)
150
+
151
+ fail_at(token.location, "expected integer token")
152
+ end
153
+
154
+ # @rbs (Token token) -> user_code_token
155
+ def token_user_code(token)
156
+ value = token.value
157
+ return value if value.is_a?(Hash)
158
+
159
+ fail_at(token.location, "expected user-code token")
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ # Builds the same Grammar AST as the text frontend through a Ruby API.
6
+ module DSL
7
+ # @rbs (class_name: Object, ?superclass: Object?, ?file: String) { (Builder) -> void } -> AST::Root
8
+ def grammar(class_name:, superclass: nil, file: "(dsl)")
9
+ builder = Builder.new(class_name: class_name, superclass: superclass, file: file)
10
+ yield builder
11
+ builder.to_ast
12
+ end
13
+ module_function :grammar
14
+
15
+ # Mutable definition context whose output is an immutable-shape AST.
16
+ class Builder
17
+ # @rbs @class_name: String
18
+ # @rbs @superclass: String?
19
+ # @rbs @file: String
20
+ # @rbs @line: Integer
21
+ # @rbs @declarations: Array[AST::declaration]
22
+ # @rbs @rules: Array[AST::Rule]
23
+ # @rbs @user_code: AST::user_code
24
+
25
+ # @rbs (class_name: Object, superclass: Object?, file: String) -> void
26
+ def initialize(class_name:, superclass:, file:)
27
+ @class_name = class_name.to_s
28
+ @superclass = superclass&.to_s
29
+ @file = file
30
+ @line = 1
31
+ @declarations = [] #: Array[AST::declaration]
32
+ @rules = [] #: Array[AST::Rule]
33
+ @user_code = Hash.new { |hash, key| hash[key] = Array.new(0) } #: AST::user_code
34
+ end
35
+
36
+ # @rbs (*Object names) -> void
37
+ def token(*names)
38
+ @declarations << AST::Tokens.new(names: names.map(&:to_s), loc: next_location)
39
+ end
40
+
41
+ # @rbs (*Object names) -> void
42
+ def options(*names)
43
+ @declarations << AST::Options.new(names: names.map(&:to_s), loc: next_location)
44
+ end
45
+
46
+ # @rbs (Integer conflicts) -> void
47
+ def expect(conflicts)
48
+ @declarations << AST::Expect.new(conflicts: conflicts, loc: next_location)
49
+ end
50
+
51
+ # @rbs (Object name) -> void
52
+ def start(name)
53
+ @declarations << AST::Start.new(name: name.to_s, loc: next_location)
54
+ end
55
+
56
+ # @rbs (Object name, Object expression) -> void
57
+ def convert(name, expression)
58
+ location = next_location
59
+ pair = AST::Conversion.new(name: name.to_s, expression: expression.to_s, loc: location)
60
+ @declarations << AST::Convert.new(pairs: [pair], loc: location)
61
+ end
62
+
63
+ # @rbs (?direction: Symbol) { (PrecedenceBuilder) -> void } -> void
64
+ def precedence(direction: :low_to_high)
65
+ location = next_location
66
+ builder = PrecedenceBuilder.new(self)
67
+ yield builder
68
+ @declarations << AST::Precedence.new(direction: direction, levels: builder.levels, loc: location)
69
+ end
70
+
71
+ # @rbs (Object lhs) { (RuleBuilder) -> void } -> void
72
+ def rule(lhs)
73
+ location = next_location
74
+ builder = RuleBuilder.new(self, location)
75
+ yield builder
76
+ @rules << AST::Rule.new(lhs: lhs.to_s, alternatives: builder.alternatives, loc: location)
77
+ end
78
+
79
+ # @rbs (Object name, Object code) -> void
80
+ def user_code(name, code)
81
+ key = name.to_s
82
+ @user_code[key] << AST::UserCode.new(name: key, code: code.to_s, loc: next_location)
83
+ end
84
+
85
+ # @rbs (Object name, ?as: Object?) -> AST::SymbolReference
86
+ def ref(name, as: nil)
87
+ AST::SymbolReference.new(name: name.to_s, named_reference: as&.to_s, loc: next_location)
88
+ end
89
+
90
+ # @rbs (Object item) -> AST::Optional
91
+ def optional(item) = AST::Optional.new(item: normalize_item(item), loc: next_location)
92
+ # @rbs (Object item) -> AST::Star
93
+ def star(item) = AST::Star.new(item: normalize_item(item), loc: next_location)
94
+ # @rbs (Object item) -> AST::Plus
95
+ def plus(item) = AST::Plus.new(item: normalize_item(item), loc: next_location)
96
+
97
+ # @rbs (*Object alternatives) -> AST::Group
98
+ def group(*alternatives)
99
+ normalized = alternatives.map { |alternative| Array(alternative).map { |item| normalize_item(item) } }
100
+ AST::Group.new(alternatives: normalized, loc: next_location)
101
+ end
102
+
103
+ # @rbs (Object item, Object separator, ?nonempty: bool) -> AST::SeparatedList
104
+ def separated_list(item, separator, nonempty: false)
105
+ AST::SeparatedList.new(item: normalize_item(item), separator: normalize_item(separator),
106
+ nonempty: nonempty, loc: next_location)
107
+ end
108
+
109
+ # @rbs (Object code) -> AST::InlineAction
110
+ def inline(code)
111
+ AST::InlineAction.new(code: code.to_s, loc: next_location)
112
+ end
113
+
114
+ # @rbs () -> AST::Root
115
+ def to_ast
116
+ location = Location.new(file: @file, line: 1, column: 1)
117
+ AST::Root.new(class_name: @class_name, superclass: @superclass, declarations: @declarations,
118
+ rules: @rules, user_code: @user_code, loc: location)
119
+ end
120
+
121
+ # @rbs (Object item) -> AST::item
122
+ def normalize_item(item)
123
+ if item.respond_to?(:loc)
124
+ located_item = item #: AST::item
125
+ return located_item
126
+ end
127
+
128
+ ref(item)
129
+ end
130
+
131
+ # @rbs () -> Location
132
+ def next_location
133
+ location = Location.new(file: @file, line: @line, column: 1)
134
+ @line += 1
135
+ location
136
+ end
137
+ end
138
+
139
+ # Collects ordered associativity levels.
140
+ class PrecedenceBuilder
141
+ attr_reader :levels #: Array[AST::PrecedenceLevel]
142
+
143
+ # @rbs @grammar: Builder
144
+
145
+ # @rbs (Builder grammar) -> void
146
+ def initialize(grammar)
147
+ @grammar = grammar
148
+ @levels = [] #: Array[AST::PrecedenceLevel]
149
+ end
150
+
151
+ # @rbs (*Object symbols) -> void
152
+ def left(*symbols) = add_level(:left, symbols)
153
+
154
+ # @rbs (*Object symbols) -> void
155
+ def right(*symbols) = add_level(:right, symbols)
156
+
157
+ # @rbs (*Object symbols) -> void
158
+ def nonassoc(*symbols) = add_level(:nonassoc, symbols)
159
+
160
+ private
161
+
162
+ # @rbs (Symbol associativity, Array[Object] symbols) -> void
163
+ def add_level(associativity, symbols)
164
+ @levels << AST::PrecedenceLevel.new(associativity: associativity, symbols: symbols.map(&:to_s),
165
+ loc: @grammar.next_location)
166
+ end
167
+ end
168
+
169
+ # Collects alternatives for one nonterminal.
170
+ class RuleBuilder
171
+ attr_reader :alternatives #: Array[AST::Alternative]
172
+
173
+ # @rbs @grammar: Builder
174
+ # @rbs @default_location: Location
175
+
176
+ # @rbs (Builder grammar, Location default_location) -> void
177
+ def initialize(grammar, default_location)
178
+ @grammar = grammar
179
+ @default_location = default_location
180
+ @alternatives = [] #: Array[AST::Alternative]
181
+ end
182
+
183
+ # @rbs (*Object items, ?action: Object?, ?precedence: Object?) -> void
184
+ def alt(*items, action: nil, precedence: nil)
185
+ location = @grammar.next_location || @default_location
186
+ normalized = items.map { |item| @grammar.normalize_item(item) }
187
+ action_node = action && AST::InlineAction.new(code: action.to_s, loc: location)
188
+ alternative = AST::Alternative.new(
189
+ items: normalized, action: action_node, precedence: precedence&.to_s, loc: location
190
+ )
191
+ @alternatives << alternative
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end