Class: Ibex::Runtime::SyntaxSession

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

Overview

Generated-language syntax boundary backed by the existing Red/Green CST incremental engine. It does not execute parser production actions.

Constant Summary collapse

TRUSTED_PROFILE =

Signature:

  • Symbol

Returns:

  • (Symbol)
:trusted_application_code

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parser_class, source, execution_profile:, resource_limits: nil, limits: nil, cancellation: nil, blender: true) ⇒ SyntaxSession

Returns a new instance of SyntaxSession.

RBS:

  • (Class parser_class, String | CST::SourceText source, execution_profile: Symbol?, ?resource_limits: ResourceLimits?, ?limits: SyntaxSessionLimits?, ?cancellation: CancellationToken?, ?blender: bool) -> void

Parameters:



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/ibex/runtime/syntax_session.rb', line 214

def initialize(
  parser_class,
  source,
  execution_profile:,
  resource_limits: nil,
  limits: nil,
  cancellation: nil,
  blender: true
)
  @execution_profile = validate_execution_profile(parser_class, execution_profile)
  @limits = limits || SyntaxSessionLimits.new #: SyntaxSessionLimits
  unless @limits.is_a?(SyntaxSessionLimits)
    raise ArgumentError, "limits must be an Ibex::Runtime::SyntaxSessionLimits"
  end
  unless cancellation.nil? || cancellation.is_a?(CancellationToken)
    raise ArgumentError, "cancellation must be an Ibex::Runtime::CancellationToken or nil"
  end

  @cancellation = cancellation #: CancellationToken?
  @parser_class = parser_class #: Class
  @resource_limits = resource_limits || ResourceLimits.new #: ResourceLimits
  @mutex = Mutex.new #: Mutex
  @revision = 0 #: Integer
  @operation_expected_tokens = [] #: Array[String]
  @operation_fallback_reasons = [] #: Array[Symbol]

  source_text = normalize_source(source)
  enforce_limit!(:source_bytes, @limits.max_source_bytes, source_text.bytesize)
  cancellation_checkpoint!
  event_observer = ->(event, parser) { handle_runtime_event(event, parser) } #: Proc
  @incremental = CST::IncrementalParseSession.new(
    parser_class,
    source_text,
    resource_limits: @resource_limits,
    blender: blender,
    event_observer: event_observer
  ) #: CST::IncrementalParseSession
  @result = snapshot(@incremental.result) #: SyntaxSessionResult
rescue ResourceLimitError => e
  raise SyntaxSessionResourceLimitError.new(resource: e.resource, limit: e.limit, observed: e.observed), cause: e
end

Instance Attribute Details

#execution_profileSymbol (readonly)

Signature:

  • Symbol

Returns:

  • (Symbol)


208
209
210
# File 'lib/ibex/runtime/syntax_session.rb', line 208

def execution_profile
  @execution_profile
end

#limitsSyntaxSessionLimits (readonly)

Signature:

  • SyntaxSessionLimits

Returns:



209
210
211
# File 'lib/ibex/runtime/syntax_session.rb', line 209

def limits
  @limits
end

Instance Method Details

#apply_edits(edits) ⇒ SyntaxSessionResult

Apply byte-oriented edits against the current source.

RBS:

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

Parameters:

Returns:



268
269
270
# File 'lib/ibex/runtime/syntax_session.rb', line 268

def apply_edits(edits)
  @mutex.synchronize { apply_edits_locked(edits) }
end

#apply_edits_locked(edits) ⇒ SyntaxSessionResult

RBS:

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

Parameters:

Returns:



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/ibex/runtime/syntax_session.rb', line 288

def apply_edits_locked(edits)
  raise ArgumentError, "edits must be an Array" unless edits.is_a?(Array)

  cancellation_checkpoint!
  enforce_limit!(:edits_per_operation, @limits.max_edits_per_operation, edits.length)
  normalized = CST::TextEdit.normalize(edits)
  inserted_bytes = normalized.sum { |edit| edit.insert_text.bytesize }
  enforce_limit!(:inserted_bytes, @limits.max_inserted_bytes, inserted_bytes)
  next_source = @incremental.source_text.apply(normalized)
  enforce_limit!(:source_bytes, @limits.max_source_bytes, next_source.bytesize)
  return @result if normalized.empty?

  reset_operation_evidence
  parsed = @incremental.edit(normalized)
  @revision += 1
  @result = snapshot(parsed)
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



345
346
347
348
349
# File 'lib/ibex/runtime/syntax_session.rb', line 345

def cancellation_checkpoint!
  return unless @cancellation&.cancelled?

  raise SyntaxSessionCancelled, "syntax session 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)


338
339
340
341
342
# File 'lib/ibex/runtime/syntax_session.rb', line 338

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

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

#handle_runtime_event(event, parser) ⇒ void

This method returns an undefined value.

RBS:

  • (Event event, Parser parser) -> void

Parameters:



352
353
354
355
356
357
358
359
360
361
362
# File 'lib/ibex/runtime/syntax_session.rb', line 352

