Class: Ibex::Runtime::Parser

Inherits:
Object
  • Object
show all
Includes:
Observation, ParserSyncRecovery
Defined in:
lib/ibex/runtime/parser.rb

Overview

Drives a table-defined LR parser without native extensions.

Subclasses provide .parser_tables, returning :tokens, :token_names, :actions, :gotos, and :productions, with optional :default_actions, :error_messages, and :recovery_sync_tokens. Actions are represented by [:shift, state], [:reduce, production], [:accept], or [:error]. Format-v2 and newer generated production entries mark their five-argument semantic methods with location_action: true. Format-v3 and newer composed actions additionally use composition_action: true for the six-argument contract carrying the lookahead location. Format-v4 location-free generated methods use values_action: true for a one-argument values contract. Format-v5 additionally marks proven-safe zero-to-four-value methods with positional_action: true. V1 and unmarked application actions retain the historical two-argument contract. Markers are honored only for the generated _ibex_action_N Symbol shape, never for callables.

Direct Known Subclasses

Frontend::GeneratedParserBase

Constant Summary collapse

ParseError =

: singleton(Ibex::Runtime::ParseError)

Ibex::Runtime::ParseError
EOF_TOKEN =

: Integer

0
ERROR_TOKEN =

: Integer

1
GENERATED_ACTION_NAME =

: Regexp

/\A_ibex_action_\d+\z/
NO_LOOKAHEAD =

: Object

Object.new.freeze
RECOVERY_SHIFTS =

: Integer

3
EMPTY_ROW =

: Hash[Integer, untyped]

empty_row.freeze
EMPTY_LOCATION_NAMES =

: Hash[Symbol, Integer]

empty_location_names.freeze
EMPTY_LOCATIONS =

: Array

empty_locations.freeze
EMPTY_GREEN_TRIVIA =

: Array

empty_green_trivia.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Observation

#observe, #unobserve

Constructor Details

#initialize(resource_limits: ResourceLimits.new) ⇒ Parser

Returns a new instance of Parser.



335
336
337
338
# File 'lib/ibex/runtime/parser.rb', line 335

def initialize(resource_limits: ResourceLimits.new)
  validate_resource_limits!(resource_limits)
  initialize_runtime_state(resource_limits, preserve_existing: false)
end

Instance Attribute Details

#incremental_reused_descendantsObject (readonly)

: Integer



323
324
325
# File 'lib/ibex/runtime/parser.rb', line 323

def incremental_reused_descendants
  @incremental_reused_descendants
end

#syntax_parse_memoObject (readonly)



322
323
324
# File 'lib/ibex/runtime/parser.rb', line 322

def syntax_parse_memo
  @syntax_parse_memo
end

Class Method Details

.incremental_session(source_text, resource_limits: nil, blender: true) ⇒ Object

Start a syntax-only incremental session backed by a generated lexer.



328
329
330
331
332
# File 'lib/ibex/runtime/parser.rb', line 328

def self.incremental_session(source_text, resource_limits: nil, blender: true)
  CST::IncrementalParseSession.new(
    self, source_text, resource_limits: resource_limits, blender: blender
  )
end

Instance Method Details

#do_parseObject

Pull tokens from next_token and parse them.



408
409
410
# File 'lib/ibex/runtime/parser.rb', line 408

def do_parse
  drive_parser(nil)
end

#expected_tokensObject

Return token names accepted in the current parser state.



621
622
623
624
625
626
627
628
629
630
631
# File 'lib/ibex/runtime/parser.rb', line 621

def expected_tokens
  ensure_runtime_initialized!
  return expected_tokens_exact if parser_tables[:exact_expected_tokens]
  return [] if @state_stack.empty?

  state = @state_stack.last
  parser_tables.fetch(:token_names).keys.filter_map do |token_id|
    action = table_lookup(parser_tables.fetch(:actions), state, token_id) || default_action(state) || ERROR_ACTION
    token_to_str(token_id) unless error_action?(action) || token_id == ERROR_TOKEN
  end
end

#expected_tokens_exactObject

