ibex-runtime 0.2.0 → 0.3.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +95 -272
  3. data/lib/ibex/runtime/cst/green/node.rb +1 -1
  4. data/lib/ibex/runtime/cst/green/token.rb +1 -1
  5. data/lib/ibex/runtime/cst/green/trivia.rb +1 -1
  6. data/lib/ibex/runtime/cst/incremental/blender.rb +1 -1
  7. data/lib/ibex/runtime/cst/incremental/lexed_syntax.rb +2 -2
  8. data/lib/ibex/runtime/cst/incremental/parse_memo.rb +2 -2
  9. data/lib/ibex/runtime/cst/incremental/relexer.rb +3 -0
  10. data/lib/ibex/runtime/cst/incremental/session.rb +26 -2
  11. data/lib/ibex/runtime/cst/parse_result.rb +7 -7
  12. data/lib/ibex/runtime/cst/serialize.rb +6 -3
  13. data/lib/ibex/runtime/cst/syntax_node.rb +4 -4
  14. data/lib/ibex/runtime/cst/syntax_token.rb +5 -5
  15. data/lib/ibex/runtime/cst/typed_node.rb +1 -1
  16. data/lib/ibex/runtime/cst/validator.rb +40 -30
  17. data/lib/ibex/runtime/embedded_source.rb +98 -0
  18. data/lib/ibex/runtime/event.rb +7 -4
  19. data/lib/ibex/runtime/event_jsonl_tracer.rb +1 -1
  20. data/lib/ibex/runtime/event_sanitizer.rb +32 -26
  21. data/lib/ibex/runtime/generated_lexer.rb +23 -16
  22. data/lib/ibex/runtime/lexer_input.rb +1 -1
  23. data/lib/ibex/runtime/location_span.rb +15 -15
  24. data/lib/ibex/runtime/observation.rb +1 -1
  25. data/lib/ibex/runtime/parser.rb +309 -191
  26. data/lib/ibex/runtime/parser_sync_recovery.rb +19 -15
  27. data/lib/ibex/runtime/repair.rb +36 -5
  28. data/lib/ibex/runtime/repair_priority_queue.rb +10 -6
  29. data/lib/ibex/runtime/repair_search.rb +92 -29
  30. data/lib/ibex/runtime/syntax_repair.rb +490 -0
  31. data/lib/ibex/runtime/syntax_session.rb +416 -0
  32. data/lib/ibex/runtime/table_format.rb +1 -1
  33. data/lib/ibex/runtime/version.rb +1 -1
  34. data/lib/ibex/runtime.rb +2 -1
  35. data/lib/ibex/tables/compact.rb +16 -12
  36. data/lib/ibex/tables/compact_actions.rb +45 -33
  37. data/lib/ibex/tables/compact_productions.rb +18 -15
  38. data/sig/ibex/runtime/cst/green/node.rbs +2 -2
  39. data/sig/ibex/runtime/cst/green/token.rbs +2 -2
  40. data/sig/ibex/runtime/cst/green/trivia.rbs +2 -2
  41. data/sig/ibex/runtime/cst/incremental/blender.rbs +2 -2
  42. data/sig/ibex/runtime/cst/incremental/lexed_syntax.rbs +3 -3
  43. data/sig/ibex/runtime/cst/incremental/parse_memo.rbs +4 -4
  44. data/sig/ibex/runtime/cst/incremental/session.rbs +8 -2
  45. data/sig/ibex/runtime/cst/parse_result.rbs +9 -9
  46. data/sig/ibex/runtime/cst/serialize.rbs +8 -6
  47. data/sig/ibex/runtime/cst/syntax_node.rbs +6 -6
  48. data/sig/ibex/runtime/cst/syntax_token.rbs +10 -10
  49. data/sig/ibex/runtime/cst/typed_node.rbs +2 -2
  50. data/sig/ibex/runtime/cst/validator.rbs +46 -42
  51. data/sig/ibex/runtime/embedded_source.rbs +21 -0
  52. data/sig/ibex/runtime/event.rbs +7 -5
  53. data/sig/ibex/runtime/event_jsonl_tracer.rbs +1 -1
  54. data/sig/ibex/runtime/event_sanitizer.rbs +50 -44
  55. data/sig/ibex/runtime/generated_lexer.rbs +31 -24
  56. data/sig/ibex/runtime/lexer_input.rbs +2 -2
  57. data/sig/ibex/runtime/location_span.rbs +28 -28
  58. data/sig/ibex/runtime/observation.rbs +2 -2
  59. data/sig/ibex/runtime/parser.rbs +266 -252
  60. data/sig/ibex/runtime/parser_sync_recovery.rbs +15 -15
  61. data/sig/ibex/runtime/repair.rbs +28 -8
  62. data/sig/ibex/runtime/repair_priority_queue.rbs +9 -7
  63. data/sig/ibex/runtime/repair_search.rbs +38 -19
  64. data/sig/ibex/runtime/syntax_repair.rbs +177 -0
  65. data/sig/ibex/runtime/syntax_session.rbs +186 -0
  66. data/sig/ibex/tables/compact.rbs +12 -12
  67. data/sig/ibex/tables/compact_actions.rbs +10 -17
  68. data/sig/ibex/tables/compact_productions.rbs +19 -19
  69. metadata +12 -6
  70. data/lib/ibex/runtime/jsonl_tracer.rb +0 -75
  71. data/sig/ibex/runtime/jsonl_tracer.rbs +0 -42
@@ -4,26 +4,34 @@
4
4
  require_relative "location_span" unless defined?(Ibex::Runtime::LocationSpan)
5
5
  require_relative "observation" unless defined?(Ibex::Runtime::Observation)
6
6
  require_relative "resource_limits" unless defined?(Ibex::Runtime::ResourceLimits)
7
+ require_relative "syntax_session" unless defined?(Ibex::Runtime::SyntaxSession)
7
8
  require_relative "repair" unless defined?(Ibex::Runtime::RepairPolicy)
8
9
  require_relative "repair_search" unless defined?(Ibex::Runtime::RepairSearch)
10
+ require_relative "syntax_repair" unless defined?(Ibex::Runtime::SyntaxRepairer)
9
11
  require_relative "parser_sync_recovery" unless defined?(Ibex::Runtime::ParserSyncRecovery)
10
12
  require_relative "table_format" unless defined?(Ibex::Runtime::PARSER_TABLE_FORMAT_VERSION)
11
13
 
12
14
  module Ibex
13
15
  module Runtime
16
+ # Semantic values and lexer locations intentionally cross the runtime
17
+ # boundary without a closed application type. Keep that boundary named so
18
+ # the rest of the parser remains structurally typed.
19
+ # @rbs!
20
+ # type runtime_value = untyped
21
+
14
22
  # Raised by the default parser error handler.
15
23
  class ParseError < StandardError
16
24
  attr_reader :token_id #: Integer?
17
25
  attr_reader :token_name #: String?
18
- attr_reader :token_value #: untyped
26
+ attr_reader :token_value #: runtime_value
19
27
  attr_reader :expected_tokens #: Array[String]
20
- attr_reader :location #: untyped
28
+ attr_reader :location #: runtime_value
21
29
  attr_reader :state #: Integer?
22
30
  attr_reader :suggestions #: Array[String]
23
31
  attr_reader :error_id #: String?
24
32
 
25
33
  # rubocop:disable Layout/LineLength
26
- # @rbs (?String? message, ?token_id: Integer?, ?token_name: String?, ?token_value: untyped, ?expected_tokens: Array[String], ?location: untyped, ?state: Integer?, ?suggestions: Array[String], ?error_id: String?, ?detail: String?) -> void
34
+ # @rbs (?String? message, ?token_id: Integer?, ?token_name: String?, ?token_value: runtime_value, ?expected_tokens: Array[String], ?location: runtime_value, ?state: Integer?, ?suggestions: Array[String], ?error_id: String?, ?detail: String?) -> void
27
35
  # rubocop:enable Layout/LineLength
28
36
  def initialize(
29
37
  message = nil,
@@ -72,7 +80,7 @@ module Ibex
72
80
  message
73
81
  end
74
82
 
75
- # @rbs (Symbol key) -> untyped
83
+ # @rbs (Symbol key) -> runtime_value
76
84
  def location_value(key)
77
85
  return nil unless @location
78
86
  return @location.public_send(key) if @location.respond_to?(key)
@@ -88,7 +96,7 @@ module Ibex
88
96
  attr_reader :limit #: Integer
89
97
  attr_reader :observed #: Integer
90
98
 
91
- # @rbs (resource: Symbol, limit: Integer, observed: Integer, state: Integer?, location: untyped) -> void
99
+ # @rbs (resource: Symbol, limit: Integer, observed: Integer, state: Integer?, location: runtime_value) -> void
92
100
  def initialize(resource:, limit:, observed:, state:, location:)
93
101
  @resource = resource
94
102
  @limit = limit
@@ -99,7 +107,7 @@ module Ibex
99
107
  )
100
108
  end
101
109
 
102
- # @rbs () -> Hash[Symbol, untyped]
110
+ # @rbs () -> Hash[Symbol, runtime_value]
103
111
  def to_h
