Class: Ibex::Runtime::SyntaxRepairer

Inherits:
Object
  • Object
show all
Defined in:
lib/ibex/runtime/syntax_repair.rb,
sig/ibex/runtime/syntax_repair.rbs

Overview

Executes one fresh syntax-only repair attempt without mutating its source SyntaxSession. Generated lexer actions retain the acknowledged trust profile; parser production actions remain suppressed.

Instance Method Summary collapse

Constructor Details

#initialize(parser_class, source, baseline, execution_profile:, resource_limits:, limits:, cancellation:, policy:, token_text:) ⇒ SyntaxRepairer

Returns a new instance of SyntaxRepairer.

RBS:

  • (Class parser_class, CST::SourceText source, SyntaxSessionResult baseline, execution_profile: Symbol, resource_limits: ResourceLimits, limits: SyntaxSessionLimits, cancellation: CancellationToken?, policy: RepairPolicy, token_text: Hash[String, String]) -> void

Parameters:



230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/ibex/runtime/syntax_repair.rb', line 230

def initialize(parser_class, source, baseline, execution_profile:, resource_limits:, limits:, cancellation:,
               policy:, token_text:)
  @parser_class = parser_class
  @source = source
  @baseline = baseline
  @execution_profile = execution_profile
  @resource_limits = resource_limits
  @limits = limits
  @cancellation = cancellation
  @policy = validate_policy(policy)
  @token_text = validate_token_text(token_text)
end

Instance Method Details

#bounded_failure(status, reason, parsed, diagnostics) ⇒ SyntaxRepairResult

RBS:

  • (Symbol status, Symbol reason, CST::SyntaxResult parsed, Array[SyntaxSessionDiagnostic] diagnostics) -> SyntaxRepairResult

Parameters:

Returns:



412
413
414
415
416
417
# File 'lib/ibex/runtime/syntax_repair.rb', line 412

def bounded_failure(status, reason, parsed, diagnostics)
  SyntaxRepairResult.new(
    status: status, bounded_status: status, reason: reason, plan: nil, text_edits: [],
    syntax_root: parsed.syntax_root, diagnostics: diagnostics, updated_source: nil, validation: nil
  )
end

#build_selected(capture, parsed, diagnostics) ⇒ SyntaxRepairResult

RBS:

  • ([RepairPlan, Array[RepairInput]] capture, CST::SyntaxResult parsed, Array[SyntaxSessionDiagnostic] diagnostics) -> SyntaxRepairResult

Parameters:

Returns:



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/ibex/runtime/syntax_repair.rb', line 281

def build_selected(capture, parsed, diagnostics)
  runtime_plan, inputs = capture
  projected = project_edits(runtime_plan, inputs)
  plan = SyntaxRepairPlan.new(runtime_plan: runtime_plan, edits: projected)
  return unavailable(:missing_token_text, parsed, diagnostics, plan: plan) if
    projected.any? { |edit| %i[insert replace].include?(edit.kind) && edit.replacement_text.nil? }

  text_edits = normalize_text_edits(projected)
  validate_edit_limits!(text_edits)
  updated_source = @source.apply(text_edits)
  enforce_limit!(:source_bytes, @limits.max_source_bytes, updated_source.bytesize)
  cancellation_checkpoint!
  validation = fresh_validation(updated_source)
  cancellation_checkpoint!
  return unavailable(:validation_source_not_consumed, parsed, diagnostics, plan: plan) unless
    updated_source.text.start_with?(validation.syntax_root.to_source)

  status = validation_status(validation, updated_source)
  SyntaxRepairResult.new(
    status: status, bounded_status: :selected, reason: nil, plan: plan, text_edits: text_edits,
    syntax_root: parsed.syntax_root, diagnostics: diagnostics, updated_source: updated_source,
    validation: validation
  )
rescue ArgumentError => e
  return unavailable(:overlapping_text_edits, parsed, diagnostics, plan: plan) if
    e.message.include?("text edits overlap")

  raise
end

#callSyntaxRepairResult

RBS:

  • () -> SyntaxRepairResult

Returns:



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/ibex/runtime/syntax_repair.rb', line 244