Return token names that survive all required default reductions. Semantic actions are not evaluated during this lookahead correction.



636
637
638
639
640
641
642
643
644
645
# File 'lib/ibex/runtime/parser.rb', line 636

def expected_tokens_exact
  ensure_runtime_initialized!
  return [] if @state_stack.empty?

  parser_tables.fetch(:token_names).keys.filter_map do |token_id|
    next if token_id == ERROR_TOKEN

    token_to_str(token_id) if exact_lookahead_accepted?(token_id)
  end
end

#finish(location: nil) ⇒ Object

Supply EOF to a caller-driven parser session and return its result.



461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/ibex/runtime/parser.rb', line 461

def finish(location: nil)
  run_push_driver do
    start_push_session
    refresh_runtime_fast_path_after_user_code!
    if @repair_policy
      enqueue_or_assign_repair_input(
        RepairInput.new(token_id: EOF_TOKEN, token_name: token_to_str(EOF_TOKEN), value: nil, location: location)
      )
    else
      @lookahead = EOF_TOKEN
      @lookahead_value = nil
      @lookahead_location = location
      @runtime_fast_path = false unless nil.equal?(location)
      materialize_compatible_lookahead
    end
    outcome = run_push_lookahead
    return outcome.fetch(1) if outcome.is_a?(Array)

    raise ParseError, "(input):1:1: parser requested input after EOF"
  end
end

#loc(reference) ⇒ Object

Return the location of a one-based RHS position or named reference while a semantic action is running.

Raises:



650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
# File 'lib/ibex/runtime/parser.rb', line 650

def loc(reference)
  locations = @semantic_locations
  raise ParseError, "(runtime):1:1: loc is only available inside a semantic action" unless locations

  index = if reference.is_a?(Integer)
            raise ArgumentError, "location index must be positive" unless reference.positive?

            reference - 1
          else
            names = @semantic_location_names || EMPTY_LOCATION_NAMES
            names.fetch(reference.to_sym) do
              raise ArgumentError, "unknown named location #{reference.inspect}"
            end
          end
  locations.fetch(index) { raise ArgumentError, "location index #{reference.inspect} is outside the RHS" }
end

#next_tokenObject

Override in pull parsers. Return [token, value], [token, value, location], false, or nil.

Raises:

  • (NotImplementedError)


509
510
511
# File 'lib/ibex/runtime/parser.rb', line 509

def next_token
  raise NotImplementedError, "(input):1:1: next_token must be implemented"
end

#on_discard(_token_id, _value, _location, _reason) ⇒ Object

Called when yacc recovery discards an application token.



570
# File 'lib/ibex/runtime/parser.rb', line 570

def on_discard(_token_id, _value, _location, _reason); end

#on_error(token_id, value, _value_stack) ⇒ Object

Override to recover from syntax errors. The default raises unless a bounded automatic repair has already been selected.

Raises:



516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/ibex/runtime/parser.rb', line 516

def on_error(token_id, value, _value_stack)
  return if @repair_selected
  return if cst_enabled?

  expected = expected_tokens
  token_name = token_to_str(token_id)
  state = @state_stack.last
  configured = parser_tables.fetch(:error_messages, EMPTY_ROW)[state]
  error_id, detail = configured_error_message(configured)
  raise ParseError.new(
    token_id: token_id,
    token_name: token_name,
    token_value: value,
    expected_tokens: expected,
    location: @lookahead_location,
    state: state,
    suggestions: token_suggestions(token_name, expected),
    error_id: error_id,
    detail: detail
  )
end

#on_error_recover(_token_id, _value, _value_stack) ⇒ Object

Called after the synthetic error token enters a recovery state. The payload describes the original error before recovery popped stacks.



561
# File 'lib/ibex/runtime/parser.rb', line 561

def on_error_recover(_token_id, _value, _value_stack); end

#on_error_recover_location(_token_id, _value, _value_stack, _location, _state) ⇒ Object

Location-aware recovery observer.



566
# File 'lib/ibex/runtime/parser.rb', line 566

def on_error_recover_location(_token_id, _value, _value_stack, _location, _state); end

