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[untyped]
- #append_inline_symbol(variant, symbol_id) ⇒ Hash[Symbol, untyped]
- #charge_inline_expansion!(production) ⇒ void
- #clone_inline_expansion_frames(frames) ⇒ Array[Hash[Symbol, untyped]]
- #combine_inline_variant(parent, child) ⇒ Hash[Symbol, untyped]
- #combined_inline_precedence(parent, child) ⇒ Integer?
- #combined_inline_steps(parent, child, physical_offset, step_offset) ⇒ Array[Hash[Symbol, untyped]]
- #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) ⇒ Hash[Symbol, untyped]
- #copy_inline_production(production, id) ⇒ IR::Production
- #drain_inline_expansion_frames(frames, definitions, worklist, variants, location) ⇒ void
- #empty_inline_variant(production) ⇒ Hash[Symbol, untyped]
- #enforce_inline_choice_limit!(count, location) ⇒ void
-
#expand_inline_production(production, definitions) ⇒ Array[Hash[Symbol, untyped]]
Explicit heap frames avoid tying grammar depth to the Ruby call stack.
- #expand_inline_rules ⇒ void
- #finish_inline_choice(variant, production, symbol_id) ⇒ Hash[Symbol, untyped]
- #finish_inline_precedence(variant, production) ⇒ Hash[Symbol, untyped]
- #inline_action_step(production, inputs, lookahead, kind, rule) ⇒ Hash[Symbol, untyped]
- #inline_expansion_frame(production, symbol_id) ⇒ Hash[Symbol, untyped]
- #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[Hash[Symbol, untyped]]
- #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
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 66 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[untyped]
193 194 195 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 193 def append_inline_array(left, right) left.empty? ? right : left + right end |
#append_inline_symbol(variant, symbol_id) ⇒ Hash[Symbol, untyped]
152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 152 def append_inline_symbol(variant, symbol_id) # @type self: Normalizer rhs = variant.fetch(:rhs) terminal = @symbols.fetch(symbol_id).terminal? variant.merge( rhs: rhs + [symbol_id], logical_refs: variant.fetch(:logical_refs) + [[:physical, rhs.length]], precedence_override: terminal ? nil : variant[:precedence_override], precedence_contributes: terminal || variant.fetch(:precedence_contributes) ) end |
#charge_inline_expansion!(production) ⇒ void
This method returns an undefined value.
368 369 370 371 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 368 def charge_inline_expansion!(production) @inline_expansion_count += 1 enforce_inline_choice_limit!(@inline_expansion_count, production.origin[:loc]) end |
#clone_inline_expansion_frames(frames) ⇒ Array[Hash[Symbol, untyped]]
123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 123 def clone_inline_expansion_frames(frames) frames.map do |frame| variant = frame.fetch(:variant) frame.merge( variant: variant.merge( rhs: variant.fetch(:rhs).dup, steps: variant.fetch(:steps).dup, logical_refs: variant.fetch(:logical_refs).dup ) ) end end |
#combine_inline_variant(parent, child) ⇒ Hash[Symbol, untyped]
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 165 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) parent.merge( rhs: append_inline_array(parent.fetch(:rhs), child.fetch(:rhs)), steps: append_inline_array(parent.fetch(:steps), steps), logical_refs: parent.fetch(:logical_refs) + [ remap_inline_reference(child.fetch(:output_ref), physical_offset, step_offset) ], 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] ) end |
#combined_inline_precedence(parent, child) ⇒ Integer?
198 199 200 201 202 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 198 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[Hash[Symbol, untyped]]
186 187 188 189 190 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 186 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
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 100 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 choice = finish_inline_choice(completed, current, frame.fetch(: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
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 292 def composed_inline_action(production, physical_length, steps) action = production.action 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: action&.location || production.origin.fetch(:loc), composition: composition ) end |
#composed_inline_production(production, variant, id) ⇒ IR::Production
276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 276 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) ⇒ Hash[Symbol, untyped]
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 311 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
359 360 361 362 363 364 365 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 359 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.
51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 51 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) ⇒ Hash[Symbol, untyped]
137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 137 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.
374 375 376 377 378 379 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 374 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[Hash[Symbol, untyped]]
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.
37 38 39 40 41 42 43 44 45 46 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 37 def (production, definitions) # @type self: Normalizer variants = [] #: Array[Hash[Symbol, untyped]] worklist = [[inline_expansion_frame(production, nil)]] #: Array[Array[Hash[Symbol, untyped]]] until worklist.empty? frames = worklist.pop || raise(Ibex::Error, "internal inline expansion worklist underflow") drain_inline_expansion_frames(frames, definitions, worklist, variants, production.origin[:loc]) end variants end |
#expand_inline_rules ⇒ void
This method returns an undefined value.
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 10 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) ⇒ Hash[Symbol, untyped]
243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 243 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 ) end |
#finish_inline_precedence(variant, production) ⇒ Hash[Symbol, untyped]
233 234 235 236 237 238 239 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 233 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) ) end |
#inline_action_step(production, inputs, lookahead, kind, rule) ⇒ Hash[Symbol, untyped]
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 258 def inline_action_step(production, inputs, lookahead, kind, rule) action = production.action { kind: kind, rule: rule, code: action&.code, loc: action&.location || production.origin.fetch(:loc), 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) ⇒ Hash[Symbol, untyped]
118 119 120 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 118 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
338 339 340 341 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 338 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
349 350 351 352 353 354 355 356 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 349 def inline_production_expansion(production, variant) existing = production.expansion { parameter: existing&.dig(:parameter) || variant[:parameter], inline: { rule: variant.fetch(:inline_rule) }, include_chain: existing&.dig(:include_chain) || [] } end |
#inline_source_provenance(location) ⇒ IR::source_provenance
344 345 346 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 344 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.
382 383 384 385 386 387 388 389 390 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 382 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
403 404 405 406 407 408 409 410 411 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 403 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]
224 225 226 227 228 229 230 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 224 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[Hash[Symbol, untyped]]
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 206 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 step.merge( inputs: inputs, stack_inputs: parent.fetch(:logical_refs) + stack_inputs, lookahead: step.fetch(:lookahead) + physical_offset ) end end |
#remap_inline_symbol(definition, id) ⇒ IR::GrammarSymbol
393 394 395 396 397 398 399 400 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 393 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
330 331 332 333 334 335 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 330 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.
90 91 92 93 94 95 96 |
# File 'lib/ibex/normalize/inline_expansion.rb', line 90 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 |