104
112
  {
105
113
  type: :resource_limit, resource: @resource, limit: @limit, observed: @observed,
@@ -250,14 +258,14 @@ module Ibex
250
258
  REPAIR_PENDING_OUTCOME = [:repair_pending].freeze #: [:repair_pending]
251
259
  TERMINAL_OUTCOMES = %i[accepted done].freeze #: Array[Symbol]
252
260
  COMPACT_ACCEPTED = Object.new.freeze #: Object
253
- empty_row = {} # @type var empty_row: Hash[Integer, untyped]
261
+ empty_row = {} # @type var empty_row: Hash[Integer, runtime_value]
254
262
  empty_location_names = {} # @type var empty_location_names: Hash[Symbol, Integer]
255
- empty_locations = [] # @type var empty_locations: Array[untyped]
263
+ empty_locations = [] # @type var empty_locations: Array[runtime_value]
256
264
  empty_green_trivia = [] # @type var empty_green_trivia: Array[CST::GreenTrivia]
257
265
 
258
- EMPTY_ROW = empty_row.freeze #: Hash[Integer, untyped]
266
+ EMPTY_ROW = empty_row.freeze #: Hash[Integer, runtime_value]
259
267
  EMPTY_LOCATION_NAMES = empty_location_names.freeze #: Hash[Symbol, Integer]
260
- EMPTY_LOCATIONS = empty_locations.freeze #: Array[untyped]
268
+ EMPTY_LOCATIONS = empty_locations.freeze #: Array[runtime_value]
261
269
  EMPTY_GREEN_TRIVIA = empty_green_trivia.freeze #: Array[CST::GreenTrivia]
262
270
  private_constant :ERROR_ACTION, :SYNC_RECOVER_ACTION, :CONTINUE_OUTCOME, :REPAIR_PENDING_OUTCOME,
263
271
  :TERMINAL_OUTCOMES, :COMPACT_ACCEPTED, :FAST_PATH_HOOK_NAMES, :FAST_PATH_HOOK_REFERENCES,
@@ -265,15 +273,15 @@ module Ibex
265
273
 
266
274
  # @rbs @yydebug: bool
267
275
  # @rbs @yydebug_output: IO
268
- # @rbs @source: untyped
276
+ # @rbs @source: runtime_value
269
277
  # @rbs @state_stack: Array[Integer]
270
- # @rbs @value_stack: Array[untyped]
271
- # @rbs @vstack: Array[untyped]
272
- # @rbs @racc_vstack: Array[untyped]
273
- # @rbs @location_stack: Array[untyped]?
274
- # @rbs @lookahead: untyped
275
- # @rbs @lookahead_value: untyped
276
- # @rbs @lookahead_location: untyped
278
+ # @rbs @value_stack: Array[runtime_value]
279
+ # @rbs @vstack: Array[runtime_value]
280
+ # @rbs @racc_vstack: Array[runtime_value]
281
+ # @rbs @location_stack: Array[runtime_value]?
282
+ # @rbs @lookahead: runtime_value
283
+ # @rbs @lookahead_value: runtime_value
284
+ # @rbs @lookahead_location: runtime_value
277
285
  # @rbs @recovery_shifts: Integer
278
286
  # @rbs @semantic_error: bool
279
287
  # @rbs @accept_requested: bool
@@ -284,27 +292,27 @@ module Ibex
284
292
  # @rbs @runtime_driver_thread: Thread?
285
293
  # @rbs @runtime_observers: Hash[Observation::Subscription, Proc]?
286
294
  # @rbs @runtime_event_sequence: Integer
287
- # @rbs @runtime_lookahead_token_display: untyped
295
+ # @rbs @runtime_lookahead_token_display: String?
288
296
  # @rbs @runtime_observation_mutex: Mutex
289
297
  # @rbs @repair_policy: RepairPolicy?
290
298
  # @rbs @repair_input_buffer: Array[RepairInput]?
291
299
  # @rbs @repair_selected: bool
292
- # @rbs @semantic_locations: Array[untyped]?
300
+ # @rbs @semantic_locations: Array[runtime_value]?
293
301
  # @rbs @semantic_location_names: Hash[Symbol, Integer]?
294
- # @rbs @semantic_result_location: untyped
295
- # @rbs @trace_value_printer: (^(untyped) -> untyped)?
296
- # @rbs @sync_recovery_context: Hash[Symbol, untyped]?
297
- # @rbs @sync_recovery_token_data: Hash[String, untyped]?
302
+ # @rbs @semantic_result_location: runtime_value
303
+ # @rbs @trace_value_printer: (^(runtime_value) -> runtime_value)?
304
+ # @rbs @sync_recovery_context: Hash[Symbol, runtime_value]?
305
+ # @rbs @sync_recovery_token_data: Hash[String, runtime_value]?
298
306
  # @rbs @sync_recovery_observers: Array[Proc]?
299
307
  # @rbs @green_builder: CST::GreenBuilder?
300
308
  # @rbs @green_kinds: CST::Kind?
301
309
  # @rbs @green_cache: CST::NodeCache?
302
310
  # @rbs @syntax_root: CST::SyntaxNode?
303
- # @rbs @syntax_diagnostics: Array[untyped]
311
+ # @rbs @syntax_diagnostics: Array[runtime_value]
304
312
  # @rbs @green_pending_skipped: Array[CST::GreenTrivia]
305
313
  # @rbs @resource_limits: ResourceLimits
306
314
  # @rbs @recovery_attempts: Integer
307
- # @rbs @runtime_parser_tables: Hash[Symbol, untyped]?
315
+ # @rbs @runtime_parser_tables: Hash[Symbol, runtime_value]?
308
316
  # @rbs @runtime_fast_path: bool
309
317
  # @rbs @runtime_fast_path_tracker_installed: bool
310
318
  # @rbs @runtime_fast_path_hooks_mutated: bool
@@ -322,7 +330,41 @@ module Ibex
322
330
  attr_reader :syntax_parse_memo #: CST::ParseMemo?
323
331
  attr_reader :incremental_reused_descendants #: Integer
324
332
 
333
+ # Report the trust boundary of syntax-only operations for this loaded
334
+ # parser class. Current generated artifacts can contain user sections
335
+ # and Ruby lexer actions, so they are always trusted application code.
336
+ # @rbs skip
337
+ def self.syntax_execution_profile
338
+ :trusted_application_code
339
+ end
340
+
341
+ # Open a generated-language syntax session backed by the existing
342
+ # incremental Red/Green CST engine. The execution profile must be passed
343
+ # explicitly because generated lexer actions execute in this path.
344
+ # @rbs skip
345
+ def self.syntax_session(
346
+ source,
347
+ execution_profile: nil,
348
+ resource_limits: nil,
349
+ limits: nil,
350
+ cancellation: nil,
351
+ blender: true
352
+ )
353
+ SyntaxSession.new(
354
+ self,
355
+ source,
356
+ execution_profile: execution_profile,
357
+ resource_limits: resource_limits,
358
+ limits: limits,
359
+ cancellation: cancellation,
360
+ blender: blender
361
+ )
362
+ end
363
+
325
364
  # Start a syntax-only incremental session backed by a generated lexer.
365
+ # Parser production actions are suppressed, but generated lexer actions
366
+ # still execute. The generated class is trusted application code, not a
367
+ # sandbox.
326
368
  # @rbs (CST::SourceText source_text, ?resource_limits: ResourceLimits?, ?blender: bool) ->
327
369
  # CST::IncrementalParseSession
328
370
  def self.incremental_session(source_text, resource_limits: nil, blender: true)
@@ -403,13 +445,17 @@ module Ibex
403
445
  end
404
446
  end
405
447
 
406
- # Pull tokens from `next_token` and parse them.
407
- # @rbs () -> untyped
448
+ # Pull tokens from `next_token` and execute parser production actions.
449
+ # A generated-lexer `next_token` executes lexer actions; a handwritten
450
+ # implementation does not invoke the generated lexer.
451
+ # @rbs () -> runtime_value
408
452
  def do_parse
409
453
  drive_parser(nil)
410
454
  end
411
455
 
412
456
  # Parse through `next_token` and return both the semantic value and Red root.
457
+ # Parser production actions execute. Generated lexer actions execute only
458
+ # when `next_token` is supplied by the generated lexer.
413
459
  # @rbs () -> CST::ParseResult
414
460
  def parse_with_syntax
415
461
  syntax_parse_result(do_parse)
@@ -422,8 +468,10 @@ module Ibex
422
468
  @syntax_root
423
469
  end
424
470
 
425
- # Parse tokens yielded by `receiver.method_id`.
426
- # @rbs (untyped receiver, Symbol method_id) -> untyped
471
+ # Parse tokens yielded by `receiver.method_id` and execute parser
472
+ # production actions. This caller-fed path does not invoke generated
473
+ # lexer actions.
474
+ # @rbs (runtime_value receiver, Symbol method_id) -> runtime_value
427
475
  def yyparse(receiver, method_id)
428
476
  stream = Enumerator.new do |tokens|
429
477
  receiver.__send__(method_id) { |token| tokens << token }
@@ -432,11 +480,11 @@ module Ibex
432
480
  end
433
481
 
434
482
  # Supply one token to a caller-driven parser session.
483
+ # Committed reductions execute parser production actions; this token-fed
484
+ # path does not invoke generated lexer actions.
435
485
  # Returns `:need_more` after consuming it, `[:accepted, result]` after
436
486
  # acceptance, or `[:rejected, result]` after recovery terminates.
437
- # rubocop:disable Layout/LineLength
438
- # @rbs (untyped token, ?untyped value, ?untyped location) -> (:need_more | [:accepted, untyped] | [:rejected, untyped])
439
- # rubocop:enable Layout/LineLength
487
+ # @rbs (runtime_value token, ?runtime_value value, ?runtime_value location) -> runtime_value
440
488
  def push(token, value = nil, location = nil)
441
489
  raise ParseError, "(input):1:1: push requires a token; call finish for EOF" if token.nil? || token == false
442
490
 
@@ -457,7 +505,9 @@ module Ibex
457
505
  end
458
506
 
459
507
  # Supply EOF to a caller-driven parser session and return its result.
460
- # @rbs (?location: untyped) -> untyped
508
+ # Committed reductions execute parser production actions; this token-fed
509
+ # path does not invoke generated lexer actions.
510
+ # @rbs (?location: runtime_value) -> runtime_value
461
511
  def finish(location: nil)
462
512
  run_push_driver do
463
513
  start_push_session
@@ -505,14 +555,14 @@ module Ibex
505
555
 
506
556
  # Override in pull parsers. Return `[token, value]`,
507
557
  # `[token, value, location]`, `false`, or `nil`.
508
- # @rbs () -> ([untyped, untyped] | [untyped, untyped, untyped] | false | nil)
558
+ # @rbs () -> ([runtime_value, runtime_value] | [runtime_value, runtime_value, runtime_value] | false | nil)
509
559
  def next_token
510
560
  raise NotImplementedError, "(input):1:1: next_token must be implemented"
511
561
  end
512
562
 
513
563
  # Override to recover from syntax errors. The default raises unless a
514
564
  # bounded automatic repair has already been selected.
515
- # @rbs (Integer token_id, untyped value, Array[untyped] value_stack) -> untyped
565
+ # @rbs (Integer token_id, runtime_value value, Array[runtime_value] value_stack) -> runtime_value
516
566
  def on_error(token_id, value, _value_stack)
517
567
  return if @repair_selected
518
568
  return if cst_enabled?
@@ -537,40 +587,40 @@ module Ibex
537
587
 
538
588
  # Called after an ordinary input token is shifted. Override to observe
539
589
  # the internal token id, semantic value, and destination state.
540
- # @rbs (Integer token_id, untyped value, Integer state) -> void
590
+ # @rbs (Integer token_id, runtime_value value, Integer state) -> void
541
591
  def on_shift(_token_id, _value, _state); end
542
592
 
543
593
  # Location-aware shift observer. The compatible hook above retains its
544
594
  # original signature and runs first.
545
- # @rbs (Integer token_id, untyped value, Integer state, untyped location) -> void
595
+ # @rbs (Integer token_id, runtime_value value, Integer state, runtime_value location) -> void
546
596
  def on_shift_location(_token_id, _value, _state, _location); end
547
597
 
548
598
  # Called after a production's semantic action and goto are committed.
549
599
  # Override to observe its id, RHS values, and semantic result.
550
- # @rbs (Integer production_id, Array[untyped] values, untyped result) -> void
600
+ # @rbs (Integer production_id, Array[runtime_value] values, runtime_value result) -> void
551
601
  def on_reduce(_production_id, _values, _result); end
552
602
 
553
603
  # Location-aware reduction observer.
554
- # @rbs (Integer production_id, Array[untyped] values, untyped result,
555
- # Array[untyped] locations, untyped result_location) -> void
604
+ # @rbs (Integer production_id, Array[runtime_value] values, runtime_value result,
605
+ # Array[runtime_value] locations, runtime_value result_location) -> void
556
606
  def on_reduce_location(_production_id, _values, _result, _locations, _result_location); end
557
607
 
558
608
  # Called after the synthetic error token enters a recovery state.
559
609
  # The payload describes the original error before recovery popped stacks.
560
- # @rbs (Integer token_id, untyped value, Array[untyped] value_stack) -> void
610
+ # @rbs (Integer token_id, runtime_value value, Array[runtime_value] value_stack) -> void
561
611
  def on_error_recover(_token_id, _value, _value_stack); end
562
612
 
563
613
  # Location-aware recovery observer.
564
- # @rbs (Integer token_id, untyped value, Array[untyped] value_stack,
565
- # untyped location, Integer state) -> void
614
+ # @rbs (Integer token_id, runtime_value value, Array[runtime_value] value_stack,
615
+ # runtime_value location, Integer state) -> void
566
616
  def on_error_recover_location(_token_id, _value, _value_stack, _location, _state); end
567
617
 
568
618
  # Called when yacc recovery discards an application token.
569
- # @rbs (Integer token_id, untyped value, untyped location, Symbol reason) -> void
619
+ # @rbs (Integer token_id, runtime_value value, runtime_value location, Symbol reason) -> void
570
620
  def on_discard(_token_id, _value, _location, _reason); end
571
621
 
572
622
  # Install an opt-in value formatter for human-readable yydebug traces.
573
- # @rbs ((^(untyped) -> untyped)? printer) -> (^(untyped) -> untyped)?
623
+ # @rbs ((^(runtime_value) -> runtime_value)? printer) -> (^(runtime_value) -> runtime_value)?
574
624
  def trace_value_printer=(printer)
575
625
  ensure_runtime_initialized!
576
626
  unless printer.nil? || printer.respond_to?(:call)
@@ -646,7 +696,7 @@ module Ibex
646
696
 
647
697
  # Return the location of a one-based RHS position or named reference
648
698
  # while a semantic action is running.
649
- # @rbs (Integer | Symbol | String reference) -> untyped
699
+ # @rbs (Integer | Symbol | String reference) -> runtime_value
650
700
  def loc(reference)
651
701
  locations = @semantic_locations
652
702
  raise ParseError, "(runtime):1:1: loc is only available inside a semantic action" unless locations
@@ -665,7 +715,7 @@ module Ibex
665
715
  end
666
716
 
667
717
  # Return the synthesized span of the reduction being evaluated.
668
- # @rbs () -> untyped
718
+ # @rbs () -> runtime_value
669
719
  def result_loc
670
720
  unless @semantic_locations
671
721
  raise ParseError, "(runtime):1:1: result_loc is only available inside a semantic action"
@@ -725,6 +775,9 @@ module Ibex
725
775
  @repair_policy = nil unless preserve_existing && defined?(@repair_policy)
726
776
  @repair_input_buffer = nil unless preserve_existing && defined?(@repair_input_buffer)
727
777
  @repair_selected = false unless preserve_existing && defined?(@repair_selected)
778
+ unless preserve_existing && defined?(@repair_search_results)
779
+ @repair_search_results = [] #: Array[RepairSearchResult]
780
+ end
728
781
  @semantic_locations = nil unless preserve_existing && defined?(@semantic_locations)
729
782
  @semantic_location_names = nil unless preserve_existing && defined?(@semantic_location_names)
730
783
  @semantic_result_location = nil unless preserve_existing && defined?(@semantic_result_location)
@@ -736,8 +789,8 @@ module Ibex
736
789
  @green_kinds = nil unless preserve_existing && defined?(@green_kinds)
737
790
  @green_cache = nil unless preserve_existing && defined?(@green_cache)
738
791
  @syntax_root = nil unless preserve_existing && defined?(@syntax_root)
739
- @syntax_diagnostics = [] unless preserve_existing && defined?(@syntax_diagnostics)
740
- @green_pending_skipped = [] unless preserve_existing && defined?(@green_pending_skipped)
792
+ @syntax_diagnostics = EMPTY_LOCATIONS unless preserve_existing && defined?(@syntax_diagnostics)
793
+ @green_pending_skipped = EMPTY_GREEN_TRIVIA unless preserve_existing && defined?(@green_pending_skipped)
741
794
  @recovery_attempts = 0 unless preserve_existing && defined?(@recovery_attempts)
742
795
  @runtime_parser_tables = nil unless preserve_existing && defined?(@runtime_parser_tables)
743
796
  @runtime_fast_path = false unless preserve_existing && defined?(@runtime_fast_path)
@@ -749,8 +802,8 @@ module Ibex
749
802
  preserve_existing && defined?(@runtime_fast_path_singleton_ancestors)
750
803
  @syntax_only = false unless preserve_existing && defined?(@syntax_only)
751
804
  @green_cache_override = nil unless preserve_existing && defined?(@green_cache_override)
752
- @green_token_states = [] unless preserve_existing && defined?(@green_token_states)
753
- @green_memo_stack = [] unless preserve_existing && defined?(@green_memo_stack)
805
+ @green_token_states = EMPTY_LOCATIONS unless preserve_existing && defined?(@green_token_states)
806
+ @green_memo_stack = EMPTY_LOCATIONS unless preserve_existing && defined?(@green_memo_stack)
754
807
  @green_parse_memo_valid = false unless preserve_existing && defined?(@green_parse_memo_valid)
755
808
  @green_initial_state = 0 unless preserve_existing && defined?(@green_initial_state)
756
809
  @syntax_parse_memo = nil unless preserve_existing && defined?(@syntax_parse_memo)
@@ -769,7 +822,7 @@ module Ibex
769
822
 
770
823
  # Keep the two historical value-stack names as read-compatible aliases.
771
824
  # Applications must not mutate or replace these internal arrays.
772
- # @rbs (Array[untyped] values) -> void
825
+ # @rbs (Array[runtime_value] values) -> void
773
826
  def install_value_stack(values)
774
827
  @value_stack = values
775
828
  @vstack = values
@@ -809,7 +862,7 @@ module Ibex
809
862
  (stack_depth + parser_state_count(tables) + 1) * (production_count + 1)
810
863
  end
811
864
 
812
- # @rbs (Hash[Symbol, untyped] tables) -> Integer
865
+ # @rbs (Hash[Symbol, runtime_value] tables) -> Integer
813
866
  def parser_state_count(tables)
814
867
  state_count = tables[:state_count]
815
868
  return state_count if state_count.is_a?(Integer)
@@ -848,7 +901,7 @@ module Ibex
848
901
  true
849
902
  end
850
903
 
851
- # @rbs (untyped source, ?initial_state: Integer?) -> untyped
904
+ # @rbs (runtime_value source, ?initial_state: Integer?) -> runtime_value
852
905
  def drive_parser(source, initial_state: nil)
853
906
  ensure_runtime_initialized!
854
907
  @runtime_observation_mutex.synchronize do
@@ -892,7 +945,7 @@ module Ibex
892
945
  # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
893
946
  # Direct comparisons avoid predicate-method dispatch inside the parser loop.
894
947
  # rubocop:disable Style/BitwisePredicate, Style/NumericPredicate
895
- # @rbs (Hash[Symbol, untyped] tables) -> (Object | [:accepted, untyped] | [:done, untyped])?
948
+ # @rbs (Hash[Symbol, runtime_value] tables) -> runtime_value
896
949
  def drive_compact_fast_parser(tables)
897
950
  actions = tables.fetch(:actions)
898
951
  gotos = tables.fetch(:gotos)
@@ -1132,7 +1185,7 @@ module Ibex
1132
1185
  # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
1133
1186
  # rubocop:enable Style/BitwisePredicate, Style/NumericPredicate
1134
1187
 
1135
- # @rbs (Hash[Symbol, untyped]? tables) -> bool
1188
+ # @rbs (Hash[Symbol, runtime_value]? tables) -> bool
1136
1189
  def compact_fast_driver_eligible?(tables)
1137
1190
  return false unless @runtime_fast_path && tables
1138
1191
  return false unless tables[:compact_fast_driver] == true
@@ -1156,7 +1209,7 @@ module Ibex
1156
1209
  @push_status = :active
1157
1210
  end
1158
1211
 
1159
- # @rbs () -> (:need_more | [:accepted, untyped] | [:rejected, untyped])
1212
+ # @rbs () -> runtime_value
1160
1213
  def run_push_lookahead
1161
1214
  loop do
1162
1215
  outcome = perform(action_for_current_state)
@@ -1179,7 +1232,7 @@ module Ibex
1179
1232
  end
1180
1233
  end
1181
1234
 
1182
- # @rbs () { () -> untyped } -> untyped
1235
+ # @rbs () { () -> runtime_value } -> runtime_value
1183
1236
  def run_push_driver
1184
1237
  ensure_runtime_initialized!
1185
1238
  @runtime_observation_mutex.synchronize do
@@ -1225,7 +1278,7 @@ module Ibex
1225
1278
  clear_sync_recovery
1226
1279
  end
1227
1280
 
1228
- # @rbs ((^() -> untyped)? source, ?initial_state: Integer?) -> void
1281
+ # @rbs ((^() -> runtime_value)? source, ?initial_state: Integer?) -> void
1229
1282
  def prepare_parse(source, initial_state: nil)
1230
1283
  @runtime_parser_tables = load_parser_tables
1231
1284
  tables = validate_parser_table_format!
@@ -1267,10 +1320,11 @@ module Ibex
1267
1320
  @semantic_error = false
1268
1321
  @repair_input_buffer = @repair_policy ? [] : nil
1269
1322
  @repair_selected = false
1323
+ @repair_search_results = [] #: Array[RepairSearchResult]
1270
1324
  clear_sync_recovery
1271
1325
  end
1272
1326
 
1273
- # @rbs (Hash[Symbol, untyped] tables, Integer? requested) -> Integer
1327
+ # @rbs (Hash[Symbol, runtime_value] tables, Integer? requested) -> Integer
1274
1328
  def resolve_initial_state(tables, requested)
1275
1329
  initial_state = requested || tables[:initial_state] || 0
1276
1330
  return initial_state if initial_state.is_a?(Integer) &&
@@ -1279,7 +1333,7 @@ module Ibex
1279
1333
  raise ParseError, "(tables):1:1: parser initial state #{initial_state.inspect} is invalid"
1280
1334
  end
1281
1335
 
1282
- # @rbs () -> Hash[Symbol, untyped]
1336
+ # @rbs () -> Hash[Symbol, runtime_value]
1283
1337
  def validate_parser_table_format!
1284
1338
  tables = parser_tables
1285
1339
  unless tables.key?(:format_version)
@@ -1296,19 +1350,19 @@ module Ibex
1296
1350
  "regenerate the parser with the installed Ibex version"
1297
1351
  end
1298
1352
 
1299
- validate_current_cst_tables!(tables, actual)
1300
- if actual >= 3 && !generated_action_contracts_validated?(tables)
1301
- validate_generated_action_contracts!(tables, actual)
1353
+ validate_current_cst_tables!(tables)
1354
+ unless generated_action_contracts_validated?(tables)
1355
+ validate_generated_action_contracts!(tables)
1302
1356
  cache_generated_action_contracts!(tables)
1303
1357
  end
1304
1358
  tables
1305
1359
  end
1306
1360
 
1307
- # @rbs (Hash[Symbol, untyped] tables, Integer actual) -> void
1308
- def validate_current_cst_tables!(tables, actual)
1361
+ # @rbs (Hash[Symbol, runtime_value] tables) -> void
1362
+ def validate_current_cst_tables!(tables)
1309
1363
  cst = tables[:cst]
1310
1364
  return if cst.nil? || cst == false
1311
- return if actual == PARSER_TABLE_FORMAT_VERSION && cst.is_a?(Hash)
1365
+ return if cst.is_a?(Hash)
1312
1366
 
1313
1367
  raise ParseError,
1314
1368
  "(tables):1:1: legacy CST parser tables are unsupported; " \
@@ -1319,7 +1373,7 @@ module Ibex
1319
1373
  # Ractor boundary. Their action-marker contract only needs one scan per
1320
1374
  # parser class and exact table object, including across parser instances.
1321
1375
  # Mutable application tables remain intentionally uncached.
1322
- # @rbs (Hash[Symbol, untyped] tables) -> bool
1376
+ # @rbs (Hash[Symbol, runtime_value] tables) -> bool
1323
1377
  def generated_action_contracts_validated?(tables)
1324
1378
  return false unless generated_action_contract_cache_accessible?
1325
1379
 
@@ -1327,7 +1381,7 @@ module Ibex
1327
1381
  cached.equal?(tables)
1328
1382
  end
1329
1383
 
1330
- # @rbs (Hash[Symbol, untyped] tables) -> void
1384
+ # @rbs (Hash[Symbol, runtime_value] tables) -> void
1331
1385
  def cache_generated_action_contracts!(tables)
1332
1386
  shareable = defined?(Ractor) && Ractor.respond_to?(:shareable?) && Ractor.shareable?(tables)
1333
1387
  return unless shareable
@@ -1353,58 +1407,62 @@ module Ibex
1353
1407
  true
1354
1408
  end
1355
1409
 
1356
- # @rbs (Hash[Symbol, untyped] tables, Integer version) -> void
1357
- def validate_generated_action_contracts!(tables, version)
1410
+ # @rbs (Hash[Symbol, runtime_value] tables) -> void
1411
+ def validate_generated_action_contracts!(tables)
1358
1412
  tables.fetch(:productions).each_with_index do |production, index|
1359
- validate_composition_action_contract!(production, index, version)
1360
- validate_values_action_contract!(production, index, version)
1361
- validate_positional_action_contract!(production, index, version)
1413
+ validate_composition_action_contract!(production, index)
1414
+ validate_values_action_contract!(production, index)
1415
+ validate_positional_action_contract!(production, index)
1362
1416
  end
1363
1417
  end
1364
1418
 
1365
- # @rbs (Hash[Symbol, untyped] production, Integer index, Integer version) -> void
1366
- def validate_composition_action_contract!(production, index, version)
1419
+ # @rbs (Hash[Symbol, runtime_value] production, Integer index) -> void
1420
+ def validate_composition_action_contract!(production, index)
1367
1421
  return unless production[:composition_action] == true
1368
1422
  return if production[:location_action] == true && generated_action_symbol?(production[:action])
1369
1423
 
1370
1424
  raise ParseError,
1371
- "(tables):1:1: parser table format version #{version} production #{index} has an inconsistent " \
1425
+ "(tables):1:1: parser table format version #{PARSER_TABLE_FORMAT_VERSION} " \
1426
+ "production #{index} has an inconsistent " \
1372
1427
  ":composition_action marker; a generated action Symbol with :location_action is required"
1373
1428
  end
1374
1429
 
1375
- # @rbs (Hash[Symbol, untyped] production, Integer index, Integer version) -> void
1376
- def validate_values_action_contract!(production, index, version)
1430
+ # @rbs (Hash[Symbol, runtime_value] production, Integer index) -> void
1431
+ def validate_values_action_contract!(production, index)
1377
1432
  borrowed = production[:borrowed_values_action] == true
1378
1433
  if borrowed && production[:values_action] != true
1379
1434
  raise ParseError,
1380
- "(tables):1:1: parser table format version #{version} production #{index} has an inconsistent " \
1435
+ "(tables):1:1: parser table format version #{PARSER_TABLE_FORMAT_VERSION} " \
1436
+ "production #{index} has an inconsistent " \
1381
1437
  ":borrowed_values_action marker; :values_action is required"
1382
1438
  end
1383
- return if version < 4 || production[:values_action] != true
1439
+ return if production[:values_action] != true
1384
1440
  return if generated_action_symbol?(production[:action]) &&
1385
1441
  production[:location_action] != true &&
1386
1442
  production[:composition_action] != true &&
1387
1443
  production.fetch(:location_context_length, 0).zero?
1388
1444
 
1389
1445
  raise ParseError,
1390
- "(tables):1:1: parser table format version #{version} production #{index} has an inconsistent " \
1446
+ "(tables):1:1: parser table format version #{PARSER_TABLE_FORMAT_VERSION} " \
1447
+ "production #{index} has an inconsistent " \
1391
1448
  ":values_action marker; a generated action Symbol without location, composition, or context " \
1392
1449
  "markers is required"
1393
1450
  end
1394
1451
 
1395
- # @rbs (Hash[Symbol, untyped] production, Integer index, Integer version) -> void
1396
- def validate_positional_action_contract!(production, index, version)
1397
- return if version < 5 || production[:positional_action] != true
1452
+ # @rbs (Hash[Symbol, runtime_value] production, Integer index) -> void
1453
+ def validate_positional_action_contract!(production, index)
1454
+ return unless production[:positional_action] == true
1398
1455
 
1399
1456
  return if positional_action_contract?(production)
1400
1457
 
1401
1458
  raise ParseError,
1402
- "(tables):1:1: parser table format version #{version} production #{index} has an inconsistent " \
1459
+ "(tables):1:1: parser table format version #{PARSER_TABLE_FORMAT_VERSION} " \
1460
+ "production #{index} has an inconsistent " \
1403
1461
  ":positional_action marker; a generated action Symbol with zero to four RHS values and no other " \
1404
1462
  "action ABI markers is required"
1405
1463
  end
1406
1464
 
1407
- # @rbs (Hash[Symbol, untyped] production) -> bool
1465
+ # @rbs (Hash[Symbol, runtime_value] production) -> bool
1408
1466
  def positional_action_contract?(production)
1409
1467
  production[:length].is_a?(Integer) &&
1410
1468
  production[:length].between?(0, 4) &&
@@ -1416,7 +1474,7 @@ module Ibex
1416
1474
  production.fetch(:location_context_length, 0).zero?
1417
1475
  end
1418
1476
 
1419
- # @rbs () -> untyped
1477
+ # @rbs () -> runtime_value
1420
1478
  def action_for_current_state
1421
1479
  loop do
1422
1480
  if @lookahead.equal?(NO_LOOKAHEAD)
@@ -1439,7 +1497,7 @@ module Ibex
1439
1497
  end
1440
1498
  end
1441
1499
 
1442
- # @rbs (untyped action) -> untyped
1500
+ # @rbs (runtime_value action) -> runtime_value
1443
1501
  def perform(action)
1444
1502
  case action.first
1445
1503
  when :shift
@@ -1481,7 +1539,7 @@ module Ibex
1481
1539
  CONTINUE_OUTCOME
1482
1540
  end
1483
1541
 
1484
- # @rbs (Hash[Symbol, untyped] production, untyped length) -> bool
1542
+ # @rbs (Hash[Symbol, runtime_value] production, runtime_value length) -> bool
1485
1543
  def fast_reduction_eligible?(production, length)
1486
1544
  action = production[:action]
1487
1545
  return false if action && !generated_values_action?(production, action)
@@ -1490,7 +1548,7 @@ module Ibex
1490
1548
  length <= @value_stack.length && length < @state_stack.length
1491
1549
  end
1492
1550
 
1493
- # @rbs (Integer production_id, Hash[Symbol, untyped] production, Integer length) -> [:continue]
1551
+ # @rbs (Integer production_id, Hash[Symbol, runtime_value] production, Integer length) -> [:continue]
1494
1552
  def fast_reduce(production_id, production, length)
1495
1553
  action = production[:action]
1496
1554
  return fast_values_reduce(production_id, production, length, action) if action
@@ -1511,7 +1569,8 @@ module Ibex
1511
1569
  CONTINUE_OUTCOME
1512
1570
  end
1513
1571
 
1514
- # @rbs (Integer production_id, Hash[Symbol, untyped] production, Integer length, Symbol action) -> untyped
1572
+ # @rbs (Integer production_id, Hash[Symbol, runtime_value] production, Integer length,
1573
+ # Symbol action) -> runtime_value
1515
1574
  def fast_values_reduce(production_id, production, length, action)
1516
1575
  hook_values = @value_stack.last(length)
1517
1576
  values = hook_values.dup
@@ -1544,7 +1603,7 @@ module Ibex
1544
1603
  CONTINUE_OUTCOME
1545
1604
  end
1546
1605
 
1547
- # @rbs (Integer next_state) -> untyped
1606
+ # @rbs (Integer next_state) -> runtime_value
1548
1607
  def shift(next_state)
1549
1608
  token_id = @lookahead
1550
1609
  value = @lookahead_value
@@ -1577,7 +1636,7 @@ module Ibex
1577
1636
  CONTINUE_OUTCOME
1578
1637
  end
1579
1638
 
1580
- # @rbs (Integer production_id, ?Hash[Symbol, untyped]? prefetched_production) -> untyped
1639
+ # @rbs (Integer production_id, ?Hash[Symbol, runtime_value]? prefetched_production) -> runtime_value
1581
1640
  def reduce(production_id, prefetched_production = nil) # rubocop:disable Metrics -- allocation-free hot path.
1582
1641
  production = prefetched_production || parser_tables.fetch(:productions).fetch(production_id)
1583
1642
  length = production.fetch(:length)
@@ -1622,23 +1681,23 @@ module Ibex
1622
1681
  CONTINUE_OUTCOME
1623
1682
  end
1624
1683
 
1625
- # @rbs (Integer next_state, untyped result, untyped location) -> void
1684
+ # @rbs (Integer next_state, runtime_value result, runtime_value location) -> void
1626
1685
  def push_reduction_result(next_state, result, location)
1627
1686
  @state_stack << next_state
1628
1687
  push_location(location)
1629
1688
  @value_stack << result
1630
1689
  end
1631
1690
 
1632
- # @rbs (Integer production_id, Integer length, Integer lhs, untyped result, Integer next_state) -> void
1691
+ # @rbs (Integer production_id, Integer length, Integer lhs, runtime_value result, Integer next_state) -> void
1633
1692
  def trace_reduction(production_id, length, lhs, result, next_state)
1634
1693
  return unless @yydebug
1635
1694
 
1636
1695
  trace("reduce #{production_id} (#{length})#{trace_value_suffix(result, lhs)} -> state #{next_state}")
1637
1696
  end
1638
1697
 
1639
- # @rbs (Array[Proc]? observers, Integer production_id, Hash[Symbol, untyped] production, Integer length,
1640
- # Integer? pre_state, Integer? post_state, Integer next_state, untyped result,
1641
- # LocationSpan? location) -> Hash[String, untyped]?
1698
+ # @rbs (Array[Proc]? observers, Integer production_id, Hash[Symbol, runtime_value] production, Integer length,
1699
+ # Integer? pre_state, Integer? post_state, Integer next_state, runtime_value result,
1700
+ # LocationSpan? location) -> Hash[String, runtime_value]?
1642
1701
  def build_reduce_event_data(
1643
1702
  observers, production_id, production, length, pre_state, post_state, next_state, result, location
1644
1703
  )
@@ -1649,9 +1708,9 @@ module Ibex
1649
1708
  )
