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,360 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ibex
5
+ module Runtime
6
+ # Parser-table shape understood by this runtime.
7
+ PARSER_TABLE_FORMAT_VERSION = 1 #: Integer
8
+
9
+ # Raised by the default parser error handler.
10
+ class ParseError < StandardError; end
11
+
12
+ # rubocop:disable Metrics/ClassLength
13
+
14
+ # Drives a table-defined LR parser without native extensions.
15
+ #
16
+ # Subclasses provide `.parser_tables`, returning `:tokens`, `:token_names`,
17
+ # `:actions`, `:gotos`, and `:productions`. Actions are represented by
18
+ # `[:shift, state]`, `[:reduce, production]`, `[:accept]`, or `[:error]`.
19
+ class Parser
20
+ EOF_TOKEN = 0 #: Integer
21
+ ERROR_TOKEN = 1 #: Integer
22
+ NO_LOOKAHEAD = Object.new.freeze #: Object
23
+ RECOVERY_SHIFTS = 3 #: Integer
24
+ empty_row = {} # @type var empty_row: Hash[Integer, untyped]
25
+
26
+ EMPTY_ROW = empty_row.freeze #: Hash[Integer, untyped]
27
+
28
+ # @rbs @yydebug: bool
29
+ # @rbs @yydebug_output: IO
30
+ # @rbs @source: (^() -> untyped)?
31
+ # @rbs @state_stack: Array[Integer]
32
+ # @rbs @value_stack: Array[untyped]
33
+ # @rbs @lookahead: untyped
34
+ # @rbs @lookahead_value: untyped
35
+ # @rbs @recovery_shifts: Integer
36
+ # @rbs @semantic_error: bool
37
+ # @rbs @accept_requested: bool
38
+ # @rbs @unknown_token_id: Integer?
39
+ # @rbs @unknown_token_name: String?
40
+
41
+ attr_accessor :yydebug #: bool
42
+ attr_writer :yydebug_output #: IO
43
+
44
+ # @rbs () -> void
45
+ def initialize
46
+ @yydebug = false
47
+ @yydebug_output = $stderr
48
+ @source = nil
49
+ @state_stack = []
50
+ @value_stack = []
51
+ @lookahead = NO_LOOKAHEAD
52
+ @lookahead_value = nil
53
+ @recovery_shifts = 0
54
+ @semantic_error = false
55
+ @accept_requested = false
56
+ @unknown_token_id = nil
57
+ end
58
+
59
+ # Pull tokens from `next_token` and parse them.
60
+ # @rbs () -> untyped
61
+ def do_parse
62
+ drive_parser(-> { next_token })
63
+ end
64
+
65
+ # Parse tokens yielded by `receiver.method_id`.
66
+ # @rbs (untyped receiver, Symbol method_id) -> untyped
67
+ def yyparse(receiver, method_id)
68
+ stream = Enumerator.new do |tokens|
69
+ receiver.__send__(method_id) { |token| tokens << token }
70
+ end
71
+ drive_parser(-> { stream.next })
72
+ end
73
+
74
+ # Override in pull parsers. Return `[token, value]`, `false`, or `nil`.
75
+ # @rbs () -> ([untyped, untyped] | false | nil)
76
+ def next_token
77
+ raise NotImplementedError, "(input):1:1: next_token must be implemented"
78
+ end
79
+
80
+ # Override to recover from syntax errors. The default always raises.
81
+ # @rbs (Integer token_id, untyped value, Array[untyped] value_stack) -> untyped
82
+ def on_error(token_id, value, _value_stack)
83
+ expected = expected_tokens
84
+ suffix = expected.empty? ? "" : "; expected #{expected.join(', ')}"
85
+ raise ParseError, "(input):1:1: unexpected #{token_to_str(token_id)}#{suffix} (#{value.inspect})"
86
+ end
87
+
88
+ # Called after an ordinary input token is shifted. Override to observe
89
+ # the internal token id, semantic value, and destination state.
90
+ # @rbs (Integer token_id, untyped value, Integer state) -> void
91
+ def on_shift(_token_id, _value, _state); end
92
+
93
+ # Called after a production's semantic action and goto are committed.
94
+ # Override to observe its id, RHS values, and semantic result.
95
+ # @rbs (Integer production_id, Array[untyped] values, untyped result) -> void
96
+ def on_reduce(_production_id, _values, _result); end
97
+
98
+ # Called after the synthetic error token enters a recovery state.
99
+ # The payload describes the original error before recovery popped stacks.
100
+ # @rbs (Integer token_id, untyped value, Array[untyped] value_stack) -> void
101
+ def on_error_recover(_token_id, _value, _value_stack); end
102
+
103
+ # Return a human-readable name for an internal token id.
104
+ # @rbs (Integer token_id) -> String
105
+ def token_to_str(token_id)
106
+ return @unknown_token_name || token_id.to_s if token_id == @unknown_token_id
107
+
108
+ parser_tables.fetch(:token_names).fetch(token_id, token_id.to_s)
109
+ end
110
+
111
+ # Enter error recovery from a semantic action without calling `on_error`.
112
+ # @rbs () -> nil
113
+ def yyerror
114
+ @semantic_error = true
115
+ nil
116
+ end
117
+
118
+ # Leave error recovery immediately.
119
+ # @rbs () -> nil
120
+ def yyerrok
121
+ @recovery_shifts = 0
122
+ nil
123
+ end
124
+
125
+ # Accept immediately after the current semantic action completes.
126
+ # @rbs () -> nil
127
+ def yyaccept
128
+ @accept_requested = true
129
+ nil
130
+ end
131
+
132
+ # Return token names accepted in the current parser state.
133
+ # @rbs () -> Array[String]
134
+ def expected_tokens
135
+ return [] if @state_stack.empty?
136
+
137
+ state = @state_stack.last
138
+ parser_tables.fetch(:token_names).keys.filter_map do |token_id|
139
+ action = table_lookup(parser_tables.fetch(:actions), state, token_id) || default_action(state) || [:error]
140
+ token_to_str(token_id) unless error_action?(action) || token_id == ERROR_TOKEN
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ # @rbs (^() -> untyped source) -> untyped
147
+ def drive_parser(source)
148
+ prepare_parse(source)
149
+ loop do
150
+ action = action_for_current_state
151
+ outcome = perform(action)
152
+ return outcome[1] if outcome[0] == :done
153
+ end
154
+ ensure
155
+ @source = nil
156
+ end
157
+
158
+ # @rbs (^() -> untyped source) -> void
159
+ def prepare_parse(source)
160
+ validate_parser_table_format!
161
+ @source = source
162
+ @state_stack = [0]
163
+ @value_stack = []
164
+ @lookahead = NO_LOOKAHEAD
165
+ @lookahead_value = nil
166
+ @recovery_shifts = 0
167
+ @semantic_error = false
168
+ @accept_requested = false
169
+ @unknown_token_id = nil
170
+ trace("start state 0")
171
+ end
172
+
173
+ # @rbs () -> void
174
+ def validate_parser_table_format!
175
+ tables = parser_tables
176
+ unless tables.key?(:format_version)
177
+ raise ParseError,
178
+ "(tables):1:1: parser tables for #{self.class} are missing :format_version; " \
179
+ "regenerate the parser with the installed Ibex version"
180
+ end
181
+
182
+ actual = tables.fetch(:format_version)
183
+ return if actual == PARSER_TABLE_FORMAT_VERSION
184
+
185
+ raise ParseError,
186
+ "(tables):1:1: unsupported parser table format version #{actual.inspect} for #{self.class}; " \
187
+ "runtime supports #{PARSER_TABLE_FORMAT_VERSION}; regenerate the parser with the installed Ibex version"
188
+ end
189
+
190
+ # @rbs () -> untyped
191
+ def action_for_current_state
192
+ read_lookahead if @lookahead.equal?(NO_LOOKAHEAD)
193
+ state = @state_stack.last
194
+ return [:error] unless parser_tables.fetch(:token_names).key?(@lookahead)
195
+
196
+ explicit = table_lookup(parser_tables.fetch(:actions), state, @lookahead)
197
+ return explicit if explicit
198
+
199
+ default_action(state) || [:error]
200
+ end
201
+
202
+ # @rbs (untyped action) -> untyped
203
+ def perform(action)
204
+ case action.first
205
+ when :shift then shift(action.fetch(1))
206
+ when :reduce then reduce(action.fetch(1))
207
+ when :accept then [:done, @value_stack.last]
208
+ when :error then recover
209
+ else raise ParseError, "(tables):1:1: unknown parser action #{action.inspect}"
210
+ end
211
+ end
212
+
213
+ # @rbs (Integer next_state) -> untyped
214
+ def shift(next_state)
215
+ token_id = @lookahead
216
+ value = @lookahead_value
217
+ trace("shift #{token_to_str(token_id)} -> state #{next_state}")
218
+ @state_stack << next_state
219
+ @value_stack << value
220
+ @lookahead = NO_LOOKAHEAD
221
+ @recovery_shifts -= 1 if @recovery_shifts.positive?
222
+ on_shift(token_id, value, next_state)
223
+ [:continue]
224
+ end
225
+
226
+ # @rbs (Integer production_id) -> untyped
227
+ def reduce(production_id)
228
+ production = parser_tables.fetch(:productions).fetch(production_id)
229
+ length = production.fetch(:length)
230
+ values = @value_stack.last(length)
231
+ hook_values = values.dup
232
+ @state_stack.pop(length)
233
+ @value_stack.pop(length)
234
+ result = reduction_value(production, values)
235
+ next_state = table_lookup(parser_tables.fetch(:gotos), @state_stack.last, production.fetch(:lhs))
236
+ raise ParseError, "(tables):1:1: missing goto for production #{production_id}" if next_state.nil?
237
+
238
+ @state_stack << next_state
239
+ @value_stack << result
240
+ trace("reduce #{production_id} (#{length}) -> state #{next_state}")
241
+ on_reduce(production_id, hook_values, result)
242
+ return [:done, result] if @accept_requested
243
+ return recover(report: false) if @semantic_error
244
+
245
+ [:continue]
246
+ end
247
+
248
+ # @rbs (Hash[Symbol, untyped] production, Array[untyped] values) -> untyped
249
+ def reduction_value(production, values)
250
+ action = production[:action]
251
+ return values.first unless action
252
+ return instance_exec(values, @value_stack.dup, &action) if action.respond_to?(:call)
253
+
254
+ __send__(action, values, @value_stack.dup)
255
+ end
256
+
257
+ # @rbs (?report: bool) -> untyped
258
+ def recover(report: true)
259
+ @semantic_error = false
260
+ if @recovery_shifts.positive?
261
+ return [:done, nil] if @lookahead == EOF_TOKEN
262
+
263
+ trace("discard #{token_to_str(@lookahead)} during recovery")
264
+ @lookahead = NO_LOOKAHEAD
265
+ return [:continue]
266
+ end
267
+
268
+ token_id = @lookahead
269
+ value = @lookahead_value
270
+ value_stack = @value_stack.dup
271
+ on_error(token_id, value, value_stack.dup) if report
272
+ return [:done, nil] unless shift_error_token
273
+
274
+ @recovery_shifts = RECOVERY_SHIFTS
275
+ on_error_recover(token_id, value, value_stack)
276
+ [:continue]
277
+ end
278
+
279
+ # @rbs () -> bool
280
+ def shift_error_token
281
+ loop do
282
+ action = table_lookup(parser_tables.fetch(:actions), @state_stack.last, ERROR_TOKEN)
283
+ if action&.first == :shift
284
+ trace("recover: shift error -> state #{action.fetch(1)}")
285
+ @state_stack << action.fetch(1)
286
+ @value_stack << nil
287
+ return true
288
+ end
289
+ return false if @state_stack.length == 1
290
+
291
+ trace("recover: pop state #{@state_stack.last}")
292
+ @state_stack.pop
293
+ @value_stack.pop
294
+ end
295
+ end
296
+
297
+ # @rbs () -> void
298
+ def read_lookahead
299
+ pair = read_external_token
300
+ if pair.nil? || pair == false
301
+ @lookahead = EOF_TOKEN
302
+ @lookahead_value = nil
303
+ else
304
+ external_token, @lookahead_value = pair
305
+ @lookahead = internal_token_id(external_token)
306
+ end
307
+ trace("read #{token_to_str(@lookahead)}")
308
+ end
309
+
310
+ # @rbs () -> untyped
311
+ def read_external_token
312
+ source = @source
313
+ raise ParseError, "(input):1:1: token source is not available" unless source
314
+
315
+ source.call
316
+ rescue StopIteration
317
+ false
318
+ end
319
+
320
+ # @rbs (untyped external_token) -> Integer
321
+ def internal_token_id(external_token)
322
+ token_id = parser_tables.fetch(:tokens)[external_token]
323
+ return token_id if token_id
324
+
325
+ @unknown_token_name = external_token.inspect
326
+ @unknown_token_id = -external_token.object_id.abs
327
+ end
328
+
329
+ # @rbs () -> Hash[Symbol, untyped]
330
+ def parser_tables
331
+ self.class.__send__(:parser_tables)
332
+ rescue NoMethodError
333
+ raise ParseError, "(tables):1:1: #{self.class} must define .parser_tables"
334
+ end
335
+
336
+ # @rbs (untyped action) -> bool
337
+ def error_action?(action)
338
+ action.nil? || action.first == :error
339
+ end
340
+
341
+ # @rbs (Integer state) -> untyped
342
+ def default_action(state)
343
+ parser_tables.fetch(:default_actions, EMPTY_ROW)[state]
344
+ end
345
+
346
+ # @rbs (untyped table, Integer row, Integer column) -> untyped
347
+ def table_lookup(table, row, column)
348
+ return table.lookup(row, column) if table.respond_to?(:lookup)
349
+
350
+ table.fetch(row, EMPTY_ROW)[column]
351
+ end
352
+
353
+ # @rbs (String message) -> void
354
+ def trace(message)
355
+ @yydebug_output.puts("ibex: #{message}") if @yydebug
356
+ end
357
+ end
358
+ # rubocop:enable Metrics/ClassLength
359
+ end
360
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "runtime/parser"
4
+
5
+ module Ibex
6
+ module Runtime
7
+ end
8
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ibex
4
+ # Parser table construction and row-displacement compression.
5
+ module Tables
6
+ # @rbs!
7
+ # private def runtime_action: (IR::parser_action action) -> IR::runtime_action
8
+ # private def self.runtime_action: (IR::parser_action action) -> IR::runtime_action
9
+
10
+ TableSet = Struct.new(:actions, :gotos, :default_actions, keyword_init: true)
11
+
12
+ # Sparse table represented by per-row offsets and ownership checks.
13
+ class Compact
14
+ attr_reader :offsets #: Array[Integer]
15
+ attr_reader :values #: Array[untyped]
16
+ attr_reader :checks #: Array[Integer?]
17
+ attr_reader :row_count #: Integer
18
+
19
+ class << self
20
+ # @rbs (Array[Hash[Integer, untyped]] rows) -> Compact
21
+ def build(rows)
22
+ offsets = Array.new(rows.length, 0)
23
+ values = [] #: Array[untyped]
24
+ checks = [] #: Array[Integer?]
25
+ rows.each_index.sort_by { |row| [-rows[row].length, row] }.each do |row|
26
+ offset = find_offset(rows[row].keys, checks)
27
+ offsets[row] = offset
28
+ rows[row].each do |column, value|
29
+ index = offset + column
30
+ values[index] = value
31
+ checks[index] = row
32
+ end
33
+ end
34
+ new(offsets: offsets, values: values, checks: checks, row_count: rows.length)
35
+ end
36
+
37
+ private
38
+
39
+ # @rbs (Array[Integer] columns, Array[Integer?] checks) -> Integer
40
+ def find_offset(columns, checks)
41
+ offset = 0
42
+ offset += 1 while columns.any? { |column| checks[offset + column] }
43
+ offset
44
+ end
45
+ end
46
+
47
+ # @rbs (offsets: Array[Integer], values: Array[untyped], checks: Array[Integer?], row_count: Integer) -> void
48
+ def initialize(offsets:, values:, checks:, row_count:)
49
+ @offsets = offsets.freeze
50
+ @values = values.freeze
51
+ @checks = checks.freeze
52
+ @row_count = row_count
53
+ freeze
54
+ end
55
+
56
+ # @rbs (Integer row, Integer column) -> untyped
57
+ def lookup(row, column)
58
+ return nil unless row.between?(0, @row_count - 1)
59
+ return nil if column.negative?
60
+
61
+ index = @offsets.fetch(row) + column
62
+ return nil unless index.between?(0, @checks.length - 1)
63
+
64
+ @checks[index] == row ? @values[index] : nil
65
+ end
66
+
67
+ # @rbs (Integer row) -> Hash[Integer, untyped]
68
+ def row(row)
69
+ return {} unless row.between?(0, @row_count - 1)
70
+
71
+ result = {} #: Hash[Integer, untyped]
72
+ @checks.each_index do |index|
73
+ next unless @checks[index] == row
74
+
75
+ result[index - @offsets[row]] = @values[index]
76
+ end
77
+ result
78
+ end
79
+ end
80
+
81
+ # @rbs (IR::Automaton automaton, ?format: Symbol | String) -> TableSet
82
+ def build(automaton, format: :compact)
83
+ action_rows = automaton.states.map do |state|
84
+ state.actions.transform_values do |action|
85
+ runtime_action(action)
86
+ end
87
+ end
88
+ goto_rows = automaton.states.map(&:gotos)
89
+ defaults = automaton.states.map do |state|
90
+ runtime_action(state.default_action) if state.default_action
91
+ end
92
+ if format.to_sym == :plain
93
+ return TableSet.new(actions: action_rows, gotos: goto_rows,
94
+ default_actions: defaults)
95
+ end
96
+ raise ArgumentError, "unknown table format #{format.inspect}" unless format.to_sym == :compact
97
+
98
+ TableSet.new(actions: Compact.build(action_rows), gotos: Compact.build(goto_rows), default_actions: defaults)
99
+ end
100
+ module_function :build
101
+
102
+ # @rbs skip
103
+ private
104
+
105
+ # @rbs skip
106
+ def runtime_action(action)
107
+ case action.fetch(:type).to_sym
108
+ when :shift
109
+ shift = action #: IR::shift_action
110
+ [:shift, shift.fetch(:state)]
111
+ when :reduce
112
+ reduce = action #: IR::reduce_action
113
+ [:reduce, reduce.fetch(:production)]
114
+ when :accept then [:accept]
115
+ when :error then [:error]
116
+ else raise Ibex::Error, "unknown parser action #{action.inspect}"
117
+ end
118
+ end
119
+ module_function :runtime_action
120
+
121
+ class << self
122
+ private :runtime_action
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ibex generates and runs Pure Ruby LR parsers.
4
+ module Ibex
5
+ VERSION = "0.1.0"
6
+ end
data/lib/ibex.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "ibex/version"
5
+ require_relative "ibex/error"
6
+ require_relative "ibex/tables"
7
+ require_relative "ibex/runtime"
8
+ require_relative "ibex/frontend"
9
+ require_relative "ibex/ir"
10
+ require_relative "ibex/normalize"
11
+ require_relative "ibex/analysis"
12
+ require_relative "ibex/lalr"
13
+ require_relative "ibex/codegen/symbol_labels"
14
+ require_relative "ibex/codegen/report"
15
+ require_relative "ibex/codegen/ruby"
16
+ require_relative "ibex/codegen/rbs"
17
+ require_relative "ibex/codegen/dot"
18
+ require_relative "ibex/codegen/html"
19
+
20
+ # Ibex generates and runs Pure Ruby LR parsers.
21
+ module Ibex
22
+ ParseError = Runtime::ParseError #: singleton(Runtime::ParseError)
23
+ end
@@ -0,0 +1,72 @@
1
+ # Generated from lib/ibex/analysis/sets.rb with RBS::Inline
2
+
3
+ module Ibex
4
+ module Analysis
5
+ # Computes nullable, FIRST, and FOLLOW sets over Grammar IR using integer bitsets.
6
+ class Sets
7
+ attr_reader nullable_bits: Integer
8
+
9
+ attr_reader first_bits: Array[Integer]
10
+
11
+ attr_reader follow_bits: Array[Integer]
12
+
13
+ @grammar: IR::Grammar
14
+
15
+ # @rbs (IR::Grammar grammar) -> void
16
+ def initialize: (IR::Grammar grammar) -> void
17
+
18
+ # @rbs (Integer | String | Symbol | IR::GrammarSymbol symbol) -> bool
19
+ def nullable?: (Integer | String | Symbol | IR::GrammarSymbol symbol) -> bool
20
+
21
+ # @rbs (Integer | String | Symbol | IR::GrammarSymbol symbol) -> Array[String]
22
+ def first: (Integer | String | Symbol | IR::GrammarSymbol symbol) -> Array[String]
23
+
24
+ # @rbs (Integer | String | Symbol | IR::GrammarSymbol symbol) -> Array[String]
25
+ def follow: (Integer | String | Symbol | IR::GrammarSymbol symbol) -> Array[String]
26
+
27
+ # @rbs (Array[Integer] symbol_ids) -> Integer
28
+ def first_of_sequence: (Array[Integer] symbol_ids) -> Integer
29
+
30
+ # @rbs (Array[Integer] symbol_ids) -> bool
31
+ def sequence_nullable?: (Array[Integer] symbol_ids) -> bool
32
+
33
+ private
34
+
35
+ # @rbs () -> void
36
+ def compute_nullable: () -> void
37
+
38
+ # @rbs () -> [Array[Array[IR::Production]], Array[Integer], Array[Integer]]
39
+ def nullable_worklist: () -> [ Array[Array[IR::Production]], Array[Integer], Array[Integer] ]
40
+
41
+ # @rbs () -> void
42
+ def compute_first: () -> void
43
+
44
+ # @rbs () -> void
45
+ def compute_follow: () -> void
46
+
47
+ # @rbs (IR::Production production, Array[Array[Integer]] dependencies) -> void
48
+ def initialize_follow: (IR::Production production, Array[Array[Integer]] dependencies) -> void
49
+
50
+ # @rbs (Array[Integer] sets, Array[Array[Integer]] dependencies, Array[Integer] seeds) -> void
51
+ def propagate_bits: (Array[Integer] sets, Array[Array[Integer]] dependencies, Array[Integer] seeds) -> void
52
+
53
+ # @rbs (Integer id) -> bool
54
+ def nullable_id?: (Integer id) -> bool
55
+
56
+ # @rbs (Integer bits) -> Array[String]
57
+ def terminal_names: (Integer bits) -> Array[String]
58
+
59
+ # @rbs (Integer | String | Symbol | IR::GrammarSymbol symbol) -> Integer
60
+ def symbol_id: (Integer | String | Symbol | IR::GrammarSymbol symbol) -> Integer
61
+
62
+ # @rbs (Integer | String | Symbol | IR::GrammarSymbol symbol) -> IR::GrammarSymbol
63
+ def definition_for: (Integer | String | Symbol | IR::GrammarSymbol symbol) -> IR::GrammarSymbol
64
+
65
+ # @rbs (Integer id) -> IR::GrammarSymbol
66
+ def required_symbol_by_id: (Integer id) -> IR::GrammarSymbol
67
+
68
+ # @rbs (Integer id) -> Integer
69
+ def bit: (Integer id) -> Integer
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,6 @@
1
+ # Generated from lib/ibex/analysis.rb with RBS::Inline
2
+
3
+ module Ibex
4
+ module Analysis
5
+ end
6
+ end
@@ -0,0 +1,16 @@
1
+ # Generated from lib/ibex/cli/counterexample_options.rb with RBS::Inline
2
+
3
+ module Ibex
4
+ # Counterexample report limits and their command-line validation.
5
+ module CLICounterexampleOptions
6
+ DEFAULTS: Hash[Symbol, Integer]
7
+
8
+ private
9
+
10
+ # @rbs (OptionParser options) -> void
11
+ def add_counterexample_options: (OptionParser options) -> void
12
+
13
+ # @rbs (Integer value, String option) -> Integer
14
+ def positive_counterexample_limit: (Integer value, String option) -> Integer
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ # Generated from lib/ibex/cli/outputs.rb with RBS::Inline
2
+
3
+ module Ibex
4
+ # Files, warnings, and progress emitted by CLI pipeline stages.
5
+ module CLIOutputs
6
+ WARNING_MESSAGES: Hash[Symbol, ^(IR::grammar_warning) -> String]
7
+
8
+ private
9
+
10
+ # @rbs (String value) -> Array[Symbol]
11
+ def warning_categories: (String value) -> Array[Symbol]
12
+
13
+ # @rbs (IR::Grammar grammar, String input_path) -> void
14
+ def handle_grammar_warnings: (IR::Grammar grammar, String input_path) -> void
15
+
16
+ # @rbs (IR::grammar_warning warning, String input_path) -> String
17
+ def format_grammar_warning: (IR::grammar_warning warning, String input_path) -> String
18
+
19
+ # @rbs (IR::Automaton automaton, String input_path) -> void
20
+ def report_conflicts: (IR::Automaton automaton, String input_path) -> void
21
+
22
+ # @rbs (IR::Automaton automaton, String input_path) -> void
23
+ def write_report: (IR::Automaton automaton, String input_path) -> void
24
+
25
+ # @rbs (String input_path, String extension) -> String
26
+ def default_output_path: (String input_path, String extension) -> String
27
+
28
+ # @rbs (String message) -> void
29
+ def report_status: (String message) -> void
30
+ end
31
+ end