def call
  cancellation_checkpoint!
  parser = @parser_class.__send__(:new, resource_limits: @resource_limits)
  captures = install_capture(parser)
  parser.repair_policy = @policy
  parser.observe { |_event| cancellation_checkpoint! }
  parsed = parser.__send__(:parse_syntax_with_cache, @source, CST::NodeCache.new)
  cancellation_checkpoint!
  diagnostics = immutable_diagnostics(parsed.diagnostics)
  return unavailable(:repair_source_not_consumed, parsed, diagnostics) unless
    @source.text.start_with?(parsed.syntax_root.to_source)

  outcomes = parser.__send__(:syntax_repair_search_results)
  return bounded_failure(:exhausted, :search_exhausted, parsed, diagnostics) if
    outcomes.any? { |outcome| outcome.status == :exhausted }
  return bounded_failure(:not_found, :no_repair_plan, parsed, diagnostics) if captures.empty?
  return unavailable(:multiple_repair_segments, parsed, diagnostics) unless captures.one?

  build_selected(captures.fetch(0), parsed, diagnostics)
rescue ResourceLimitError => e
  raise SyntaxSessionResourceLimitError.new(resource: e.resource, limit: e.limit, observed: e.observed), cause: e
end

#cancellation_checkpoint!void

This method returns an undefined value.

RBS:

  • () -> void



483
484
485
486
487
# File 'lib/ibex/runtime/syntax_repair.rb', line 483

def cancellation_checkpoint!
  return unless @cancellation&.cancelled?

  raise SyntaxSessionCancelled, "syntax repair operation was cancelled"
end

#enforce_limit!(resource, limit, observed) ⇒ void

This method returns an undefined value.

RBS:

  • (Symbol resource, Integer limit, Integer observed) -> void

Parameters:

  • resource (Symbol)
  • limit (Integer)
  • observed (Integer)


476
477
478
479
480
# File 'lib/ibex/runtime/syntax_repair.rb', line 476

def enforce_limit!(resource, limit, observed)
  return unless observed > limit

  raise SyntaxSessionResourceLimitError.new(resource: resource, limit: limit, observed: observed)
end

#first_diagnostic_byte(result) ⇒ Integer?

RBS:

  • (SyntaxSessionResult result) -> Integer?

Parameters:

Returns:

  • (Integer, nil)


405
406
407
408
# File 'lib/ibex/runtime/syntax_repair.rb', line 405

def first_diagnostic_byte(result)
  value = result.diagnostics.first&.data&.dig("location", "start_byte")
  value if value.is_a?(Integer)
end

#fresh_validation(source) ⇒ SyntaxSessionResult

RBS:

  • (CST::SourceText source) -> SyntaxSessionResult

Parameters:

Returns:



383
384
385
386
387
388
389
# File 'lib/ibex/runtime/syntax_repair.rb', line 383

def fresh_validation(source)
  SyntaxSession.new(
    @parser_class, source, execution_profile: @execution_profile,
                           resource_limits: @resource_limits, limits: @limits,
                           cancellation: @cancellation, blender: false
  ).result
end

#immutable_diagnostics(diagnostics) ⇒ Array[SyntaxSessionDiagnostic]

RBS:

  • (Array[Object] diagnostics) -> Array[SyntaxSessionDiagnostic]

Parameters:

  • diagnostics (Array[Object])

Returns:



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/ibex/runtime/syntax_repair.rb', line 429

def immutable_diagnostics(diagnostics)
  diagnostics.map do |diagnostic|
    if diagnostic.is_a?(ParseError)
      SyntaxSessionDiagnostic.new(
        kind: :parse_error,
        data: {
          message: diagnostic.message, token_id: diagnostic.token_id, token_name: diagnostic.token_name,
          expected_tokens: diagnostic.expected_tokens, location: EventSanitizer.location(diagnostic.location)
        }
      )
    else
      data = diagnostic #: Hash[Object?, Object?]
      SyntaxSessionDiagnostic.new(kind: :syntax_error, data: data)
    end
  end.freeze
end

#input_range(input) ⇒ [ Integer, Integer ]