1650
1709
  end
1651
1710
 
1652
- # @rbs (Integer production_id, Hash[Symbol, untyped] production, Integer length,
1653
- # Integer? pre_state, Integer? post_state, Integer next_state, untyped result,
1654
- # LocationSpan? location) -> Hash[String, untyped]
1711
+ # @rbs (Integer production_id, Hash[Symbol, runtime_value] production, Integer length,
1712
+ # Integer? pre_state, Integer? post_state, Integer next_state, runtime_value result,
1713
+ # LocationSpan? location) -> Hash[String, runtime_value]
1655
1714
  def runtime_reduce_data(production_id, production, length, pre_state, post_state, next_state, result, location)
1656
1715
  {
1657
1716
  "production_id" => production_id,
@@ -1665,15 +1724,15 @@ module Ibex
1665
1724
  }.freeze
1666
1725
  end
1667
1726
 
1668
- # @rbs (untyped result) -> [:accepted, untyped]
1727
+ # @rbs (runtime_value result) -> [:accepted, runtime_value]
1669
1728
  def accept_reduction(result)
1670
1729
  result = finalize_cst(result)
1671
1730
  emit_accept_event(EventSanitizer.value(result), "semantic") if @runtime_observers
1672
1731
  [:accepted, result]
1673
1732
  end