#on_reduce(_production_id, _values, _result) ⇒ Object Also known as: __ibex_fast_path_on_reduce

Called after a production's semantic action and goto are committed. Override to observe its id, RHS values, and semantic result.



551
# File 'lib/ibex/runtime/parser.rb', line 551

def on_reduce(_production_id, _values, _result); end

#on_reduce_location(_production_id, _values, _result, _locations, _result_location) ⇒ Object Also known as: __ibex_fast_path_on_reduce_location

Location-aware reduction observer.



556
# File 'lib/ibex/runtime/parser.rb', line 556

def on_reduce_location(_production_id, _values, _result, _locations, _result_location); end

#on_repair(_plan) ⇒ Object

Called once after a repair is selected and before its edited token prefix is replayed through normal parser actions.



586
# File 'lib/ibex/runtime/parser.rb', line 586

def on_repair(_plan); end

#on_shift(_token_id, _value, _state) ⇒ Object Also known as: __ibex_fast_path_on_shift

Called after an ordinary input token is shifted. Override to observe the internal token id, semantic value, and destination state.



541
# File 'lib/ibex/runtime/parser.rb', line 541

def on_shift(_token_id, _value, _state); end

#on_shift_location(_token_id, _value, _state, _location) ⇒ Object Also known as: __ibex_fast_path_on_shift_location

Location-aware shift observer. The compatible hook above retains its original signature and runs first.



546
# File 'lib/ibex/runtime/parser.rb', line 546

def on_shift_location(_token_id, _value, _state, _location); end

#parse_with_syntaxObject

Parse through next_token and return both the semantic value and Red root.



414
415
416
# File 'lib/ibex/runtime/parser.rb', line 414

def parse_with_syntax
  syntax_parse_result(do_parse)
end

#push(token, value = nil, location = nil) ⇒ Object

Supply one token to a caller-driven parser session. Returns :need_more after consuming it, [:accepted, result] after acceptance, or [:rejected, result] after recovery terminates. rubocop:disable Layout/LineLength rubocop:enable Layout/LineLength

Raises:



440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/ibex/runtime/parser.rb', line 440

def push(token, value = nil, location = nil)
  raise ParseError, "(input):1:1: push requires a token; call finish for EOF" if token.nil? || token == false

  run_push_driver do
    start_push_session
    refresh_runtime_fast_path_after_user_code!
    if @repair_policy
      enqueue_or_assign_repair_input(repair_input(token, value, location))
    else
      @lookahead = internal_token_id(token)
      @lookahead_value = value
      @lookahead_location = location
      @runtime_fast_path = false unless nil.equal?(location)
      materialize_compatible_lookahead
    end
    run_push_lookahead
  end
end

#repair_policyObject



360
361
362
363
# File 'lib/ibex/runtime/parser.rb', line 360

def repair_policy
  ensure_runtime_initialized!
  @repair_policy
end

#repair_policy=(policy) ⇒ Object

Enable bounded automatic repair for the next parser session. Assign nil to restore the compatible yacc-only behavior.



374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/ibex/runtime/parser.rb', line 374

def repair_policy=(policy)
  ensure_runtime_initialized!
  unless policy.nil? || policy.is_a?(RepairPolicy)
    raise ArgumentError, "repair_policy must be an Ibex::Runtime::RepairPolicy or nil"
  end

  @runtime_observation_mutex.synchronize do
    ensure_driver_available_without_lock!
    if @push_status == :active
      raise ParseError, "(repair):1:1: repair_policy cannot change during an active push session"
    end

    @repair_policy = policy
  end
end

#reset_pushObject

Discard a caller-driven session so this parser can accept a new one.



485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/ibex/runtime/parser.rb', line 485

def reset_push
  ensure_runtime_initialized!
  @runtime_observation_mutex.synchronize do
    ensure_driver_available_without_lock!
    @push_status = :idle
    @source = nil
    @state_stack = []
    install_value_stack([])
    @location_stack = nil
    @lookahead = NO_LOOKAHEAD
    @lookahead_value = nil
    @lookahead_location = nil
    @runtime_lookahead_token_display = nil
    @repair_input_buffer = nil
    @repair_selected = false
    @runtime_parser_tables = nil
    @runtime_fast_path = false
  end
  nil
