Module: Ibex::NormalizeInlineExpansion
- Included in:
- Normalizer
- Defined in:
- lib/ibex/normalize/inline_expansion.rb,
sig/ibex/normalize/inline_expansion.rbs
Overview
Deterministically substitutes inline-rule productions before LR construction. rubocop:disable Metrics/ModuleLength -- expansion, action plans, and id remapping share one invariant.
Instance Method Summary collapse
- #advance_inline_expansion_frame?(frames, definitions, worklist, variants, location) ⇒ Boolean
-
#append_inline_array(left, right) ⇒ Array[Object?]
The helper is shared by homogeneous integer and step arrays; callers assert the concrete element type at each use site.
- #append_inline_symbol(variant, symbol_id) ⇒ inline_variant
- #charge_inline_expansion!(production) ⇒ void
- #clone_inline_expansion_frames(frames) ⇒ Array[inline_frame]
- #combine_inline_variant(parent, child) ⇒ inline_variant
- #combined_inline_precedence(parent, child) ⇒ Integer?
- #combined_inline_steps(parent, child, physical_offset, step_offset) ⇒ Array[inline_step]
- #complete_inline_expansion_frame?(frames, variants, worklist, location) ⇒ Boolean
- #composed_inline_action(production, physical_length, steps) ⇒ IR::Action
- #composed_inline_production(production, variant, id) ⇒ IR::Production
- #composition_plan_step(step, physical_length) ⇒ IR::action_composition_step
- #copy_inline_production(production, id) ⇒ IR::Production
- #drain_inline_expansion_frames(frames, definitions, worklist, variants, location) ⇒ void
- #empty_inline_variant(production) ⇒ inline_variant
- #enforce_inline_choice_limit!(count, location) ⇒ void
-
#expand_inline_production(production, definitions) ⇒ Array[inline_variant]
Explicit heap frames avoid tying grammar depth to the Ruby call stack.
- #expand_inline_rules ⇒ void
- #finish_inline_choice(variant, production, symbol_id) ⇒ inline_variant
- #finish_inline_precedence(variant, production) ⇒ inline_variant
- #inline_action_step(production, inputs, lookahead, kind, rule) ⇒ inline_step
- #inline_expansion_frame(production, symbol_id) ⇒ inline_frame
- #inline_implicit_code(rhs_length) ⇒ String
- #inline_production_expansion(production, variant) ⇒ IR::production_expansion
- #inline_source_provenance(location) ⇒ IR::source_provenance
- #rebuild_inline_symbols(productions) ⇒ void
- #remap_inline_production(production, id, id_map) ⇒ IR::Production
- #remap_inline_reference(reference, physical_offset, step_offset) ⇒ Array[Symbol | Integer]
- #remap_inline_steps(parent, child, physical_offset, step_offset) ⇒ Array[inline_step]
- #remap_inline_symbol(definition, id) ⇒ IR::GrammarSymbol
- #resolve_inline_reference(reference, physical_length) ⇒ Integer
- #schedule_inline_choice_branches(frames, choices, symbol_id, worklist) ⇒ void
Instance Method Details
#advance_inline_expansion_frame?(frames, definitions, worklist, variants, location) ⇒ Boolean
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 101 def advance_inline_expansion_frame?(frames, definitions, worklist, variants, location) frame = frames.fetch(-1) current = frame.fetch(:production) index = frame.fetch(:index) symbol_id = current.rhs.fetch(index) frame[:index] = index + 1 unless @inline_symbol_ids.include?(symbol_id) frame[:variant] = append_inline_symbol(frame.fetch(:variant), symbol_id) return false end choices = definitions.fetch(symbol_id) if choices.one? frames << inline_expansion_frame(choices.fetch(0), symbol_id) return false end schedule_inline_choice_branches(frames, choices, symbol_id, worklist) enforce_inline_choice_limit!(variants.length + worklist.length, location) true end |
#append_inline_array(left, right) ⇒ Array[Object?]
The helper is shared by homogeneous integer and step arrays; callers assert the concrete element type at each use site.
238 239 240 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 238 def append_inline_array(left, right) left.empty? ? right : left + right end |
#append_inline_symbol(variant, symbol_id) ⇒ inline_variant
189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 189 def append_inline_symbol(variant, symbol_id) # @type self: Normalizer rhs = variant.fetch(:rhs) terminal = @symbols.fetch(symbol_id).terminal? reference = [:physical, rhs.length] #: inline_reference logical_refs = variant.fetch(:logical_refs) + [reference] #: Array[inline_reference] variant.merge( rhs: rhs + [symbol_id], logical_refs: logical_refs, precedence_override: terminal ? nil : variant[:precedence_override], precedence_contributes: terminal || variant.fetch(:precedence_contributes) ) #: inline_variant end |
#charge_inline_expansion!(production) ⇒ void
This method returns an undefined value.
418 419 420 421 422 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 418 def charge_inline_expansion!(production) @inline_expansion_count += 1 location = production.origin[:loc] #: IR::location? enforce_inline_choice_limit!(@inline_expansion_count, location) end |
#clone_inline_expansion_frames(frames) ⇒ Array[inline_frame]
159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 159 def clone_inline_expansion_frames(frames) frames.map do |frame| variant = frame.fetch(:variant) cloned = frame.merge( variant: variant.merge( rhs: variant.fetch(:rhs).dup, steps: variant.fetch(:steps).dup, logical_refs: variant.fetch(:logical_refs).dup ) ) cloned #: inline_frame end end |
#combine_inline_variant(parent, child) ⇒ inline_variant
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 204 def combine_inline_variant(parent, child) physical_offset = parent.fetch(:rhs).length step_offset = parent.fetch(:steps).length steps = combined_inline_steps(parent, child, physical_offset, step_offset) output_ref = child.fetch(:output_ref) || raise(Ibex::Error, "inline choice has no output reference") rhs = append_inline_array(parent.fetch(:rhs), child.fetch(:rhs)) #: Array[Integer] combined_steps = append_inline_array(parent.fetch(:steps), steps) #: Array[inline_step] logical_refs = parent.fetch(:logical_refs) + [ remap_inline_reference(output_ref, physical_offset, step_offset) ] #: Array[inline_reference] parent.merge( rhs: rhs, steps: combined_steps, logical_refs: logical_refs, inline_used: true, inline_rule: parent[:inline_rule] || child[:inline_rule], precedence_override: combined_inline_precedence(parent, child), precedence_contributes: parent.fetch(:precedence_contributes) || child.fetch(:precedence_contributes), parameter: parent[:parameter] || child[:parameter] ) #: inline_variant end |
#combined_inline_precedence(parent, child) ⇒ Integer?
243 244 245 246 247 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 243 def combined_inline_precedence(parent, child) return child[:precedence_override] if child.fetch(:precedence_contributes) parent[:precedence_override] end |
#combined_inline_steps(parent, child, physical_offset, step_offset) ⇒ Array[inline_step]
229 230 231 232 233 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 229 def combined_inline_steps(parent, child, physical_offset, step_offset) return child.fetch(:steps) if physical_offset.zero? && step_offset.zero? && parent.fetch(:logical_refs).empty? remap_inline_steps(parent, child, physical_offset, step_offset) end |
#complete_inline_expansion_frame?(frames, variants, worklist, location) ⇒ Boolean
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 135 def complete_inline_expansion_frame?(frames, variants, worklist, location) frame = frames.fetch(-1) current = frame.fetch(:production) completed = finish_inline_precedence(frame.fetch(:variant), current) frames.pop if frames.empty? variants << completed enforce_inline_choice_limit!(variants.length + worklist.length, location) return true end symbol_id = frame.fetch(:symbol_id) || raise(Ibex::Error, "inline expansion frame is missing its symbol") choice = finish_inline_choice(completed, current, symbol_id) parent = frames.fetch(-1) parent[:variant] = combine_inline_variant(parent.fetch(:variant), choice) false end |
#composed_inline_action(production, physical_length, steps) ⇒ IR::Action
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 339 def composed_inline_action(production, physical_length, steps) action = production.action location = action&.location || production.origin.fetch(:loc) typed_location = location #: IR::location fragments = steps.map do |step| { kind: step.fetch(:kind), source: inline_source_provenance(step.fetch(:loc)) } end plan_steps = steps.map { |step| composition_plan_step(step, physical_length) } composition = { strategy: "sequence", fragments: fragments, plan: { version: 1, physical: physical_length, steps: plan_steps } } #: IR::action_composition IR::Action.new( code: action&.code || inline_implicit_code(production.rhs.length), location: typed_location, composition: composition ) end |
#composed_inline_production(production, variant, id) ⇒ IR::Production
323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 323 def composed_inline_production(production, variant, id) steps = variant.fetch(:steps) caller = inline_action_step( production, variant.fetch(:logical_refs), variant.fetch(:rhs).length, :rule, nil ) all_steps = steps + [caller] action = composed_inline_action(production, variant.fetch(:rhs).length, all_steps) IR::Production.new( id: id, lhs: production.lhs, rhs: variant.fetch(:rhs), action: action, precedence_override: variant[:precedence_override], origin: production.origin, documentation: production.documentation, expansion: inline_production_expansion(production, variant) ) end |
#composition_plan_step(step, physical_length) ⇒ IR::action_composition_step
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 360 def composition_plan_step(step, physical_length) { kind: step.fetch(:kind), rule: step[:rule], code: step[:code], loc: step.fetch(:loc), named_refs: step.fetch(:named_refs), context_length: step.fetch(:context_length), inputs: step.fetch(:inputs).map { |reference| resolve_inline_reference(reference, physical_length) }, stack_inputs: step.fetch(:stack_inputs).map do |reference| resolve_inline_reference(reference, physical_length) end, lookahead: step.fetch(:lookahead) < physical_length ? step.fetch(:lookahead) : nil, result_var: step.fetch(:result_var), result_type: step[:result_type] } end |
#copy_inline_production(production, id) ⇒ IR::Production
409 410 411 412 413 414 415 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 409 def copy_inline_production(production, id) IR::Production.new( id: id, lhs: production.lhs, rhs: production.rhs, action: production.action, precedence_override: production.precedence_override, origin: production.origin, documentation: production.documentation, expansion: production.expansion ) end |
#drain_inline_expansion_frames(frames, definitions, worklist, variants, location) ⇒ void
This method returns an undefined value.
86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 86 def drain_inline_expansion_frames(frames, definitions, worklist, variants, location) loop do frame = frames.fetch(-1) current = frame.fetch(:production) if frame.fetch(:index) < current.rhs.length return if advance_inline_expansion_frame?(frames, definitions, worklist, variants, location) elsif complete_inline_expansion_frame?(frames, variants, worklist, location) return end end end |
#empty_inline_variant(production) ⇒ inline_variant
174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 174 def empty_inline_variant(production) { rhs: [], steps: [], logical_refs: [], output_ref: nil, inline_used: false, inline_rule: nil, precedence_override: nil, precedence_contributes: false, parameter: production.expansion&.dig(:parameter) } end |
#enforce_inline_choice_limit!(count, location) ⇒ void
This method returns an undefined value.
425 426 427 428 429 430 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 425 def enforce_inline_choice_limit!(count, location) # @type self: Normalizer return if count <= @max_inline_expansions fail_hash(location, "inline expansion limit of #{@max_inline_expansions} exceeded") end |
#expand_inline_production(production, definitions) ⇒ Array[inline_variant]
Explicit heap frames avoid tying grammar depth to the Ruby call stack. The single-definition path reuses its accumulated arrays, so a long acyclic chain remains linear in the size of its final action plan.
71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 71 def (production, definitions) # @type self: Normalizer variants = [] #: Array[inline_variant] worklist = [[inline_expansion_frame(production, nil)]] #: Array[Array[inline_frame]] until worklist.empty? frames = worklist.pop || raise(Ibex::Error, "internal inline expansion worklist underflow") location = production.origin[:loc] #: IR::location? drain_inline_expansion_frames(frames, definitions, worklist, variants, location) end variants end |
#expand_inline_rules ⇒ void
This method returns an undefined value.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 44 def # @type self: Normalizer return if @inline_symbol_ids.empty? definitions = @productions.group_by(&:lhs) = [] #: Array[IR::Production] @productions.each do |production| next if @inline_symbol_ids.include?(production.lhs) variants = (production, definitions) variants.each do |variant| if variant[:inline_used] charge_inline_expansion!(production) << composed_inline_production(production, variant, .length) else << copy_inline_production(production, .length) end end end rebuild_inline_symbols() end |
#finish_inline_choice(variant, production, symbol_id) ⇒ inline_variant
288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 288 def finish_inline_choice(variant, production, symbol_id) steps = variant.fetch(:steps) rule = @inline_rule_by_symbol.fetch(symbol_id) step = inline_action_step(production, variant.fetch(:logical_refs), variant.fetch(:rhs).length, :inline, rule) steps << step variant.merge( steps: steps, output_ref: [:step, steps.length - 1], inline_used: true, inline_rule: rule ) #: inline_variant end |
#finish_inline_precedence(variant, production) ⇒ inline_variant
279 280 281 282 283 284 285 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 279 def finish_inline_precedence(variant, production) explicit = production.precedence_override variant.merge( precedence_override: explicit || variant[:precedence_override], precedence_contributes: !explicit.nil? || variant.fetch(:precedence_contributes) ) #: inline_variant end |
#inline_action_step(production, inputs, lookahead, kind, rule) ⇒ inline_step
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 303 def inline_action_step(production, inputs, lookahead, kind, rule) action = production.action location = action&.location || production.origin.fetch(:loc) typed_location = location #: IR::location { kind: kind, rule: rule, code: action&.code, loc: typed_location, named_refs: action&.named_refs || [], context_length: action&.context_length || 0, inputs: inputs, stack_inputs: [], lookahead: lookahead, result_var: @options.fetch(:result_var), result_type: @symbols.fetch(production.lhs).semantic_type } end |
#inline_expansion_frame(production, symbol_id) ⇒ inline_frame
154 155 156 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 154 def inline_expansion_frame(production, symbol_id) { production: production, symbol_id: symbol_id, index: 0, variant: empty_inline_variant(production) } end |
#inline_implicit_code(rhs_length) ⇒ String
387 388 389 390 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 387 def inline_implicit_code(rhs_length) expression = rhs_length.zero? ? "nil" : "val[0]" @options.fetch(:result_var) ? " result = #{expression} " : " #{expression} " end |
#inline_production_expansion(production, variant) ⇒ IR::production_expansion
398 399 400 401 402 403 404 405 406 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 398 def inline_production_expansion(production, variant) existing = production.expansion inline_rule = variant.fetch(:inline_rule) || raise(Ibex::Error, "inline expansion has no rule") { parameter: existing&.dig(:parameter) || variant[:parameter], inline: { rule: inline_rule }, include_chain: existing&.dig(:include_chain) || [] } end |
#inline_source_provenance(location) ⇒ IR::source_provenance
393 394 395 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 393 def inline_source_provenance(location) { file: location[:file], root: @resolution&.root_directory, byte_span: nil } end |
#rebuild_inline_symbols(productions) ⇒ void
This method returns an undefined value.
433 434 435 436 437 438 439 440 441 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 433 def rebuild_inline_symbols(productions) kept = @symbols.reject { |definition| @inline_symbol_ids.include?(definition.id) } id_map = kept.each_with_index.to_h { |definition, id| [definition.id, id] } @symbols = kept.each_with_index.map { |definition, id| remap_inline_symbol(definition, id) } @symbols_by_name = @symbols.to_h { |definition| [definition.name, definition] } @productions = productions.each_with_index.map do |production, id| remap_inline_production(production, id, id_map) end end |
#remap_inline_production(production, id, id_map) ⇒ IR::Production
454 455 456 457 458 459 460 461 462 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 454 def remap_inline_production(production, id, id_map) IR::Production.new( id: id, lhs: id_map.fetch(production.lhs), rhs: production.rhs.map { |symbol_id| id_map.fetch(symbol_id) }, action: production.action, precedence_override: production.precedence_override && id_map.fetch(production.precedence_override), origin: production.origin, documentation: production.documentation, expansion: production.expansion ) end |
#remap_inline_reference(reference, physical_offset, step_offset) ⇒ Array[Symbol | Integer]
270 271 272 273 274 275 276 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 270 def remap_inline_reference(reference, physical_offset, step_offset) kind, index = reference raise Ibex::Error, "invalid inline slot kind" unless kind.is_a?(Symbol) raise Ibex::Error, "invalid inline slot reference" unless index.is_a?(Integer) [kind, index + (kind == :physical ? physical_offset : step_offset)] end |
#remap_inline_steps(parent, child, physical_offset, step_offset) ⇒ Array[inline_step]
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 251 def remap_inline_steps(parent, child, physical_offset, step_offset) child.fetch(:steps).map do |step| inputs = step.fetch(:inputs).map do |reference| remap_inline_reference(reference, physical_offset, step_offset) end stack_inputs = step.fetch(:stack_inputs).map do |reference| remap_inline_reference(reference, physical_offset, step_offset) end remapped = step.merge( inputs: inputs, stack_inputs: parent.fetch(:logical_refs) + stack_inputs, lookahead: step.fetch(:lookahead) + physical_offset ) remapped #: inline_step end end |
#remap_inline_symbol(definition, id) ⇒ IR::GrammarSymbol
444 445 446 447 448 449 450 451 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 444 def remap_inline_symbol(definition, id) IR::GrammarSymbol.new( id: id, name: definition.name, kind: definition.kind, reserved: definition.reserved, precedence: definition.precedence, location: definition.location, display_name: definition.display_name, semantic_type: definition.semantic_type, documentation: definition.documentation ) end |
#resolve_inline_reference(reference, physical_length) ⇒ Integer
379 380 381 382 383 384 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 379 def resolve_inline_reference(reference, physical_length) kind, index = reference raise Ibex::Error, "invalid inline slot reference" unless index.is_a?(Integer) index + (kind == :step ? physical_length : 0) end |
#schedule_inline_choice_branches(frames, choices, symbol_id, worklist) ⇒ void
This method returns an undefined value.
125 126 127 128 129 130 131 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 125 def schedule_inline_choice_branches(frames, choices, symbol_id, worklist) choices.reverse_each do |choice| branch = clone_inline_expansion_frames(frames) branch << inline_expansion_frame(choice, symbol_id) worklist << branch end end |