1674
1733
 
1675
- # @rbs (Integer production_id, Hash[Symbol, untyped] production, Array[untyped] values,
1676
- # Array[untyped] locations, LocationSpan? location) -> untyped
1734
+ # @rbs (Integer production_id, Hash[Symbol, runtime_value] production, Array[runtime_value] values,
1735
+ # Array[runtime_value] locations, LocationSpan? location) -> runtime_value
1677
1736
  def reduction_value(production_id, production, values, locations, location) # rubocop:disable Metrics -- explicit hot path.
1678
1737
  previous_locations = @semantic_locations
1679
1738
  previous_names = @semantic_location_names
@@ -1721,8 +1780,8 @@ module Ibex
1721
1780
  @semantic_result_location = previous_result_location
1722
1781
  end
1723
1782
 
1724
- # @rbs (Hash[Symbol, untyped] production, Symbol action, Array[untyped] values,
1725
- # Array[untyped] locations, LocationSpan? location) -> untyped
1783
+ # @rbs (Hash[Symbol, runtime_value] production, Symbol action, Array[runtime_value] values,
1784
+ # Array[runtime_value] locations, LocationSpan? location) -> runtime_value
1726
1785
  def values_reduction_value(production, action, values, locations, location)
1727
1786
  previous_locations = @semantic_locations
