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.
- checksums.yaml +7 -0
- data/.rubocop.yml +43 -0
- data/CHANGELOG.md +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +194 -0
- data/Rakefile +25 -0
- data/Steepfile +10 -0
- data/docs/architecture.md +99 -0
- data/docs/compat-notes.md +37 -0
- data/docs/grammar-reference.md +127 -0
- data/docs/lexer-coverage.md +14 -0
- data/docs/phase10-extensions.md +27 -0
- data/docs/racc-migration.md +47 -0
- data/exe/ibex +6 -0
- data/gemfiles/Gemfile +7 -0
- data/gemfiles/Gemfile.lock +98 -0
- data/lib/ibex/analysis/sets.rb +194 -0
- data/lib/ibex/analysis.rb +8 -0
- data/lib/ibex/cli/counterexample_options.rb +38 -0
- data/lib/ibex/cli/outputs.rb +88 -0
- data/lib/ibex/cli.rb +333 -0
- data/lib/ibex/codegen/dot.rb +52 -0
- data/lib/ibex/codegen/html.rb +110 -0
- data/lib/ibex/codegen/rbs.rb +54 -0
- data/lib/ibex/codegen/report.rb +149 -0
- data/lib/ibex/codegen/ruby.rb +235 -0
- data/lib/ibex/codegen/symbol_labels.rb +18 -0
- data/lib/ibex/error.rb +7 -0
- data/lib/ibex/frontend/action_scanner.rb +266 -0
- data/lib/ibex/frontend/ast.rb +146 -0
- data/lib/ibex/frontend/bootstrap_parser.rb +163 -0
- data/lib/ibex/frontend/dsl.rb +196 -0
- data/lib/ibex/frontend/generated_parser.rb +434 -0
- data/lib/ibex/frontend/generated_parser_base.rb +273 -0
- data/lib/ibex/frontend/grammar.y +156 -0
- data/lib/ibex/frontend/lexer.rb +165 -0
- data/lib/ibex/frontend/parser/declarations.rb +163 -0
- data/lib/ibex/frontend/parser/rules.rb +154 -0
- data/lib/ibex/frontend/parser.rb +23 -0
- data/lib/ibex/frontend/regenerator.rb +41 -0
- data/lib/ibex/frontend/source_cursor.rb +94 -0
- data/lib/ibex/frontend/token_adapter/declaration_state.rb +248 -0
- data/lib/ibex/frontend/token_adapter/delimiter_tracker.rb +37 -0
- data/lib/ibex/frontend/token_adapter/rule_state.rb +162 -0
- data/lib/ibex/frontend/token_adapter.rb +108 -0
- data/lib/ibex/frontend.rb +28 -0
- data/lib/ibex/ir/automaton_ir.rb +109 -0
- data/lib/ibex/ir/grammar_ir.rb +192 -0
- data/lib/ibex/ir/serialize.rb +154 -0
- data/lib/ibex/ir.rb +53 -0
- data/lib/ibex/lalr/builder.rb +270 -0
- data/lib/ibex/lalr/conflict.rb +120 -0
- data/lib/ibex/lalr/conflict_search.rb +332 -0
- data/lib/ibex/lalr/conflict_search_limits.rb +39 -0
- data/lib/ibex/lalr/counterexample.rb +138 -0
- data/lib/ibex/lalr/default_reductions.rb +67 -0
- data/lib/ibex/lalr.rb +27 -0
- data/lib/ibex/normalize/declarations.rb +60 -0
- data/lib/ibex/normalize/diagnostics.rb +99 -0
- data/lib/ibex/normalize/expander.rb +218 -0
- data/lib/ibex/normalize/expression.rb +54 -0
- data/lib/ibex/normalize.rb +166 -0
- data/lib/ibex/runtime/parser.rb +360 -0
- data/lib/ibex/runtime.rb +8 -0
- data/lib/ibex/tables.rb +125 -0
- data/lib/ibex/version.rb +6 -0
- data/lib/ibex.rb +23 -0
- data/sig/ibex/analysis/sets.rbs +72 -0
- data/sig/ibex/analysis.rbs +6 -0
- data/sig/ibex/cli/counterexample_options.rbs +16 -0
- data/sig/ibex/cli/outputs.rbs +31 -0
- data/sig/ibex/cli.rbs +108 -0
- data/sig/ibex/codegen/dot.rbs +19 -0
- data/sig/ibex/codegen/html.rbs +35 -0
- data/sig/ibex/codegen/rbs.rbs +31 -0
- data/sig/ibex/codegen/report.rbs +39 -0
- data/sig/ibex/codegen/ruby.rbs +92 -0
- data/sig/ibex/codegen/symbol_labels.rbs +11 -0
- data/sig/ibex/error.rbs +7 -0
- data/sig/ibex/frontend/action_scanner.rbs +74 -0
- data/sig/ibex/frontend/ast.rbs +223 -0
- data/sig/ibex/frontend/bootstrap_parser.rbs +80 -0
- data/sig/ibex/frontend/dsl.rbs +124 -0
- data/sig/ibex/frontend/generated_parser.rbs +163 -0
- data/sig/ibex/frontend/generated_parser_base.rbs +114 -0
- data/sig/ibex/frontend/lexer.rbs +61 -0
- data/sig/ibex/frontend/parser/declarations.rbs +56 -0
- data/sig/ibex/frontend/parser/rules.rbs +48 -0
- data/sig/ibex/frontend/parser.rbs +16 -0
- data/sig/ibex/frontend/regenerator.rbs +16 -0
- data/sig/ibex/frontend/source_cursor.rbs +73 -0
- data/sig/ibex/frontend/token_adapter/declaration_state.rbs +97 -0
- data/sig/ibex/frontend/token_adapter/delimiter_tracker.rbs +25 -0
- data/sig/ibex/frontend/token_adapter/rule_state.rbs +77 -0
- data/sig/ibex/frontend/token_adapter.rbs +67 -0
- data/sig/ibex/frontend.rbs +15 -0
- data/sig/ibex/ir/automaton_ir.rbs +77 -0
- data/sig/ibex/ir/grammar_ir.rbs +143 -0
- data/sig/ibex/ir/serialize.rbs +50 -0
- data/sig/ibex/ir.rbs +43 -0
- data/sig/ibex/lalr/builder.rbs +85 -0
- data/sig/ibex/lalr/conflict.rbs +47 -0
- data/sig/ibex/lalr/conflict_search.rbs +131 -0
- data/sig/ibex/lalr/conflict_search_limits.rbs +29 -0
- data/sig/ibex/lalr/counterexample.rbs +53 -0
- data/sig/ibex/lalr/default_reductions.rbs +20 -0
- data/sig/ibex/lalr.rbs +23 -0
- data/sig/ibex/normalize/declarations.rbs +20 -0
- data/sig/ibex/normalize/diagnostics.rbs +32 -0
- data/sig/ibex/normalize/expander.rbs +66 -0
- data/sig/ibex/normalize/expression.rbs +21 -0
- data/sig/ibex/normalize.rbs +96 -0
- data/sig/ibex/runtime/parser.rbs +167 -0
- data/sig/ibex/runtime.rbs +6 -0
- data/sig/ibex/tables.rbs +50 -0
- data/sig/ibex/version.rbs +6 -0
- data/sig/ibex.rbs +6 -0
- 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
|
data/lib/ibex/runtime.rb
ADDED
data/lib/ibex/tables.rb
ADDED
|
@@ -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
|
data/lib/ibex/version.rb
ADDED
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,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
|