RBS:

  • (RepairInput input) -> [Integer, Integer]

Parameters:

Returns:

  • ([ Integer, Integer ])


328
329
330
331
332
333
334
335
336
337
# File 'lib/ibex/runtime/syntax_repair.rb', line 328

def input_range(input)
  location = input.location
  start_byte = location_value(location, :start_byte)
  end_byte = location_value(location, :end_byte)
  unless start_byte.is_a?(Integer) && end_byte.is_a?(Integer)
    raise ArgumentError, "syntax repair requires byte-oriented generated lexer locations"
  end

  [start_byte, end_byte]
end

#install_capture(parser) ⇒ Array[[ RepairPlan, Array[RepairInput] ]]

RBS:

  • (Parser parser) -> Array[[RepairPlan, Array[RepairInput]]]

Parameters:

Returns:



270
271
272
273
274
275
276
277
# File 'lib/ibex/runtime/syntax_repair.rb', line 270

def install_capture(parser)
  captures = [] #: Array[[RepairPlan, Array[RepairInput]]]
  parser.define_singleton_method(:on_repair) do |plan|
    inputs = __send__(:syntax_repair_inputs)
    captures << [plan, inputs]
  end
  captures
end

#location_value(location, key) ⇒ Object? #location_value(location, key) ⇒ Object

Overloads:

  • #location_value(location, key) ⇒ Object?

    Parameters:

    • location (Object)
    • key (Symbol)

    Returns:

    • (Object, nil)
  • #location_value(location, key) ⇒ Object

    Parameters:

    • location (Object)
    • key (Symbol)

    Returns:

    • (Object)

RBS:

  • (Object location, Symbol key) -> Object?

  • (untyped location, Symbol key) -> untyped



448
449
450
451
452
453
454
455
456
457
# File 'lib/ibex/runtime/syntax_repair.rb', line 448

def location_value(location, key)
  return location.public_send(key) if location.respond_to?(key)

  if location.is_a?(Hash)
    hash = location #: Hash[Object, Object]
    return hash[key] || hash[key.to_s]
  end

  nil
end

#normalize_text_edits(edits) ⇒ Array[CST::TextEdit]

RBS:

  • (Array[SyntaxRepairEdit] edits) -> Array[CST::TextEdit]

Parameters:

Returns:



367
368
369
370
371
372
373
# File 'lib/ibex/runtime/syntax_repair.rb', line 367

def normalize_text_edits(edits)
  CST::TextEdit.normalize(edits.map do |edit|
    insert_text = edit.replacement_text || raise(ArgumentError, "missing token text")
    delete_length = edit.kind == :insert ? 0 : edit.end_byte - edit.start_byte
    CST::TextEdit.new(start: edit.start_byte, delete_length: delete_length, insert_text: insert_text)
  end)
end

#original_text(start_byte, end_byte) ⇒ String

RBS:

  • (Integer start_byte, Integer end_byte) -> String

Parameters:

  • start_byte (Integer)
  • end_byte (Integer)

Returns:

  • (String)


362
363
364
# File 'lib/ibex/runtime/syntax_repair.rb', line 362

def original_text(start_byte, end_byte)
  (@source.text.byteslice(start_byte, end_byte - start_byte) || "".b).freeze
end

#project_edits(plan, inputs) ⇒ Array[SyntaxRepairEdit]

RBS:

  • (RepairPlan plan, Array[RepairInput] inputs) -> Array[SyntaxRepairEdit]

Parameters:

Returns:



312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/ibex/runtime/syntax_repair.rb', line 312

def project_edits(plan, inputs)
  plan.edits.map do |edit|
    input = inputs.fetch(edit.position)
    start_byte, end_byte = input_range(input)
    replacement = replacement_text(edit)
    end_byte = start_byte if edit.kind == :insert
    SyntaxRepairEdit.new(
      kind: edit.kind, position: edit.position, token_id: edit.token_id,
      token_name: edit.token_name, cost: edit.cost, start_byte: start_byte,
      end_byte: end_byte, original_text: edit.kind == :insert ? "".b : original_text(start_byte, end_byte),
      replacement_text: replacement
    )
  end