1728
1787
  previous_names = @semantic_location_names
@@ -1737,41 +1796,37 @@ module Ibex
1737
1796
  @semantic_result_location = previous_result_location
1738
1797
  end
1739
1798
 
1740
- # @rbs (Integer production_id, Hash[Symbol, untyped] production, Array[untyped] values,
1741
- # Array[untyped] locations, LocationSpan? location) -> untyped
1799
+ # @rbs (Integer production_id, Hash[Symbol, runtime_value] production, Array[runtime_value] values,
1800
+ # Array[runtime_value] locations, LocationSpan? location) -> runtime_value
1742
1801
  def actionless_reduction_value(_production_id, _production, values, _locations, _location)
1743
1802
  values.first
1744
1803
  end
1745
1804
 
1746
- # @rbs (Hash[Symbol, untyped] production, untyped action) -> bool
1805
+ # @rbs (Hash[Symbol, runtime_value] production, runtime_value action) -> bool
1747
1806
  def generated_location_action?(production, action)
1748
- parser_tables.fetch(:format_version) >= 2 &&
1749
- production[:location_action] == true &&
1807
+ production[:location_action] == true &&
1750
1808
  generated_action_symbol?(action)
1751
1809
  end
1752
1810
 
1753
- # @rbs (Hash[Symbol, untyped] production, untyped action) -> bool
1811
+ # @rbs (Hash[Symbol, runtime_value] production, runtime_value action) -> bool
1754
1812
  def generated_values_action?(production, action)
1755
- parser_tables.fetch(:format_version) >= 4 &&
1756
- production[:values_action] == true &&
1813
+ production[:values_action] == true &&
1757
1814
  generated_action_symbol?(action)
1758
1815
  end
1759
1816
 
1760
- # @rbs (Hash[Symbol, untyped] production, untyped action) -> bool
1817
+ # @rbs (Hash[Symbol, runtime_value] production, runtime_value action) -> bool
1761
1818
  def generated_positional_action?(production, action)
1762
- parser_tables.fetch(:format_version) >= 5 &&
1763
- production[:positional_action] == true &&
1819
+ production[:positional_action] == true &&
1764
1820
  generated_action_symbol?(action)
1765
1821
  end
1766
1822
 
1767
- # @rbs (Hash[Symbol, untyped] production, untyped action) -> bool
1823
+ # @rbs (Hash[Symbol, runtime_value] production, runtime_value action) -> bool
1768
1824
  def generated_composition_action?(production, action)
1769
- parser_tables.fetch(:format_version) >= 3 &&
1770
- production[:composition_action] == true &&
1825
+ production[:composition_action] == true &&
1771
1826
  generated_location_action?(production, action)
1772
1827
  end
1773
1828
 
1774
- # @rbs (untyped action) -> bool
1829
+ # @rbs (runtime_value action) -> bool
1775
1830
  def generated_action_symbol?(action)
1776
1831
  action.is_a?(Symbol) && action.name.match?(GENERATED_ACTION_NAME)
1777
1832
  end
@@ -1781,7 +1836,7 @@ module Ibex
1781
1836
  parser_tables[:cst].is_a?(Hash)
1782
1837
  end
1783
1838
 
1784
- # @rbs (Integer token_id, untyped value, untyped location, Symbol reason) -> void
1839
+ # @rbs (Integer token_id, runtime_value value, runtime_value location, Symbol reason) -> void
1785
1840
  def capture_cst_error(token_id, value, location, reason)
1786
1841
  return unless cst_enabled?
1787
1842
  return if token_id == EOF_TOKEN
@@ -1791,7 +1846,7 @@ module Ibex
1791
1846
  }.freeze
1792
1847
  end
1793
1848
 
1794
- # @rbs (untyped value) -> untyped
1849
+ # @rbs (runtime_value value) -> runtime_value
1795
1850
  def finalize_cst(value)
1796
1851
  return value unless cst_enabled?
1797
1852
 
@@ -1811,7 +1866,7 @@ module Ibex
1811
1866
  nil
1812
1867
  end
1813
1868
 
1814
- # @rbs (untyped location, Symbol key) -> untyped
1869
+ # @rbs (runtime_value location, Symbol key) -> runtime_value
1815
1870
  def cst_location_value(location, key)
1816
1871
  if location.is_a?(Hash)
1817
1872
  value = location[key]
