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,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ class TokenAdapter
6
+ # Classifies tokens through the class header and declaration section.
7
+ class DeclarationState
8
+ DECLARATIONS = {
9
+ "token" => %i[TOKEN token_symbols], "options" => %i[OPTIONS options_identifiers],
10
+ "expect" => %i[EXPECT expect_integer], "start" => %i[START start_symbol],
11
+ "convert" => %i[CONVERT convert_name], "pragma" => %i[PRAGMA pragma_value],
12
+ "rule" => %i[RULE rules]
13
+ }.freeze #: Hash[String, [external_token, Symbol]]
14
+ ASSOCIATIONS = {
15
+ "left" => :LEFT, "right" => :RIGHT, "nonassoc" => :NONASSOC
16
+ }.freeze #: Hash[String, external_token]
17
+ SCALAR_TYPES = {
18
+ literal: :LITERAL, integer: :INTEGER, action: :ACTION, user_code: :USER_CODE
19
+ }.freeze #: Hash[Symbol, external_token]
20
+ EXPECTATIONS = {
21
+ class_keyword: "class", class_name: "identifier", superclass_name: "identifier",
22
+ expect_integer: "integer", start_symbol: "a grammar symbol"
23
+ }.freeze #: Hash[Symbol, String]
24
+
25
+ attr_reader :conversion_name #: Token?
26
+ attr_reader :declaration #: Symbol?
27
+ attr_reader :precedence_closer #: String?
28
+ attr_reader :state #: Symbol
29
+
30
+ # @rbs () -> void
31
+ def initialize
32
+ @state = :class_keyword
33
+ end
34
+
35
+ # @rbs (Token token, Array[Token] remaining) -> external_token
36
+ def classify(token, remaining)
37
+ return classify_identifier(token, remaining) if token.type == :identifier
38
+ return classify_scalar(token, remaining) if SCALAR_TYPES.key?(token.type)
39
+
40
+ classify_punctuation(token)
41
+ end
42
+
43
+ # @rbs () -> bool
44
+ def rules?
45
+ @state == :rules
46
+ end
47
+
48
+ # @rbs () -> bool
49
+ def extended_pragma?
50
+ @extended_pragma == true
51
+ end
52
+
53
+ # @rbs (Token? token) -> String?
54
+ def expectation(token)
55
+ return "identifier" if @state == :pragma_value && token&.type != :identifier
56
+
57
+ expected = EXPECTATIONS[@state]
58
+ return expected if expected
59
+
60
+ if @declaration == :precedence
61
+ precedence_expectation(token)
62
+ elsif @declaration == :convert
63
+ "end"
64
+ elsif @state == :declaration
65
+ token&.type == :eof ? "rule" : "a declaration or rule"
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # @rbs (Token token, Array[Token] remaining) -> external_token
72
+ def classify_identifier(token, remaining)
73
+ case @state
74
+ when :class_keyword then class_keyword(token)
75
+ when :class_name, :superclass_name then constant_name(remaining)
76
+ when :declaration then begin_declaration(token)
77
+ when :token_symbols, :options_identifiers then declaration_symbol(token)
78
+ when :precedence_association, :precedence_symbols then precedence_identifier(token)
79
+ when :start_symbol then finish_single_symbol(:IDENTIFIER)
80
+ when :pragma_value then finish_pragma(token)
81
+ when :convert_name then begin_conversion(token, :IDENTIFIER, remaining)
82
+ else :IDENTIFIER
83
+ end
84
+ end
85
+
86
+ # @rbs (Token token) -> external_token
87
+ def class_keyword(token)
88
+ return :IDENTIFIER unless string_value(token) == "class"
89
+
90
+ @state = :class_name
91
+ :CLASS
92
+ end
93
+
94
+ # @rbs (Array[Token] remaining) -> external_token
95
+ def constant_name(remaining)
96
+ following = remaining.first
97
+ raise Ibex::Error, "unexpected end of token stream" unless following
98
+
99
+ @state = if following.type == :scope
100
+ @state
101
+ elsif following.type == :<
102
+ :superclass_marker
103
+ else
104
+ :declaration
105
+ end
106
+ :IDENTIFIER
107
+ end
108
+
109
+ # @rbs (Token token) -> external_token
110
+ def begin_declaration(token)
111
+ value = string_value(token)
112
+ return begin_precedence(token) if %w[prechigh preclow].include?(value)
113
+
114
+ raise Ibex::Error, "#{token.location}: duplicate pragma extended" if value == "pragma" && @extended_pragma
115
+
116
+ terminal, next_state = DECLARATIONS[value]
117
+ return :IDENTIFIER unless terminal
118
+
119
+ @state = next_state
120
+ @declaration = value.to_sym unless terminal == :RULE
121
+ @declaration = nil if terminal == :RULE
122
+ terminal
123
+ end
124
+
125
+ # @rbs (Token token) -> external_token
126
+ def finish_pragma(token)
127
+ value = string_value(token)
128
+ raise Ibex::Error, "#{token.location}: unknown pragma #{value}" unless value == "extended"
129
+
130
+ @extended_pragma = true
131
+ @state = :declaration
132
+ @declaration = nil
133
+ :IDENTIFIER
134
+ end
135
+
136
+ # @rbs (Token token) -> external_token
137
+ def begin_precedence(token)
138
+ high_to_low = string_value(token) == "prechigh"
139
+ @precedence_closer = high_to_low ? "preclow" : "prechigh"
140
+ @declaration = :precedence
141
+ @state = :precedence_association
142
+ high_to_low ? :PRECHIGH : :PRECLOW
143
+ end
144
+
145
+ # @rbs (Token token) -> external_token
146
+ def declaration_symbol(token)
147
+ return begin_declaration(token) if declaration_boundary?(string_value(token))
148
+
149
+ :IDENTIFIER
150
+ end
151
+
152
+ # @rbs (String value) -> bool
153
+ def declaration_boundary?(value)
154
+ DECLARATIONS.key?(value) || %w[prechigh preclow].include?(value)
155
+ end
156
+
157
+ # @rbs (Token token) -> external_token
158
+ def precedence_identifier(token)
159
+ value = string_value(token)
160
+ return finish_precedence(token) if value == @precedence_closer
161
+
162
+ association = ASSOCIATIONS[value]
163
+ @state = :precedence_symbols if association
164
+ association || :IDENTIFIER
165
+ end
166
+
167
+ # @rbs (Token token) -> external_token
168
+ def finish_precedence(token)
169
+ @state = :declaration
170
+ @declaration = nil
171
+ @precedence_closer = nil
172
+ string_value(token) == "prechigh" ? :PRECHIGH : :PRECLOW
173
+ end
174
+
175
+ # @rbs (Token token, external_token type, Array[Token] remaining) -> external_token
176
+ def begin_conversion(token, type, remaining)
177
+ if token.type == :identifier && token.value == "end"
178
+ @state = :declaration
179
+ @declaration = nil
180
+ return :END
181
+ end
182
+
183
+ validate_conversion_line(token, remaining)
184
+ @conversion_name = token
185
+ @state = :convert_expression
186
+ type
187
+ end
188
+
189
+ # @rbs (Token token, Array[Token] remaining) -> external_token
190
+ def classify_scalar(token, remaining)
191
+ type = SCALAR_TYPES.fetch(token.type)
192
+ return finish_single_symbol(type) if @state == :expect_integer && type == :INTEGER
193
+ return finish_single_symbol(type) if @state == :start_symbol && type == :LITERAL
194
+ return begin_conversion(token, type, remaining) if @state == :convert_name && type == :LITERAL
195
+ return finish_conversion(type) if @state == :convert_expression && type == :LITERAL
196
+
197
+ type
198
+ end
199
+
200
+ # @rbs (external_token type) -> external_token
201
+ def finish_single_symbol(type)
202
+ @state = :declaration
203
+ @declaration = nil
204
+ type
205
+ end
206
+
207
+ # @rbs (external_token type) -> external_token
208
+ def finish_conversion(type)
209
+ @state = :convert_name
210
+ @conversion_name = nil
211
+ type
212
+ end
213
+
214
+ # @rbs (Token token) -> external_token
215
+ def classify_punctuation(token)
216
+ @state = :superclass_name if @state == :superclass_marker && token.type == :<
217
+ string_value(token)
218
+ end
219
+
220
+ # @rbs (Token name, Array[Token] remaining) -> void
221
+ def validate_conversion_line(name, remaining)
222
+ line = name.location.line
223
+ rest = remaining.take_while do |token|
224
+ token.type != :eof && token.location.line == line && !(token.type == :identifier && token.value == "end")
225
+ end
226
+ return if rest.length == 1 && rest.first.type == :literal
227
+
228
+ raise Ibex::Error, "#{name.location}: expected a quoted Ruby conversion expression"
229
+ end
230
+
231
+ # @rbs (Token? token) -> String?
232
+ def precedence_expectation(token)
233
+ return @precedence_closer if token&.type == :eof
234
+
235
+ "left or right or nonassoc" if @state == :precedence_association
236
+ end
237
+
238
+ # @rbs (Token token) -> String
239
+ def string_value(token)
240
+ value = token.value
241
+ return value if value.is_a?(String)
242
+
243
+ raise Ibex::Error, "#{token.location}: expected text token"
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ class TokenAdapter
6
+ # Tracks ordinary EBNF groups separately from separated-list calls.
7
+ class DelimiterTracker
8
+ attr_reader :group_opening #: Token?
9
+ attr_reader :open_kind #: delimiter_kind?
10
+
11
+ # @rbs @stack: Array[[delimiter_kind, Token]]
12
+
13
+ # @rbs () -> void
14
+ def initialize
15
+ @stack = [] #: Array[[delimiter_kind, Token]]
16
+ end
17
+
18
+ # @rbs () -> bool
19
+ def empty?
20
+ @stack.empty?
21
+ end
22
+
23
+ # @rbs (Token token, external_token external, external_token? previous_external) -> void
24
+ def observe(token, external, previous_external)
25
+ if external == "("
26
+ separated = %i[SEPARATED_LIST SEPARATED_NONEMPTY_LIST].include?(previous_external)
27
+ @stack << [separated ? :separated : :group, token] #: [delimiter_kind, Token]
28
+ elsif external == ")"
29
+ @stack.pop
30
+ end
31
+ @open_kind = @stack.last&.first
32
+ @group_opening = @stack.reverse.find { |kind, _opening| kind == :group }&.last
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ class TokenAdapter
6
+ # Classifies context-dependent tokens in the grammar rule section.
7
+ class RuleState
8
+ SCALAR_TYPES = {
9
+ literal: :LITERAL, integer: :INTEGER, action: :ACTION, user_code: :USER_CODE
10
+ }.freeze #: Hash[Symbol, external_token]
11
+
12
+ attr_reader :section #: parser_section
13
+ attr_reader :state #: Symbol
14
+
15
+ # @rbs @delimiters: DelimiterTracker
16
+ # @rbs @lhs_column: Integer?
17
+ # @rbs @rule_seen: bool?
18
+
19
+ # @rbs () -> void
20
+ def initialize
21
+ @state = :rules_lhs
22
+ @section = :rules
23
+ @delimiters = DelimiterTracker.new
24
+ end
25
+
26
+ # @rbs (Token token, Array[Token] remaining, last_external: external_token?,
27
+ # previous_external: external_token?) -> external_token
28
+ def classify(token, remaining, last_external:, previous_external:)
29
+ external = if token.type == :identifier
30
+ classify_identifier(token, remaining, last_external, previous_external)
31
+ elsif SCALAR_TYPES.key?(token.type)
32
+ SCALAR_TYPES.fetch(token.type)
33
+ else
34
+ classify_punctuation(token)
35
+ end
36
+ @delimiters.observe(token, external, last_external)
37
+ external
38
+ end
39
+
40
+ # @rbs () -> Token?
41
+ def group_opening
42
+ @delimiters.group_opening
43
+ end
44
+
45
+ # @rbs () -> delimiter_kind?
46
+ def open_delimiter_kind
47
+ @delimiters.open_kind
48
+ end
49
+
50
+ # @rbs (Token? token) -> String?
51
+ def expectation(token)
52
+ if @state == :rules_lhs && @section == :rules
53
+ rules_lhs_expectation(token)
54
+ elsif @state == :rule_colon
55
+ ":"
56
+ elsif @state == :rule_rhs && %w[) ,].include?(token&.value)
57
+ "a grammar symbol"
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ # @rbs (Token token, Array[Token] remaining, external_token? last_external,
64
+ # external_token? previous_external) -> external_token
65
+ def classify_identifier(token, remaining, last_external, previous_external)
66
+ return rule_lhs(token) if @state == :rules_lhs
67
+ return :IDENTIFIER unless @state == :rule_rhs
68
+ return :IDENTIFIER if named_reference_name?(last_external, previous_external)
69
+
70
+ value = string_value(token)
71
+ return :IDENTIFIER if value == "end" && @delimiters.open_kind == :separated
72
+ return finish_rules if value == "end"
73
+
74
+ following = remaining.first
75
+ return :IDENTIFIER unless following
76
+ return start_rule(token) if rule_boundary?(token, following)
77
+ return separated_terminal(token) if separated_list_call?(token, following)
78
+
79
+ :IDENTIFIER
80
+ end
81
+
82
+ # @rbs (Token token) -> external_token
83
+ def rule_lhs(token)
84
+ if string_value(token) == "end"
85
+ @section = :user_code
86
+ return :END
87
+ end
88
+
89
+ @rule_seen = true
90
+ @lhs_column = token.location.column
91
+ @state = :rule_colon
92
+ :LHS
93
+ end
94
+
95
+ # @rbs (external_token? last_external, external_token? previous_external) -> bool
96
+ def named_reference_name?(last_external, previous_external)
97
+ last_external == ":" && %i[IDENTIFIER LITERAL].include?(previous_external)
98
+ end
99
+
100
+ # @rbs (Token token, Token following) -> bool
101
+ def rule_boundary?(token, following)
102
+ lhs_column = @lhs_column
103
+ return false unless lhs_column
104
+
105
+ @delimiters.empty? && following.type == :":" && token.location.column <= lhs_column
106
+ end
107
+
108
+ # @rbs (Token token) -> external_token
109
+ def start_rule(token)
110
+ @rule_seen = true
111
+ @lhs_column = token.location.column
112
+ @state = :rule_colon
113
+ :LHS
114
+ end
115
+
116
+ # @rbs () -> external_token
117
+ def finish_rules
118
+ @section = :user_code
119
+ :END
120
+ end
121
+
122
+ # @rbs (Token token, Token following) -> bool
123
+ def separated_list_call?(token, following)
124
+ %w[separated_list separated_nonempty_list].include?(string_value(token)) && following.type == :"("
125
+ end
126
+
127
+ # @rbs (Token token) -> external_token
128
+ def separated_terminal(token)
129
+ string_value(token) == "separated_list" ? :SEPARATED_LIST : :SEPARATED_NONEMPTY_LIST
130
+ end
131
+
132
+ # @rbs (Token token) -> external_token
133
+ def classify_punctuation(token)
134
+ @state = :rule_rhs if @state == :rule_colon && token.type == :":"
135
+ reset_rule_boundary if @state == :rule_rhs && token.type == :";" && @delimiters.empty?
136
+ string_value(token)
137
+ end
138
+
139
+ # @rbs () -> void
140
+ def reset_rule_boundary
141
+ @lhs_column = nil
142
+ @state = :rules_lhs
143
+ end
144
+
145
+ # @rbs (Token? token) -> String
146
+ def rules_lhs_expectation(token)
147
+ return "identifier" unless token&.type == :eof
148
+
149
+ @rule_seen ? "end" : "at least one rule"
150
+ end
151
+
152
+ # @rbs (Token token) -> String
153
+ def string_value(token)
154
+ value = token.value
155
+ return value if value.is_a?(String)
156
+
157
+ raise Ibex::Error, "#{token.location}: expected text token"
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module Frontend
5
+ # Delivers lexer tokens through declaration and rule context classifiers.
6
+ class TokenAdapter
7
+ attr_reader :eof_token #: Token?
8
+ attr_reader :last_token #: Token?
9
+ attr_reader :previous_external #: external_token?
10
+
11
+ # @rbs @tokens: Array[Token]
12
+ # @rbs @index: Integer
13
+ # @rbs @declarations: DeclarationState
14
+ # @rbs @classifier: DeclarationState | RuleState
15
+ # @rbs @rules: RuleState?
16
+ # @rbs @last_external: external_token?
17
+
18
+ # @rbs (Array[Token] tokens) -> void
19
+ def initialize(tokens)
20
+ @tokens = tokens
21
+ @index = 0
22
+ @declarations = DeclarationState.new
23
+ @classifier = @declarations
24
+ end
25
+
26
+ # @rbs () -> ([external_token, Token] | false)
27
+ def next_token
28
+ token = @tokens.fetch(@index)
29
+ @index += 1
30
+ @last_token = token
31
+ if token.type == :eof
32
+ @eof_token = token
33
+ return false
34
+ end
35
+
36
+ external = classify(token)
37
+ enter_rules if @classifier == @declarations && @declarations.rules?
38
+ @previous_external = @last_external
39
+ @last_external = external
40
+ [external, token]
41
+ end
42
+
43
+ # @rbs () -> Token?
44
+ def conversion_name
45
+ @declarations.conversion_name
46
+ end
47
+
48
+ # @rbs () -> Symbol?
49
+ def declaration
50
+ @declarations.declaration
51
+ end
52
+
53
+ # @rbs () -> String?
54
+ def precedence_closer
55
+ @declarations.precedence_closer
56
+ end
57
+
58
+ # @rbs () -> bool
59
+ def extended_pragma?
60
+ @declarations.extended_pragma?
61
+ end
62
+
63
+ # @rbs () -> parser_section
64
+ def section
65
+ rules = @rules
66
+ rules ? rules.section : :declarations
67
+ end
68
+
69
+ # @rbs () -> Symbol
70
+ def state
71
+ @classifier.state
72
+ end
73
+
74
+ # @rbs () -> Token?
75
+ def group_opening
76
+ @rules&.group_opening
77
+ end
78
+
79
+ # @rbs () -> delimiter_kind?
80
+ def open_delimiter_kind
81
+ @rules&.open_delimiter_kind
82
+ end
83
+
84
+ # @rbs (Token? token) -> String?
85
+ def expectation(token)
86
+ @classifier.expectation(token)
87
+ end
88
+
89
+ private
90
+
91
+ # @rbs (Token token) -> external_token
92
+ def classify(token)
93
+ remaining = @tokens.drop(@index)
94
+ rules = @rules
95
+ return @declarations.classify(token, remaining) unless rules
96
+
97
+ rules.classify(token, remaining, last_external: @last_external,
98
+ previous_external: @previous_external)
99
+ end
100
+
101
+ # @rbs () -> void
102
+ def enter_rules
103
+ @rules = RuleState.new
104
+ @classifier = @rules
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "error"
4
+ require_relative "tables"
5
+ require_relative "runtime"
6
+ require_relative "frontend/source_cursor"
7
+ require_relative "frontend/action_scanner"
8
+ require_relative "frontend/lexer"
9
+ require_relative "frontend/ast"
10
+ require_relative "frontend/token_adapter/declaration_state"
11
+ require_relative "frontend/token_adapter/delimiter_tracker"
12
+ require_relative "frontend/token_adapter/rule_state"
13
+ require_relative "frontend/token_adapter"
14
+ require_relative "frontend/generated_parser_base"
15
+ require_relative "frontend/generated_parser"
16
+ require_relative "frontend/parser"
17
+ require_relative "frontend/dsl"
18
+
19
+ module Ibex
20
+ module Frontend
21
+ # @rbs!
22
+ # type user_code_token = { name: String, code: String }
23
+ # type token_value = String | Integer | user_code_token | nil
24
+ # type external_token = Symbol | String
25
+ # type parser_section = :declarations | :rules | :user_code
26
+ # type delimiter_kind = :group | :separated
27
+ end
28
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ module IR
5
+ # A merged LALR item with its lookahead token ids.
6
+ class AutomatonItem
7
+ attr_reader :production #: Integer
8
+ attr_reader :dot #: Integer
9
+ attr_reader :lookaheads #: Array[Integer]
10
+
11
+ # @rbs (production: Integer, dot: Integer, lookaheads: Array[Integer]) -> void
12
+ def initialize(production:, dot:, lookaheads:)
13
+ @production = production
14
+ @dot = dot
15
+ @lookaheads = lookaheads.sort.freeze
16
+ freeze
17
+ end
18
+
19
+ # @rbs (Grammar grammar) -> Hash[Symbol, untyped]
20
+ def to_h(grammar)
21
+ { production: @production, dot: @dot,
22
+ lookaheads: @lookaheads.map do |id|
23
+ symbol = grammar.symbol_by_id(id) || raise(Ibex::Error, "missing grammar symbol id #{id}")
24
+ symbol.name
25
+ end }
26
+ end
27
+ end
28
+
29
+ # A deterministic LALR automaton state.
30
+ class AutomatonState
31
+ attr_reader :id #: Integer
32
+ attr_reader :items #: Array[AutomatonItem]
33
+ attr_reader :transitions #: Hash[Integer, Integer]
34
+ attr_reader :actions #: Hash[Integer, parser_action]
35
+ attr_reader :gotos #: Hash[Integer, Integer]
36
+ attr_reader :default_action #: parser_action?
37
+ attr_reader :conflicts #: Array[conflict]
38
+
39
+ # @rbs (id: Integer, items: Array[AutomatonItem], transitions: Hash[Integer, Integer],
40
+ # actions: Hash[Integer, parser_action], gotos: Hash[Integer, Integer], ?default_action: parser_action?,
41
+ # ?conflicts: Array[conflict]) -> void
42
+ def initialize(id:, items:, transitions:, actions:, gotos:, default_action: nil, conflicts: [])
43
+ @id = id
44
+ @items = items.freeze
45
+ @transitions = IR.deep_freeze(transitions)
46
+ @actions = IR.deep_freeze(actions)
47
+ @gotos = IR.deep_freeze(gotos)
48
+ @default_action = IR.deep_freeze(default_action)
49
+ @conflicts = IR.deep_freeze(conflicts)
50
+ freeze
51
+ end
52
+
53
+ # @rbs (Grammar grammar) -> Hash[Symbol, untyped]
54
+ def to_h(grammar)
55
+ { id: @id, items: @items.map { |item| item.to_h(grammar) },
56
+ transitions: named_keys(@transitions, grammar), actions: named_keys(@actions, grammar),
57
+ gotos: named_keys(@gotos, grammar), default_action: @default_action, conflicts: @conflicts }
58
+ end
59
+
60
+ private
61
+
62
+ # @rbs (Hash[Integer, untyped] values, Grammar grammar) -> Hash[String, untyped]
63
+ def named_keys(values, grammar)
64
+ values.to_h do |symbol_id, value|
65
+ symbol = grammar.symbol_by_id(symbol_id) || raise(Ibex::Error, "missing grammar symbol id #{symbol_id}")
66
+ [symbol.name, value]
67
+ end
68
+ end
69
+ end
70
+
71
+ # Immutable LALR automaton and its source grammar.
72
+ class Automaton
73
+ attr_reader :algorithm #: String
74
+ attr_reader :grammar_digest #: String
75
+ attr_reader :grammar #: Grammar
76
+ attr_reader :states #: Array[AutomatonState]
77
+ attr_reader :conflict_summary #: conflict_summary
78
+ attr_reader :schema_version #: Integer
79
+
80
+ # @rbs (grammar: Grammar, states: Array[AutomatonState], conflict_summary: conflict_summary,
81
+ # ?algorithm: String, ?grammar_digest: String?, ?schema_version: Integer) -> void
82
+ def initialize(grammar:, states:, conflict_summary:, algorithm: "lalr1", grammar_digest: nil,
83
+ schema_version: SCHEMA_VERSION)
84
+ @algorithm = algorithm.freeze
85
+ @grammar = grammar
86
+ @grammar_digest = (grammar_digest || digest_for(grammar)).freeze
87
+ @states = states.freeze
88
+ @conflict_summary = IR.deep_freeze(conflict_summary)
89
+ @schema_version = schema_version
90
+ freeze
91
+ end
92
+
93
+ # @rbs () -> Hash[Symbol, untyped]
94
+ def to_h
95
+ { ibex_ir: "automaton", schema_version: @schema_version, algorithm: @algorithm,
96
+ grammar_digest: @grammar_digest, grammar: @grammar.to_h,
97
+ states: @states.map { |state| state.to_h(@grammar) }, conflict_summary: @conflict_summary }
98
+ end
99
+
100
+ private
101
+
102
+ # @rbs (Grammar grammar) -> String
103
+ def digest_for(grammar)
104
+ require "digest"
105
+ "sha256:#{Digest::SHA256.hexdigest(IR::Serialize.dump(grammar))}"
106
+ end
107
+ end
108
+ end
109
+ end