def handle_runtime_event(event, parser)
  cancellation_checkpoint!
  case event.type
  when :error
    parser.expected_tokens.each do |token|
      @operation_expected_tokens << token unless @operation_expected_tokens.include?(token)
    end
  when :cst_fallback
    @operation_fallback_reasons << event.data.fetch("reason").to_s.to_sym
  end
end

#immutable_diagnostics(diagnostics) ⇒ Array[SyntaxSessionDiagnostic]

RBS:

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

Parameters:

  • diagnostics (Array[Object?])

Returns:



395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/ibex/runtime/syntax_session.rb', line 395

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.is_a?(Hash) ? diagnostic : { message: diagnostic.to_s }
      SyntaxSessionDiagnostic.new(kind: :syntax_error, data: data)
    end
  end.freeze
end

#normalize_source(source) ⇒ CST::SourceText

RBS:

  • (String | CST::SourceText source) -> CST::SourceText

Parameters:

Returns:



327
328
329
330
331
332
333
334
335
# File 'lib/ibex/runtime/syntax_session.rb', line 327

def normalize_source(source)
  return source if source.is_a?(CST::SourceText)
  raise ArgumentError, "source must be a String or Ibex::Runtime::CST::SourceText" unless source.is_a?(String)
  unless source.encoding.ascii_compatible?
    raise Encoding::CompatibilityError, "syntax session source must use an ASCII-compatible encoding"
  end

  CST::SourceText.new(source)
end

#repair(policy: RepairPolicy.new, token_text: {}) ⇒ SyntaxRepairResult

Propose byte edits through the existing bounded repair search, then validate them with a fresh syntax-only parse. The session is unchanged.

RBS:

  • (?policy: RepairPolicy, ?token_text: Hash[String, String]) -> SyntaxRepairResult

Parameters:

  • policy: (RepairPolicy) (defaults to: RepairPolicy.new)
  • token_text: (Hash[String, String]) (defaults to: {})

Returns:



275
276
277
278
279
280
281
282
283
# File 'lib/ibex/runtime/syntax_session.rb', line 275

def repair(policy: RepairPolicy.new, token_text: {})
  @mutex.synchronize do
    SyntaxRepairer.new(
      @parser_class, @incremental.source_text, @result,
      execution_profile: @execution_profile, resource_limits: @resource_limits,
      limits: @limits, cancellation: @cancellation, policy: policy, token_text: token_text
    ).call
  end
end

#reset_operation_evidencevoid

This method returns an undefined value.

RBS:

  • () -> void



365
366
367
368
# File 'lib/ibex/runtime/syntax_session.rb', line 365

def reset_operation_evidence
  @operation_expected_tokens = [] #: Array[String]
  @operation_fallback_reasons = [] #: Array[Symbol]
end

#resultSyntaxSessionResult

RBS:

  • () -> SyntaxSessionResult

Returns:



262
263
264
# File 'lib/ibex/runtime/syntax_session.rb', line 262

def result
  @mutex.synchronize { @result }
end

#snapshot(parsed) ⇒ SyntaxSessionResult

RBS:

  • (CST::SyntaxResult parsed) -> SyntaxSessionResult

Parameters:

Returns:



371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/ibex/runtime/syntax_session.rb', line 371

def snapshot(parsed)
  token_count = @incremental.token_memo.tokens.length
  reused_tokens = if @incremental.last_full_fallback?
                    0
                  else
                    @incremental.last_relex_result&.reused_count.to_i
                  end
  metrics = SyntaxSessionMetrics.new(
    reused_ratio: parsed.reused_ratio,
    reused_tokens: reused_tokens,
    token_count: token_count,
    fallback_reasons: @operation_fallback_reasons
  )
  SyntaxSessionResult.new(
    revision: @revision,
    syntax_root: parsed.syntax_root,
    diagnostics: immutable_diagnostics(parsed.diagnostics),
    expected_tokens: @operation_expected_tokens,
    metrics: metrics,
    status: parsed.diagnostics.empty? ? :ok : :syntax_error
  )
end

#source_textCST::SourceText

RBS:

  • () -> CST::SourceText

Returns:



257
258
259
# File 'lib/ibex/runtime/syntax_session.rb', line 257

def source_text
  @mutex.synchronize { @incremental.source_text }
end

#validate_execution_profile(parser_class, acknowledged) ⇒ Symbol

RBS:

  • (Class parser_class, Symbol? acknowledged) -> Symbol

Parameters:

  • parser_class (Class)
  • acknowledged (Symbol, nil)

Returns:

  • (Symbol)


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

def validate_execution_profile(parser_class, acknowledged)
  unless parser_class.is_a?(Class) && parser_class <= Parser
    raise ArgumentError, "parser_class must inherit from Ibex::Runtime::Parser"
  end

  profile = parser_class.syntax_execution_profile
  unless profile == TRUSTED_PROFILE
    raise SyntaxSessionTrustError,
          "declarative syntax artifacts are not available; generated parser profile was #{profile.inspect}"
  end
  return profile if acknowledged == profile

  raise SyntaxSessionTrustError,
        "syntax_session executes generated lexer actions; " \
        "pass execution_profile: #{profile.inspect} to acknowledge it"
end