@@ -1825,20 +1880,28 @@ module Ibex
1825
1880
  nil
1826
1881
  end
1827
1882
 
1828
- # @rbs (Hash[Symbol, untyped] tables) -> void
1829
- def prepare_green_cst(tables)
1883
+ # @rbs (Hash[Symbol, runtime_value] tables) -> void
1884
+ def prepare_green_cst(tables) # rubocop:disable Metrics/MethodLength
1830
1885
  @syntax_root = nil
1831
1886
  @syntax_parse_memo = nil
1832
- @syntax_diagnostics.clear
1833
- @green_pending_skipped.clear
1834
1887
  config = tables[:cst]
1835
1888
  unless config.is_a?(Hash)
1836
1889
  @green_builder = nil
1837
1890
  @green_kinds = nil
1838
1891
  @green_cache = nil
1892
+ @syntax_diagnostics = EMPTY_LOCATIONS
1893
+ @green_pending_skipped = EMPTY_GREEN_TRIVIA
1894
+ @green_token_states = EMPTY_LOCATIONS
1895
+ @green_memo_stack = EMPTY_LOCATIONS
1839
1896
  return
1840
1897
  end
1841
1898
 
1899
+ @syntax_diagnostics = [] if @syntax_diagnostics.frozen?
1900
+ @green_pending_skipped = [] if @green_pending_skipped.frozen?
1901
+ @green_token_states = [] if @green_token_states.frozen?
1902
+ @green_memo_stack = [] if @green_memo_stack.frozen?
1903
+ @syntax_diagnostics.clear
1904
+ @green_pending_skipped.clear
1842
1905
  kinds = tables[:cst_kinds]
1843
1906
  kinds = CST::Kind.new(config.fetch(:kinds), slots: config.fetch(:slots)) unless kinds.is_a?(CST::Kind)
1844
1907
  cache = @green_cache_override || CST::NodeCache.new
@@ -1853,13 +1916,13 @@ module Ibex
1853
1916
  @green_builder = CST::GreenBuilder.new(kinds: kinds, cache: cache)
1854
1917
  end
1855
1918
 
1856
- # @rbs (Hash[Symbol, untyped] tables) -> void
1919
+ # @rbs (Hash[Symbol, runtime_value] tables) -> void
1857
1920
  def reset_cst_results(tables)
1858
1921
  prepare_green_cst(tables)
1859
1922
  end
1860
1923
 
1861
1924
  # rubocop:disable Metrics/PerceivedComplexity -- generated locations have a deliberate allocation-free path.
1862
- # @rbs (Integer token_id, untyped value, untyped location, Integer from_state) -> void
1925
+ # @rbs (Integer token_id, runtime_value value, runtime_value location, Integer from_state) -> void
1863
1926
  def shift_green_token(token_id, value, location, from_state)
1864
1927
  builder = @green_builder
1865
1928
  kinds = @green_kinds
@@ -1889,7 +1952,7 @@ module Ibex
1889
1952
  # rubocop:enable Metrics/PerceivedComplexity
1890
1953
 
1891
1954
  # @rbs (CST::GreenBuilder builder, CST::Kind kinds, Integer token_id, Integer from_state,
1892
- # Array[CST::GreenTrivia] trailing, Array[CST::GreenTrivia] location_leading, untyped repair,
1955
+ # Array[CST::GreenTrivia] trailing, Array[CST::GreenTrivia] location_leading, runtime_value repair,
1893
1956
  # String token_text, Symbol lexer_state) -> void
1894
1957
  def commit_green_token_shift(
1895
1958
  builder, kinds, token_id, from_state, trailing, location_leading, repair, token_text, lexer_state
@@ -1918,7 +1981,7 @@ module Ibex
1918
1981
  @green_reused_right_edge = false
1919
1982
  end
1920
1983
 
1921
- # @rbs (Integer production_id, Hash[Symbol, untyped] production, Integer length, Integer left_state) -> void
1984
+ # @rbs (Integer production_id, Hash[Symbol, runtime_value] production, Integer length, Integer left_state) -> void
1922
1985
  def reduce_green(production_id, production, length, left_state)
1923
1986
  builder = @green_builder
1924
1987
  return unless builder
@@ -1936,7 +1999,7 @@ module Ibex
1936
1999
  @green_memo_stack << entry
1937
2000
  end
1938
2001
 
1939
- # @rbs (untyped value, untyped location) -> String
2002
+ # @rbs (runtime_value value, runtime_value location) -> String
1940
2003
  def green_token_text(value, location)
1941
2004
  text = if location.is_a?(Hash) && location.key?(:ibex_lexer_start_state)
1942
2005
  location[:ibex_cst_text]
@@ -1949,7 +2012,7 @@ module Ibex
1949
2012
  ""
1950
2013
  end
1951
2014
 
1952
- # @rbs (untyped location, Symbol key) -> Array[CST::GreenTrivia]
2015
+ # @rbs (runtime_value location, Symbol key) -> Array[CST::GreenTrivia]
1953
2016
  def green_location_trivia(location, key)
1954
2017
  trivia = if location.is_a?(Hash) && location.key?(:ibex_lexer_start_state)
1955
2018
  location[key]
@@ -1963,7 +2026,7 @@ module Ibex
1963
2026
  trivia.grep(CST::GreenTrivia)
1964
2027
  end
1965
2028
 
1966
- # @rbs (untyped value) -> untyped
2029
+ # @rbs (runtime_value value) -> runtime_value
1967
2030
  def finalize_red_green_cst(value)
1968
2031
  builder = @green_builder || raise(ParseError, "(cst):1:1: Green builder is unavailable")
1969
2032
  kinds = @green_kinds || raise(ParseError, "(cst):1:1: kind metadata is unavailable")
@@ -2065,7 +2128,7 @@ module Ibex
2065
2128
  )
2066
2129
  end
2067
2130
 
2068
- # @rbs (untyped value) -> CST::ParseResult
2131
+ # @rbs (runtime_value value) -> CST::ParseResult
2069
2132
  def syntax_parse_result(value)
2070
2133
  root = @syntax_root
2071
2134
  raise ParseError, "(cst):1:1: parse_with_syntax requires a format-v6 CST parser" unless root
@@ -2095,7 +2158,7 @@ module Ibex
2095
2158
  )
2096
2159
  end
2097
2160
 
2098
- # @rbs (untyped location) -> Symbol
2161
+ # @rbs (runtime_value location) -> Symbol
2099
2162
  def green_lexer_state(location)
2100
2163
  state = if location.is_a?(Hash) && location.key?(:ibex_lexer_start_state)
2101
2164
  location[:ibex_lexer_start_state]
@@ -2105,7 +2168,7 @@ module Ibex
2105
2168
  state ? state.to_sym : :INITIAL
2106
2169
  end
2107
2170
 
2108
- # @rbs (CST::NodeCache cache) { () -> untyped } -> untyped
2171
+ # @rbs (CST::NodeCache cache) { () -> runtime_value } -> runtime_value
2109
2172
  def with_syntax_only(cache)
2110
2173
  previous_mode = @syntax_only
2111
2174
  previous_cache = @green_cache_override
@@ -2118,7 +2181,7 @@ module Ibex
2118
2181
  @green_cache_override = previous_cache
2119
2182
  end
2120
2183
 
2121
- # @rbs (untyped source, CST::NodeCache cache) -> CST::SyntaxResult
2184
+ # @rbs (runtime_value source, CST::NodeCache cache) -> CST::SyntaxResult
2122
2185
  def parse_syntax_token_source(source, cache)
2123
2186
  with_syntax_only(cache) do
2124
2187
  value = drive_parser(source)
@@ -2152,7 +2215,7 @@ module Ibex
2152
2215
  @incremental_reused_descendants += subtree.green.descendant_count
2153
2216
  end
2154
2217
 
2155
- # @rbs (Symbol type, Hash[untyped, untyped] data) -> void
2218
+ # @rbs (Symbol type, Hash[runtime_value, runtime_value] data) -> void
2156
2219
  def emit_incremental_event(type, data)
2157
2220
  emit_runtime_event(type, data) if @runtime_observers
2158
2221
  end
@@ -2177,7 +2240,7 @@ module Ibex
2177
2240
  )
2178
2241
  end
2179
2242
 
2180
- # @rbs (?report: bool) -> untyped
2243
+ # @rbs (?report: bool) -> runtime_value
2181
2244
  def recover(report: true)
2182
2245
  @runtime_fast_path = false
2183
2246
  materialize_lookahead_token_display! unless @lookahead.equal?(NO_LOOKAHEAD)
@@ -2187,7 +2250,7 @@ module Ibex
2187
2250
  begin_recovery(report)
2188
2251
  end
2189
2252
 
2190
- # @rbs () -> untyped
2253
+ # @rbs () -> runtime_value
2191
2254
  def continue_recovery
2192
2255
  return reject_recovery_eof if @lookahead == EOF_TOKEN
2193
2256
 
@@ -2214,7 +2277,7 @@ module Ibex
2214
2277
  [:done, cst_enabled? ? failed_cst : nil]
2215
2278
  end
2216
2279
 
2217
- # @rbs (String token_display) -> Hash[String, untyped]
2280
+ # @rbs (String token_display) -> Hash[String, runtime_value]
2218
2281
  def runtime_discard_data(token_display)
2219
2282
  runtime_token_data(
2220
2283
  token_id: @lookahead,
@@ -2225,7 +2288,7 @@ module Ibex
2225
2288
  ).merge("reason" => "recovery").freeze
2226
2289
  end
2227
2290
 
2228
- # @rbs (bool report) -> untyped
2291
+ # @rbs (bool report) -> runtime_value
2229
2292
  def begin_recovery(report)
2230
2293
  consume_recovery_attempt!
2231
2294
  repair = report ? selected_repair : nil
@@ -2240,7 +2303,7 @@ module Ibex
2240
2303
  fallback_recovery(context, token_data, recovery_observers)
2241
2304
  end
2242
2305
 
2243
- # @rbs (bool report) -> Hash[Symbol, untyped]
2306
+ # @rbs (bool report) -> Hash[Symbol, runtime_value]
2244
2307
  def recovery_context(report)
2245
2308
  {
2246
2309
  token_id: @lookahead,
@@ -2253,7 +2316,7 @@ module Ibex
2253
2316
  }
2254
2317
  end
2255
2318
 