end

#resource_limitsObject



366
367
368
369
# File 'lib/ibex/runtime/parser.rb', line 366

def resource_limits
  ensure_runtime_initialized!
  @resource_limits
end

#resource_limits=(limits) ⇒ Object

Replace the limits used by future sessions.



392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/ibex/runtime/parser.rb', line 392

def resource_limits=(limits)
  ensure_runtime_initialized!
  validate_resource_limits!(limits)

  @runtime_observation_mutex.synchronize do
    ensure_driver_available_without_lock!
    if @push_status == :active
      raise ParseError, "(resource):1:1: resource_limits cannot change during an active push session"
    end

    @resource_limits = limits
  end
end

#result_locObject

Return the synthesized span of the reduction being evaluated.



669
670
671
672
673
674
675
# File 'lib/ibex/runtime/parser.rb', line 669

def result_loc
  unless @semantic_locations
    raise ParseError, "(runtime):1:1: result_loc is only available inside a semantic action"
  end

  @semantic_result_location
end

#syntax_rootObject

Return the Red source-file root built by the most recent CST parse.



420
421
422
423
# File 'lib/ibex/runtime/parser.rb', line 420

def syntax_root
  ensure_runtime_initialized!
  @syntax_root
end

#token_to_str(token_id) ⇒ Object Also known as: __ibex_fast_path_token_to_str

Return a human-readable name for an internal token id.



590
591
592
593
594
# File 'lib/ibex/runtime/parser.rb', line 590

def token_to_str(token_id)
  return @unknown_token_name || token_id.to_s if token_id == @unknown_token_id

  parser_tables.fetch(:token_names).fetch(token_id, token_id.to_s)
end

#trace_value_printer=(printer) ⇒ Object

Install an opt-in value formatter for human-readable yydebug traces.



574
575
576
577
578
579
580
581
# File 'lib/ibex/runtime/parser.rb', line 574

def trace_value_printer=(printer)
  ensure_runtime_initialized!
  unless printer.nil? || printer.respond_to?(:call)
    raise ArgumentError, "trace value printer must respond to call or be nil"
  end

  @trace_value_printer = printer
end

#yyacceptObject

Accept immediately after the current semantic action completes.



613
614
615
616
617
# File 'lib/ibex/runtime/parser.rb', line 613

def yyaccept
  @runtime_fast_path = false
  @accept_requested = true
  nil
end

#yydebugObject



341
342
343
344
# File 'lib/ibex/runtime/parser.rb', line 341

def yydebug
  ensure_runtime_initialized!
  @yydebug
end

#yydebug=(enabled) ⇒ Object



347
348
349
350
351
# File 'lib/ibex/runtime/parser.rb', line 347

def yydebug=(enabled)
  ensure_runtime_initialized!
  @runtime_fast_path = false
  @yydebug = enabled
end

#yydebug_output=(output) ⇒ Object



354
355
356
357
# File 'lib/ibex/runtime/parser.rb', line 354

def yydebug_output=(output)
  ensure_runtime_initialized!
  @yydebug_output = output
end

#yyerrokObject

Leave error recovery immediately.



606
607
608
609
# File 'lib/ibex/runtime/parser.rb', line 606

def yyerrok
  @recovery_shifts = 0
  nil
end

#yyerrorObject

Enter error recovery from a semantic action without calling on_error.



598
599
600
601
602
# File 'lib/ibex/runtime/parser.rb', line 598

def yyerror
  @runtime_fast_path = false
  @semantic_error = true
  nil
end

#yyparse(receiver, method_id) ⇒ Object

Parse tokens yielded by receiver.method_id.



427
428
429
430
431
432
# File 'lib/ibex/runtime/parser.rb', line 427

def yyparse(receiver, method_id)
  stream = Enumerator.new do |tokens|
    receiver.__send__(method_id) { |token| tokens << token }
  end
  drive_parser(-> { stream.next })
end