end

#punctuation_literal(name) ⇒ String?

RBS:

  • (String name) -> String?

Parameters:

  • name (String)

Returns:

  • (String, nil)


350
351
352
353
354
355
356
357
358
359
# File 'lib/ibex/runtime/syntax_repair.rb', line 350

def punctuation_literal(name)
  inner = if name.length >= 2 && ["'", '"'].include?(name[0]) && name[-1] == name[0]
            name[1...-1]
          else
            name
          end
  return unless inner && !inner.empty? && inner.match?(/\A[[:punct:]]+\z/)

  inner.b.freeze
end

#replacement_text(edit) ⇒ String?

RBS:

  • (RepairEdit edit) -> String?

Parameters:

Returns:

  • (String, nil)


340
341
342
343
344
345
346
347
# File 'lib/ibex/runtime/syntax_repair.rb', line 340

def replacement_text(edit)
  return "".b if edit.kind == :delete

  explicit = @token_text[edit.token_name]
  return explicit if explicit

  punctuation_literal(edit.token_name)
end

#unavailable(reason, parsed, diagnostics, plan: nil) ⇒ SyntaxRepairResult

RBS:

  • (Symbol reason, CST::SyntaxResult parsed, Array[SyntaxSessionDiagnostic] diagnostics, ?plan: SyntaxRepairPlan?) -> SyntaxRepairResult

Parameters:

Returns:



421
422
423
424
425
426
# File 'lib/ibex/runtime/syntax_repair.rb', line 421

def unavailable(reason, parsed, diagnostics, plan: nil)
  SyntaxRepairResult.new(
    status: :unavailable, bounded_status: :selected, reason: reason, plan: plan, text_edits: [],
    syntax_root: parsed.syntax_root, diagnostics: diagnostics, updated_source: nil, validation: nil
  )
end

#validate_edit_limits!(edits) ⇒ void

This method returns an undefined value.

RBS:

  • (Array[CST::TextEdit] edits) -> void

Parameters:



376
377
378
379
380
# File 'lib/ibex/runtime/syntax_repair.rb', line 376

def validate_edit_limits!(edits)
  enforce_limit!(:edits_per_operation, @limits.max_edits_per_operation, edits.length)
  inserted = edits.sum { |edit| edit.insert_text.bytesize }
  enforce_limit!(:inserted_bytes, @limits.max_inserted_bytes, inserted)
end

#validate_policy(policy) ⇒ RepairPolicy

RBS:

  • (RepairPolicy policy) -> RepairPolicy

Parameters:

Returns:



460
461
462
463
464
# File 'lib/ibex/runtime/syntax_repair.rb', line 460

def validate_policy(policy)
  return policy if policy.is_a?(RepairPolicy)

  raise ArgumentError, "policy must be an Ibex::Runtime::RepairPolicy"
end

#validate_token_text(value) ⇒ Hash[String, String]

RBS:

  • (Hash[String, String] value) -> Hash[String, String]

Parameters:

  • value (Hash[String, String])

Returns:

  • (Hash[String, String])


467
468
469
470
471
472
473
# File 'lib/ibex/runtime/syntax_repair.rb', line 467

def validate_token_text(value)
  unless value.is_a?(Hash) && value.all? { |name, text| name.is_a?(String) && text.is_a?(String) && !text.empty? }
    raise ArgumentError, "token_text must map String token names to nonempty String source bytes"
  end

  value.to_h { |name, text| [name.dup.freeze, text.b.freeze] }.freeze
end

#validation_status(validation, source) ⇒ Symbol

RBS:

  • (SyntaxSessionResult validation, CST::SourceText source) -> Symbol

Parameters:

Returns:

  • (Symbol)


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

def validation_status(validation, source)
  return :accepted if validation.success? && validation.syntax_root.to_source == source.text

  baseline_count = @baseline.diagnostics.length
  current_count = validation.diagnostics.length
  return :progress if current_count < baseline_count

  before = first_diagnostic_byte(@baseline)
  after = first_diagnostic_byte(validation)
  after.is_a?(Integer) && before.is_a?(Integer) && after > before ? :progress : :rejected
end