2256
- # @rbs (Hash[Symbol, untyped] context) -> [Hash[String, untyped]?, Array[Proc]?]
2319
+ # @rbs (Hash[Symbol, runtime_value] context) -> [Hash[String, runtime_value]?, Array[Proc]?]
2257
2320
  def publish_error_context(context)
2258
2321
  error_observers = runtime_observer_snapshot if @runtime_observers
2259
2322
  if error_observers
@@ -2267,7 +2330,7 @@ module Ibex
2267
2330
  [token_data, (runtime_observer_snapshot if @runtime_observers)]
2268
2331
  end
2269
2332
 
2270
- # @rbs (Hash[Symbol, untyped] context, untyped repair) -> void
2333
+ # @rbs (Hash[Symbol, runtime_value] context, runtime_value repair) -> void
2271
2334
  def notify_error_handler(context, repair)
2272
2335
  @repair_selected = repair.is_a?(RepairPlan)
2273
2336
  begin
@@ -2284,8 +2347,8 @@ module Ibex
2284
2347
  CONTINUE_OUTCOME
2285
2348
  end
2286
2349
 
2287
- # @rbs (Hash[Symbol, untyped] context, Hash[String, untyped]? token_data,
2288
- # Array[Proc]? recovery_observers) -> untyped
2350
+ # @rbs (Hash[Symbol, runtime_value] context, Hash[String, runtime_value]? token_data,
2351
+ # Array[Proc]? recovery_observers) -> runtime_value
2289
2352
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
2290
2353
  # Green restoration mirrors the existing state/value/location transactional fallback.
2291
2354
  def fallback_recovery(context, token_data, recovery_observers)
@@ -2316,8 +2379,8 @@ module Ibex
2316
2379
  end
2317
2380
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
2318
2381
 
2319
- # @rbs (untyped token_id, untyped token_display, untyped value, untyped location,
2320
- # Integer state) -> Hash[String, untyped]
2382
+ # @rbs (runtime_value token_id, runtime_value token_display, runtime_value value, runtime_value location,
2383
+ # Integer state) -> Hash[String, runtime_value]
2321
2384
  def runtime_original_token_data(token_id, token_display, value, location, state)
2322
2385
  runtime_token_data(
2323
2386
  token_id: token_id,
@@ -2328,8 +2391,8 @@ module Ibex
2328
2391
  )
2329
2392
  end
2330
2393
 
2331
- # @rbs (untyped token_id, untyped token_display, untyped value, untyped location,
2332
- # Integer original_state, Hash[String, untyped]? token_data,
2394
+ # @rbs (runtime_value token_id, runtime_value token_display, runtime_value value, runtime_value location,
2395
+ # Integer original_state, Hash[String, runtime_value]? token_data,
2333
2396
  # Array[Proc]? observers) -> [:done, nil]
2334
2397
  def reject_without_recovery(token_id, token_display, value, location, original_state, token_data, observers)
2335
2398
  if observers
@@ -2345,8 +2408,8 @@ module Ibex
2345
2408
  [:done, cst_enabled? ? failed_cst : nil]
2346
2409
  end
2347
2410
 
2348
- # @rbs (untyped token_id, untyped token_display, untyped value, untyped location,
2349
- # Integer original_state, Array[untyped] value_stack, Hash[String, untyped]? token_data,
2411
+ # @rbs (runtime_value token_id, runtime_value token_display, runtime_value value, runtime_value location,
2412
+ # Integer original_state, Array[runtime_value] value_stack, Hash[String, runtime_value]? token_data,
2350
2413
  # String reason, Array[Proc]? observers) -> [:continue]
2351
2414
  def finish_recovery(
2352
2415
  token_id, token_display, value, location, original_state, value_stack, token_data, reason, observers
@@ -2372,8 +2435,8 @@ module Ibex
2372
2435
  CONTINUE_OUTCOME
2373
2436
  end
2374
2437
 
2375
- # @rbs (token_id: untyped, token_display: untyped, value: untyped,
2376
- # location: untyped, state: Integer) -> Hash[String, untyped]
2438
+ # @rbs (token_id: runtime_value, token_display: runtime_value, value: runtime_value,
2439
+ # location: runtime_value, state: Integer) -> Hash[String, runtime_value]
2377
2440
  def runtime_token_data(token_id:, token_display:, value:, location:, state:)
2378
2441
  if token_id.equal?(NO_LOOKAHEAD)
2379
2442
  return {
@@ -2394,7 +2457,7 @@ module Ibex
2394
2457
  }.freeze
2395
2458
  end
2396
2459
 
2397
- # @rbs () -> Hash[String, untyped]
2460
+ # @rbs () -> Hash[String, runtime_value]
2398
2461
  def runtime_current_token_data
2399
2462
  runtime_token_data(
2400
2463
  token_id: @lookahead,
@@ -2405,7 +2468,7 @@ module Ibex
2405
2468
  )
2406
2469
  end
2407
2470
 
2408
- # @rbs (untyped result_summary, String reason) -> void
2471
+ # @rbs (runtime_value result_summary, String reason) -> void
2409
2472
  def emit_accept_event(result_summary, reason)
2410
2473
  emit_runtime_event(
2411
2474
  :accept,
@@ -2413,7 +2476,7 @@ module Ibex
2413
2476
  )
2414
2477
  end
2415
2478
 
2416
- # @rbs (String reason, Hash[String, untyped] token_data, ?observers: Array[Proc]?) -> void
2479
+ # @rbs (String reason, Hash[String, runtime_value] token_data, ?observers: Array[Proc]?) -> void
2417
2480
  def emit_reject_event(reason, token_data, observers: runtime_observer_snapshot)
2418
2481
  emit_runtime_event(
2419
2482
  :reject,
@@ -2434,10 +2497,13 @@ module Ibex
2434
2497
  @state_stack << action.fetch(1)
2435
2498
  push_location(@lookahead_location)
2436
2499
  @value_stack << nil
2437
- @green_builder&.absorb_into_error(popped)
2438
- empty = [] #: Array[CST::ParseMemo::Entry]
2439
- children = popped.zero? ? empty : @green_memo_stack.pop(popped)
2440
- @green_memo_stack << CST::ParseMemo::Entry.new(@state_stack.last, children: children)
2500
+ builder = @green_builder
2501
+ if builder
2502
+ builder.absorb_into_error(popped)
2503
+ empty = [] #: Array[CST::ParseMemo::Entry]
2504
+ children = popped.zero? ? empty : @green_memo_stack.pop(popped)
2505
+ @green_memo_stack << CST::ParseMemo::Entry.new(@state_stack.last, children: children)
2506
+ end
2441
2507
  return true
2442
2508
  end
2443
2509
  return false if @state_stack.length == 1
@@ -2480,8 +2546,21 @@ module Ibex
2480
2546
  return unless policy
2481
2547
 
2482
2548
  tokens, complete = repair_search_tokens(policy)
2483
- RepairSearch.new(parser_tables, policy, tokens, complete: complete).search(@state_stack)
2549
+ result = RepairSearch.new(parser_tables, policy, tokens, complete: complete).search_result(@state_stack)
2550
+ @repair_search_results << result
2551
+ return result.plan if result.selected?
2552
+ return RepairSearch::NEED_INPUT if result.status == :need_input
2553
+
2554
+ nil
2555
+ end
2556
+
2557
+ # @rbs skip
2558
+ # steep:ignore Ruby::UndeclaredMethodDefinition
2559
+ # steep:ignore:start
2560
+ def syntax_repair_search_results
2561
+ @repair_search_results.dup.freeze
2484
2562
  end
2563
+ # steep:ignore:end
2485
2564
 
2486
2565
  # @rbs (RepairPolicy policy) -> [Array[RepairInput], bool]
2487
2566
  def repair_search_tokens(policy)
@@ -2520,6 +2599,16 @@ module Ibex
2520
2599
  )
2521
2600
  end
2522
2601
 
2602
+ # Snapshot the original buffered prefix before a selected repair is
2603
+ # replayed. Syntax tooling consumes locations and token identities only.
2604
+ # @rbs skip
2605
+ # steep:ignore Ruby::UndeclaredMethodDefinition
2606
+ # steep:ignore:start
2607
+ def syntax_repair_inputs
2608
+ ([current_repair_input] + (@repair_input_buffer || [])).freeze
2609
+ end
2610
+ # steep:ignore:end
2611
+
2523
2612
  # @rbs (Array[RepairInput] source, Array[RepairEdit] edits) -> Array[RepairInput]
2524
2613
  def replay_repair_edits(source, edits)
2525
2614
  by_position = edits.group_by(&:position)
@@ -2578,13 +2667,13 @@ module Ibex
2578
2667
  @runtime_lookahead_token_display = nil
2579
2668
  end
2580
2669
 
2581
- # @rbs (RepairEdit edit, value: untyped, location: untyped) -> RepairInput
2670
+ # @rbs (RepairEdit edit, value: runtime_value, location: runtime_value) -> RepairInput
2582
2671
  def synthetic_repair_input(edit, value:, location:)
2583
2672
  location = cst_repair_location(location, edit.kind) if cst_enabled?
2584
2673
  RepairInput.new(token_id: edit.token_id, token_name: edit.token_name, value: value, location: location)
2585
2674
  end
2586
2675
 
2587
- # @rbs (untyped location, Symbol kind) -> Hash[Symbol, untyped]
2676
+ # @rbs (runtime_value location, Symbol kind) -> Hash[Symbol, runtime_value]
2588
2677
  def cst_repair_location(location, kind)
2589
2678
  return location.merge(ibex_repair: kind).freeze if location.is_a?(Hash)
2590
2679
 
@@ -2626,7 +2715,7 @@ module Ibex
2626
2715
  def read_compatible_lookahead
2627
2716
  token = read_external_token
2628
2717
  refresh_runtime_fast_path_after_user_code!
2629
- if token.is_a?(CST::ReusableSubtree)
2718
+ if defined?(CST::ReusableSubtree) && token.is_a?(CST::ReusableSubtree)
2630
2719
  push_reusable_green_subtree(token)
2631
2720
  return
2632
2721
  end
@@ -2665,7 +2754,7 @@ module Ibex
2665
2754
  @runtime_lookahead_token_display = token_to_str(@lookahead)
2666
2755
  end
2667
2756
 
2668
- # @rbs (untyped external_token, untyped value, untyped location) -> RepairInput
2757
+ # @rbs (runtime_value external_token, runtime_value value, runtime_value location) -> RepairInput
2669
2758
  def repair_input(external_token, value, location)
2670
2759
  token_id = parser_tables.fetch(:tokens)[external_token]
2671
2760
  if token_id
@@ -2682,7 +2771,7 @@ module Ibex
2682
2771
  )
