ibex-runtime 0.2.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/LICENSE.txt +21 -0
- data/README.md +337 -0
- data/lib/ibex/runtime/ast_data.rb +23 -0
- data/lib/ibex/runtime/cst/annotation.rb +16 -0
- data/lib/ibex/runtime/cst/cursor.rb +68 -0
- data/lib/ibex/runtime/cst/diff.rb +80 -0
- data/lib/ibex/runtime/cst/editing.rb +56 -0
- data/lib/ibex/runtime/cst/editor.rb +94 -0
- data/lib/ibex/runtime/cst/green/builder.rb +211 -0
- data/lib/ibex/runtime/cst/green/cache.rb +150 -0
- data/lib/ibex/runtime/cst/green/node.rb +78 -0
- data/lib/ibex/runtime/cst/green/token.rb +91 -0
- data/lib/ibex/runtime/cst/green/trivia.rb +36 -0
- data/lib/ibex/runtime/cst/incremental/blender.rb +233 -0
- data/lib/ibex/runtime/cst/incremental/lexed_syntax.rb +28 -0
- data/lib/ibex/runtime/cst/incremental/parse_memo.rb +95 -0
- data/lib/ibex/runtime/cst/incremental/relexer.rb +134 -0
- data/lib/ibex/runtime/cst/incremental/session.rb +229 -0
- data/lib/ibex/runtime/cst/incremental/token_memo.rb +66 -0
- data/lib/ibex/runtime/cst/kind.rb +133 -0
- data/lib/ibex/runtime/cst/parse_result.rb +41 -0
- data/lib/ibex/runtime/cst/rewriter.rb +75 -0
- data/lib/ibex/runtime/cst/serialize.rb +127 -0
- data/lib/ibex/runtime/cst/serialized_tree.rb +44 -0
- data/lib/ibex/runtime/cst/source_text.rb +112 -0
- data/lib/ibex/runtime/cst/syntax_node.rb +432 -0
- data/lib/ibex/runtime/cst/syntax_token.rb +160 -0
- data/lib/ibex/runtime/cst/text_edit.rb +77 -0
- data/lib/ibex/runtime/cst/typed_node.rb +87 -0
- data/lib/ibex/runtime/cst/validator.rb +428 -0
- data/lib/ibex/runtime/cst.rb +38 -0
- data/lib/ibex/runtime/event.rb +44 -0
- data/lib/ibex/runtime/event_jsonl_tracer.rb +37 -0
- data/lib/ibex/runtime/event_sanitizer.rb +273 -0
- data/lib/ibex/runtime/generated_lexer.rb +514 -0
- data/lib/ibex/runtime/jsonl_tracer.rb +75 -0
- data/lib/ibex/runtime/lexer_input.rb +107 -0
- data/lib/ibex/runtime/location_span.rb +114 -0
- data/lib/ibex/runtime/observation.rb +82 -0
- data/lib/ibex/runtime/parser.rb +3027 -0
- data/lib/ibex/runtime/parser_sync_recovery.rb +108 -0
- data/lib/ibex/runtime/repair.rb +126 -0
- data/lib/ibex/runtime/repair_priority_queue.rb +60 -0
- data/lib/ibex/runtime/repair_search.rb +318 -0
- data/lib/ibex/runtime/resource_limits.rb +64 -0
- data/lib/ibex/runtime/table_format.rb +11 -0
- data/lib/ibex/runtime/version.rb +7 -0
- data/lib/ibex/runtime.rb +27 -0
- data/lib/ibex/tables/compact.rb +186 -0
- data/lib/ibex/tables/compact_actions.rb +135 -0
- data/lib/ibex/tables/compact_productions.rb +167 -0
- data/sig/ibex/runtime/ast_data.rbs +12 -0
- data/sig/ibex/runtime/cst/annotation.rbs +13 -0
- data/sig/ibex/runtime/cst/cursor.rbs +34 -0
- data/sig/ibex/runtime/cst/diff.rbs +29 -0
- data/sig/ibex/runtime/cst/editing.rbs +23 -0
- data/sig/ibex/runtime/cst/editor.rbs +43 -0
- data/sig/ibex/runtime/cst/green/builder.rbs +84 -0
- data/sig/ibex/runtime/cst/green/cache.rbs +60 -0
- data/sig/ibex/runtime/cst/green/node.rbs +49 -0
- data/sig/ibex/runtime/cst/green/token.rbs +59 -0
- data/sig/ibex/runtime/cst/green/trivia.rbs +29 -0
- data/sig/ibex/runtime/cst/incremental/blender.rbs +114 -0
- data/sig/ibex/runtime/cst/incremental/lexed_syntax.rbs +20 -0
- data/sig/ibex/runtime/cst/incremental/parse_memo.rbs +59 -0
- data/sig/ibex/runtime/cst/incremental/relexer.rbs +54 -0
- data/sig/ibex/runtime/cst/incremental/session.rbs +89 -0
- data/sig/ibex/runtime/cst/incremental/token_memo.rbs +31 -0
- data/sig/ibex/runtime/cst/kind.rbs +93 -0
- data/sig/ibex/runtime/cst/parse_result.rbs +35 -0
- data/sig/ibex/runtime/cst/rewriter.rbs +31 -0
- data/sig/ibex/runtime/cst/serialize.rbs +39 -0
- data/sig/ibex/runtime/cst/serialized_tree.rbs +34 -0
- data/sig/ibex/runtime/cst/source_text.rbs +51 -0
- data/sig/ibex/runtime/cst/syntax_node.rbs +195 -0
- data/sig/ibex/runtime/cst/syntax_token.rbs +112 -0
- data/sig/ibex/runtime/cst/text_edit.rbs +33 -0
- data/sig/ibex/runtime/cst/typed_node.rbs +50 -0
- data/sig/ibex/runtime/cst/validator.rbs +108 -0
- data/sig/ibex/runtime/cst.rbs +9 -0
- data/sig/ibex/runtime/event.rbs +26 -0
- data/sig/ibex/runtime/event_jsonl_tracer.rbs +25 -0
- data/sig/ibex/runtime/event_sanitizer.rbs +92 -0
- data/sig/ibex/runtime/generated_lexer.rbs +169 -0
- data/sig/ibex/runtime/jsonl_tracer.rbs +42 -0
- data/sig/ibex/runtime/lexer_input.rbs +44 -0
- data/sig/ibex/runtime/location_span.rbs +64 -0
- data/sig/ibex/runtime/observation.rbs +35 -0
- data/sig/ibex/runtime/parser.rbs +926 -0
- data/sig/ibex/runtime/parser_sync_recovery.rbs +60 -0
- data/sig/ibex/runtime/repair.rbs +92 -0
- data/sig/ibex/runtime/repair_priority_queue.rbs +27 -0
- data/sig/ibex/runtime/repair_search.rbs +114 -0
- data/sig/ibex/runtime/resource_limits.rbs +39 -0
- data/sig/ibex/runtime/table_format.rbs +11 -0
- data/sig/ibex/runtime/version.rbs +7 -0
- data/sig/ibex/runtime.rbs +6 -0
- data/sig/ibex/tables/compact.rbs +70 -0
- data/sig/ibex/tables/compact_actions.rbs +57 -0
- data/sig/ibex/tables/compact_productions.rbs +68 -0
- metadata +144 -0
|
@@ -0,0 +1,3027 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require_relative "location_span" unless defined?(Ibex::Runtime::LocationSpan)
|
|
5
|
+
require_relative "observation" unless defined?(Ibex::Runtime::Observation)
|
|
6
|
+
require_relative "resource_limits" unless defined?(Ibex::Runtime::ResourceLimits)
|
|
7
|
+
require_relative "repair" unless defined?(Ibex::Runtime::RepairPolicy)
|
|
8
|
+
require_relative "repair_search" unless defined?(Ibex::Runtime::RepairSearch)
|
|
9
|
+
require_relative "parser_sync_recovery" unless defined?(Ibex::Runtime::ParserSyncRecovery)
|
|
10
|
+
require_relative "table_format" unless defined?(Ibex::Runtime::PARSER_TABLE_FORMAT_VERSION)
|
|
11
|
+
|
|
12
|
+
module Ibex
|
|
13
|
+
module Runtime
|
|
14
|
+
# Raised by the default parser error handler.
|
|
15
|
+
class ParseError < StandardError
|
|
16
|
+
attr_reader :token_id #: Integer?
|
|
17
|
+
attr_reader :token_name #: String?
|
|
18
|
+
attr_reader :token_value #: untyped
|
|
19
|
+
attr_reader :expected_tokens #: Array[String]
|
|
20
|
+
attr_reader :location #: untyped
|
|
21
|
+
attr_reader :state #: Integer?
|
|
22
|
+
attr_reader :suggestions #: Array[String]
|
|
23
|
+
attr_reader :error_id #: String?
|
|
24
|
+
|
|
25
|
+
# 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
|
|
27
|
+
# rubocop:enable Layout/LineLength
|
|
28
|
+
def initialize(
|
|
29
|
+
message = nil,
|
|
30
|
+
token_id: nil,
|
|
31
|
+
token_name: nil,
|
|
32
|
+
token_value: nil,
|
|
33
|
+
expected_tokens: [],
|
|
34
|
+
location: nil,
|
|
35
|
+
state: nil,
|
|
36
|
+
suggestions: [],
|
|
37
|
+
error_id: nil,
|
|
38
|
+
detail: nil
|
|
39
|
+
)
|
|
40
|
+
@token_id = token_id
|
|
41
|
+
@token_name = token_name
|
|
42
|
+
@token_value = token_value
|
|
43
|
+
@expected_tokens = expected_tokens.dup.freeze
|
|
44
|
+
@location = location
|
|
45
|
+
@state = state
|
|
46
|
+
@suggestions = suggestions.dup.freeze
|
|
47
|
+
@error_id = error_id&.dup&.freeze
|
|
48
|
+
@detail = detail
|
|
49
|
+
super(message || diagnostic_message)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @rbs () -> String
|
|
53
|
+
def location_label
|
|
54
|
+
file = location_value(:file) || "(input)"
|
|
55
|
+
line = location_value(:line) || 1
|
|
56
|
+
column = location_value(:column) || 1
|
|
57
|
+
"#{file}:#{line}:#{column}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
# @rbs () -> String
|
|
63
|
+
def diagnostic_message
|
|
64
|
+
expected = @expected_tokens.empty? ? "" : "; expected #{@expected_tokens.join(', ')}"
|
|
65
|
+
default = "unexpected #{@token_name || @token_id}#{expected} (#{@token_value.inspect})"
|
|
66
|
+
identifier = @error_id ? "[#{@error_id}] " : ""
|
|
67
|
+
message = "#{location_label}: #{identifier}#{@detail || default}"
|
|
68
|
+
source_line = location_value(:source_line)
|
|
69
|
+
column = location_value(:column)
|
|
70
|
+
message += "\n#{source_line}\n#{' ' * [column.to_i - 1, 0].max}^" if source_line
|
|
71
|
+
message += "\ndid you mean #{@suggestions.join(' or ')}?" unless @suggestions.empty?
|
|
72
|
+
message
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @rbs (Symbol key) -> untyped
|
|
76
|
+
def location_value(key)
|
|
77
|
+
return nil unless @location
|
|
78
|
+
return @location.public_send(key) if @location.respond_to?(key)
|
|
79
|
+
return @location[key] || @location[key.to_s] if @location.is_a?(Hash)
|
|
80
|
+
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Raised when a configured parser-session resource budget is exhausted.
|
|
86
|
+
class ResourceLimitError < ParseError
|
|
87
|
+
attr_reader :resource #: Symbol
|
|
88
|
+
attr_reader :limit #: Integer
|
|
89
|
+
attr_reader :observed #: Integer
|
|
90
|
+
|
|
91
|
+
# @rbs (resource: Symbol, limit: Integer, observed: Integer, state: Integer?, location: untyped) -> void
|
|
92
|
+
def initialize(resource:, limit:, observed:, state:, location:)
|
|
93
|
+
@resource = resource
|
|
94
|
+
@limit = limit
|
|
95
|
+
@observed = observed
|
|
96
|
+
super(
|
|
97
|
+
"parser resource limit exceeded: #{resource} is #{observed}, configured maximum is #{limit}",
|
|
98
|
+
state: state, location: location
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @rbs () -> Hash[Symbol, untyped]
|
|
103
|
+
def to_h
|
|
104
|
+
{
|
|
105
|
+
type: :resource_limit, resource: @resource, limit: @limit, observed: @observed,
|
|
106
|
+
state: state, location: location
|
|
107
|
+
}.freeze
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# rubocop:disable Metrics/ClassLength
|
|
112
|
+
|
|
113
|
+
# Drives a table-defined LR parser without native extensions.
|
|
114
|
+
#
|
|
115
|
+
# Subclasses provide `.parser_tables`, returning `:tokens`, `:token_names`,
|
|
116
|
+
# `:actions`, `:gotos`, and `:productions`, with optional
|
|
117
|
+
# `:default_actions`, `:error_messages`, and `:recovery_sync_tokens`.
|
|
118
|
+
# Actions are represented by
|
|
119
|
+
# `[:shift, state]`, `[:reduce, production]`, `[:accept]`, or `[:error]`.
|
|
120
|
+
# Format-v2 and newer generated production entries mark their five-argument
|
|
121
|
+
# semantic methods with `location_action: true`. Format-v3 and newer
|
|
122
|
+
# composed actions additionally use `composition_action: true` for the
|
|
123
|
+
# six-argument contract carrying the lookahead location. Format-v4
|
|
124
|
+
# location-free generated methods use `values_action: true` for a
|
|
125
|
+
# one-argument values contract. Format-v5 additionally marks proven-safe
|
|
126
|
+
# zero-to-four-value methods with `positional_action: true`. V1 and
|
|
127
|
+
# unmarked application actions retain the historical two-argument
|
|
128
|
+
# contract. Markers are honored only for the generated `_ibex_action_N`
|
|
129
|
+
# Symbol shape, never for callables.
|
|
130
|
+
class Parser
|
|
131
|
+
# Keep direct singleton-hook mutation visible even when an application
|
|
132
|
+
# replaces Ruby's mutation callbacks without calling super.
|
|
133
|
+
module FastPathMutationTracker
|
|
134
|
+
# @rbs (Ibex::Runtime::Parser parser, UnboundMethod lookup) -> bool
|
|
135
|
+
def self.effective_for?(parser, lookup)
|
|
136
|
+
added = lookup.bind_call(parser, :singleton_method_added) # @type var added: Method
|
|
137
|
+
removed = lookup.bind_call(parser, :singleton_method_removed) # @type var removed: Method
|
|
138
|
+
undefined = lookup.bind_call(parser, :singleton_method_undefined) # @type var undefined: Method
|
|
139
|
+
added.owner.equal?(self) && removed.owner.equal?(self) && undefined.owner.equal?(self)
|
|
140
|
+
rescue NameError
|
|
141
|
+
false
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
# @rbs (Symbol name) -> void
|
|
147
|
+
def singleton_method_added(name)
|
|
148
|
+
case name
|
|
149
|
+
when :on_shift, :on_shift_location, :on_reduce, :on_reduce_location, :token_to_str
|
|
150
|
+
@runtime_fast_path = false
|
|
151
|
+
@runtime_fast_path_hooks_mutated = true
|
|
152
|
+
end
|
|
153
|
+
super
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @rbs (Symbol name) -> void
|
|
157
|
+
def singleton_method_removed(name)
|
|
158
|
+
case name
|
|
159
|
+
when :on_shift, :on_shift_location, :on_reduce, :on_reduce_location, :token_to_str
|
|
160
|
+
@runtime_fast_path = false
|
|
161
|
+
@runtime_fast_path_hooks_mutated = true
|
|
162
|
+
end
|
|
163
|
+
super
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @rbs (Symbol name) -> void
|
|
167
|
+
def singleton_method_undefined(name)
|
|
168
|
+
case name
|
|
169
|
+
when :on_shift, :on_shift_location, :on_reduce, :on_reduce_location, :token_to_str
|
|
170
|
+
@runtime_fast_path = false
|
|
171
|
+
@runtime_fast_path_hooks_mutated = true
|
|
172
|
+
end
|
|
173
|
+
super
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Invalidates the generated-class hook cache when the parser class
|
|
178
|
+
# changes its own effective hook surface.
|
|
179
|
+
module FastPathClassMutationTracker
|
|
180
|
+
# @rbs (Symbol name) -> void
|
|
181
|
+
def method_added(name)
|
|
182
|
+
__ibex_invalidate_fast_path_hooks(name)
|
|
183
|
+
super
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @rbs (Symbol name) -> void
|
|
187
|
+
def method_removed(name)
|
|
188
|
+
__ibex_invalidate_fast_path_hooks(name)
|
|
189
|
+
super
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# @rbs (Symbol name) -> void
|
|
193
|
+
def method_undefined(name)
|
|
194
|
+
__ibex_invalidate_fast_path_hooks(name)
|
|
195
|
+
super
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# @rbs (*Module modules) -> self
|
|
199
|
+
def include(*modules)
|
|
200
|
+
__ibex_bump_fast_path_hook_version
|
|
201
|
+
super
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# @rbs (*Module modules) -> self
|
|
205
|
+
def prepend(*modules)
|
|
206
|
+
__ibex_bump_fast_path_hook_version
|
|
207
|
+
super
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
private
|
|
211
|
+
|
|
212
|
+
# @rbs (Symbol name) -> void
|
|
213
|
+
def __ibex_invalidate_fast_path_hooks(name)
|
|
214
|
+
__ibex_bump_fast_path_hook_version if FAST_PATH_HOOK_NAMES.include?(name)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# @rbs () -> void
|
|
218
|
+
def __ibex_bump_fast_path_hook_version
|
|
219
|
+
current = instance_variable_get(:@__ibex_fast_path_hook_version) || 0
|
|
220
|
+
instance_variable_set(:@__ibex_fast_path_hook_version, current + 1)
|
|
221
|
+
remove_instance_variable(:@__ibex_fast_path_hook_cache) if
|
|
222
|
+
instance_variable_defined?(:@__ibex_fast_path_hook_cache)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
private :method_added, :method_removed, :method_undefined
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
include Observation
|
|
229
|
+
include ParserSyncRecovery
|
|
230
|
+
|
|
231
|
+
ParseError = Ibex::Runtime::ParseError #: singleton(Ibex::Runtime::ParseError)
|
|
232
|
+
EOF_TOKEN = 0 #: Integer
|
|
233
|
+
ERROR_TOKEN = 1 #: Integer
|
|
234
|
+
GENERATED_ACTION_NAME = /\A_ibex_action_\d+\z/ #: Regexp
|
|
235
|
+
NO_LOOKAHEAD = Object.new.freeze #: Object
|
|
236
|
+
RECOVERY_SHIFTS = 3 #: Integer
|
|
237
|
+
FAST_PATH_HOOK_NAMES = %i[
|
|
238
|
+
on_shift on_shift_location on_reduce on_reduce_location token_to_str
|
|
239
|
+
].freeze #: Array[Symbol]
|
|
240
|
+
FAST_PATH_HOOK_REFERENCES = {
|
|
241
|
+
on_shift: :__ibex_fast_path_on_shift,
|
|
242
|
+
on_shift_location: :__ibex_fast_path_on_shift_location,
|
|
243
|
+
on_reduce: :__ibex_fast_path_on_reduce,
|
|
244
|
+
on_reduce_location: :__ibex_fast_path_on_reduce_location,
|
|
245
|
+
token_to_str: :__ibex_fast_path_token_to_str
|
|
246
|
+
}.freeze #: Hash[Symbol, Symbol]
|
|
247
|
+
ERROR_ACTION = [:error].freeze #: [:error]
|
|
248
|
+
SYNC_RECOVER_ACTION = [:sync_recover].freeze #: [:sync_recover]
|
|
249
|
+
CONTINUE_OUTCOME = [:continue].freeze #: [:continue]
|
|
250
|
+
REPAIR_PENDING_OUTCOME = [:repair_pending].freeze #: [:repair_pending]
|
|
251
|
+
TERMINAL_OUTCOMES = %i[accepted done].freeze #: Array[Symbol]
|
|
252
|
+
COMPACT_ACCEPTED = Object.new.freeze #: Object
|
|
253
|
+
empty_row = {} # @type var empty_row: Hash[Integer, untyped]
|
|
254
|
+
empty_location_names = {} # @type var empty_location_names: Hash[Symbol, Integer]
|
|
255
|
+
empty_locations = [] # @type var empty_locations: Array[untyped]
|
|
256
|
+
empty_green_trivia = [] # @type var empty_green_trivia: Array[CST::GreenTrivia]
|
|
257
|
+
|
|
258
|
+
EMPTY_ROW = empty_row.freeze #: Hash[Integer, untyped]
|
|
259
|
+
EMPTY_LOCATION_NAMES = empty_location_names.freeze #: Hash[Symbol, Integer]
|
|
260
|
+
EMPTY_LOCATIONS = empty_locations.freeze #: Array[untyped]
|
|
261
|
+
EMPTY_GREEN_TRIVIA = empty_green_trivia.freeze #: Array[CST::GreenTrivia]
|
|
262
|
+
private_constant :ERROR_ACTION, :SYNC_RECOVER_ACTION, :CONTINUE_OUTCOME, :REPAIR_PENDING_OUTCOME,
|
|
263
|
+
:TERMINAL_OUTCOMES, :COMPACT_ACCEPTED, :FAST_PATH_HOOK_NAMES, :FAST_PATH_HOOK_REFERENCES,
|
|
264
|
+
:FastPathMutationTracker, :FastPathClassMutationTracker
|
|
265
|
+
|
|
266
|
+
# @rbs @yydebug: bool
|
|
267
|
+
# @rbs @yydebug_output: IO
|
|
268
|
+
# @rbs @source: untyped
|
|
269
|
+
# @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
|
|
277
|
+
# @rbs @recovery_shifts: Integer
|
|
278
|
+
# @rbs @semantic_error: bool
|
|
279
|
+
# @rbs @accept_requested: bool
|
|
280
|
+
# @rbs @unknown_token_id: Integer?
|
|
281
|
+
# @rbs @unknown_token_name: String?
|
|
282
|
+
# @rbs @push_status: :idle | :active | :finished
|
|
283
|
+
# @rbs @driver_status: :idle | :pull | :push
|
|
284
|
+
# @rbs @runtime_driver_thread: Thread?
|
|
285
|
+
# @rbs @runtime_observers: Hash[Observation::Subscription, Proc]?
|
|
286
|
+
# @rbs @runtime_event_sequence: Integer
|
|
287
|
+
# @rbs @runtime_lookahead_token_display: untyped
|
|
288
|
+
# @rbs @runtime_observation_mutex: Mutex
|
|
289
|
+
# @rbs @repair_policy: RepairPolicy?
|
|
290
|
+
# @rbs @repair_input_buffer: Array[RepairInput]?
|
|
291
|
+
# @rbs @repair_selected: bool
|
|
292
|
+
# @rbs @semantic_locations: Array[untyped]?
|
|
293
|
+
# @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]?
|
|
298
|
+
# @rbs @sync_recovery_observers: Array[Proc]?
|
|
299
|
+
# @rbs @green_builder: CST::GreenBuilder?
|
|
300
|
+
# @rbs @green_kinds: CST::Kind?
|
|
301
|
+
# @rbs @green_cache: CST::NodeCache?
|
|
302
|
+
# @rbs @syntax_root: CST::SyntaxNode?
|
|
303
|
+
# @rbs @syntax_diagnostics: Array[untyped]
|
|
304
|
+
# @rbs @green_pending_skipped: Array[CST::GreenTrivia]
|
|
305
|
+
# @rbs @resource_limits: ResourceLimits
|
|
306
|
+
# @rbs @recovery_attempts: Integer
|
|
307
|
+
# @rbs @runtime_parser_tables: Hash[Symbol, untyped]?
|
|
308
|
+
# @rbs @runtime_fast_path: bool
|
|
309
|
+
# @rbs @runtime_fast_path_tracker_installed: bool
|
|
310
|
+
# @rbs @runtime_fast_path_hooks_mutated: bool
|
|
311
|
+
# @rbs @runtime_fast_path_singleton_ancestors: Array[Module]?
|
|
312
|
+
# @rbs @syntax_only: bool
|
|
313
|
+
# @rbs @green_cache_override: CST::NodeCache?
|
|
314
|
+
# @rbs @green_token_states: Array[Symbol]
|
|
315
|
+
# @rbs @green_memo_stack: Array[CST::ParseMemo::Entry]
|
|
316
|
+
# @rbs @green_parse_memo_valid: bool
|
|
317
|
+
# @rbs @green_initial_state: Integer
|
|
318
|
+
# @rbs @syntax_parse_memo: CST::ParseMemo?
|
|
319
|
+
# @rbs @green_reused_right_edge: bool
|
|
320
|
+
# @rbs @incremental_reused_descendants: Integer
|
|
321
|
+
|
|
322
|
+
attr_reader :syntax_parse_memo #: CST::ParseMemo?
|
|
323
|
+
attr_reader :incremental_reused_descendants #: Integer
|
|
324
|
+
|
|
325
|
+
# Start a syntax-only incremental session backed by a generated lexer.
|
|
326
|
+
# @rbs (CST::SourceText source_text, ?resource_limits: ResourceLimits?, ?blender: bool) ->
|
|
327
|
+
# CST::IncrementalParseSession
|
|
328
|
+
def self.incremental_session(source_text, resource_limits: nil, blender: true)
|
|
329
|
+
CST::IncrementalParseSession.new(
|
|
330
|
+
self, source_text, resource_limits: resource_limits, blender: blender
|
|
331
|
+
)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# @rbs (?resource_limits: ResourceLimits) -> void
|
|
335
|
+
def initialize(resource_limits: ResourceLimits.new)
|
|
336
|
+
validate_resource_limits!(resource_limits)
|
|
337
|
+
initialize_runtime_state(resource_limits, preserve_existing: false)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# @rbs () -> bool
|
|
341
|
+
def yydebug
|
|
342
|
+
ensure_runtime_initialized!
|
|
343
|
+
@yydebug
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# @rbs (bool enabled) -> bool
|
|
347
|
+
def yydebug=(enabled)
|
|
348
|
+
ensure_runtime_initialized!
|
|
349
|
+
@runtime_fast_path = false
|
|
350
|
+
@yydebug = enabled
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# @rbs (IO output) -> IO
|
|
354
|
+
def yydebug_output=(output)
|
|
355
|
+
ensure_runtime_initialized!
|
|
356
|
+
@yydebug_output = output
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# @rbs () -> RepairPolicy?
|
|
360
|
+
def repair_policy
|
|
361
|
+
ensure_runtime_initialized!
|
|
362
|
+
@repair_policy
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# @rbs () -> ResourceLimits
|
|
366
|
+
def resource_limits
|
|
367
|
+
ensure_runtime_initialized!
|
|
368
|
+
@resource_limits
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Enable bounded automatic repair for the next parser session.
|
|
372
|
+
# Assign nil to restore the compatible yacc-only behavior.
|
|
373
|
+
# @rbs (RepairPolicy? policy) -> RepairPolicy?
|
|
374
|
+
def repair_policy=(policy)
|
|
375
|
+
ensure_runtime_initialized!
|
|
376
|
+
unless policy.nil? || policy.is_a?(RepairPolicy)
|
|
377
|
+
raise ArgumentError, "repair_policy must be an Ibex::Runtime::RepairPolicy or nil"
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
@runtime_observation_mutex.synchronize do
|
|
381
|
+
ensure_driver_available_without_lock!
|
|
382
|
+
if @push_status == :active
|
|
383
|
+
raise ParseError, "(repair):1:1: repair_policy cannot change during an active push session"
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
@repair_policy = policy
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Replace the limits used by future sessions.
|
|
391
|
+
# @rbs (ResourceLimits limits) -> ResourceLimits
|
|
392
|
+
def resource_limits=(limits)
|
|
393
|
+
ensure_runtime_initialized!
|
|
394
|
+
validate_resource_limits!(limits)
|
|
395
|
+
|
|
396
|
+
@runtime_observation_mutex.synchronize do
|
|
397
|
+
ensure_driver_available_without_lock!
|
|
398
|
+
if @push_status == :active
|
|
399
|
+
raise ParseError, "(resource):1:1: resource_limits cannot change during an active push session"
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
@resource_limits = limits
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Pull tokens from `next_token` and parse them.
|
|
407
|
+
# @rbs () -> untyped
|
|
408
|
+
def do_parse
|
|
409
|
+
drive_parser(nil)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Parse through `next_token` and return both the semantic value and Red root.
|
|
413
|
+
# @rbs () -> CST::ParseResult
|
|
414
|
+
def parse_with_syntax
|
|
415
|
+
syntax_parse_result(do_parse)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# Return the Red source-file root built by the most recent CST parse.
|
|
419
|
+
# @rbs () -> CST::SyntaxNode?
|
|
420
|
+
def syntax_root
|
|
421
|
+
ensure_runtime_initialized!
|
|
422
|
+
@syntax_root
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Parse tokens yielded by `receiver.method_id`.
|
|
426
|
+
# @rbs (untyped receiver, Symbol method_id) -> untyped
|
|
427
|
+
def yyparse(receiver, method_id)
|
|
428
|
+
stream = Enumerator.new do |tokens|
|
|
429
|
+
receiver.__send__(method_id) { |token| tokens << token }
|
|
430
|
+
end
|
|
431
|
+
drive_parser(-> { stream.next })
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Supply one token to a caller-driven parser session.
|
|
435
|
+
# Returns `:need_more` after consuming it, `[:accepted, result]` after
|
|
436
|
+
# 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
|
|
440
|
+
def push(token, value = nil, location = nil)
|
|
441
|
+
raise ParseError, "(input):1:1: push requires a token; call finish for EOF" if token.nil? || token == false
|
|
442
|
+
|
|
443
|
+
run_push_driver do
|
|
444
|
+
start_push_session
|
|
445
|
+
refresh_runtime_fast_path_after_user_code!
|
|
446
|
+
if @repair_policy
|
|
447
|
+
enqueue_or_assign_repair_input(repair_input(token, value, location))
|
|
448
|
+
else
|
|
449
|
+
@lookahead = internal_token_id(token)
|
|
450
|
+
@lookahead_value = value
|
|
451
|
+
@lookahead_location = location
|
|
452
|
+
@runtime_fast_path = false unless nil.equal?(location)
|
|
453
|
+
materialize_compatible_lookahead
|
|
454
|
+
end
|
|
455
|
+
run_push_lookahead
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Supply EOF to a caller-driven parser session and return its result.
|
|
460
|
+
# @rbs (?location: untyped) -> untyped
|
|
461
|
+
def finish(location: nil)
|
|
462
|
+
run_push_driver do
|
|
463
|
+
start_push_session
|
|
464
|
+
refresh_runtime_fast_path_after_user_code!
|
|
465
|
+
if @repair_policy
|
|
466
|
+
enqueue_or_assign_repair_input(
|
|
467
|
+
RepairInput.new(token_id: EOF_TOKEN, token_name: token_to_str(EOF_TOKEN), value: nil, location: location)
|
|
468
|
+
)
|
|
469
|
+
else
|
|
470
|
+
@lookahead = EOF_TOKEN
|
|
471
|
+
@lookahead_value = nil
|
|
472
|
+
@lookahead_location = location
|
|
473
|
+
@runtime_fast_path = false unless nil.equal?(location)
|
|
474
|
+
materialize_compatible_lookahead
|
|
475
|
+
end
|
|
476
|
+
outcome = run_push_lookahead
|
|
477
|
+
return outcome.fetch(1) if outcome.is_a?(Array)
|
|
478
|
+
|
|
479
|
+
raise ParseError, "(input):1:1: parser requested input after EOF"
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Discard a caller-driven session so this parser can accept a new one.
|
|
484
|
+
# @rbs () -> nil
|
|
485
|
+
def reset_push
|
|
486
|
+
ensure_runtime_initialized!
|
|
487
|
+
@runtime_observation_mutex.synchronize do
|
|
488
|
+
ensure_driver_available_without_lock!
|
|
489
|
+
@push_status = :idle
|
|
490
|
+
@source = nil
|
|
491
|
+
@state_stack = []
|
|
492
|
+
install_value_stack([])
|
|
493
|
+
@location_stack = nil
|
|
494
|
+
@lookahead = NO_LOOKAHEAD
|
|
495
|
+
@lookahead_value = nil
|
|
496
|
+
@lookahead_location = nil
|
|
497
|
+
@runtime_lookahead_token_display = nil
|
|
498
|
+
@repair_input_buffer = nil
|
|
499
|
+
@repair_selected = false
|
|
500
|
+
@runtime_parser_tables = nil
|
|
501
|
+
@runtime_fast_path = false
|
|
502
|
+
end
|
|
503
|
+
nil
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# Override in pull parsers. Return `[token, value]`,
|
|
507
|
+
# `[token, value, location]`, `false`, or `nil`.
|
|
508
|
+
# @rbs () -> ([untyped, untyped] | [untyped, untyped, untyped] | false | nil)
|
|
509
|
+
def next_token
|
|
510
|
+
raise NotImplementedError, "(input):1:1: next_token must be implemented"
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# Override to recover from syntax errors. The default raises unless a
|
|
514
|
+
# bounded automatic repair has already been selected.
|
|
515
|
+
# @rbs (Integer token_id, untyped value, Array[untyped] value_stack) -> untyped
|
|
516
|
+
def on_error(token_id, value, _value_stack)
|
|
517
|
+
return if @repair_selected
|
|
518
|
+
return if cst_enabled?
|
|
519
|
+
|
|
520
|
+
expected = expected_tokens
|
|
521
|
+
token_name = token_to_str(token_id)
|
|
522
|
+
state = @state_stack.last
|
|
523
|
+
configured = parser_tables.fetch(:error_messages, EMPTY_ROW)[state]
|
|
524
|
+
error_id, detail = configured_error_message(configured)
|
|
525
|
+
raise ParseError.new(
|
|
526
|
+
token_id: token_id,
|
|
527
|
+
token_name: token_name,
|
|
528
|
+
token_value: value,
|
|
529
|
+
expected_tokens: expected,
|
|
530
|
+
location: @lookahead_location,
|
|
531
|
+
state: state,
|
|
532
|
+
suggestions: token_suggestions(token_name, expected),
|
|
533
|
+
error_id: error_id,
|
|
534
|
+
detail: detail
|
|
535
|
+
)
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# Called after an ordinary input token is shifted. Override to observe
|
|
539
|
+
# the internal token id, semantic value, and destination state.
|
|
540
|
+
# @rbs (Integer token_id, untyped value, Integer state) -> void
|
|
541
|
+
def on_shift(_token_id, _value, _state); end
|
|
542
|
+
|
|
543
|
+
# Location-aware shift observer. The compatible hook above retains its
|
|
544
|
+
# original signature and runs first.
|
|
545
|
+
# @rbs (Integer token_id, untyped value, Integer state, untyped location) -> void
|
|
546
|
+
def on_shift_location(_token_id, _value, _state, _location); end
|
|
547
|
+
|
|
548
|
+
# Called after a production's semantic action and goto are committed.
|
|
549
|
+
# Override to observe its id, RHS values, and semantic result.
|
|
550
|
+
# @rbs (Integer production_id, Array[untyped] values, untyped result) -> void
|
|
551
|
+
def on_reduce(_production_id, _values, _result); end
|
|
552
|
+
|
|
553
|
+
# Location-aware reduction observer.
|
|
554
|
+
# @rbs (Integer production_id, Array[untyped] values, untyped result,
|
|
555
|
+
# Array[untyped] locations, untyped result_location) -> void
|
|
556
|
+
def on_reduce_location(_production_id, _values, _result, _locations, _result_location); end
|
|
557
|
+
|
|
558
|
+
# Called after the synthetic error token enters a recovery state.
|
|
559
|
+
# The payload describes the original error before recovery popped stacks.
|
|
560
|
+
# @rbs (Integer token_id, untyped value, Array[untyped] value_stack) -> void
|
|
561
|
+
def on_error_recover(_token_id, _value, _value_stack); end
|
|
562
|
+
|
|
563
|
+
# Location-aware recovery observer.
|
|
564
|
+
# @rbs (Integer token_id, untyped value, Array[untyped] value_stack,
|
|
565
|
+
# untyped location, Integer state) -> void
|
|
566
|
+
def on_error_recover_location(_token_id, _value, _value_stack, _location, _state); end
|
|
567
|
+
|
|
568
|
+
# Called when yacc recovery discards an application token.
|
|
569
|
+
# @rbs (Integer token_id, untyped value, untyped location, Symbol reason) -> void
|
|
570
|
+
def on_discard(_token_id, _value, _location, _reason); end
|
|
571
|
+
|
|
572
|
+
# Install an opt-in value formatter for human-readable yydebug traces.
|
|
573
|
+
# @rbs ((^(untyped) -> untyped)? printer) -> (^(untyped) -> untyped)?
|
|
574
|
+
def trace_value_printer=(printer)
|
|
575
|
+
ensure_runtime_initialized!
|
|
576
|
+
unless printer.nil? || printer.respond_to?(:call)
|
|
577
|
+
raise ArgumentError, "trace value printer must respond to call or be nil"
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
@trace_value_printer = printer
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
# Called once after a repair is selected and before its edited token
|
|
584
|
+
# prefix is replayed through normal parser actions.
|
|
585
|
+
# @rbs (RepairPlan plan) -> void
|
|
586
|
+
def on_repair(_plan); end
|
|
587
|
+
|
|
588
|
+
# Return a human-readable name for an internal token id.
|
|
589
|
+
# @rbs (Integer token_id) -> String
|
|
590
|
+
def token_to_str(token_id)
|
|
591
|
+
return @unknown_token_name || token_id.to_s if token_id == @unknown_token_id
|
|
592
|
+
|
|
593
|
+
parser_tables.fetch(:token_names).fetch(token_id, token_id.to_s)
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
# Enter error recovery from a semantic action without calling `on_error`.
|
|
597
|
+
# @rbs () -> nil
|
|
598
|
+
def yyerror
|
|
599
|
+
@runtime_fast_path = false
|
|
600
|
+
@semantic_error = true
|
|
601
|
+
nil
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
# Leave error recovery immediately.
|
|
605
|
+
# @rbs () -> nil
|
|
606
|
+
def yyerrok
|
|
607
|
+
@recovery_shifts = 0
|
|
608
|
+
nil
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
# Accept immediately after the current semantic action completes.
|
|
612
|
+
# @rbs () -> nil
|
|
613
|
+
def yyaccept
|
|
614
|
+
@runtime_fast_path = false
|
|
615
|
+
@accept_requested = true
|
|
616
|
+
nil
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
# Return token names accepted in the current parser state.
|
|
620
|
+
# @rbs () -> Array[String]
|
|
621
|
+
def expected_tokens
|
|
622
|
+
ensure_runtime_initialized!
|
|
623
|
+
return expected_tokens_exact if parser_tables[:exact_expected_tokens]
|
|
624
|
+
return [] if @state_stack.empty?
|
|
625
|
+
|
|
626
|
+
state = @state_stack.last
|
|
627
|
+
parser_tables.fetch(:token_names).keys.filter_map do |token_id|
|
|
628
|
+
action = table_lookup(parser_tables.fetch(:actions), state, token_id) || default_action(state) || ERROR_ACTION
|
|
629
|
+
token_to_str(token_id) unless error_action?(action) || token_id == ERROR_TOKEN
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
# Return token names that survive all required default reductions.
|
|
634
|
+
# Semantic actions are not evaluated during this lookahead correction.
|
|
635
|
+
# @rbs () -> Array[String]
|
|
636
|
+
def expected_tokens_exact
|
|
637
|
+
ensure_runtime_initialized!
|
|
638
|
+
return [] if @state_stack.empty?
|
|
639
|
+
|
|
640
|
+
parser_tables.fetch(:token_names).keys.filter_map do |token_id|
|
|
641
|
+
next if token_id == ERROR_TOKEN
|
|
642
|
+
|
|
643
|
+
token_to_str(token_id) if exact_lookahead_accepted?(token_id)
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# Return the location of a one-based RHS position or named reference
|
|
648
|
+
# while a semantic action is running.
|
|
649
|
+
# @rbs (Integer | Symbol | String reference) -> untyped
|
|
650
|
+
def loc(reference)
|
|
651
|
+
locations = @semantic_locations
|
|
652
|
+
raise ParseError, "(runtime):1:1: loc is only available inside a semantic action" unless locations
|
|
653
|
+
|
|
654
|
+
index = if reference.is_a?(Integer)
|
|
655
|
+
raise ArgumentError, "location index must be positive" unless reference.positive?
|
|
656
|
+
|
|
657
|
+
reference - 1
|
|
658
|
+
else
|
|
659
|
+
names = @semantic_location_names || EMPTY_LOCATION_NAMES
|
|
660
|
+
names.fetch(reference.to_sym) do
|
|
661
|
+
raise ArgumentError, "unknown named location #{reference.inspect}"
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
locations.fetch(index) { raise ArgumentError, "location index #{reference.inspect} is outside the RHS" }
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# Return the synthesized span of the reduction being evaluated.
|
|
668
|
+
# @rbs () -> untyped
|
|
669
|
+
def result_loc
|
|
670
|
+
unless @semantic_locations
|
|
671
|
+
raise ParseError, "(runtime):1:1: result_loc is only available inside a semantic action"
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
@semantic_result_location
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
private
|
|
678
|
+
|
|
679
|
+
alias __ibex_fast_path_on_shift on_shift
|
|
680
|
+
alias __ibex_fast_path_on_shift_location on_shift_location
|
|
681
|
+
alias __ibex_fast_path_on_reduce on_reduce
|
|
682
|
+
alias __ibex_fast_path_on_reduce_location on_reduce_location
|
|
683
|
+
alias __ibex_fast_path_token_to_str token_to_str
|
|
684
|
+
|
|
685
|
+
# Racc-generated parsers commonly define an application initializer
|
|
686
|
+
# without calling super. Complete only missing runtime state so those
|
|
687
|
+
# initializers retain values they deliberately configured.
|
|
688
|
+
# @rbs () -> void
|
|
689
|
+
def ensure_runtime_initialized!
|
|
690
|
+
return if defined?(@runtime_observation_mutex) && @runtime_observation_mutex
|
|
691
|
+
|
|
692
|
+
limits = if defined?(@resource_limits) && @resource_limits.is_a?(ResourceLimits)
|
|
693
|
+
@resource_limits
|
|
694
|
+
else
|
|
695
|
+
ResourceLimits.new
|
|
696
|
+
end
|
|
697
|
+
initialize_runtime_state(limits, preserve_existing: true)
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
701
|
+
# The explicit branches keep every runtime ivar typed while preserving application-owned values.
|
|
702
|
+
# @rbs (ResourceLimits resource_limits, preserve_existing: bool) -> void
|
|
703
|
+
def initialize_runtime_state(resource_limits, preserve_existing:)
|
|
704
|
+
@resource_limits = resource_limits unless preserve_existing && defined?(@resource_limits)
|
|
705
|
+
@yydebug = false unless preserve_existing && defined?(@yydebug)
|
|
706
|
+
@yydebug_output = $stderr unless preserve_existing && defined?(@yydebug_output)
|
|
707
|
+
@source = nil unless preserve_existing && defined?(@source)
|
|
708
|
+
@state_stack = [] unless preserve_existing && defined?(@state_stack)
|
|
709
|
+
install_value_stack([]) unless preserve_existing && defined?(@value_stack)
|
|
710
|
+
@location_stack = nil unless preserve_existing && defined?(@location_stack)
|
|
711
|
+
@lookahead = NO_LOOKAHEAD unless preserve_existing && defined?(@lookahead)
|
|
712
|
+
@lookahead_value = nil unless preserve_existing && defined?(@lookahead_value)
|
|
713
|
+
@lookahead_location = nil unless preserve_existing && defined?(@lookahead_location)
|
|
714
|
+
@recovery_shifts = 0 unless preserve_existing && defined?(@recovery_shifts)
|
|
715
|
+
@semantic_error = false unless preserve_existing && defined?(@semantic_error)
|
|
716
|
+
@accept_requested = false unless preserve_existing && defined?(@accept_requested)
|
|
717
|
+
@unknown_token_id = nil unless preserve_existing && defined?(@unknown_token_id)
|
|
718
|
+
@push_status = :idle unless preserve_existing && defined?(@push_status)
|
|
719
|
+
@driver_status = :idle unless preserve_existing && defined?(@driver_status)
|
|
720
|
+
@runtime_driver_thread = nil unless preserve_existing && defined?(@runtime_driver_thread)
|
|
721
|
+
@runtime_observers = nil unless preserve_existing && defined?(@runtime_observers)
|
|
722
|
+
@runtime_event_sequence = 0 unless preserve_existing && defined?(@runtime_event_sequence)
|
|
723
|
+
@runtime_lookahead_token_display = nil unless preserve_existing && defined?(@runtime_lookahead_token_display)
|
|
724
|
+
@runtime_observation_mutex = Mutex.new unless preserve_existing && defined?(@runtime_observation_mutex)
|
|
725
|
+
@repair_policy = nil unless preserve_existing && defined?(@repair_policy)
|
|
726
|
+
@repair_input_buffer = nil unless preserve_existing && defined?(@repair_input_buffer)
|
|
727
|
+
@repair_selected = false unless preserve_existing && defined?(@repair_selected)
|
|
728
|
+
@semantic_locations = nil unless preserve_existing && defined?(@semantic_locations)
|
|
729
|
+
@semantic_location_names = nil unless preserve_existing && defined?(@semantic_location_names)
|
|
730
|
+
@semantic_result_location = nil unless preserve_existing && defined?(@semantic_result_location)
|
|
731
|
+
@trace_value_printer = nil unless preserve_existing && defined?(@trace_value_printer)
|
|
732
|
+
@sync_recovery_context = nil unless preserve_existing && defined?(@sync_recovery_context)
|
|
733
|
+
@sync_recovery_token_data = nil unless preserve_existing && defined?(@sync_recovery_token_data)
|
|
734
|
+
@sync_recovery_observers = nil unless preserve_existing && defined?(@sync_recovery_observers)
|
|
735
|
+
@green_builder = nil unless preserve_existing && defined?(@green_builder)
|
|
736
|
+
@green_kinds = nil unless preserve_existing && defined?(@green_kinds)
|
|
737
|
+
@green_cache = nil unless preserve_existing && defined?(@green_cache)
|
|
738
|
+
@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)
|
|
741
|
+
@recovery_attempts = 0 unless preserve_existing && defined?(@recovery_attempts)
|
|
742
|
+
@runtime_parser_tables = nil unless preserve_existing && defined?(@runtime_parser_tables)
|
|
743
|
+
@runtime_fast_path = false unless preserve_existing && defined?(@runtime_fast_path)
|
|
744
|
+
@runtime_fast_path_tracker_installed = false unless
|
|
745
|
+
preserve_existing && defined?(@runtime_fast_path_tracker_installed)
|
|
746
|
+
@runtime_fast_path_hooks_mutated = false unless
|
|
747
|
+
preserve_existing && defined?(@runtime_fast_path_hooks_mutated)
|
|
748
|
+
@runtime_fast_path_singleton_ancestors = nil unless
|
|
749
|
+
preserve_existing && defined?(@runtime_fast_path_singleton_ancestors)
|
|
750
|
+
@syntax_only = false unless preserve_existing && defined?(@syntax_only)
|
|
751
|
+
@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)
|
|
754
|
+
@green_parse_memo_valid = false unless preserve_existing && defined?(@green_parse_memo_valid)
|
|
755
|
+
@green_initial_state = 0 unless preserve_existing && defined?(@green_initial_state)
|
|
756
|
+
@syntax_parse_memo = nil unless preserve_existing && defined?(@syntax_parse_memo)
|
|
757
|
+
@green_reused_right_edge = false unless preserve_existing && defined?(@green_reused_right_edge)
|
|
758
|
+
@incremental_reused_descendants = 0 unless
|
|
759
|
+
preserve_existing && defined?(@incremental_reused_descendants)
|
|
760
|
+
end
|
|
761
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
762
|
+
|
|
763
|
+
# @rbs (ResourceLimits resource_limits) -> void
|
|
764
|
+
def validate_resource_limits!(resource_limits)
|
|
765
|
+
return if resource_limits.is_a?(ResourceLimits)
|
|
766
|
+
|
|
767
|
+
raise ArgumentError, "resource_limits must be an Ibex::Runtime::ResourceLimits"
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
# Keep the two historical value-stack names as read-compatible aliases.
|
|
771
|
+
# Applications must not mutate or replace these internal arrays.
|
|
772
|
+
# @rbs (Array[untyped] values) -> void
|
|
773
|
+
def install_value_stack(values)
|
|
774
|
+
@value_stack = values
|
|
775
|
+
@vstack = values
|
|
776
|
+
@racc_vstack = values
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
# @rbs (Integer token_id) -> bool
|
|
780
|
+
def exact_lookahead_accepted?(token_id)
|
|
781
|
+
stack = @state_stack.dup
|
|
782
|
+
seen = {} #: Hash[Array[Integer], bool]
|
|
783
|
+
remaining = exact_lookahead_step_budget(stack.length)
|
|
784
|
+
loop do
|
|
785
|
+
return false unless remaining.positive?
|
|
786
|
+
return false if seen.key?(stack)
|
|
787
|
+
|
|
788
|
+
remaining -= 1
|
|
789
|
+
seen[stack.dup.freeze] = true
|
|
790
|
+
state = stack.last
|
|
791
|
+
action = table_lookup(parser_tables.fetch(:actions), state, token_id) || default_action(state) || ERROR_ACTION
|
|
792
|
+
case action.first
|
|
793
|
+
when :shift, :accept then return true
|
|
794
|
+
when :error then return false
|
|
795
|
+
end
|
|
796
|
+
return false unless action.first == :reduce
|
|
797
|
+
return false unless exact_reduction_applied?(stack, action.fetch(1))
|
|
798
|
+
end
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
# @rbs (Integer stack_depth) -> Integer
|
|
802
|
+
def exact_lookahead_step_budget(stack_depth)
|
|
803
|
+
tables = parser_tables
|
|
804
|
+
production_count = tables.fetch(:productions).length
|
|
805
|
+
unless production_count.is_a?(Integer)
|
|
806
|
+
raise ParseError, "(tables):1:1: parser table production count must be an Integer"
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
(stack_depth + parser_state_count(tables) + 1) * (production_count + 1)
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
# @rbs (Hash[Symbol, untyped] tables) -> Integer
|
|
813
|
+
def parser_state_count(tables)
|
|
814
|
+
state_count = tables[:state_count]
|
|
815
|
+
return state_count if state_count.is_a?(Integer)
|
|
816
|
+
|
|
817
|
+
actions = tables.fetch(:actions)
|
|
818
|
+
state_count = actions.respond_to?(:row_count) ? actions.row_count : actions.length
|
|
819
|
+
return state_count if state_count.is_a?(Integer)
|
|
820
|
+
|
|
821
|
+
raise ParseError, "(tables):1:1: parser table state count must be an Integer"
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
# @rbs (Array[Integer] stack, Integer production_id) -> bool
|
|
825
|
+
def exact_reduction_applied?(stack, production_id)
|
|
826
|
+
production = parser_tables.fetch(:productions).fetch(production_id)
|
|
827
|
+
length = production.fetch(:length)
|
|
828
|
+
return false if length >= stack.length
|
|
829
|
+
|
|
830
|
+
if length.is_a?(Integer)
|
|
831
|
+
if length.negative?
|
|
832
|
+
stack.pop(length)
|
|
833
|
+
return false
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
remaining = length
|
|
837
|
+
while remaining.positive?
|
|
838
|
+
stack.pop
|
|
839
|
+
remaining -= 1
|
|
840
|
+
end
|
|
841
|
+
else
|
|
842
|
+
stack.pop(length)
|
|
843
|
+
end
|
|
844
|
+
target = table_lookup(parser_tables.fetch(:gotos), stack.last, production.fetch(:lhs))
|
|
845
|
+
return false unless target
|
|
846
|
+
|
|
847
|
+
stack << target
|
|
848
|
+
true
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
# @rbs (untyped source, ?initial_state: Integer?) -> untyped
|
|
852
|
+
def drive_parser(source, initial_state: nil)
|
|
853
|
+
ensure_runtime_initialized!
|
|
854
|
+
@runtime_observation_mutex.synchronize do
|
|
855
|
+
ensure_driver_available_without_lock!
|
|
856
|
+
if @push_status == :active
|
|
857
|
+
raise ParseError, "(input):1:1: cannot start another parser driver during an active push session"
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
@driver_status = :pull
|
|
861
|
+
@runtime_driver_thread = Thread.current
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
begin
|
|
865
|
+
prepare_parse(source, initial_state: initial_state)
|
|
866
|
+
tables = @runtime_parser_tables
|
|
867
|
+
if tables && compact_fast_driver_eligible?(tables)
|
|
868
|
+
fast_outcome = drive_compact_fast_parser(tables)
|
|
869
|
+
return @value_stack.last if fast_outcome.equal?(COMPACT_ACCEPTED)
|
|
870
|
+
return fast_outcome.fetch(1) if fast_outcome.is_a?(Array) &&
|
|
871
|
+
TERMINAL_OUTCOMES.include?(fast_outcome[0])
|
|
872
|
+
end
|
|
873
|
+
loop do
|
|
874
|
+
outcome = perform(action_for_current_state)
|
|
875
|
+
case outcome[0]
|
|
876
|
+
when :accepted, :done then return outcome[1]
|
|
877
|
+
end
|
|
878
|
+
end
|
|
879
|
+
ensure
|
|
880
|
+
@source = nil
|
|
881
|
+
@runtime_parser_tables = nil
|
|
882
|
+
@runtime_fast_path = false
|
|
883
|
+
release_driver
|
|
884
|
+
end
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
# Generated compact tables can keep the unobserved, location-free pull
|
|
888
|
+
# loop on their frozen displacement arrays. Any extension boundary or
|
|
889
|
+
# exceptional table shape returns control to the generic driver without
|
|
890
|
+
# replaying a committed action.
|
|
891
|
+
# rubocop:disable Metrics/AbcSize, Metrics/BlockNesting, Metrics/CyclomaticComplexity
|
|
892
|
+
# rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
893
|
+
# Direct comparisons avoid predicate-method dispatch inside the parser loop.
|
|
894
|
+
# rubocop:disable Style/BitwisePredicate, Style/NumericPredicate
|
|
895
|
+
# @rbs (Hash[Symbol, untyped] tables) -> (Object | [:accepted, untyped] | [:done, untyped])?
|
|
896
|
+
def drive_compact_fast_parser(tables)
|
|
897
|
+
actions = tables.fetch(:actions)
|
|
898
|
+
gotos = tables.fetch(:gotos)
|
|
899
|
+
productions = tables.fetch(:productions)
|
|
900
|
+
default_codes = tables.fetch(:compact_default_actions)
|
|
901
|
+
token_ids = tables.fetch(:tokens)
|
|
902
|
+
dense_action_codes = actions.dense_codes
|
|
903
|
+
action_column_count = actions.column_count
|
|
904
|
+
dense_goto_values = gotos.dense_values
|
|
905
|
+
goto_dense_width = gotos.dense_width
|
|
906
|
+
return unless dense_action_codes && action_column_count &&
|
|
907
|
+
dense_goto_values && goto_dense_width &&
|
|
908
|
+
(tables[:compact_action_encoding] == :signed || default_codes.empty?)
|
|
909
|
+
|
|
910
|
+
production_lhs_ids = productions.lhs_ids
|
|
911
|
+
production_lengths = productions.lengths
|
|
912
|
+
production_actions = productions.actions
|
|
913
|
+
production_flags = productions.flags
|
|
914
|
+
borrowed_values_flag = Ibex::Tables::CompactProductions::BORROWED_VALUES_ACTION
|
|
915
|
+
positional_action_flag = Ibex::Tables::CompactProductions::POSITIONAL_ACTION
|
|
916
|
+
states = @state_stack
|
|
917
|
+
values = @value_stack
|
|
918
|
+
stack_limit = @resource_limits.max_stack_depth
|
|
919
|
+
accept_code = Ibex::Tables::CompactActions::ACCEPT_CODE
|
|
920
|
+
error_code = Ibex::Tables::CompactActions::ERROR_CODE
|
|
921
|
+
shift_base = Ibex::Tables::CompactActions::SHIFT_BASE
|
|
922
|
+
reduce_base = Ibex::Tables::CompactActions::REDUCE_BASE
|
|
923
|
+
|
|
924
|
+
while @runtime_fast_path
|
|
925
|
+
if @lookahead.equal?(NO_LOOKAHEAD)
|
|
926
|
+
source = @source
|
|
927
|
+
|
|
928
|
+
token = begin
|
|
929
|
+
source ? source.call : next_token
|
|
930
|
+
rescue StopIteration
|
|
931
|
+
false
|
|
932
|
+
end
|
|
933
|
+
@runtime_fast_path = false if
|
|
934
|
+
@yydebug || @runtime_observers || @repair_policy || @location_stack ||
|
|
935
|
+
@semantic_error || @accept_requested
|
|
936
|
+
if token.nil? || token == false
|
|
937
|
+
@lookahead = EOF_TOKEN
|
|
938
|
+
@lookahead_value = nil
|
|
939
|
+
@lookahead_location = nil
|
|
940
|
+
else
|
|
941
|
+
external_token, @lookahead_value, @lookahead_location = token
|
|
942
|
+
if external_token.nil? || external_token == false
|
|
943
|
+
@lookahead = EOF_TOKEN
|
|
944
|
+
else
|
|
945
|
+
token_id = token_ids[external_token]
|
|
946
|
+
if token_id
|
|
947
|
+
@lookahead = token_id
|
|
948
|
+
else
|
|
949
|
+
@unknown_token_name = external_token.inspect
|
|
950
|
+
@unknown_token_id = -external_token.object_id.abs
|
|
951
|
+
@lookahead = @unknown_token_id
|
|
952
|
+
end
|
|
953
|
+
end
|
|
954
|
+
end
|
|
955
|
+
@runtime_fast_path = false unless nil.equal?(@lookahead_location)
|
|
956
|
+
unless @runtime_fast_path
|
|
957
|
+
materialize_compatible_lookahead
|
|
958
|
+
return
|
|
959
|
+
end
|
|
960
|
+
end
|
|
961
|
+
return if @unknown_token_id == @lookahead
|
|
962
|
+
|
|
963
|
+
state = states[-1]
|
|
964
|
+
code = dense_action_codes[(state * action_column_count) + @lookahead] ||
|
|
965
|
+
default_codes[state]
|
|
966
|
+
return unless code
|
|
967
|
+
|
|
968
|
+
if code == accept_code
|
|
969
|
+
return COMPACT_ACCEPTED
|
|
970
|
+
elsif code == error_code
|
|
971
|
+
return
|
|
972
|
+
elsif code > 0
|
|
973
|
+
ensure_stack_capacity! if states.length >= stack_limit
|
|
974
|
+
states << (code - shift_base)
|
|
975
|
+
values << @lookahead_value
|
|
976
|
+
@lookahead = NO_LOOKAHEAD
|
|
977
|
+
@lookahead_location = nil
|
|
978
|
+
@runtime_lookahead_token_display = nil
|
|
979
|
+
else
|
|
980
|
+
production_id = reduce_base - code
|
|
981
|
+
lhs = production_lhs_ids[production_id]
|
|
982
|
+
length = production_lengths[production_id]
|
|
983
|
+
return unless length <= values.length && length < states.length
|
|
984
|
+
|
|
985
|
+
semantic_action = production_actions[production_id]
|
|
986
|
+
if !semantic_action && length == 1
|
|
987
|
+
goto_row = states[-2]
|
|
988
|
+
next_state = dense_goto_values[(goto_row * goto_dense_width) + lhs]
|
|
989
|
+
raise ParseError, "(tables):1:1: missing goto for production #{production_id}" if next_state.nil?
|
|
990
|
+
|
|
991
|
+
states[-1] = next_state
|
|
992
|
+
next
|
|
993
|
+
end
|
|
994
|
+
hook_values = EMPTY_LOCATIONS
|
|
995
|
+
if semantic_action
|
|
996
|
+
flags = production_flags[production_id]
|
|
997
|
+
positional_action = (flags & positional_action_flag) != 0
|
|
998
|
+
if positional_action
|
|
999
|
+
case length
|
|
1000
|
+
when 1
|
|
1001
|
+
value0 = values[-1]
|
|
1002
|
+
when 2
|
|
1003
|
+
value0 = values[-2]
|
|
1004
|
+
value1 = values[-1]
|
|
1005
|
+
when 3
|
|
1006
|
+
value0 = values[-3]
|
|
1007
|
+
value1 = values[-2]
|
|
1008
|
+
value2 = values[-1]
|
|
1009
|
+
when 4
|
|
1010
|
+
value0 = values[-4]
|
|
1011
|
+
value1 = values[-3]
|
|
1012
|
+
value2 = values[-2]
|
|
1013
|
+
value3 = values[-1]
|
|
1014
|
+
end
|
|
1015
|
+
else
|
|
1016
|
+
reduction_values = values.last(length)
|
|
1017
|
+
hook_values = if (flags & borrowed_values_flag) == 0
|
|
1018
|
+
reduction_values.dup
|
|
1019
|
+
else
|
|
1020
|
+
reduction_values
|
|
1021
|
+
end
|
|
1022
|
+
end
|
|
1023
|
+
else
|
|
1024
|
+
positional_action = false
|
|
1025
|
+
result = length.zero? ? nil : values[-length]
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
case length
|
|
1029
|
+
when 0
|
|
1030
|
+
nil
|
|
1031
|
+
when 1
|
|
1032
|
+
states.pop
|
|
1033
|
+
values.pop
|
|
1034
|
+
when 2
|
|
1035
|
+
states.pop
|
|
1036
|
+
states.pop
|
|
1037
|
+
values.pop
|
|
1038
|
+
values.pop
|
|
1039
|
+
when 3
|
|
1040
|
+
states.pop
|
|
1041
|
+
states.pop
|
|
1042
|
+
states.pop
|
|
1043
|
+
values.pop
|
|
1044
|
+
values.pop
|
|
1045
|
+
values.pop
|
|
1046
|
+
when 4
|
|
1047
|
+
states.pop
|
|
1048
|
+
states.pop
|
|
1049
|
+
states.pop
|
|
1050
|
+
states.pop
|
|
1051
|
+
values.pop
|
|
1052
|
+
values.pop
|
|
1053
|
+
values.pop
|
|
1054
|
+
values.pop
|
|
1055
|
+
else
|
|
1056
|
+
remaining = length
|
|
1057
|
+
while remaining > 0
|
|
1058
|
+
states.pop
|
|
1059
|
+
values.pop
|
|
1060
|
+
remaining -= 1
|
|
1061
|
+
end
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
if semantic_action
|
|
1065
|
+
previous_locations = @semantic_locations
|
|
1066
|
+
previous_names = @semantic_location_names
|
|
1067
|
+
previous_result_location = @semantic_result_location
|
|
1068
|
+
@semantic_locations = EMPTY_LOCATIONS
|
|
1069
|
+
@semantic_location_names = EMPTY_LOCATION_NAMES
|
|
1070
|
+
@semantic_result_location = nil
|
|
1071
|
+
begin
|
|
1072
|
+
result = if positional_action
|
|
1073
|
+
case length
|
|
1074
|
+
when 0 then __send__(semantic_action)
|
|
1075
|
+
when 1 then __send__(semantic_action, value0)
|
|
1076
|
+
when 2 then __send__(semantic_action, value0, value1)
|
|
1077
|
+
when 3 then __send__(semantic_action, value0, value1, value2)
|
|
1078
|
+
when 4 then __send__(semantic_action, value0, value1, value2, value3)
|
|
1079
|
+
end
|
|
1080
|
+
else
|
|
1081
|
+
__send__(semantic_action, reduction_values)
|
|
1082
|
+
end
|
|
1083
|
+
ensure
|
|
1084
|
+
@semantic_locations = previous_locations
|
|
1085
|
+
@semantic_location_names = previous_names
|
|
1086
|
+
@semantic_result_location = previous_result_location
|
|
1087
|
+
end
|
|
1088
|
+
@runtime_fast_path = false if
|
|
1089
|
+
@yydebug || @runtime_observers || @repair_policy || @location_stack ||
|
|
1090
|
+
@semantic_error || @accept_requested
|
|
1091
|
+
end
|
|
1092
|
+
|
|
1093
|
+
goto_row = states[-1]
|
|
1094
|
+
next_state = dense_goto_values[(goto_row * goto_dense_width) + lhs]
|
|
1095
|
+
raise ParseError, "(tables):1:1: missing goto for production #{production_id}" if next_state.nil?
|
|
1096
|
+
|
|
1097
|
+
ensure_stack_capacity! if states.length >= stack_limit
|
|
1098
|
+
states << next_state
|
|
1099
|
+
location_stack = @location_stack
|
|
1100
|
+
location_stack << nil if location_stack
|
|
1101
|
+
values << result
|
|
1102
|
+
if semantic_action
|
|
1103
|
+
trace_reduction(production_id, length, lhs, result, next_state) if @yydebug
|
|
1104
|
+
unless @runtime_fast_path
|
|
1105
|
+
if positional_action
|
|
1106
|
+
case length
|
|
1107
|
+
when 0 then hook_values = EMPTY_LOCATIONS
|
|
1108
|
+
when 1 then hook_values = [value0]
|
|
1109
|
+
when 2 then hook_values = [value0, value1]
|
|
1110
|
+
when 3 then hook_values = [value0, value1, value2]
|
|
1111
|
+
when 4 then hook_values = [value0, value1, value2, value3]
|
|
1112
|
+
end
|
|
1113
|
+
end
|
|
1114
|
+
lookup = Object.instance_method(:method)
|
|
1115
|
+
unless runtime_method_unchanged?(lookup, :on_reduce, :__ibex_fast_path_on_reduce)
|
|
1116
|
+
on_reduce(production_id, hook_values, result)
|
|
1117
|
+
end
|
|
1118
|
+
unless runtime_method_unchanged?(lookup, :on_reduce_location, :__ibex_fast_path_on_reduce_location)
|
|
1119
|
+
on_reduce_location(production_id, hook_values, result, Array.new(length), nil)
|
|
1120
|
+
end
|
|
1121
|
+
end
|
|
1122
|
+
return accept_reduction(result) if @accept_requested
|
|
1123
|
+
return recover(report: false) if @semantic_error
|
|
1124
|
+
|
|
1125
|
+
next
|
|
1126
|
+
end
|
|
1127
|
+
end
|
|
1128
|
+
end
|
|
1129
|
+
nil
|
|
1130
|
+
end
|
|
1131
|
+
# rubocop:enable Metrics/AbcSize, Metrics/BlockNesting, Metrics/CyclomaticComplexity
|
|
1132
|
+
# rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
1133
|
+
# rubocop:enable Style/BitwisePredicate, Style/NumericPredicate
|
|
1134
|
+
|
|
1135
|
+
# @rbs (Hash[Symbol, untyped]? tables) -> bool
|
|
1136
|
+
def compact_fast_driver_eligible?(tables)
|
|
1137
|
+
return false unless @runtime_fast_path && tables
|
|
1138
|
+
return false unless tables[:compact_fast_driver] == true
|
|
1139
|
+
return false unless tables.fetch(:eager_reductions, EMPTY_ROW).empty?
|
|
1140
|
+
return false unless defined?(Ibex::Tables::Compact)
|
|
1141
|
+
|
|
1142
|
+
tables.fetch(:actions).instance_of?(Ibex::Tables::CompactActions) &&
|
|
1143
|
+
tables.fetch(:gotos).instance_of?(Ibex::Tables::Compact) &&
|
|
1144
|
+
tables.fetch(:productions).instance_of?(Ibex::Tables::CompactProductions) &&
|
|
1145
|
+
tables.fetch(:productions).direct_values?
|
|
1146
|
+
end
|
|
1147
|
+
|
|
1148
|
+
# @rbs () -> void
|
|
1149
|
+
def start_push_session
|
|
1150
|
+
if @push_status == :finished
|
|
1151
|
+
raise ParseError, "(input):1:1: push session is finished; call reset_push before supplying more input"
|
|
1152
|
+
end
|
|
1153
|
+
return if @push_status == :active
|
|
1154
|
+
|
|
1155
|
+
prepare_parse(-> { raise ParseError, "(input):1:1: push session needs another token" })
|
|
1156
|
+
@push_status = :active
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
# @rbs () -> (:need_more | [:accepted, untyped] | [:rejected, untyped])
|
|
1160
|
+
def run_push_lookahead
|
|
1161
|
+
loop do
|
|
1162
|
+
outcome = perform(action_for_current_state)
|
|
1163
|
+
return :need_more if outcome.first == :repair_pending
|
|
1164
|
+
|
|
1165
|
+
case outcome.first
|
|
1166
|
+
when :accepted
|
|
1167
|
+
finish_push_session
|
|
1168
|
+
return [:accepted, outcome.fetch(1)]
|
|
1169
|
+
when :done
|
|
1170
|
+
finish_push_session
|
|
1171
|
+
return [:rejected, outcome.fetch(1)]
|
|
1172
|
+
end
|
|
1173
|
+
next unless @lookahead.equal?(NO_LOOKAHEAD)
|
|
1174
|
+
|
|
1175
|
+
repair_buffer = @repair_input_buffer
|
|
1176
|
+
next if repair_buffer && !repair_buffer.empty?
|
|
1177
|
+
|
|
1178
|
+
return :need_more
|
|
1179
|
+
end
|
|
1180
|
+
end
|
|
1181
|
+
|
|
1182
|
+
# @rbs () { () -> untyped } -> untyped
|
|
1183
|
+
def run_push_driver
|
|
1184
|
+
ensure_runtime_initialized!
|
|
1185
|
+
@runtime_observation_mutex.synchronize do
|
|
1186
|
+
ensure_driver_available_without_lock!
|
|
1187
|
+
@driver_status = :push
|
|
1188
|
+
@runtime_driver_thread = Thread.current
|
|
1189
|
+
end
|
|
1190
|
+
begin
|
|
1191
|
+
yield
|
|
1192
|
+
rescue StandardError
|
|
1193
|
+
finish_push_session
|
|
1194
|
+
raise
|
|
1195
|
+
ensure
|
|
1196
|
+
release_driver
|
|
1197
|
+
end
|
|
1198
|
+
end
|
|
1199
|
+
|
|
1200
|
+
# @rbs () -> void
|
|
1201
|
+
def ensure_driver_available_without_lock!
|
|
1202
|
+
return if @driver_status == :idle
|
|
1203
|
+
|
|
1204
|
+
raise ParseError, "(input):1:1: parser driver is already running"
|
|
1205
|
+
end
|
|
1206
|
+
|
|
1207
|
+
# @rbs () -> void
|
|
1208
|
+
def release_driver
|
|
1209
|
+
@runtime_observation_mutex.synchronize do
|
|
1210
|
+
@driver_status = :idle
|
|
1211
|
+
@runtime_driver_thread = nil
|
|
1212
|
+
end
|
|
1213
|
+
end
|
|
1214
|
+
|
|
1215
|
+
# @rbs () -> void
|
|
1216
|
+
def finish_push_session
|
|
1217
|
+
@push_status = :finished
|
|
1218
|
+
@source = nil
|
|
1219
|
+
@lookahead = NO_LOOKAHEAD
|
|
1220
|
+
@lookahead_location = nil
|
|
1221
|
+
@repair_input_buffer = nil
|
|
1222
|
+
@repair_selected = false
|
|
1223
|
+
@runtime_parser_tables = nil
|
|
1224
|
+
@runtime_fast_path = false
|
|
1225
|
+
clear_sync_recovery
|
|
1226
|
+
end
|
|
1227
|
+
|
|
1228
|
+
# @rbs ((^() -> untyped)? source, ?initial_state: Integer?) -> void
|
|
1229
|
+
def prepare_parse(source, initial_state: nil)
|
|
1230
|
+
@runtime_parser_tables = load_parser_tables
|
|
1231
|
+
tables = validate_parser_table_format!
|
|
1232
|
+
initial_state = resolve_initial_state(tables, initial_state)
|
|
1233
|
+
|
|
1234
|
+
@source = source
|
|
1235
|
+
@state_stack.clear << initial_state
|
|
1236
|
+
@value_stack.clear
|
|
1237
|
+
initialize_runtime_fast_path(tables)
|
|
1238
|
+
@lookahead = NO_LOOKAHEAD
|
|
1239
|
+
@lookahead_value = nil
|
|
1240
|
+
@lookahead_location = nil
|
|
1241
|
+
@runtime_lookahead_token_display = nil
|
|
1242
|
+
reset_parse_recovery_state
|
|
1243
|
+
@accept_requested = false
|
|
1244
|
+
@unknown_token_id = nil
|
|
1245
|
+
reset_cst_results(tables)
|
|
1246
|
+
@recovery_attempts = 0
|
|
1247
|
+
trace("start state #{initial_state}") if @yydebug
|
|
1248
|
+
@runtime_event_sequence = 0
|
|
1249
|
+
return unless @runtime_observers
|
|
1250
|
+
|
|
1251
|
+
emit_runtime_event(
|
|
1252
|
+
:start,
|
|
1253
|
+
{
|
|
1254
|
+
"driver" => @driver_status.to_s,
|
|
1255
|
+
"initial_state" => initial_state,
|
|
1256
|
+
"table_format_version" => tables.fetch(:format_version),
|
|
1257
|
+
"grammar_digest" => tables[:grammar_digest],
|
|
1258
|
+
"state_count" => tables[:state_count],
|
|
1259
|
+
"production_count" => tables[:production_count]
|
|
1260
|
+
}
|
|
1261
|
+
)
|
|
1262
|
+
end
|
|
1263
|
+
|
|
1264
|
+
# @rbs () -> void
|
|
1265
|
+
def reset_parse_recovery_state
|
|
1266
|
+
@recovery_shifts = 0
|
|
1267
|
+
@semantic_error = false
|
|
1268
|
+
@repair_input_buffer = @repair_policy ? [] : nil
|
|
1269
|
+
@repair_selected = false
|
|
1270
|
+
clear_sync_recovery
|
|
1271
|
+
end
|
|
1272
|
+
|
|
1273
|
+
# @rbs (Hash[Symbol, untyped] tables, Integer? requested) -> Integer
|
|
1274
|
+
def resolve_initial_state(tables, requested)
|
|
1275
|
+
initial_state = requested || tables[:initial_state] || 0
|
|
1276
|
+
return initial_state if initial_state.is_a?(Integer) &&
|
|
1277
|
+
initial_state.between?(0, parser_state_count(tables) - 1)
|
|
1278
|
+
|
|
1279
|
+
raise ParseError, "(tables):1:1: parser initial state #{initial_state.inspect} is invalid"
|
|
1280
|
+
end
|
|
1281
|
+
|
|
1282
|
+
# @rbs () -> Hash[Symbol, untyped]
|
|
1283
|
+
def validate_parser_table_format!
|
|
1284
|
+
tables = parser_tables
|
|
1285
|
+
unless tables.key?(:format_version)
|
|
1286
|
+
raise ParseError,
|
|
1287
|
+
"(tables):1:1: parser tables for #{self.class} are missing :format_version; " \
|
|
1288
|
+
"regenerate the parser with the installed Ibex version"
|
|
1289
|
+
end
|
|
1290
|
+
|
|
1291
|
+
actual = tables.fetch(:format_version)
|
|
1292
|
+
unless SUPPORTED_PARSER_TABLE_FORMAT_VERSIONS.include?(actual)
|
|
1293
|
+
raise ParseError,
|
|
1294
|
+
"(tables):1:1: unsupported parser table format version #{actual.inspect} for #{self.class}; " \
|
|
1295
|
+
"runtime supports #{SUPPORTED_PARSER_TABLE_FORMAT_VERSIONS.join(', ')}; " \
|
|
1296
|
+
"regenerate the parser with the installed Ibex version"
|
|
1297
|
+
end
|
|
1298
|
+
|
|
1299
|
+
validate_current_cst_tables!(tables, actual)
|
|
1300
|
+
if actual >= 3 && !generated_action_contracts_validated?(tables)
|
|
1301
|
+
validate_generated_action_contracts!(tables, actual)
|
|
1302
|
+
cache_generated_action_contracts!(tables)
|
|
1303
|
+
end
|
|
1304
|
+
tables
|
|
1305
|
+
end
|
|
1306
|
+
|
|
1307
|
+
# @rbs (Hash[Symbol, untyped] tables, Integer actual) -> void
|
|
1308
|
+
def validate_current_cst_tables!(tables, actual)
|
|
1309
|
+
cst = tables[:cst]
|
|
1310
|
+
return if cst.nil? || cst == false
|
|
1311
|
+
return if actual == PARSER_TABLE_FORMAT_VERSION && cst.is_a?(Hash)
|
|
1312
|
+
|
|
1313
|
+
raise ParseError,
|
|
1314
|
+
"(tables):1:1: legacy CST parser tables are unsupported; " \
|
|
1315
|
+
"regenerate the parser with the installed Ibex version"
|
|
1316
|
+
end
|
|
1317
|
+
|
|
1318
|
+
# Generated parser tables are deeply frozen before they can cross a
|
|
1319
|
+
# Ractor boundary. Their action-marker contract only needs one scan per
|
|
1320
|
+
# parser class and exact table object, including across parser instances.
|
|
1321
|
+
# Mutable application tables remain intentionally uncached.
|
|
1322
|
+
# @rbs (Hash[Symbol, untyped] tables) -> bool
|
|
1323
|
+
def generated_action_contracts_validated?(tables)
|
|
1324
|
+
return false unless generated_action_contract_cache_accessible?
|
|
1325
|
+
|
|
1326
|
+
cached = self.class.instance_variable_get(:@__ibex_validated_parser_tables)
|
|
1327
|
+
cached.equal?(tables)
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
# @rbs (Hash[Symbol, untyped] tables) -> void
|
|
1331
|
+
def cache_generated_action_contracts!(tables)
|
|
1332
|
+
shareable = defined?(Ractor) && Ractor.respond_to?(:shareable?) && Ractor.shareable?(tables)
|
|
1333
|
+
return unless shareable
|
|
1334
|
+
return unless generated_action_contract_cache_accessible?
|
|
1335
|
+
|
|
1336
|
+
self.class.instance_variable_set(:@__ibex_validated_parser_tables, tables)
|
|
1337
|
+
rescue FrozenError
|
|
1338
|
+
nil
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
# Ruby 3.0-3.3 expose the main Ractor as an object instead of exposing
|
|
1342
|
+
# Ractor.main?. Class instance variables cannot be read or written from
|
|
1343
|
+
# another Ractor on those versions.
|
|
1344
|
+
# @rbs () -> bool
|
|
1345
|
+
def generated_action_contract_cache_accessible?
|
|
1346
|
+
return true unless Object.const_defined?(:Ractor, false)
|
|
1347
|
+
|
|
1348
|
+
ractor = Object.const_get(:Ractor)
|
|
1349
|
+
return ractor.__send__(:main?) if ractor.respond_to?(:main?)
|
|
1350
|
+
return ractor.__send__(:current).equal?(ractor.__send__(:main)) if
|
|
1351
|
+
ractor.respond_to?(:current) && ractor.respond_to?(:main)
|
|
1352
|
+
|
|
1353
|
+
true
|
|
1354
|
+
end
|
|
1355
|
+
|
|
1356
|
+
# @rbs (Hash[Symbol, untyped] tables, Integer version) -> void
|
|
1357
|
+
def validate_generated_action_contracts!(tables, version)
|
|
1358
|
+
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)
|
|
1362
|
+
end
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
# @rbs (Hash[Symbol, untyped] production, Integer index, Integer version) -> void
|
|
1366
|
+
def validate_composition_action_contract!(production, index, version)
|
|
1367
|
+
return unless production[:composition_action] == true
|
|
1368
|
+
return if production[:location_action] == true && generated_action_symbol?(production[:action])
|
|
1369
|
+
|
|
1370
|
+
raise ParseError,
|
|
1371
|
+
"(tables):1:1: parser table format version #{version} production #{index} has an inconsistent " \
|
|
1372
|
+
":composition_action marker; a generated action Symbol with :location_action is required"
|
|
1373
|
+
end
|
|
1374
|
+
|
|
1375
|
+
# @rbs (Hash[Symbol, untyped] production, Integer index, Integer version) -> void
|
|
1376
|
+
def validate_values_action_contract!(production, index, version)
|
|
1377
|
+
borrowed = production[:borrowed_values_action] == true
|
|
1378
|
+
if borrowed && production[:values_action] != true
|
|
1379
|
+
raise ParseError,
|
|
1380
|
+
"(tables):1:1: parser table format version #{version} production #{index} has an inconsistent " \
|
|
1381
|
+
":borrowed_values_action marker; :values_action is required"
|
|
1382
|
+
end
|
|
1383
|
+
return if version < 4 || production[:values_action] != true
|
|
1384
|
+
return if generated_action_symbol?(production[:action]) &&
|
|
1385
|
+
production[:location_action] != true &&
|
|
1386
|
+
production[:composition_action] != true &&
|
|
1387
|
+
production.fetch(:location_context_length, 0).zero?
|
|
1388
|
+
|
|
1389
|
+
raise ParseError,
|
|
1390
|
+
"(tables):1:1: parser table format version #{version} production #{index} has an inconsistent " \
|
|
1391
|
+
":values_action marker; a generated action Symbol without location, composition, or context " \
|
|
1392
|
+
"markers is required"
|
|
1393
|
+
end
|
|
1394
|
+
|
|
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
|
|
1398
|
+
|
|
1399
|
+
return if positional_action_contract?(production)
|
|
1400
|
+
|
|
1401
|
+
raise ParseError,
|
|
1402
|
+
"(tables):1:1: parser table format version #{version} production #{index} has an inconsistent " \
|
|
1403
|
+
":positional_action marker; a generated action Symbol with zero to four RHS values and no other " \
|
|
1404
|
+
"action ABI markers is required"
|
|
1405
|
+
end
|
|
1406
|
+
|
|
1407
|
+
# @rbs (Hash[Symbol, untyped] production) -> bool
|
|
1408
|
+
def positional_action_contract?(production)
|
|
1409
|
+
production[:length].is_a?(Integer) &&
|
|
1410
|
+
production[:length].between?(0, 4) &&
|
|
1411
|
+
generated_action_symbol?(production[:action]) &&
|
|
1412
|
+
production[:values_action] != true &&
|
|
1413
|
+
production[:borrowed_values_action] != true &&
|
|
1414
|
+
production[:location_action] != true &&
|
|
1415
|
+
production[:composition_action] != true &&
|
|
1416
|
+
production.fetch(:location_context_length, 0).zero?
|
|
1417
|
+
end
|
|
1418
|
+
|
|
1419
|
+
# @rbs () -> untyped
|
|
1420
|
+
def action_for_current_state
|
|
1421
|
+
loop do
|
|
1422
|
+
if @lookahead.equal?(NO_LOOKAHEAD)
|
|
1423
|
+
state = @state_stack.last
|
|
1424
|
+
eager = parser_tables.fetch(:eager_reductions, EMPTY_ROW)[state]
|
|
1425
|
+
return eager if eager
|
|
1426
|
+
|
|
1427
|
+
read_lookahead
|
|
1428
|
+
next if @lookahead.equal?(NO_LOOKAHEAD)
|
|
1429
|
+
end
|
|
1430
|
+
return SYNC_RECOVER_ACTION if sync_recovery_active?
|
|
1431
|
+
|
|
1432
|
+
state = @state_stack.last
|
|
1433
|
+
return ERROR_ACTION unless parser_tables.fetch(:token_names).key?(@lookahead)
|
|
1434
|
+
|
|
1435
|
+
explicit = table_lookup(parser_tables.fetch(:actions), state, @lookahead)
|
|
1436
|
+
return explicit if explicit
|
|
1437
|
+
|
|
1438
|
+
return default_action(state) || ERROR_ACTION
|
|
1439
|
+
end
|
|
1440
|
+
end
|
|
1441
|
+
|
|
1442
|
+
# @rbs (untyped action) -> untyped
|
|
1443
|
+
def perform(action)
|
|
1444
|
+
case action.first
|
|
1445
|
+
when :shift
|
|
1446
|
+
next_state = action.fetch(1)
|
|
1447
|
+
@runtime_fast_path ? fast_shift(next_state) : shift(next_state)
|
|
1448
|
+
when :reduce
|
|
1449
|
+
production_id = action.fetch(1)
|
|
1450
|
+
if @runtime_fast_path
|
|
1451
|
+
production = parser_tables.fetch(:productions).fetch(production_id)
|
|
1452
|
+
length = production.fetch(:length)
|
|
1453
|
+
return fast_reduce(production_id, production, length) if fast_reduction_eligible?(production, length)
|
|
1454
|
+
end
|
|
1455
|
+
|
|
1456
|
+
reduce(production_id, production)
|
|
1457
|
+
when :accept
|
|
1458
|
+
result = finalize_cst(@value_stack.last)
|
|
1459
|
+
emit_accept_event(EventSanitizer.value(result), "table") if @runtime_observers
|
|
1460
|
+
[:accepted, result]
|
|
1461
|
+
when :error
|
|
1462
|
+
materialize_lookahead_token_display!
|
|
1463
|
+
recover
|
|
1464
|
+
when :sync_recover
|
|
1465
|
+
materialize_lookahead_token_display!
|
|
1466
|
+
continue_sync_recovery
|
|
1467
|
+
else raise ParseError, "(tables):1:1: unknown parser action #{action.inspect}"
|
|
1468
|
+
end
|
|
1469
|
+
end
|
|
1470
|
+
|
|
1471
|
+
# @rbs (Integer next_state) -> [:continue]
|
|
1472
|
+
def fast_shift(next_state)
|
|
1473
|
+
value = @lookahead_value
|
|
1474
|
+
ensure_stack_capacity!
|
|
1475
|
+
@state_stack << next_state
|
|
1476
|
+
@value_stack << value
|
|
1477
|
+
@lookahead = NO_LOOKAHEAD
|
|
1478
|
+
@lookahead_location = nil
|
|
1479
|
+
@runtime_lookahead_token_display = nil
|
|
1480
|
+
@recovery_shifts -= 1 if @recovery_shifts.positive?
|
|
1481
|
+
CONTINUE_OUTCOME
|
|
1482
|
+
end
|
|
1483
|
+
|
|
1484
|
+
# @rbs (Hash[Symbol, untyped] production, untyped length) -> bool
|
|
1485
|
+
def fast_reduction_eligible?(production, length)
|
|
1486
|
+
action = production[:action]
|
|
1487
|
+
return false if action && !generated_values_action?(production, action)
|
|
1488
|
+
|
|
1489
|
+
length.is_a?(Integer) && !length.negative? &&
|
|
1490
|
+
length <= @value_stack.length && length < @state_stack.length
|
|
1491
|
+
end
|
|
1492
|
+
|
|
1493
|
+
# @rbs (Integer production_id, Hash[Symbol, untyped] production, Integer length) -> [:continue]
|
|
1494
|
+
def fast_reduce(production_id, production, length)
|
|
1495
|
+
action = production[:action]
|
|
1496
|
+
return fast_values_reduce(production_id, production, length, action) if action
|
|
1497
|
+
|
|
1498
|
+
result = length.zero? ? nil : @value_stack.fetch(@value_stack.length - length)
|
|
1499
|
+
remaining = length
|
|
1500
|
+
while remaining.positive?
|
|
1501
|
+
@state_stack.pop
|
|
1502
|
+
@value_stack.pop
|
|
1503
|
+
remaining -= 1
|
|
1504
|
+
end
|
|
1505
|
+
next_state = table_lookup(parser_tables.fetch(:gotos), @state_stack.last, production.fetch(:lhs))
|
|
1506
|
+
raise ParseError, "(tables):1:1: missing goto for production #{production_id}" if next_state.nil?
|
|
1507
|
+
|
|
1508
|
+
ensure_stack_capacity!
|
|
1509
|
+
@state_stack << next_state
|
|
1510
|
+
@value_stack << result
|
|
1511
|
+
CONTINUE_OUTCOME
|
|
1512
|
+
end
|
|
1513
|
+
|
|
1514
|
+
# @rbs (Integer production_id, Hash[Symbol, untyped] production, Integer length, Symbol action) -> untyped
|
|
1515
|
+
def fast_values_reduce(production_id, production, length, action)
|
|
1516
|
+
hook_values = @value_stack.last(length)
|
|
1517
|
+
values = hook_values.dup
|
|
1518
|
+
remaining = length
|
|
1519
|
+
while remaining.positive?
|
|
1520
|
+
@state_stack.pop
|
|
1521
|
+
@value_stack.pop
|
|
1522
|
+
remaining -= 1
|
|
1523
|
+
end
|
|
1524
|
+
result = values_reduction_value(production, action, values, EMPTY_LOCATIONS, nil)
|
|
1525
|
+
refresh_runtime_fast_path_after_user_code!
|
|
1526
|
+
next_state = table_lookup(parser_tables.fetch(:gotos), @state_stack.last, production.fetch(:lhs))
|
|
1527
|
+
raise ParseError, "(tables):1:1: missing goto for production #{production_id}" if next_state.nil?
|
|
1528
|
+
|
|
1529
|
+
ensure_stack_capacity!
|
|
1530
|
+
push_reduction_result(next_state, result, nil)
|
|
1531
|
+
trace_reduction(production_id, length, production.fetch(:lhs), result, next_state)
|
|
1532
|
+
unless @runtime_fast_path
|
|
1533
|
+
lookup = Object.instance_method(:method)
|
|
1534
|
+
unless runtime_method_unchanged?(lookup, :on_reduce, :__ibex_fast_path_on_reduce)
|
|
1535
|
+
on_reduce(production_id, hook_values, result)
|
|
1536
|
+
end
|
|
1537
|
+
unless runtime_method_unchanged?(lookup, :on_reduce_location, :__ibex_fast_path_on_reduce_location)
|
|
1538
|
+
on_reduce_location(production_id, hook_values, result, Array.new(length), nil)
|
|
1539
|
+
end
|
|
1540
|
+
end
|
|
1541
|
+
return accept_reduction(result) if @accept_requested
|
|
1542
|
+
return recover(report: false) if @semantic_error
|
|
1543
|
+
|
|
1544
|
+
CONTINUE_OUTCOME
|
|
1545
|
+
end
|
|
1546
|
+
|
|
1547
|
+
# @rbs (Integer next_state) -> untyped
|
|
1548
|
+
def shift(next_state)
|
|
1549
|
+
token_id = @lookahead
|
|
1550
|
+
value = @lookahead_value
|
|
1551
|
+
location = @lookahead_location
|
|
1552
|
+
from_state = @state_stack.last
|
|
1553
|
+
token_display = token_to_str(token_id)
|
|
1554
|
+
trace("shift #{token_display}#{trace_value_suffix(value, token_id)} -> state #{next_state}") if @yydebug
|
|
1555
|
+
event_observers = runtime_observer_snapshot if @runtime_observers
|
|
1556
|
+
event_data = if event_observers
|
|
1557
|
+
runtime_token_data(
|
|
1558
|
+
token_id: token_id,
|
|
1559
|
+
token_display: token_display,
|
|
1560
|
+
value: value,
|
|
1561
|
+
location: location,
|
|
1562
|
+
state: next_state
|
|
1563
|
+
).merge("from_state" => @state_stack.last).freeze
|
|
1564
|
+
end
|
|
1565
|
+
ensure_stack_capacity!
|
|
1566
|
+
@state_stack << next_state
|
|
1567
|
+
push_location(location)
|
|
1568
|
+
@value_stack << value
|
|
1569
|
+
shift_green_token(token_id, value, location, from_state)
|
|
1570
|
+
@lookahead = NO_LOOKAHEAD
|
|
1571
|
+
@lookahead_location = nil
|
|
1572
|
+
@runtime_lookahead_token_display = nil
|
|
1573
|
+
@recovery_shifts -= 1 if @recovery_shifts.positive?
|
|
1574
|
+
on_shift(token_id, value, next_state)
|
|
1575
|
+
on_shift_location(token_id, value, next_state, location)
|
|
1576
|
+
emit_runtime_event(:shift, event_data, observers: event_observers) if event_data
|
|
1577
|
+
CONTINUE_OUTCOME
|
|
1578
|
+
end
|
|
1579
|
+
|
|
1580
|
+
# @rbs (Integer production_id, ?Hash[Symbol, untyped]? prefetched_production) -> untyped
|
|
1581
|
+
def reduce(production_id, prefetched_production = nil) # rubocop:disable Metrics -- allocation-free hot path.
|
|
1582
|
+
production = prefetched_production || parser_tables.fetch(:productions).fetch(production_id)
|
|
1583
|
+
length = production.fetch(:length)
|
|
1584
|
+
event_observers = runtime_observer_snapshot if @runtime_observers
|
|
1585
|
+
pre_state = @state_stack.last if event_observers
|
|
1586
|
+
values = @value_stack.last(length)
|
|
1587
|
+
locations = pop_reduction_locations(length)
|
|
1588
|
+
hook_values = values.dup
|
|
1589
|
+
# Array#last above preserves the native negative-length exception before either stack mutates.
|
|
1590
|
+
if length.is_a?(Integer) && !length.negative? &&
|
|
1591
|
+
length <= @value_stack.length && length < @state_stack.length
|
|
1592
|
+
remaining = length
|
|
1593
|
+
while remaining.positive?
|
|
1594
|
+
@state_stack.pop
|
|
1595
|
+
@value_stack.pop
|
|
1596
|
+
remaining -= 1
|
|
1597
|
+
end
|
|
1598
|
+
else
|
|
1599
|
+
@state_stack.pop(length)
|
|
1600
|
+
@value_stack.pop(length)
|
|
1601
|
+
end
|
|
1602
|
+
post_state = @state_stack.last if event_observers
|
|
1603
|
+
location = LocationSpan.for_reduction(locations, lookahead: @lookahead_location)
|
|
1604
|
+
result = reduction_value(production_id, production, values, locations, location)
|
|
1605
|
+
refresh_runtime_fast_path_after_user_code!
|
|
1606
|
+
reduce_green(production_id, production, length, @state_stack.last)
|
|
1607
|
+
next_state = table_lookup(parser_tables.fetch(:gotos), @state_stack.last, production.fetch(:lhs))
|
|
1608
|
+
raise ParseError, "(tables):1:1: missing goto for production #{production_id}" if next_state.nil?
|
|
1609
|
+
|
|
1610
|
+
ensure_stack_capacity!
|
|
1611
|
+
push_reduction_result(next_state, result, location)
|
|
1612
|
+
trace_reduction(production_id, length, production.fetch(:lhs), result, next_state)
|
|
1613
|
+
event_data = build_reduce_event_data(
|
|
1614
|
+
event_observers, production_id, production, length, pre_state, post_state, next_state, result, location
|
|
1615
|
+
)
|
|
1616
|
+
on_reduce(production_id, hook_values, result)
|
|
1617
|
+
on_reduce_location(production_id, hook_values, result, locations.dup, location)
|
|
1618
|
+
emit_runtime_event(:reduce, event_data, observers: event_observers) if event_data
|
|
1619
|
+
return accept_reduction(result) if @accept_requested
|
|
1620
|
+
return recover(report: false) if @semantic_error
|
|
1621
|
+
|
|
1622
|
+
CONTINUE_OUTCOME
|
|
1623
|
+
end
|
|
1624
|
+
|
|
1625
|
+
# @rbs (Integer next_state, untyped result, untyped location) -> void
|
|
1626
|
+
def push_reduction_result(next_state, result, location)
|
|
1627
|
+
@state_stack << next_state
|
|
1628
|
+
push_location(location)
|
|
1629
|
+
@value_stack << result
|
|
1630
|
+
end
|
|
1631
|
+
|
|
1632
|
+
# @rbs (Integer production_id, Integer length, Integer lhs, untyped result, Integer next_state) -> void
|
|
1633
|
+
def trace_reduction(production_id, length, lhs, result, next_state)
|
|
1634
|
+
return unless @yydebug
|
|
1635
|
+
|
|
1636
|
+
trace("reduce #{production_id} (#{length})#{trace_value_suffix(result, lhs)} -> state #{next_state}")
|
|
1637
|
+
end
|
|
1638
|
+
|
|
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]?
|
|
1642
|
+
def build_reduce_event_data(
|
|
1643
|
+
observers, production_id, production, length, pre_state, post_state, next_state, result, location
|
|
1644
|
+
)
|
|
1645
|
+
return unless observers
|
|
1646
|
+
|
|
1647
|
+
runtime_reduce_data(
|
|
1648
|
+
production_id, production, length, pre_state, post_state, next_state, result, location
|
|
1649
|
+
)
|
|
1650
|
+
end
|
|
1651
|
+
|
|
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]
|
|
1655
|
+
def runtime_reduce_data(production_id, production, length, pre_state, post_state, next_state, result, location)
|
|
1656
|
+
{
|
|
1657
|
+
"production_id" => production_id,
|
|
1658
|
+
"lhs" => production.fetch(:lhs),
|
|
1659
|
+
"rhs_length" => length,
|
|
1660
|
+
"pre_state" => pre_state,
|
|
1661
|
+
"post_state" => post_state,
|
|
1662
|
+
"goto_state" => next_state,
|
|
1663
|
+
"result" => EventSanitizer.value(result),
|
|
1664
|
+
"location" => EventSanitizer.location(location)
|
|
1665
|
+
}.freeze
|
|
1666
|
+
end
|
|
1667
|
+
|
|
1668
|
+
# @rbs (untyped result) -> [:accepted, untyped]
|
|
1669
|
+
def accept_reduction(result)
|
|
1670
|
+
result = finalize_cst(result)
|
|
1671
|
+
emit_accept_event(EventSanitizer.value(result), "semantic") if @runtime_observers
|
|
1672
|
+
[:accepted, result]
|
|
1673
|
+
end
|
|
1674
|
+
|
|
1675
|
+
# @rbs (Integer production_id, Hash[Symbol, untyped] production, Array[untyped] values,
|
|
1676
|
+
# Array[untyped] locations, LocationSpan? location) -> untyped
|
|
1677
|
+
def reduction_value(production_id, production, values, locations, location) # rubocop:disable Metrics -- explicit hot path.
|
|
1678
|
+
previous_locations = @semantic_locations
|
|
1679
|
+
previous_names = @semantic_location_names
|
|
1680
|
+
previous_result_location = @semantic_result_location
|
|
1681
|
+
return values.first if @syntax_only
|
|
1682
|
+
|
|
1683
|
+
action = production[:action]
|
|
1684
|
+
return actionless_reduction_value(production_id, production, values, locations, location) unless action
|
|
1685
|
+
|
|
1686
|
+
context_length = production.fetch(:location_context_length, 0)
|
|
1687
|
+
action_locations = if context_length.positive?
|
|
1688
|
+
(@location_stack || []).last(context_length)
|
|
1689
|
+
else
|
|
1690
|
+
locations
|
|
1691
|
+
end
|
|
1692
|
+
@semantic_locations = action_locations
|
|
1693
|
+
@semantic_location_names = production.fetch(:location_names, EMPTY_LOCATION_NAMES)
|
|
1694
|
+
@semantic_result_location = location
|
|
1695
|
+
return __send__(action, *values) if generated_positional_action?(production, action)
|
|
1696
|
+
return __send__(action, values) if generated_values_action?(production, action)
|
|
1697
|
+
|
|
1698
|
+
value_stack = @value_stack.dup
|
|
1699
|
+
callable = action.respond_to?(:call)
|
|
1700
|
+
if generated_location_action?(production, action)
|
|
1701
|
+
location_stack = (@location_stack || []).dup
|
|
1702
|
+
if generated_composition_action?(production, action)
|
|
1703
|
+
if callable
|
|
1704
|
+
return instance_exec(
|
|
1705
|
+
values, value_stack, locations, location_stack, location, @lookahead_location, &action
|
|
1706
|
+
)
|
|
1707
|
+
end
|
|
1708
|
+
|
|
1709
|
+
return __send__(action, values, value_stack, locations, location_stack, location, @lookahead_location)
|
|
1710
|
+
end
|
|
1711
|
+
|
|
1712
|
+
return instance_exec(values, value_stack, locations, location_stack, location, &action) if callable
|
|
1713
|
+
|
|
1714
|
+
return __send__(action, values, value_stack, locations, location_stack, location)
|
|
1715
|
+
end
|
|
1716
|
+
|
|
1717
|
+
callable ? instance_exec(values, value_stack, &action) : __send__(action, values, value_stack)
|
|
1718
|
+
ensure
|
|
1719
|
+
@semantic_locations = previous_locations
|
|
1720
|
+
@semantic_location_names = previous_names
|
|
1721
|
+
@semantic_result_location = previous_result_location
|
|
1722
|
+
end
|
|
1723
|
+
|
|
1724
|
+
# @rbs (Hash[Symbol, untyped] production, Symbol action, Array[untyped] values,
|
|
1725
|
+
# Array[untyped] locations, LocationSpan? location) -> untyped
|
|
1726
|
+
def values_reduction_value(production, action, values, locations, location)
|
|
1727
|
+
previous_locations = @semantic_locations
|
|
1728
|
+
previous_names = @semantic_location_names
|
|
1729
|
+
previous_result_location = @semantic_result_location
|
|
1730
|
+
@semantic_locations = locations
|
|
1731
|
+
@semantic_location_names = production.fetch(:location_names, EMPTY_LOCATION_NAMES)
|
|
1732
|
+
@semantic_result_location = location
|
|
1733
|
+
__send__(action, values)
|
|
1734
|
+
ensure
|
|
1735
|
+
@semantic_locations = previous_locations
|
|
1736
|
+
@semantic_location_names = previous_names
|
|
1737
|
+
@semantic_result_location = previous_result_location
|
|
1738
|
+
end
|
|
1739
|
+
|
|
1740
|
+
# @rbs (Integer production_id, Hash[Symbol, untyped] production, Array[untyped] values,
|
|
1741
|
+
# Array[untyped] locations, LocationSpan? location) -> untyped
|
|
1742
|
+
def actionless_reduction_value(_production_id, _production, values, _locations, _location)
|
|
1743
|
+
values.first
|
|
1744
|
+
end
|
|
1745
|
+
|
|
1746
|
+
# @rbs (Hash[Symbol, untyped] production, untyped action) -> bool
|
|
1747
|
+
def generated_location_action?(production, action)
|
|
1748
|
+
parser_tables.fetch(:format_version) >= 2 &&
|
|
1749
|
+
production[:location_action] == true &&
|
|
1750
|
+
generated_action_symbol?(action)
|
|
1751
|
+
end
|
|
1752
|
+
|
|
1753
|
+
# @rbs (Hash[Symbol, untyped] production, untyped action) -> bool
|
|
1754
|
+
def generated_values_action?(production, action)
|
|
1755
|
+
parser_tables.fetch(:format_version) >= 4 &&
|
|
1756
|
+
production[:values_action] == true &&
|
|
1757
|
+
generated_action_symbol?(action)
|
|
1758
|
+
end
|
|
1759
|
+
|
|
1760
|
+
# @rbs (Hash[Symbol, untyped] production, untyped action) -> bool
|
|
1761
|
+
def generated_positional_action?(production, action)
|
|
1762
|
+
parser_tables.fetch(:format_version) >= 5 &&
|
|
1763
|
+
production[:positional_action] == true &&
|
|
1764
|
+
generated_action_symbol?(action)
|
|
1765
|
+
end
|
|
1766
|
+
|
|
1767
|
+
# @rbs (Hash[Symbol, untyped] production, untyped action) -> bool
|
|
1768
|
+
def generated_composition_action?(production, action)
|
|
1769
|
+
parser_tables.fetch(:format_version) >= 3 &&
|
|
1770
|
+
production[:composition_action] == true &&
|
|
1771
|
+
generated_location_action?(production, action)
|
|
1772
|
+
end
|
|
1773
|
+
|
|
1774
|
+
# @rbs (untyped action) -> bool
|
|
1775
|
+
def generated_action_symbol?(action)
|
|
1776
|
+
action.is_a?(Symbol) && action.name.match?(GENERATED_ACTION_NAME)
|
|
1777
|
+
end
|
|
1778
|
+
|
|
1779
|
+
# @rbs () -> bool
|
|
1780
|
+
def cst_enabled?
|
|
1781
|
+
parser_tables[:cst].is_a?(Hash)
|
|
1782
|
+
end
|
|
1783
|
+
|
|
1784
|
+
# @rbs (Integer token_id, untyped value, untyped location, Symbol reason) -> void
|
|
1785
|
+
def capture_cst_error(token_id, value, location, reason)
|
|
1786
|
+
return unless cst_enabled?
|
|
1787
|
+
return if token_id == EOF_TOKEN
|
|
1788
|
+
|
|
1789
|
+
@syntax_diagnostics << {
|
|
1790
|
+
token_id: token_id, value: value, location: location, reason: reason
|
|
1791
|
+
}.freeze
|
|
1792
|
+
end
|
|
1793
|
+
|
|
1794
|
+
# @rbs (untyped value) -> untyped
|
|
1795
|
+
def finalize_cst(value)
|
|
1796
|
+
return value unless cst_enabled?
|
|
1797
|
+
|
|
1798
|
+
finalize_red_green_cst(value)
|
|
1799
|
+
end
|
|
1800
|
+
|
|
1801
|
+
# @rbs () -> nil
|
|
1802
|
+
def failed_cst
|
|
1803
|
+
finalize_failed_green_cst
|
|
1804
|
+
nil
|
|
1805
|
+
end
|
|
1806
|
+
|
|
1807
|
+
# @rbs (ParseError error) -> nil
|
|
1808
|
+
def cst_lexical_failure(error)
|
|
1809
|
+
@syntax_diagnostics << error
|
|
1810
|
+
finalize_lexical_green_cst(error)
|
|
1811
|
+
nil
|
|
1812
|
+
end
|
|
1813
|
+
|
|
1814
|
+
# @rbs (untyped location, Symbol key) -> untyped
|
|
1815
|
+
def cst_location_value(location, key)
|
|
1816
|
+
if location.is_a?(Hash)
|
|
1817
|
+
value = location[key]
|
|
1818
|
+
return value if value || location.key?(key)
|
|
1819
|
+
return if location.key?(:ibex_lexer_start_state)
|
|
1820
|
+
|
|
1821
|
+
return location[key.name]
|
|
1822
|
+
end
|
|
1823
|
+
return location.public_send(key) if location.respond_to?(key)
|
|
1824
|
+
|
|
1825
|
+
nil
|
|
1826
|
+
end
|
|
1827
|
+
|
|
1828
|
+
# @rbs (Hash[Symbol, untyped] tables) -> void
|
|
1829
|
+
def prepare_green_cst(tables)
|
|
1830
|
+
@syntax_root = nil
|
|
1831
|
+
@syntax_parse_memo = nil
|
|
1832
|
+
@syntax_diagnostics.clear
|
|
1833
|
+
@green_pending_skipped.clear
|
|
1834
|
+
config = tables[:cst]
|
|
1835
|
+
unless config.is_a?(Hash)
|
|
1836
|
+
@green_builder = nil
|
|
1837
|
+
@green_kinds = nil
|
|
1838
|
+
@green_cache = nil
|
|
1839
|
+
return
|
|
1840
|
+
end
|
|
1841
|
+
|
|
1842
|
+
kinds = tables[:cst_kinds]
|
|
1843
|
+
kinds = CST::Kind.new(config.fetch(:kinds), slots: config.fetch(:slots)) unless kinds.is_a?(CST::Kind)
|
|
1844
|
+
cache = @green_cache_override || CST::NodeCache.new
|
|
1845
|
+
@green_kinds = kinds
|
|
1846
|
+
@green_cache = cache
|
|
1847
|
+
@green_token_states.clear
|
|
1848
|
+
@green_memo_stack.clear
|
|
1849
|
+
@green_parse_memo_valid = true
|
|
1850
|
+
@green_initial_state = @state_stack.first || 0
|
|
1851
|
+
@green_reused_right_edge = false
|
|
1852
|
+
@incremental_reused_descendants = 0
|
|
1853
|
+
@green_builder = CST::GreenBuilder.new(kinds: kinds, cache: cache)
|
|
1854
|
+
end
|
|
1855
|
+
|
|
1856
|
+
# @rbs (Hash[Symbol, untyped] tables) -> void
|
|
1857
|
+
def reset_cst_results(tables)
|
|
1858
|
+
prepare_green_cst(tables)
|
|
1859
|
+
end
|
|
1860
|
+
|
|
1861
|
+
# 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
|
|
1863
|
+
def shift_green_token(token_id, value, location, from_state)
|
|
1864
|
+
builder = @green_builder
|
|
1865
|
+
kinds = @green_kinds
|
|
1866
|
+
return unless builder && kinds
|
|
1867
|
+
|
|
1868
|
+
if location.is_a?(Hash) && location.key?(:ibex_lexer_start_state)
|
|
1869
|
+
trailing = location[:cst_previous_trailing] || EMPTY_GREEN_TRIVIA
|
|
1870
|
+
location_leading = location[:leading_trivia] || EMPTY_GREEN_TRIVIA
|
|
1871
|
+
repair = location[:ibex_repair]
|
|
1872
|
+
token_text = location[:ibex_cst_text]
|
|
1873
|
+
token_text = value unless token_text.is_a?(String)
|
|
1874
|
+
token_text = "" unless token_text.is_a?(String)
|
|
1875
|
+
lexer_state = location[:ibex_lexer_start_state]
|
|
1876
|
+
lexer_state = lexer_state ? lexer_state.to_sym : :INITIAL
|
|
1877
|
+
else
|
|
1878
|
+
trailing = green_location_trivia(location, :cst_previous_trailing)
|
|
1879
|
+
location_leading = green_location_trivia(location, :leading_trivia)
|
|
1880
|
+
repair = cst_location_value(location, :ibex_repair)
|
|
1881
|
+
token_text = green_token_text(value, location)
|
|
1882
|
+
lexer_state = green_lexer_state(location)
|
|
1883
|
+
end
|
|
1884
|
+
|
|
1885
|
+
commit_green_token_shift(
|
|
1886
|
+
builder, kinds, token_id, from_state, trailing, location_leading, repair, token_text, lexer_state
|
|
1887
|
+
)
|
|
1888
|
+
end
|
|
1889
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
1890
|
+
|
|
1891
|
+
# @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,
|
|
1893
|
+
# String token_text, Symbol lexer_state) -> void
|
|
1894
|
+
def commit_green_token_shift(
|
|
1895
|
+
builder, kinds, token_id, from_state, trailing, location_leading, repair, token_text, lexer_state
|
|
1896
|
+
)
|
|
1897
|
+
builder.append_trailing_to_last_token(trailing) if !@green_reused_right_edge && !trailing.empty?
|
|
1898
|
+
if repair == :insert
|
|
1899
|
+
builder.missing(token_id)
|
|
1900
|
+
@green_token_states << lexer_state
|
|
1901
|
+
@green_memo_stack << CST::ParseMemo::Entry.new(from_state)
|
|
1902
|
+
return
|
|
1903
|
+
end
|
|
1904
|
+
|
|
1905
|
+
if @green_pending_skipped.empty?
|
|
1906
|
+
leading = location_leading
|
|
1907
|
+
flags = 0
|
|
1908
|
+
else
|
|
1909
|
+
leading = @green_pending_skipped + location_leading
|
|
1910
|
+
@green_pending_skipped.clear
|
|
1911
|
+
flags = CST::Flags::CONTAINS_SKIPPED
|
|
1912
|
+
end
|
|
1913
|
+
flags |= CST::Flags::CONTAINS_ERROR if repair == :replace || token_id == ERROR_TOKEN
|
|
1914
|
+
kind = token_id.negative? ? kinds.fetch(:lexical_error_token) : token_id
|
|
1915
|
+
builder.token(kind, token_text, leading: leading, flags: flags)
|
|
1916
|
+
@green_token_states << lexer_state
|
|
1917
|
+
@green_memo_stack << CST::ParseMemo::Entry.new(from_state)
|
|
1918
|
+
@green_reused_right_edge = false
|
|
1919
|
+
end
|
|
1920
|
+
|
|
1921
|
+
# @rbs (Integer production_id, Hash[Symbol, untyped] production, Integer length, Integer left_state) -> void
|
|
1922
|
+
def reduce_green(production_id, production, length, left_state)
|
|
1923
|
+
builder = @green_builder
|
|
1924
|
+
return unless builder
|
|
1925
|
+
|
|
1926
|
+
config = parser_tables.fetch(:cst)
|
|
1927
|
+
slot = config.fetch(:slots).fetch(production_id, nil)
|
|
1928
|
+
kind = slot ? slot.fetch(:node_kind) : production.fetch(:lhs)
|
|
1929
|
+
flags = length.zero? ? CST::Flags::SYNTHETIC : 0
|
|
1930
|
+
builder.node(kind, length, flags: flags)
|
|
1931
|
+
entry = if length.zero?
|
|
1932
|
+
CST::ParseMemo::Entry.new(left_state)
|
|
1933
|
+
else
|
|
1934
|
+
CST::ParseMemo::Entry.new(left_state, children: @green_memo_stack.pop(length).freeze)
|
|
1935
|
+
end
|
|
1936
|
+
@green_memo_stack << entry
|
|
1937
|
+
end
|
|
1938
|
+
|
|
1939
|
+
# @rbs (untyped value, untyped location) -> String
|
|
1940
|
+
def green_token_text(value, location)
|
|
1941
|
+
text = if location.is_a?(Hash) && location.key?(:ibex_lexer_start_state)
|
|
1942
|
+
location[:ibex_cst_text]
|
|
1943
|
+
else
|
|
1944
|
+
cst_location_value(location, :ibex_cst_text)
|
|
1945
|
+
end
|
|
1946
|
+
return text if text.is_a?(String)
|
|
1947
|
+
return value if value.is_a?(String)
|
|
1948
|
+
|
|
1949
|
+
""
|
|
1950
|
+
end
|
|
1951
|
+
|
|
1952
|
+
# @rbs (untyped location, Symbol key) -> Array[CST::GreenTrivia]
|
|
1953
|
+
def green_location_trivia(location, key)
|
|
1954
|
+
trivia = if location.is_a?(Hash) && location.key?(:ibex_lexer_start_state)
|
|
1955
|
+
location[key]
|
|
1956
|
+
else
|
|
1957
|
+
cst_location_value(location, key)
|
|
1958
|
+
end
|
|
1959
|
+
return EMPTY_GREEN_TRIVIA unless trivia.is_a?(Array)
|
|
1960
|
+
return trivia if trivia.frozen?
|
|
1961
|
+
return trivia if trivia.all?(CST::GreenTrivia)
|
|
1962
|
+
|
|
1963
|
+
trivia.grep(CST::GreenTrivia)
|
|
1964
|
+
end
|
|
1965
|
+
|
|
1966
|
+
# @rbs (untyped value) -> untyped
|
|
1967
|
+
def finalize_red_green_cst(value)
|
|
1968
|
+
builder = @green_builder || raise(ParseError, "(cst):1:1: Green builder is unavailable")
|
|
1969
|
+
kinds = @green_kinds || raise(ParseError, "(cst):1:1: kind metadata is unavailable")
|
|
1970
|
+
incomplete = @lookahead != EOF_TOKEN
|
|
1971
|
+
unless incomplete || @green_reused_right_edge
|
|
1972
|
+
builder.append_trailing_to_last_token(green_location_trivia(@lookahead_location, :cst_previous_trailing))
|
|
1973
|
+
end
|
|
1974
|
+
empty_trivia = [] #: Array[CST::GreenTrivia]
|
|
1975
|
+
eof_leading = incomplete ? empty_trivia : green_location_trivia(@lookahead_location, :leading_trivia)
|
|
1976
|
+
if @green_pending_skipped.empty?
|
|
1977
|
+
leading = eof_leading
|
|
1978
|
+
else
|
|
1979
|
+
leading = @green_pending_skipped + eof_leading
|
|
1980
|
+
@green_pending_skipped.clear
|
|
1981
|
+
end
|
|
1982
|
+
eof = builder.make_token(EOF_TOKEN, "", leading: leading)
|
|
1983
|
+
@green_token_states << green_lexer_state(@lookahead_location)
|
|
1984
|
+
green = if builder.size == 1
|
|
1985
|
+
builder.finish_source_file(eof, incomplete: incomplete)
|
|
1986
|
+
else
|
|
1987
|
+
builder.finish_synthetic_root([eof], incomplete: incomplete)
|
|
1988
|
+
end
|
|
1989
|
+
install_syntax_root(green, kinds)
|
|
1990
|
+
install_parse_memo(green)
|
|
1991
|
+
value
|
|
1992
|
+
end
|
|
1993
|
+
|
|
1994
|
+
# @rbs () -> void
|
|
1995
|
+
def finalize_failed_green_cst
|
|
1996
|
+
builder = @green_builder || raise(ParseError, "(cst):1:1: Green builder is unavailable")
|
|
1997
|
+
kinds = @green_kinds || raise(ParseError, "(cst):1:1: kind metadata is unavailable")
|
|
1998
|
+
trailing = [] #: Array[CST::GreenNode | CST::GreenToken]
|
|
1999
|
+
builder.append_trailing_to_last_token(green_location_trivia(@lookahead_location, :cst_previous_trailing))
|
|
2000
|
+
leading = @green_pending_skipped + green_location_trivia(@lookahead_location, :leading_trivia)
|
|
2001
|
+
@green_pending_skipped.clear
|
|
2002
|
+
if @lookahead.equal?(NO_LOOKAHEAD)
|
|
2003
|
+
trailing << builder.make_token(EOF_TOKEN, "", leading: leading) unless leading.empty?
|
|
2004
|
+
elsif @lookahead == EOF_TOKEN
|
|
2005
|
+
trailing << builder.make_token(EOF_TOKEN, "", leading: leading)
|
|
2006
|
+
else
|
|
2007
|
+
kind = @lookahead.is_a?(Integer) && @lookahead >= 0 ? @lookahead : kinds.fetch(:lexical_error_token)
|
|
2008
|
+
trailing << builder.make_token(
|
|
2009
|
+
kind, green_token_text(@lookahead_value, @lookahead_location),
|
|
2010
|
+
leading: leading,
|
|
2011
|
+
flags: CST::Flags::CONTAINS_ERROR
|
|
2012
|
+
)
|
|
2013
|
+
end
|
|
2014
|
+
install_syntax_root(builder.finish_synthetic_root(trailing), kinds)
|
|
2015
|
+
end
|
|
2016
|
+
|
|
2017
|
+
# @rbs (ParseError error) -> void
|
|
2018
|
+
def finalize_lexical_green_cst(error)
|
|
2019
|
+
builder = @green_builder || raise(ParseError, "(cst):1:1: Green builder is unavailable")
|
|
2020
|
+
kinds = @green_kinds || raise(ParseError, "(cst):1:1: kind metadata is unavailable")
|
|
2021
|
+
unmatched_text = cst_location_value(error.location, :ibex_cst_unmatched_text)
|
|
2022
|
+
text = if unmatched_text.is_a?(String)
|
|
2023
|
+
unmatched_text
|
|
2024
|
+
elsif error.token_value.is_a?(String)
|
|
2025
|
+
error.token_value
|
|
2026
|
+
else
|
|
2027
|
+
error.token_value.to_s
|
|
2028
|
+
end
|
|
2029
|
+
token = builder.make_token(
|
|
2030
|
+
kinds.fetch(:lexical_error_token), text,
|
|
2031
|
+
leading: pending_green_trivia + green_location_trivia(error.location, :leading_trivia),
|
|
2032
|
+
flags: CST::Flags::CONTAINS_ERROR
|
|
2033
|
+
)
|
|
2034
|
+
file = cst_location_value(error.location, :file)
|
|
2035
|
+
install_syntax_root(
|
|
2036
|
+
builder.finish_synthetic_root([token]), kinds,
|
|
2037
|
+
file: file.is_a?(String) ? file : nil
|
|
2038
|
+
)
|
|
2039
|
+
end
|
|
2040
|
+
|
|
2041
|
+
# @rbs () -> Array[CST::GreenTrivia]
|
|
2042
|
+
def pending_green_trivia
|
|
2043
|
+
return [] unless respond_to?(:take_cst_pending_green_trivia, true)
|
|
2044
|
+
|
|
2045
|
+
__send__(:take_cst_pending_green_trivia)
|
|
2046
|
+
end
|
|
2047
|
+
|
|
2048
|
+
# @rbs (CST::GreenNode green, CST::Kind kinds, ?file: String?) -> void
|
|
2049
|
+
def install_syntax_root(green, kinds, file: nil)
|
|
2050
|
+
config = parser_tables.fetch(:cst)
|
|
2051
|
+
policy = config.fetch(:trivia_policy)
|
|
2052
|
+
location_file = cst_location_value(@lookahead_location, :file)
|
|
2053
|
+
source_file = file || (location_file if location_file.is_a?(String))
|
|
2054
|
+
source = CST::SourceText.new(green.to_source.freeze, file: source_file)
|
|
2055
|
+
@syntax_root = CST::SyntaxNode.new(
|
|
2056
|
+
green: green, kinds: kinds, trivia_policy: policy, source_text: source
|
|
2057
|
+
)
|
|
2058
|
+
emit_incremental_event(
|
|
2059
|
+
:cst_built,
|
|
2060
|
+
{
|
|
2061
|
+
"descendant_count" => green.descendant_count,
|
|
2062
|
+
"full_width" => green.full_width,
|
|
2063
|
+
"contains_error" => green.flags.anybits?(CST::Flags::CONTAINS_ERROR)
|
|
2064
|
+
}
|
|
2065
|
+
)
|
|
2066
|
+
end
|
|
2067
|
+
|
|
2068
|
+
# @rbs (untyped value) -> CST::ParseResult
|
|
2069
|
+
def syntax_parse_result(value)
|
|
2070
|
+
root = @syntax_root
|
|
2071
|
+
raise ParseError, "(cst):1:1: parse_with_syntax requires a format-v6 CST parser" unless root
|
|
2072
|
+
|
|
2073
|
+
CST::ParseResult.new(value: value, syntax_root: root, diagnostics: @syntax_diagnostics)
|
|
2074
|
+
end
|
|
2075
|
+
|
|
2076
|
+
# @rbs () -> Array[Symbol]
|
|
2077
|
+
def syntax_token_states
|
|
2078
|
+
@green_token_states.dup.freeze
|
|
2079
|
+
end
|
|
2080
|
+
|
|
2081
|
+
# @rbs (CST::GreenNode green) -> void
|
|
2082
|
+
def install_parse_memo(green)
|
|
2083
|
+
return unless @green_parse_memo_valid && @green_memo_stack.one?
|
|
2084
|
+
|
|
2085
|
+
states = [@green_initial_state] #: Array[Integer?]
|
|
2086
|
+
@green_memo_stack.fetch(0).append_to(states)
|
|
2087
|
+
states << @state_stack.last
|
|
2088
|
+
return unless states.length == green.descendant_count
|
|
2089
|
+
|
|
2090
|
+
@syntax_parse_memo = CST::ParseMemo.new(
|
|
2091
|
+
left_states: states,
|
|
2092
|
+
grammar_digest: parser_tables.fetch(:grammar_digest),
|
|
2093
|
+
state_count: parser_tables.fetch(:state_count),
|
|
2094
|
+
production_count: parser_tables.fetch(:production_count)
|
|
2095
|
+
)
|
|
2096
|
+
end
|
|
2097
|
+
|
|
2098
|
+
# @rbs (untyped location) -> Symbol
|
|
2099
|
+
def green_lexer_state(location)
|
|
2100
|
+
state = if location.is_a?(Hash) && location.key?(:ibex_lexer_start_state)
|
|
2101
|
+
location[:ibex_lexer_start_state]
|
|
2102
|
+
else
|
|
2103
|
+
cst_location_value(location, :ibex_lexer_start_state)
|
|
2104
|
+
end
|
|
2105
|
+
state ? state.to_sym : :INITIAL
|
|
2106
|
+
end
|
|
2107
|
+
|
|
2108
|
+
# @rbs (CST::NodeCache cache) { () -> untyped } -> untyped
|
|
2109
|
+
def with_syntax_only(cache)
|
|
2110
|
+
previous_mode = @syntax_only
|
|
2111
|
+
previous_cache = @green_cache_override
|
|
2112
|
+
@syntax_only = true
|
|
2113
|
+
@green_cache_override = cache
|
|
2114
|
+
@runtime_fast_path = false
|
|
2115
|
+
yield
|
|
2116
|
+
ensure
|
|
2117
|
+
@syntax_only = previous_mode
|
|
2118
|
+
@green_cache_override = previous_cache
|
|
2119
|
+
end
|
|
2120
|
+
|
|
2121
|
+
# @rbs (untyped source, CST::NodeCache cache) -> CST::SyntaxResult
|
|
2122
|
+
def parse_syntax_token_source(source, cache)
|
|
2123
|
+
with_syntax_only(cache) do
|
|
2124
|
+
value = drive_parser(source)
|
|
2125
|
+
parsed = syntax_parse_result(value)
|
|
2126
|
+
CST::SyntaxResult.new(
|
|
2127
|
+
syntax_root: parsed.syntax_root,
|
|
2128
|
+
diagnostics: parsed.diagnostics,
|
|
2129
|
+
reused_ratio: 0.0
|
|
2130
|
+
)
|
|
2131
|
+
end
|
|
2132
|
+
end
|
|
2133
|
+
|
|
2134
|
+
# @rbs (CST::ReusableSubtree subtree) -> void
|
|
2135
|
+
def push_reusable_green_subtree(subtree)
|
|
2136
|
+
unless @syntax_only && @state_stack.last == subtree.left_state
|
|
2137
|
+
raise ParseError, "(incremental):1:1: reusable subtree state mismatch"
|
|
2138
|
+
end
|
|
2139
|
+
|
|
2140
|
+
builder = @green_builder || raise(ParseError, "(incremental):1:1: Green builder is unavailable")
|
|
2141
|
+
builder.append_trailing_to_last_token(subtree.previous_trailing) unless @green_reused_right_edge
|
|
2142
|
+
next_state = table_lookup(parser_tables.fetch(:gotos), @state_stack.last, subtree.lhs)
|
|
2143
|
+
raise ParseError, "(incremental):1:1: reusable subtree has no goto" unless next_state
|
|
2144
|
+
|
|
2145
|
+
ensure_stack_capacity!
|
|
2146
|
+
@state_stack << next_state
|
|
2147
|
+
push_location(nil)
|
|
2148
|
+
@value_stack << nil
|
|
2149
|
+
builder.subtree(subtree.green)
|
|
2150
|
+
@green_memo_stack << CST::ParseMemo::Entry.new(nil, segment: subtree.left_states)
|
|
2151
|
+
@green_reused_right_edge = true
|
|
2152
|
+
@incremental_reused_descendants += subtree.green.descendant_count
|
|
2153
|
+
end
|
|
2154
|
+
|
|
2155
|
+
# @rbs (Symbol type, Hash[untyped, untyped] data) -> void
|
|
2156
|
+
def emit_incremental_event(type, data)
|
|
2157
|
+
emit_runtime_event(type, data) if @runtime_observers
|
|
2158
|
+
end
|
|
2159
|
+
|
|
2160
|
+
# @rbs () -> void
|
|
2161
|
+
def discard_green_lookahead
|
|
2162
|
+
@green_parse_memo_valid = false
|
|
2163
|
+
builder = @green_builder
|
|
2164
|
+
kinds = @green_kinds
|
|
2165
|
+
return unless builder && kinds
|
|
2166
|
+
|
|
2167
|
+
kind = @lookahead.is_a?(Integer) && @lookahead >= 0 ? @lookahead : kinds.fetch(:lexical_error_token)
|
|
2168
|
+
token = builder.make_token(
|
|
2169
|
+
kind, green_token_text(@lookahead_value, @lookahead_location),
|
|
2170
|
+
leading: green_location_trivia(@lookahead_location, :leading_trivia),
|
|
2171
|
+
flags: CST::Flags::CONTAINS_ERROR
|
|
2172
|
+
)
|
|
2173
|
+
return if builder.append_to_last_error(token)
|
|
2174
|
+
|
|
2175
|
+
@green_pending_skipped << CST::GreenTrivia.new(
|
|
2176
|
+
kind: kinds.fetch(:skipped_tokens), text: token.to_source
|
|
2177
|
+
)
|
|
2178
|
+
end
|
|
2179
|
+
|
|
2180
|
+
# @rbs (?report: bool) -> untyped
|
|
2181
|
+
def recover(report: true)
|
|
2182
|
+
@runtime_fast_path = false
|
|
2183
|
+
materialize_lookahead_token_display! unless @lookahead.equal?(NO_LOOKAHEAD)
|
|
2184
|
+
@semantic_error = false
|
|
2185
|
+
return continue_recovery if @recovery_shifts.positive?
|
|
2186
|
+
|
|
2187
|
+
begin_recovery(report)
|
|
2188
|
+
end
|
|
2189
|
+
|
|
2190
|
+
# @rbs () -> untyped
|
|
2191
|
+
def continue_recovery
|
|
2192
|
+
return reject_recovery_eof if @lookahead == EOF_TOKEN
|
|
2193
|
+
|
|
2194
|
+
token_display = token_to_str(@lookahead)
|
|
2195
|
+
event_observers = runtime_observer_snapshot if @runtime_observers
|
|
2196
|
+
event_data = runtime_discard_data(token_display) if event_observers
|
|
2197
|
+
trace("discard #{token_display} during recovery") if @yydebug
|
|
2198
|
+
on_discard(@lookahead, @lookahead_value, @lookahead_location, :recovery)
|
|
2199
|
+
discard_green_lookahead
|
|
2200
|
+
@lookahead = NO_LOOKAHEAD
|
|
2201
|
+
@lookahead_location = nil
|
|
2202
|
+
@runtime_lookahead_token_display = nil
|
|
2203
|
+
emit_runtime_event(:discard, event_data, observers: event_observers) if event_data
|
|
2204
|
+
CONTINUE_OUTCOME
|
|
2205
|
+
end
|
|
2206
|
+
|
|
2207
|
+
# @rbs () -> [:done, nil]
|
|
2208
|
+
def reject_recovery_eof
|
|
2209
|
+
if @runtime_observers
|
|
2210
|
+
observers = runtime_observer_snapshot
|
|
2211
|
+
data = runtime_current_token_data
|
|
2212
|
+
emit_reject_event("eof_during_recovery", data, observers: observers)
|
|
2213
|
+
end
|
|
2214
|
+
[:done, cst_enabled? ? failed_cst : nil]
|
|
2215
|
+
end
|
|
2216
|
+
|
|
2217
|
+
# @rbs (String token_display) -> Hash[String, untyped]
|
|
2218
|
+
def runtime_discard_data(token_display)
|
|
2219
|
+
runtime_token_data(
|
|
2220
|
+
token_id: @lookahead,
|
|
2221
|
+
token_display: token_display,
|
|
2222
|
+
value: @lookahead_value,
|
|
2223
|
+
location: @lookahead_location,
|
|
2224
|
+
state: @state_stack.last
|
|
2225
|
+
).merge("reason" => "recovery").freeze
|
|
2226
|
+
end
|
|
2227
|
+
|
|
2228
|
+
# @rbs (bool report) -> untyped
|
|
2229
|
+
def begin_recovery(report)
|
|
2230
|
+
consume_recovery_attempt!
|
|
2231
|
+
repair = report ? selected_repair : nil
|
|
2232
|
+
return REPAIR_PENDING_OUTCOME if repair.equal?(RepairSearch::NEED_INPUT)
|
|
2233
|
+
|
|
2234
|
+
context = recovery_context(report)
|
|
2235
|
+
capture_cst_error(context[:token_id], context[:value], context[:location], context[:reason].to_sym)
|
|
2236
|
+
token_data, recovery_observers = publish_error_context(context)
|
|
2237
|
+
notify_error_handler(context, repair) if report
|
|
2238
|
+
return commit_repair(repair) if repair.is_a?(RepairPlan)
|
|
2239
|
+
|
|
2240
|
+
fallback_recovery(context, token_data, recovery_observers)
|
|
2241
|
+
end
|
|
2242
|
+
|
|
2243
|
+
# @rbs (bool report) -> Hash[Symbol, untyped]
|
|
2244
|
+
def recovery_context(report)
|
|
2245
|
+
{
|
|
2246
|
+
token_id: @lookahead,
|
|
2247
|
+
value: @lookahead_value,
|
|
2248
|
+
token_display: @runtime_lookahead_token_display,
|
|
2249
|
+
location: @lookahead_location,
|
|
2250
|
+
value_stack: @value_stack.dup,
|
|
2251
|
+
state: @state_stack.last,
|
|
2252
|
+
reason: report ? "syntax" : "semantic"
|
|
2253
|
+
}
|
|
2254
|
+
end
|
|
2255
|
+
|
|
2256
|
+
# @rbs (Hash[Symbol, untyped] context) -> [Hash[String, untyped]?, Array[Proc]?]
|
|
2257
|
+
def publish_error_context(context)
|
|
2258
|
+
error_observers = runtime_observer_snapshot if @runtime_observers
|
|
2259
|
+
if error_observers
|
|
2260
|
+
token_data = runtime_original_token_data(
|
|
2261
|
+
context[:token_id], context[:token_display], context[:value], context[:location], context[:state]
|
|
2262
|
+
)
|
|
2263
|
+
emit_runtime_event(
|
|
2264
|
+
:error, token_data.merge("reason" => context[:reason]), observers: error_observers
|
|
2265
|
+
)
|
|
2266
|
+
end
|
|
2267
|
+
[token_data, (runtime_observer_snapshot if @runtime_observers)]
|
|
2268
|
+
end
|
|
2269
|
+
|
|
2270
|
+
# @rbs (Hash[Symbol, untyped] context, untyped repair) -> void
|
|
2271
|
+
def notify_error_handler(context, repair)
|
|
2272
|
+
@repair_selected = repair.is_a?(RepairPlan)
|
|
2273
|
+
begin
|
|
2274
|
+
on_error(context[:token_id], context[:value], context.fetch(:value_stack).dup)
|
|
2275
|
+
ensure
|
|
2276
|
+
@repair_selected = false
|
|
2277
|
+
end
|
|
2278
|
+
end
|
|
2279
|
+
|
|
2280
|
+
# @rbs (RepairPlan repair) -> [:continue]
|
|
2281
|
+
def commit_repair(repair)
|
|
2282
|
+
on_repair(repair)
|
|
2283
|
+
apply_repair(repair)
|
|
2284
|
+
CONTINUE_OUTCOME
|
|
2285
|
+
end
|
|
2286
|
+
|
|
2287
|
+
# @rbs (Hash[Symbol, untyped] context, Hash[String, untyped]? token_data,
|
|
2288
|
+
# Array[Proc]? recovery_observers) -> untyped
|
|
2289
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
2290
|
+
# Green restoration mirrors the existing state/value/location transactional fallback.
|
|
2291
|
+
def fallback_recovery(context, token_data, recovery_observers)
|
|
2292
|
+
state_stack = @state_stack.dup if sync_recovery_configured?
|
|
2293
|
+
value_stack = @value_stack.dup if state_stack
|
|
2294
|
+
location_stack = @location_stack&.dup if state_stack
|
|
2295
|
+
green_stack = @green_builder&.snapshot if state_stack
|
|
2296
|
+
unless shift_error_token
|
|
2297
|
+
if state_stack
|
|
2298
|
+
@state_stack = state_stack
|
|
2299
|
+
install_value_stack(value_stack || [])
|
|
2300
|
+
@location_stack = location_stack
|
|
2301
|
+
@green_builder&.restore(green_stack || [])
|
|
2302
|
+
return begin_sync_recovery(context, token_data, recovery_observers)
|
|
2303
|
+
end
|
|
2304
|
+
|
|
2305
|
+
return reject_without_recovery(
|
|
2306
|
+
context[:token_id], context[:token_display], context[:value], context[:location], context[:state],
|
|
2307
|
+
token_data, recovery_observers
|
|
2308
|
+
)
|
|
2309
|
+
end
|
|
2310
|
+
|
|
2311
|
+
@recovery_shifts = RECOVERY_SHIFTS
|
|
2312
|
+
finish_recovery(
|
|
2313
|
+
context[:token_id], context[:token_display], context[:value], context[:location], context[:state],
|
|
2314
|
+
context[:value_stack], token_data, context[:reason], recovery_observers
|
|
2315
|
+
)
|
|
2316
|
+
end
|
|
2317
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
2318
|
+
|
|
2319
|
+
# @rbs (untyped token_id, untyped token_display, untyped value, untyped location,
|
|
2320
|
+
# Integer state) -> Hash[String, untyped]
|
|
2321
|
+
def runtime_original_token_data(token_id, token_display, value, location, state)
|
|
2322
|
+
runtime_token_data(
|
|
2323
|
+
token_id: token_id,
|
|
2324
|
+
token_display: token_display,
|
|
2325
|
+
value: value,
|
|
2326
|
+
location: location,
|
|
2327
|
+
state: state
|
|
2328
|
+
)
|
|
2329
|
+
end
|
|
2330
|
+
|
|
2331
|
+
# @rbs (untyped token_id, untyped token_display, untyped value, untyped location,
|
|
2332
|
+
# Integer original_state, Hash[String, untyped]? token_data,
|
|
2333
|
+
# Array[Proc]? observers) -> [:done, nil]
|
|
2334
|
+
def reject_without_recovery(token_id, token_display, value, location, original_state, token_data, observers)
|
|
2335
|
+
if observers
|
|
2336
|
+
reject_data = token_data || runtime_original_token_data(
|
|
2337
|
+
token_id, token_display, value, location, original_state
|
|
2338
|
+
)
|
|
2339
|
+
emit_reject_event(
|
|
2340
|
+
"no_recovery_state",
|
|
2341
|
+
reject_data.merge("state" => @state_stack.last),
|
|
2342
|
+
observers: observers
|
|
2343
|
+
)
|
|
2344
|
+
end
|
|
2345
|
+
[:done, cst_enabled? ? failed_cst : nil]
|
|
2346
|
+
end
|
|
2347
|
+
|
|
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,
|
|
2350
|
+
# String reason, Array[Proc]? observers) -> [:continue]
|
|
2351
|
+
def finish_recovery(
|
|
2352
|
+
token_id, token_display, value, location, original_state, value_stack, token_data, reason, observers
|
|
2353
|
+
)
|
|
2354
|
+
recover_data = if observers
|
|
2355
|
+
token_data || runtime_original_token_data(
|
|
2356
|
+
token_id, token_display, value, location, original_state
|
|
2357
|
+
)
|
|
2358
|
+
end
|
|
2359
|
+
on_error_recover(token_id, value, value_stack)
|
|
2360
|
+
on_error_recover_location(token_id, value, value_stack, location, @state_stack.last)
|
|
2361
|
+
if recover_data
|
|
2362
|
+
emit_runtime_event(
|
|
2363
|
+
:recover,
|
|
2364
|
+
recover_data.merge(
|
|
2365
|
+
"from_state" => original_state,
|
|
2366
|
+
"state" => @state_stack.last,
|
|
2367
|
+
"reason" => reason
|
|
2368
|
+
),
|
|
2369
|
+
observers: observers
|
|
2370
|
+
)
|
|
2371
|
+
end
|
|
2372
|
+
CONTINUE_OUTCOME
|
|
2373
|
+
end
|
|
2374
|
+
|
|
2375
|
+
# @rbs (token_id: untyped, token_display: untyped, value: untyped,
|
|
2376
|
+
# location: untyped, state: Integer) -> Hash[String, untyped]
|
|
2377
|
+
def runtime_token_data(token_id:, token_display:, value:, location:, state:)
|
|
2378
|
+
if token_id.equal?(NO_LOOKAHEAD)
|
|
2379
|
+
return {
|
|
2380
|
+
"state" => state,
|
|
2381
|
+
"token_id" => nil,
|
|
2382
|
+
"token" => nil,
|
|
2383
|
+
"value" => nil,
|
|
2384
|
+
"location" => nil
|
|
2385
|
+
}.freeze
|
|
2386
|
+
end
|
|
2387
|
+
|
|
2388
|
+
{
|
|
2389
|
+
"state" => state,
|
|
2390
|
+
"token_id" => token_id,
|
|
2391
|
+
"token" => EventSanitizer.value(token_display),
|
|
2392
|
+
"value" => EventSanitizer.value(value),
|
|
2393
|
+
"location" => EventSanitizer.location(location)
|
|
2394
|
+
}.freeze
|
|
2395
|
+
end
|
|
2396
|
+
|
|
2397
|
+
# @rbs () -> Hash[String, untyped]
|
|
2398
|
+
def runtime_current_token_data
|
|
2399
|
+
runtime_token_data(
|
|
2400
|
+
token_id: @lookahead,
|
|
2401
|
+
token_display: @runtime_lookahead_token_display,
|
|
2402
|
+
value: @lookahead_value,
|
|
2403
|
+
location: @lookahead_location,
|
|
2404
|
+
state: @state_stack.last
|
|
2405
|
+
)
|
|
2406
|
+
end
|
|
2407
|
+
|
|
2408
|
+
# @rbs (untyped result_summary, String reason) -> void
|
|
2409
|
+
def emit_accept_event(result_summary, reason)
|
|
2410
|
+
emit_runtime_event(
|
|
2411
|
+
:accept,
|
|
2412
|
+
{ "state" => @state_stack.last, "result" => result_summary, "reason" => reason }
|
|
2413
|
+
)
|
|
2414
|
+
end
|
|
2415
|
+
|
|
2416
|
+
# @rbs (String reason, Hash[String, untyped] token_data, ?observers: Array[Proc]?) -> void
|
|
2417
|
+
def emit_reject_event(reason, token_data, observers: runtime_observer_snapshot)
|
|
2418
|
+
emit_runtime_event(
|
|
2419
|
+
:reject,
|
|
2420
|
+
token_data.merge("reason" => reason),
|
|
2421
|
+
observers: observers
|
|
2422
|
+
)
|
|
2423
|
+
end
|
|
2424
|
+
|
|
2425
|
+
# @rbs () -> bool
|
|
2426
|
+
def shift_error_token
|
|
2427
|
+
popped = 0
|
|
2428
|
+
@green_parse_memo_valid = false
|
|
2429
|
+
loop do
|
|
2430
|
+
action = table_lookup(parser_tables.fetch(:actions), @state_stack.last, ERROR_TOKEN)
|
|
2431
|
+
if action&.first == :shift
|
|
2432
|
+
trace("recover: shift error -> state #{action.fetch(1)}") if @yydebug
|
|
2433
|
+
ensure_stack_capacity!
|
|
2434
|
+
@state_stack << action.fetch(1)
|
|
2435
|
+
push_location(@lookahead_location)
|
|
2436
|
+
@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)
|
|
2441
|
+
return true
|
|
2442
|
+
end
|
|
2443
|
+
return false if @state_stack.length == 1
|
|
2444
|
+
|
|
2445
|
+
trace("recover: pop state #{@state_stack.last}") if @yydebug
|
|
2446
|
+
@state_stack.pop
|
|
2447
|
+
@value_stack.pop
|
|
2448
|
+
@location_stack&.pop
|
|
2449
|
+
popped += 1
|
|
2450
|
+
end
|
|
2451
|
+
end
|
|
2452
|
+
|
|
2453
|
+
# @rbs () -> void
|
|
2454
|
+
def ensure_stack_capacity!
|
|
2455
|
+
observed = @state_stack.length + 1
|
|
2456
|
+
limit = @resource_limits.max_stack_depth
|
|
2457
|
+
return if observed <= limit
|
|
2458
|
+
|
|
2459
|
+
raise ResourceLimitError.new(
|
|
2460
|
+
resource: :stack_depth, limit: limit, observed: observed,
|
|
2461
|
+
state: @state_stack.last, location: @lookahead_location
|
|
2462
|
+
)
|
|
2463
|
+
end
|
|
2464
|
+
|
|
2465
|
+
# @rbs () -> void
|
|
2466
|
+
def consume_recovery_attempt!
|
|
2467
|
+
@recovery_attempts += 1
|
|
2468
|
+
limit = @resource_limits.max_recovery_attempts
|
|
2469
|
+
return if @recovery_attempts <= limit
|
|
2470
|
+
|
|
2471
|
+
raise ResourceLimitError.new(
|
|
2472
|
+
resource: :recovery_attempts, limit: limit, observed: @recovery_attempts,
|
|
2473
|
+
state: @state_stack.last, location: @lookahead_location
|
|
2474
|
+
)
|
|
2475
|
+
end
|
|
2476
|
+
|
|
2477
|
+
# @rbs () -> (RepairPlan | Object | nil)
|
|
2478
|
+
def selected_repair
|
|
2479
|
+
policy = @repair_policy
|
|
2480
|
+
return unless policy
|
|
2481
|
+
|
|
2482
|
+
tokens, complete = repair_search_tokens(policy)
|
|
2483
|
+
RepairSearch.new(parser_tables, policy, tokens, complete: complete).search(@state_stack)
|
|
2484
|
+
end
|
|
2485
|
+
|
|
2486
|
+
# @rbs (RepairPolicy policy) -> [Array[RepairInput], bool]
|
|
2487
|
+
def repair_search_tokens(policy)
|
|
2488
|
+
current = RepairInput.new(
|
|
2489
|
+
token_id: @lookahead,
|
|
2490
|
+
token_name: @runtime_lookahead_token_display || token_to_str(@lookahead),
|
|
2491
|
+
value: @lookahead_value,
|
|
2492
|
+
location: @lookahead_location
|
|
2493
|
+
)
|
|
2494
|
+
buffer = @repair_input_buffer || raise(ParseError, "(repair):1:1: repair input buffer is unavailable")
|
|
2495
|
+
while @driver_status == :pull && buffer.length + 1 < policy.max_lookahead &&
|
|
2496
|
+
!buffer.last&.eof?
|
|
2497
|
+
buffer << repair_input_from_external(read_external_token)
|
|
2498
|
+
end
|
|
2499
|
+
tokens = ([current] + buffer).take(policy.max_lookahead)
|
|
2500
|
+
complete = tokens.any?(&:eof?) || tokens.length >= policy.max_lookahead
|
|
2501
|
+
[tokens.freeze, complete]
|
|
2502
|
+
end
|
|
2503
|
+
|
|
2504
|
+
# @rbs (RepairPlan plan) -> void
|
|
2505
|
+
def apply_repair(plan)
|
|
2506
|
+
source = [current_repair_input] + (@repair_input_buffer || [])
|
|
2507
|
+
@repair_input_buffer = replay_repair_edits(source, plan.edits)
|
|
2508
|
+
clear_repair_lookahead
|
|
2509
|
+
@recovery_shifts = 0
|
|
2510
|
+
trace("repair cost #{plan.cost}: #{repair_trace(plan)}") if @yydebug
|
|
2511
|
+
end
|
|
2512
|
+
|
|
2513
|
+
# @rbs () -> RepairInput
|
|
2514
|
+
def current_repair_input
|
|
2515
|
+
RepairInput.new(
|
|
2516
|
+
token_id: @lookahead,
|
|
2517
|
+
token_name: @runtime_lookahead_token_display || token_to_str(@lookahead),
|
|
2518
|
+
value: @lookahead_value,
|
|
2519
|
+
location: @lookahead_location
|
|
2520
|
+
)
|
|
2521
|
+
end
|
|
2522
|
+
|
|
2523
|
+
# @rbs (Array[RepairInput] source, Array[RepairEdit] edits) -> Array[RepairInput]
|
|
2524
|
+
def replay_repair_edits(source, edits)
|
|
2525
|
+
by_position = edits.group_by(&:position)
|
|
2526
|
+
repaired = [] #: Array[RepairInput]
|
|
2527
|
+
source.each_with_index do |input, position|
|
|
2528
|
+
consumed = append_repair_edits(repaired, input, by_position.fetch(position, []))
|
|
2529
|
+
repaired << input unless consumed
|
|
2530
|
+
end
|
|
2531
|
+
by_position.fetch(source.length, []).each do |edit|
|
|
2532
|
+
repaired << synthetic_repair_input(edit, value: nil, location: @lookahead_location)
|
|
2533
|
+
end
|
|
2534
|
+
repaired
|
|
2535
|
+
end
|
|
2536
|
+
|
|
2537
|
+
# @rbs (Array[RepairInput] output, RepairInput input, Array[RepairEdit] edits) -> bool
|
|
2538
|
+
def append_repair_edits(output, input, edits)
|
|
2539
|
+
preserve_deleted_repair_input(input) if edits.any? { |edit| edit.kind == :delete }
|
|
2540
|
+
edits.each do |edit|
|
|
2541
|
+
if edit.kind == :insert
|
|
2542
|
+
output << synthetic_repair_input(edit, value: nil, location: input.location)
|
|
2543
|
+
elsif edit.kind == :replace
|
|
2544
|
+
output << synthetic_repair_input(edit, value: input.value, location: input.location)
|
|
2545
|
+
end
|
|
2546
|
+
end
|
|
2547
|
+
edits.any? { |edit| %i[delete replace].include?(edit.kind) }
|
|
2548
|
+
end
|
|
2549
|
+
|
|
2550
|
+
# Retain a repair deletion in the concrete tree even though it is absent
|
|
2551
|
+
# from the repaired token stream.
|
|
2552
|
+
# @rbs (RepairInput input) -> void
|
|
2553
|
+
def preserve_deleted_repair_input(input)
|
|
2554
|
+
return unless cst_enabled?
|
|
2555
|
+
|
|
2556
|
+
builder = @green_builder
|
|
2557
|
+
kinds = @green_kinds
|
|
2558
|
+
return unless builder && kinds
|
|
2559
|
+
|
|
2560
|
+
previous = green_location_trivia(input.location, :cst_previous_trailing)
|
|
2561
|
+
empty = [] #: Array[CST::GreenTrivia]
|
|
2562
|
+
previous = empty if builder.append_trailing_to_last_token(previous)
|
|
2563
|
+
leading = previous + green_location_trivia(input.location, :leading_trivia)
|
|
2564
|
+
source = leading.each_with_object(String.new(encoding: Encoding::BINARY)) do |trivia, text|
|
|
2565
|
+
text << trivia.text
|
|
2566
|
+
end
|
|
2567
|
+
source << green_token_text(input.value, input.location)
|
|
2568
|
+
@green_pending_skipped << CST::GreenTrivia.new(
|
|
2569
|
+
kind: kinds.fetch(:skipped_tokens), text: source
|
|
2570
|
+
)
|
|
2571
|
+
end
|
|
2572
|
+
|
|
2573
|
+
# @rbs () -> void
|
|
2574
|
+
def clear_repair_lookahead
|
|
2575
|
+
@lookahead = NO_LOOKAHEAD
|
|
2576
|
+
@lookahead_value = nil
|
|
2577
|
+
@lookahead_location = nil
|
|
2578
|
+
@runtime_lookahead_token_display = nil
|
|
2579
|
+
end
|
|
2580
|
+
|
|
2581
|
+
# @rbs (RepairEdit edit, value: untyped, location: untyped) -> RepairInput
|
|
2582
|
+
def synthetic_repair_input(edit, value:, location:)
|
|
2583
|
+
location = cst_repair_location(location, edit.kind) if cst_enabled?
|
|
2584
|
+
RepairInput.new(token_id: edit.token_id, token_name: edit.token_name, value: value, location: location)
|
|
2585
|
+
end
|
|
2586
|
+
|
|
2587
|
+
# @rbs (untyped location, Symbol kind) -> Hash[Symbol, untyped]
|
|
2588
|
+
def cst_repair_location(location, kind)
|
|
2589
|
+
return location.merge(ibex_repair: kind).freeze if location.is_a?(Hash)
|
|
2590
|
+
|
|
2591
|
+
{
|
|
2592
|
+
file: cst_location_value(location, :file),
|
|
2593
|
+
line: cst_location_value(location, :line),
|
|
2594
|
+
column: cst_location_value(location, :column),
|
|
2595
|
+
end_line: cst_location_value(location, :end_line),
|
|
2596
|
+
end_column: cst_location_value(location, :end_column),
|
|
2597
|
+
origin: location,
|
|
2598
|
+
ibex_repair: kind
|
|
2599
|
+
}.freeze
|
|
2600
|
+
end
|
|
2601
|
+
|
|
2602
|
+
# @rbs (RepairPlan plan) -> String
|
|
2603
|
+
def repair_trace(plan)
|
|
2604
|
+
plan.edits.map { |edit| "#{edit.kind}(#{edit.token_name}@#{edit.position})" }.join(", ")
|
|
2605
|
+
end
|
|
2606
|
+
|
|
2607
|
+
# @rbs () -> void
|
|
2608
|
+
def read_lookahead
|
|
2609
|
+
return read_repair_lookahead if @repair_policy
|
|
2610
|
+
|
|
2611
|
+
read_compatible_lookahead
|
|
2612
|
+
end
|
|
2613
|
+
|
|
2614
|
+
# @rbs () -> void
|
|
2615
|
+
def read_repair_lookahead
|
|
2616
|
+
buffer = @repair_input_buffer
|
|
2617
|
+
input = if buffer&.any?
|
|
2618
|
+
buffer.shift
|
|
2619
|
+
else
|
|
2620
|
+
repair_input_from_external(read_external_token)
|
|
2621
|
+
end
|
|
2622
|
+
assign_repair_input(input)
|
|
2623
|
+
end
|
|
2624
|
+
|
|
2625
|
+
# @rbs () -> void
|
|
2626
|
+
def read_compatible_lookahead
|
|
2627
|
+
token = read_external_token
|
|
2628
|
+
refresh_runtime_fast_path_after_user_code!
|
|
2629
|
+
if token.is_a?(CST::ReusableSubtree)
|
|
2630
|
+
push_reusable_green_subtree(token)
|
|
2631
|
+
return
|
|
2632
|
+
end
|
|
2633
|
+
|
|
2634
|
+
if token.nil? || token == false
|
|
2635
|
+
@lookahead = EOF_TOKEN
|
|
2636
|
+
@lookahead_value = nil
|
|
2637
|
+
@lookahead_location = nil
|
|
2638
|
+
else
|
|
2639
|
+
external_token, @lookahead_value, @lookahead_location = token
|
|
2640
|
+
@lookahead = if external_token.nil? || external_token == false
|
|
2641
|
+
EOF_TOKEN
|
|
2642
|
+
else
|
|
2643
|
+
internal_token_id(external_token)
|
|
2644
|
+
end
|
|
2645
|
+
end
|
|
2646
|
+
@runtime_fast_path = false unless nil.equal?(@lookahead_location)
|
|
2647
|
+
materialize_compatible_lookahead
|
|
2648
|
+
end
|
|
2649
|
+
|
|
2650
|
+
# @rbs () -> void
|
|
2651
|
+
def materialize_compatible_lookahead
|
|
2652
|
+
if @runtime_fast_path
|
|
2653
|
+
@runtime_lookahead_token_display = nil
|
|
2654
|
+
else
|
|
2655
|
+
token_display = materialize_lookahead_token_display!
|
|
2656
|
+
trace("read #{token_display}") if @yydebug
|
|
2657
|
+
end
|
|
2658
|
+
end
|
|
2659
|
+
|
|
2660
|
+
# @rbs () -> String
|
|
2661
|
+
def materialize_lookahead_token_display!
|
|
2662
|
+
token_display = @runtime_lookahead_token_display
|
|
2663
|
+
return token_display if token_display
|
|
2664
|
+
|
|
2665
|
+
@runtime_lookahead_token_display = token_to_str(@lookahead)
|
|
2666
|
+
end
|
|
2667
|
+
|
|
2668
|
+
# @rbs (untyped external_token, untyped value, untyped location) -> RepairInput
|
|
2669
|
+
def repair_input(external_token, value, location)
|
|
2670
|
+
token_id = parser_tables.fetch(:tokens)[external_token]
|
|
2671
|
+
if token_id
|
|
2672
|
+
return RepairInput.new(
|
|
2673
|
+
token_id: token_id, token_name: token_to_str(token_id), value: value, location: location
|
|
2674
|
+
)
|
|
2675
|
+
end
|
|
2676
|
+
|
|
2677
|
+
RepairInput.new(
|
|
2678
|
+
token_id: -external_token.object_id.abs,
|
|
2679
|
+
token_name: external_token.inspect,
|
|
2680
|
+
value: value,
|
|
2681
|
+
location: location
|
|
2682
|
+
)
|
|
2683
|
+
end
|
|
2684
|
+
|
|
2685
|
+
# @rbs (untyped token) -> RepairInput
|
|
2686
|
+
def repair_input_from_external(token)
|
|
2687
|
+
if token.nil? || token == false
|
|
2688
|
+
return RepairInput.new(token_id: EOF_TOKEN, token_name: token_to_str(EOF_TOKEN), value: nil, location: nil)
|
|
2689
|
+
end
|
|
2690
|
+
|
|
2691
|
+
external_token, value, location = token
|
|
2692
|
+
if external_token.nil? || external_token == false
|
|
2693
|
+
return RepairInput.new(
|
|
2694
|
+
token_id: EOF_TOKEN,
|
|
2695
|
+
token_name: token_to_str(EOF_TOKEN),
|
|
2696
|
+
value: nil,
|
|
2697
|
+
location: location
|
|
2698
|
+
)
|
|
2699
|
+
end
|
|
2700
|
+
repair_input(external_token, value, location)
|
|
2701
|
+
end
|
|
2702
|
+
|
|
2703
|
+
# @rbs (RepairInput input) -> void
|
|
2704
|
+
def enqueue_or_assign_repair_input(input)
|
|
2705
|
+
buffer = @repair_input_buffer || raise(ParseError, "(repair):1:1: repair input buffer is unavailable")
|
|
2706
|
+
if @lookahead.equal?(NO_LOOKAHEAD) && buffer.empty?
|
|
2707
|
+
assign_repair_input(input)
|
|
2708
|
+
else
|
|
2709
|
+
buffer << input
|
|
2710
|
+
end
|
|
2711
|
+
end
|
|
2712
|
+
|
|
2713
|
+
# @rbs (RepairInput input) -> void
|
|
2714
|
+
def assign_repair_input(input)
|
|
2715
|
+
@lookahead = input.token_id
|
|
2716
|
+
@lookahead_value = input.value
|
|
2717
|
+
@lookahead_location = input.location
|
|
2718
|
+
@runtime_lookahead_token_display = input.token_name
|
|
2719
|
+
if input.token_id.negative?
|
|
2720
|
+
@unknown_token_id = input.token_id
|
|
2721
|
+
@unknown_token_name = input.token_name
|
|
2722
|
+
end
|
|
2723
|
+
trace("read #{input.token_name}") if @yydebug
|
|
2724
|
+
end
|
|
2725
|
+
|
|
2726
|
+
# @rbs () -> untyped
|
|
2727
|
+
def read_external_token
|
|
2728
|
+
source = @source
|
|
2729
|
+
return next_token unless source
|
|
2730
|
+
return source.next_for_state(@state_stack.last) if source.respond_to?(:next_for_state)
|
|
2731
|
+
|
|
2732
|
+
source.call
|
|
2733
|
+
rescue StopIteration
|
|
2734
|
+
false
|
|
2735
|
+
end
|
|
2736
|
+
|
|
2737
|
+
# @rbs (untyped external_token) -> Integer
|
|
2738
|
+
def internal_token_id(external_token)
|
|
2739
|
+
token_id = parser_tables.fetch(:tokens)[external_token]
|
|
2740
|
+
return token_id if token_id
|
|
2741
|
+
|
|
2742
|
+
@unknown_token_name = external_token.inspect
|
|
2743
|
+
@unknown_token_id = -external_token.object_id.abs
|
|
2744
|
+
end
|
|
2745
|
+
|
|
2746
|
+
# @rbs () -> Hash[Symbol, untyped]
|
|
2747
|
+
def parser_tables
|
|
2748
|
+
tables = @runtime_parser_tables
|
|
2749
|
+
return tables if tables
|
|
2750
|
+
|
|
2751
|
+
load_parser_tables
|
|
2752
|
+
end
|
|
2753
|
+
|
|
2754
|
+
# @rbs () -> Hash[Symbol, untyped]
|
|
2755
|
+
def load_parser_tables
|
|
2756
|
+
self.class.__send__(:parser_tables)
|
|
2757
|
+
rescue NoMethodError
|
|
2758
|
+
raise ParseError, "(tables):1:1: #{self.class} must define .parser_tables"
|
|
2759
|
+
end
|
|
2760
|
+
|
|
2761
|
+
# @rbs (untyped action) -> bool
|
|
2762
|
+
def error_action?(action)
|
|
2763
|
+
action.nil? || action.first == :error
|
|
2764
|
+
end
|
|
2765
|
+
|
|
2766
|
+
# @rbs (untyped configured) -> [String?, String?]
|
|
2767
|
+
def configured_error_message(configured)
|
|
2768
|
+
return [nil, configured] unless configured.is_a?(Hash)
|
|
2769
|
+
|
|
2770
|
+
error_id = configured[:id] || configured["id"]
|
|
2771
|
+
message = configured[:message] || configured["message"]
|
|
2772
|
+
[error_id, message]
|
|
2773
|
+
end
|
|
2774
|
+
|
|
2775
|
+
# @rbs (String actual, Array[String] expected) -> Array[String]
|
|
2776
|
+
def token_suggestions(actual, expected)
|
|
2777
|
+
word = normalized_token_word(actual)
|
|
2778
|
+
return [] unless word
|
|
2779
|
+
|
|
2780
|
+
threshold = word.length < 5 ? 1 : 2
|
|
2781
|
+
expected.filter_map do |candidate|
|
|
2782
|
+
candidate_word = normalized_token_word(candidate)
|
|
2783
|
+
candidate if candidate_word && edit_distance(word, candidate_word) <= threshold
|
|
2784
|
+
end
|
|
2785
|
+
end
|
|
2786
|
+
|
|
2787
|
+
# @rbs (String token_name) -> String?
|
|
2788
|
+
def normalized_token_word(token_name)
|
|
2789
|
+
word = token_name.delete_prefix(":")
|
|
2790
|
+
word.upcase if word.match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/)
|
|
2791
|
+
end
|
|
2792
|
+
|
|
2793
|
+
# @rbs (String left, String right) -> Integer
|
|
2794
|
+
def edit_distance(left, right)
|
|
2795
|
+
previous = (0..right.length).to_a
|
|
2796
|
+
left.each_char.with_index(1) do |left_character, row|
|
|
2797
|
+
current = [row]
|
|
2798
|
+
right.each_char.with_index(1) do |right_character, column|
|
|
2799
|
+
current[column] = [
|
|
2800
|
+
current[column - 1] + 1,
|
|
2801
|
+
previous[column] + 1,
|
|
2802
|
+
previous[column - 1] + (left_character == right_character ? 0 : 1)
|
|
2803
|
+
].min
|
|
2804
|
+
end
|
|
2805
|
+
previous = current
|
|
2806
|
+
end
|
|
2807
|
+
previous.last
|
|
2808
|
+
end
|
|
2809
|
+
|
|
2810
|
+
# @rbs (Integer state) -> untyped
|
|
2811
|
+
def default_action(state)
|
|
2812
|
+
parser_tables.fetch(:default_actions, EMPTY_ROW)[state]
|
|
2813
|
+
end
|
|
2814
|
+
|
|
2815
|
+
# @rbs (Hash[Symbol, untyped] tables) -> bool
|
|
2816
|
+
def track_locations?(tables)
|
|
2817
|
+
tables.fetch(:uses_locations, false) || !@runtime_observers.nil?
|
|
2818
|
+
end
|
|
2819
|
+
|
|
2820
|
+
# @rbs (Hash[Symbol, untyped] tables) -> void
|
|
2821
|
+
def initialize_runtime_fast_path(tables)
|
|
2822
|
+
@location_stack = track_locations?(tables) ? [] : nil
|
|
2823
|
+
@runtime_fast_path = runtime_fast_path_eligible?(tables)
|
|
2824
|
+
return unless @runtime_fast_path
|
|
2825
|
+
return if @runtime_fast_path_tracker_installed
|
|
2826
|
+
|
|
2827
|
+
install_runtime_fast_path_tracker!
|
|
2828
|
+
end
|
|
2829
|
+
|
|
2830
|
+
# @rbs (?Class? singleton) -> void
|
|
2831
|
+
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)
|
|
2834
|
+
@runtime_fast_path_tracker_installed = true
|
|
2835
|
+
@runtime_fast_path_hooks_mutated = false
|
|
2836
|
+
@runtime_fast_path_singleton_ancestors = singleton.ancestors.freeze
|
|
2837
|
+
rescue FrozenError, TypeError
|
|
2838
|
+
@runtime_fast_path = false
|
|
2839
|
+
end
|
|
2840
|
+
|
|
2841
|
+
# @rbs () -> void
|
|
2842
|
+
def install_runtime_fast_path_class_tracker!
|
|
2843
|
+
parser_class = self.class
|
|
2844
|
+
return if parser_class.instance_variable_get(:@__ibex_fast_path_class_tracker_installed) == true
|
|
2845
|
+
|
|
2846
|
+
singleton = parser_class.singleton_class
|
|
2847
|
+
singleton.prepend(FastPathClassMutationTracker)
|
|
2848
|
+
parser_class.instance_variable_set(:@__ibex_fast_path_hook_version, 0)
|
|
2849
|
+
parser_class.instance_variable_set(:@__ibex_fast_path_class_tracker_installed, true)
|
|
2850
|
+
rescue FrozenError, TypeError
|
|
2851
|
+
nil
|
|
2852
|
+
end
|
|
2853
|
+
|
|
2854
|
+
# The generic driver remains authoritative whenever a public runtime
|
|
2855
|
+
# extension can observe a committed shift or reduction.
|
|
2856
|
+
# @rbs (Hash[Symbol, untyped] tables) -> bool
|
|
2857
|
+
def runtime_fast_path_eligible?(tables)
|
|
2858
|
+
!@syntax_only &&
|
|
2859
|
+
!@yydebug &&
|
|
2860
|
+
@runtime_observers.nil? &&
|
|
2861
|
+
@repair_policy.nil? &&
|
|
2862
|
+
!tables[:cst] &&
|
|
2863
|
+
!tables.fetch(:uses_locations, false) &&
|
|
2864
|
+
@location_stack.nil? &&
|
|
2865
|
+
runtime_fast_path_hooks_eligible?
|
|
2866
|
+
end
|
|
2867
|
+
|
|
2868
|
+
# @rbs () -> bool
|
|
2869
|
+
def runtime_fast_path_hooks_eligible?
|
|
2870
|
+
cached = cached_runtime_fast_path_hooks_eligibility
|
|
2871
|
+
return cached unless cached.nil?
|
|
2872
|
+
|
|
2873
|
+
lookup = Object.instance_method(:method)
|
|
2874
|
+
hooks_unchanged =
|
|
2875
|
+
runtime_method_unchanged?(lookup, :on_shift, :__ibex_fast_path_on_shift) &&
|
|
2876
|
+
runtime_method_unchanged?(lookup, :on_shift_location, :__ibex_fast_path_on_shift_location) &&
|
|
2877
|
+
runtime_method_unchanged?(lookup, :on_reduce, :__ibex_fast_path_on_reduce) &&
|
|
2878
|
+
runtime_method_unchanged?(lookup, :on_reduce_location, :__ibex_fast_path_on_reduce_location) &&
|
|
2879
|
+
runtime_method_unchanged?(lookup, :token_to_str, :__ibex_fast_path_token_to_str)
|
|
2880
|
+
return false unless hooks_unchanged
|
|
2881
|
+
return true unless @runtime_fast_path_tracker_installed
|
|
2882
|
+
|
|
2883
|
+
tracker_effective = FastPathMutationTracker.effective_for?(self, lookup)
|
|
2884
|
+
if tracker_effective
|
|
2885
|
+
@runtime_fast_path_hooks_mutated = false
|
|
2886
|
+
singleton = Object.instance_method(:singleton_class).bind_call(self)
|
|
2887
|
+
@runtime_fast_path_singleton_ancestors = singleton.ancestors.freeze
|
|
2888
|
+
end
|
|
2889
|
+
tracker_effective
|
|
2890
|
+
end
|
|
2891
|
+
|
|
2892
|
+
# @rbs () -> bool?
|
|
2893
|
+
def cached_runtime_fast_path_hooks_eligibility
|
|
2894
|
+
return nil unless @runtime_fast_path_tracker_installed && !@runtime_fast_path_hooks_mutated
|
|
2895
|
+
|
|
2896
|
+
singleton = Object.instance_method(:singleton_class).bind_call(self)
|
|
2897
|
+
return false if singleton.frozen?
|
|
2898
|
+
return nil unless @runtime_fast_path_singleton_ancestors == singleton.ancestors
|
|
2899
|
+
|
|
2900
|
+
runtime_fast_path_class_hooks_eligible?
|
|
2901
|
+
end
|
|
2902
|
+
|
|
2903
|
+
# @rbs () -> bool
|
|
2904
|
+
def runtime_fast_path_class_hooks_eligible?
|
|
2905
|
+
install_runtime_fast_path_class_tracker!
|
|
2906
|
+
parser_class = self.class
|
|
2907
|
+
version = parser_class.instance_variable_get(:@__ibex_fast_path_hook_version) || 0
|
|
2908
|
+
cached = parser_class.instance_variable_get(:@__ibex_fast_path_hook_cache)
|
|
2909
|
+
return cached.fetch(1) if cached.is_a?(Array) && cached[0] == version
|
|
2910
|
+
|
|
2911
|
+
eligible = FAST_PATH_HOOK_REFERENCES.all? do |name, reference|
|
|
2912
|
+
runtime_methods_equivalent?(
|
|
2913
|
+
parser_class.instance_method(name),
|
|
2914
|
+
Parser.instance_method(reference)
|
|
2915
|
+
)
|
|
2916
|
+
rescue NameError
|
|
2917
|
+
false
|
|
2918
|
+
end
|
|
2919
|
+
parser_class.instance_variable_set(:@__ibex_fast_path_hook_cache, [version, eligible].freeze)
|
|
2920
|
+
eligible
|
|
2921
|
+
rescue FrozenError, TypeError
|
|
2922
|
+
false
|
|
2923
|
+
end
|
|
2924
|
+
|
|
2925
|
+
# @rbs (UnboundMethod lookup, Symbol name, Symbol reference) -> bool
|
|
2926
|
+
def runtime_method_unchanged?(lookup, name, reference)
|
|
2927
|
+
implementation = runtime_core_method(lookup, name)
|
|
2928
|
+
expected = runtime_core_method(lookup, reference)
|
|
2929
|
+
return false if implementation.nil? || expected.nil?
|
|
2930
|
+
|
|
2931
|
+
runtime_methods_equivalent?(implementation, expected)
|
|
2932
|
+
end
|
|
2933
|
+
|
|
2934
|
+
# Ruby 3.0 and 3.1 do not preserve UnboundMethod#== for an inherited
|
|
2935
|
+
# method and its alias even though both still identify the same body.
|
|
2936
|
+
# @rbs (Method | UnboundMethod implementation, Method | UnboundMethod expected) -> bool
|
|
2937
|
+
def runtime_methods_equivalent?(implementation, expected)
|
|
2938
|
+
return true if implementation == expected
|
|
2939
|
+
|
|
2940
|
+
implementation.owner.equal?(expected.owner) &&
|
|
2941
|
+
implementation.original_name == expected.original_name &&
|
|
2942
|
+
implementation.source_location == expected.source_location
|
|
2943
|
+
end
|
|
2944
|
+
|
|
2945
|
+
# Bypass an application-defined `method` helper while retaining Ruby's
|
|
2946
|
+
# complete singleton/prepend/subclass lookup semantics.
|
|
2947
|
+
# @rbs (UnboundMethod lookup, Symbol name) -> Method?
|
|
2948
|
+
def runtime_core_method(lookup, name)
|
|
2949
|
+
lookup.bind_call(self, name)
|
|
2950
|
+
rescue NameError
|
|
2951
|
+
nil
|
|
2952
|
+
end
|
|
2953
|
+
|
|
2954
|
+
# Re-check after lexer and semantic-action callbacks because those are
|
|
2955
|
+
# supported points at which an application can install instrumentation.
|
|
2956
|
+
# A disabled fast path never becomes active again within the session.
|
|
2957
|
+
# @rbs () -> void
|
|
2958
|
+
def refresh_runtime_fast_path_after_user_code!
|
|
2959
|
+
return unless @runtime_fast_path
|
|
2960
|
+
|
|
2961
|
+
@runtime_fast_path = false unless
|
|
2962
|
+
!@yydebug &&
|
|
2963
|
+
@runtime_observers.nil? &&
|
|
2964
|
+
@repair_policy.nil? &&
|
|
2965
|
+
@location_stack.nil? &&
|
|
2966
|
+
@semantic_error == false &&
|
|
2967
|
+
@accept_requested == false
|
|
2968
|
+
end
|
|
2969
|
+
|
|
2970
|
+
# Keep allocation out of the ordinary two-element lexer path. If a
|
|
2971
|
+
# location first appears after values have already shifted, backfill the
|
|
2972
|
+
# parallel prefix with nil exactly once.
|
|
2973
|
+
# @rbs (untyped location) -> void
|
|
2974
|
+
def push_location(location)
|
|
2975
|
+
stack = @location_stack
|
|
2976
|
+
if stack
|
|
2977
|
+
stack << location
|
|
2978
|
+
elsif location
|
|
2979
|
+
new_stack = Array.new(@value_stack.length)
|
|
2980
|
+
new_stack << location
|
|
2981
|
+
@location_stack = new_stack
|
|
2982
|
+
end
|
|
2983
|
+
end
|
|
2984
|
+
|
|
2985
|
+
# @rbs (Integer length) -> Array[untyped]
|
|
2986
|
+
def pop_reduction_locations(length)
|
|
2987
|
+
stack = @location_stack
|
|
2988
|
+
return Array.new(length) unless stack
|
|
2989
|
+
|
|
2990
|
+
locations = stack.last(length)
|
|
2991
|
+
stack.pop(length)
|
|
2992
|
+
locations
|
|
2993
|
+
end
|
|
2994
|
+
|
|
2995
|
+
# @rbs (untyped table, Integer row, Integer column) -> untyped
|
|
2996
|
+
def table_lookup(table, row, column)
|
|
2997
|
+
return table.lookup(row, column) if table.respond_to?(:lookup)
|
|
2998
|
+
|
|
2999
|
+
table.fetch(row, EMPTY_ROW)[column]
|
|
3000
|
+
end
|
|
3001
|
+
|
|
3002
|
+
# @rbs (String message) -> void
|
|
3003
|
+
def trace(message)
|
|
3004
|
+
@yydebug_output.puts("ibex: #{message}") if @yydebug
|
|
3005
|
+
end
|
|
3006
|
+
|
|
3007
|
+
# @rbs (untyped value, Integer symbol_id) -> String
|
|
3008
|
+
def trace_value_suffix(value, symbol_id)
|
|
3009
|
+
return "" unless @yydebug
|
|
3010
|
+
|
|
3011
|
+
printer = @trace_value_printer
|
|
3012
|
+
rendered = if printer
|
|
3013
|
+
printer.call(value)
|
|
3014
|
+
else
|
|
3015
|
+
method_name = parser_tables.fetch(:value_printers, EMPTY_ROW)[symbol_id]
|
|
3016
|
+
return "" unless method_name
|
|
3017
|
+
|
|
3018
|
+
__send__(method_name, value)
|
|
3019
|
+
end
|
|
3020
|
+
" value=#{rendered}"
|
|
3021
|
+
rescue StandardError => e
|
|
3022
|
+
" value=<printer error: #{e.class}>"
|
|
3023
|
+
end
|
|
3024
|
+
end
|
|
3025
|
+
# rubocop:enable Metrics/ClassLength
|
|
3026
|
+
end
|
|
3027
|
+
end
|