2683
2772
  end
2684
2773
 
2685
- # @rbs (untyped token) -> RepairInput
2774
+ # @rbs (runtime_value token) -> RepairInput
2686
2775
  def repair_input_from_external(token)
2687
2776
  if token.nil? || token == false
2688
2777
  return RepairInput.new(token_id: EOF_TOKEN, token_name: token_to_str(EOF_TOKEN), value: nil, location: nil)
@@ -2723,7 +2812,7 @@ module Ibex
2723
2812
  trace("read #{input.token_name}") if @yydebug
2724
2813
  end
2725
2814
 
2726
- # @rbs () -> untyped
2815
+ # @rbs () -> runtime_value
2727
2816
  def read_external_token
2728
2817
  source = @source
2729
2818
  return next_token unless source
@@ -2734,7 +2823,7 @@ module Ibex
2734
2823
  false
2735
2824
  end
2736
2825
 
2737
- # @rbs (untyped external_token) -> Integer
2826
+ # @rbs (runtime_value external_token) -> Integer
2738
2827
  def internal_token_id(external_token)
2739
2828
  token_id = parser_tables.fetch(:tokens)[external_token]
2740
2829
  return token_id if token_id
@@ -2743,7 +2832,7 @@ module Ibex
2743
2832
  @unknown_token_id = -external_token.object_id.abs
2744
2833
  end
2745
2834
 
2746
- # @rbs () -> Hash[Symbol, untyped]
2835
+ # @rbs () -> Hash[Symbol, runtime_value]
2747
2836
  def parser_tables
2748
2837
  tables = @runtime_parser_tables
2749
2838
  return tables if tables
@@ -2751,19 +2840,19 @@ module Ibex
2751
2840
  load_parser_tables
2752
2841
  end
2753
2842
 
2754
- # @rbs () -> Hash[Symbol, untyped]
2843
+ # @rbs () -> Hash[Symbol, runtime_value]
2755
2844
  def load_parser_tables
2756
2845
  self.class.__send__(:parser_tables)
2757
2846
  rescue NoMethodError
2758
2847
  raise ParseError, "(tables):1:1: #{self.class} must define .parser_tables"
2759
2848
  end
2760
2849
 
2761
- # @rbs (untyped action) -> bool
2850
+ # @rbs (runtime_value action) -> bool
2762
2851
  def error_action?(action)
2763
2852
  action.nil? || action.first == :error
2764
2853
  end
2765
2854
 
2766
- # @rbs (untyped configured) -> [String?, String?]
2855
+ # @rbs (runtime_value configured) -> [String?, String?]
2767
2856
  def configured_error_message(configured)
2768
2857
  return [nil, configured] unless configured.is_a?(Hash)
2769
2858
 
@@ -2807,17 +2896,17 @@ module Ibex
2807
2896
  previous.last
2808
2897
  end
2809
2898
 
2810
- # @rbs (Integer state) -> untyped
2899
+ # @rbs (Integer state) -> runtime_value
2811
2900
  def default_action(state)
2812
2901
  parser_tables.fetch(:default_actions, EMPTY_ROW)[state]
2813
2902
  end
2814
2903
 
2815
- # @rbs (Hash[Symbol, untyped] tables) -> bool
2904
+ # @rbs (Hash[Symbol, runtime_value] tables) -> bool
2816
2905
  def track_locations?(tables)
2817
2906
  tables.fetch(:uses_locations, false) || !@runtime_observers.nil?
2818
2907
  end
2819
2908
 
2820
- # @rbs (Hash[Symbol, untyped] tables) -> void
2909
+ # @rbs (Hash[Symbol, runtime_value] tables) -> void
2821
2910
  def initialize_runtime_fast_path(tables)
2822
2911
  @location_stack = track_locations?(tables) ? [] : nil
2823
2912
  @runtime_fast_path = runtime_fast_path_eligible?(tables)
@@ -2829,11 +2918,29 @@ module Ibex
2829
2918
 
2830
2919
  # @rbs (?Class? singleton) -> void
2831
2920
  def install_runtime_fast_path_tracker!(singleton = nil)
2832
- singleton ||= Object.instance_method(:singleton_class).bind_call(self)
2833
- Module.instance_method(:prepend).bind_call(singleton, FastPathMutationTracker)
2921
+ if singleton
2922
+ prepend_lookup = Module.instance_method(:prepend)
2923
+ elsif RUBY_ENGINE == "ruby"
2924
+ thread = ::Thread.current
2925
+ singleton_lookup = thread.thread_variable_get(:__ibex_runtime_parser_singleton_class_lookup)
2926
+ unless singleton_lookup.is_a?(UnboundMethod)
2927
+ singleton_lookup = Object.instance_method(:singleton_class)
2928
+ thread.thread_variable_set(:__ibex_runtime_parser_singleton_class_lookup, singleton_lookup)
2929
+ end
2930
+ singleton = singleton_lookup.bind_call(self)
2931
+ prepend_lookup = thread.thread_variable_get(:__ibex_runtime_parser_module_prepend_lookup)
2932
+ unless prepend_lookup.is_a?(UnboundMethod)
2933
+ prepend_lookup = Module.instance_method(:prepend)
2934
+ thread.thread_variable_set(:__ibex_runtime_parser_module_prepend_lookup, prepend_lookup)
2935
+ end
2936
+ else
2937
+ singleton = Object.instance_method(:singleton_class).bind_call(self)
2938
+ prepend_lookup = Module.instance_method(:prepend)
2939
+ end
2940
+ prepend_lookup.bind_call(singleton, FastPathMutationTracker)
2834
2941
  @runtime_fast_path_tracker_installed = true
2835
2942
  @runtime_fast_path_hooks_mutated = false
2836
- @runtime_fast_path_singleton_ancestors = singleton.ancestors.freeze
2943
+ @runtime_fast_path_singleton_ancestors = nil
2837
2944
  rescue FrozenError, TypeError
2838
2945
  @runtime_fast_path = false
2839
2946
  end
@@ -2844,7 +2951,9 @@ module Ibex
2844
2951
  return if parser_class.instance_variable_get(:@__ibex_fast_path_class_tracker_installed) == true
2845
2952
 
2846
2953
  singleton = parser_class.singleton_class
2847
- singleton.prepend(FastPathClassMutationTracker)
2954
+ # Keep the same module object out of both a class and its superclass
2955
+ # callback chains; TruffleRuby can re-enter `super` in that topology.
2956
+ singleton.prepend(FastPathClassMutationTracker.dup)
2848
2957
  parser_class.instance_variable_set(:@__ibex_fast_path_hook_version, 0)
2849
2958
  parser_class.instance_variable_set(:@__ibex_fast_path_class_tracker_installed, true)
2850
2959
  rescue FrozenError, TypeError
@@ -2853,7 +2962,7 @@ module Ibex
2853
2962
 
2854
2963
  # The generic driver remains authoritative whenever a public runtime
2855
2964
  # extension can observe a committed shift or reduction.
2856
- # @rbs (Hash[Symbol, untyped] tables) -> bool
2965
+ # @rbs (Hash[Symbol, runtime_value] tables) -> bool
2857
2966
  def runtime_fast_path_eligible?(tables)
2858
2967
  !@syntax_only &&
2859
2968
  !@yydebug &&
@@ -2866,11 +2975,20 @@ module Ibex
2866
2975
  end
2867
2976
 
2868
2977
  # @rbs () -> bool
2869
- def runtime_fast_path_hooks_eligible?
2978
+ def runtime_fast_path_hooks_eligible? # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
2870
2979
  cached = cached_runtime_fast_path_hooks_eligibility
2871
2980
  return cached unless cached.nil?
2872
2981
 
2873
2982
  lookup = Object.instance_method(:method)
2983
+ if RUBY_ENGINE == "ruby"
2984
+ thread = ::Thread.current
2985
+ cached_lookup = thread.thread_variable_get(:__ibex_runtime_parser_object_method_lookup)
2986
+ if cached_lookup.is_a?(UnboundMethod)
2987
+ lookup = cached_lookup
2988
+ else
2989
+ thread.thread_variable_set(:__ibex_runtime_parser_object_method_lookup, lookup)
2990
+ end
2991
+ end
2874
2992
  hooks_unchanged =
2875
2993
  runtime_method_unchanged?(lookup, :on_shift, :__ibex_fast_path_on_shift) &&
2876
2994
  runtime_method_unchanged?(lookup, :on_shift_location, :__ibex_fast_path_on_shift_location) &&
@@ -2970,7 +3088,7 @@ module Ibex
2970
3088
  # Keep allocation out of the ordinary two-element lexer path. If a
2971
3089
  # location first appears after values have already shifted, backfill the
2972
3090
  # parallel prefix with nil exactly once.
2973
- # @rbs (untyped location) -> void
3091
+ # @rbs (runtime_value location) -> void
2974
3092
  def push_location(location)
2975
3093
  stack = @location_stack
2976
3094
  if stack
@@ -2982,7 +3100,7 @@ module Ibex
2982
3100
  end
2983
3101
  end
2984
3102
 
2985
- # @rbs (Integer length) -> Array[untyped]
3103
+ # @rbs (Integer length) -> Array[runtime_value]
2986
3104
  def pop_reduction_locations(length)
2987
3105
  stack = @location_stack
2988
3106
  return Array.new(length) unless stack
@@ -2992,7 +3110,7 @@ module Ibex
2992
3110
  locations
2993
3111
  end
2994
3112
 
2995
- # @rbs (untyped table, Integer row, Integer column) -> untyped
3113
+ # @rbs (runtime_value table, Integer row, Integer column) -> runtime_value
2996
3114
  def table_lookup(table, row, column)
2997
3115
  return table.lookup(row, column) if table.respond_to?(:lookup)
2998
3116
 
@@ -3004,7 +3122,7 @@ module Ibex
3004
3122
  @yydebug_output.puts("ibex: #{message}") if @yydebug
3005
3123
  end
3006
3124
 
3007
- # @rbs (untyped value, Integer symbol_id) -> String
3125
+ # @rbs (runtime_value value, Integer symbol_id) -> String
3008
3126
  def trace_value_suffix(value, symbol_id)
3009
3127
  return "" unless @yydebug
3010
3128