Class: Ibex::Runtime::CST::IncrementalParseSession

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

Overview

A mutable, single-owner session around immutable source and syntax results.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parser_class, source_text, resource_limits: nil, blender: true) ⇒ IncrementalParseSession

Returns a new instance of IncrementalParseSession.

RBS:

  • (Class parser_class, SourceText source_text, ?resource_limits: ResourceLimits?, ?blender: bool) -> void

Parameters:

  • parser_class (Class)
  • source_text (SourceText)
  • resource_limits: (ResourceLimits, nil) (defaults to: nil)
  • blender: (Boolean) (defaults to: true)


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 26

def initialize(parser_class, source_text, resource_limits: nil, blender: true)
  unless source_text.is_a?(SourceText)
    raise ArgumentError, "incremental_session requires an Ibex::Runtime::CST::SourceText"
  end

  @resource_limits = resource_limits || ResourceLimits.new #: ResourceLimits
  @parser = parser_class.__send__(:new, resource_limits: @resource_limits) #: Parser
  validate_parser!
  @cache = NodeCache.new #: NodeCache
  @source_text = source_text
  @last_relex_result = nil #: RelexResult?
  @last_blender = nil #: Blender?
  @blender_enabled = blender
  @mutex = Mutex.new
  @result, @token_memo, @parse_memo = parse_current(0.0)
  enforce_memo_budget!
end

Instance Attribute Details

#last_blenderBlender? (readonly)

Signature:

  • Blender?

Returns:



17
18
19
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 17

def last_blender
  @last_blender
end

#last_relex_resultRelexResult? (readonly)

Signature:

  • RelexResult?

Returns:



16
17
18
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 16

def last_relex_result
  @last_relex_result
end

#parse_memoParseMemo (readonly)

Signature:

  • ParseMemo

Returns:



15
16
17
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 15

def parse_memo
  @parse_memo
end

#resultSyntaxResult (readonly)

Signature:

  • SyntaxResult

Returns:



13
14
15
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 13

def result
  @result
end

#source_textSourceText (readonly)

Signature:

  • SourceText

Returns:



12
13
14
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 12

def source_text
  @source_text
end

#token_memoTokenMemo (readonly)

Signature:

  • TokenMemo

Returns:



14
15
16
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 14

def token_memo
  @token_memo
end

Instance Method Details

#blender_reused_ratio(blender, root, relexed) ⇒ Float

RBS:

  • (Blender blender, GreenNode root, RelexResult relexed) -> Float

Parameters:

Returns:

  • (Float)


118
119
120
121
122
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 118

def blender_reused_ratio(blender, root, relexed)
  return relexed.reused_ratio unless @blender_enabled

  blender.reused_descendants.fdiv(root.descendant_count)
end

#build_blender(old_root, old_memo, lexed, edits) ⇒ Blender

RBS:

  • (SyntaxNode old_root, ParseMemo old_memo, LexedSyntax lexed, Array[TextEdit] edits) -> Blender

Parameters:

Returns:



87
88
89
90
91
92
93
94
95
96
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 87

def build_blender(old_root, old_memo, lexed, edits)
  Blender.new(
    old_root: old_root,
    parse_memo: old_memo,
    lexed: lexed,
    edits: edits,
    max_decomposed_nodes: @resource_limits.max_incremental_decomposed_nodes,
    enabled: @blender_enabled
  )
end

#edit(edits) ⇒ SyntaxResult

Apply edits expressed against the current source and return syntax only.

RBS:

  • (Array[TextEdit] edits) -> SyntaxResult

Parameters:

Returns:



46
47
48
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 46

def edit(edits)
  @mutex.synchronize { edit_locked(TextEdit.normalize(edits)) }
end

#edit_locked(edits) ⇒ SyntaxResult

RBS:

  • (Array[TextEdit] edits) -> SyntaxResult

Parameters:

Returns:



64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 64

def edit_locked(edits)
  return @result if edits.empty?

  previous_memo = @token_memo
  previous_root = @result.syntax_root
  previous_parse_memo = @parse_memo
  @source_text = @source_text.apply(edits)
  lexed = scan_current
  return finish_full_fallback(previous_memo, edits, :lexical_error) unless lexed

  relexed = Relexer.reconcile(previous_memo, lexed.memo, edits)
  blender = build_blender(previous_root, previous_parse_memo, lexed.with_memo(relexed.memo), edits)
  finish_blended_edit(relexed, blender)
end

#emit_reuse_event(blender, relexed, reused_ratio) ⇒ void

This method returns an undefined value.

RBS:

  • (Blender blender, RelexResult relexed, Float reused_ratio) -> void

Parameters:



125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 125

def emit_reuse_event(blender, relexed, reused_ratio)
  @parser.__send__(
    :emit_incremental_event,
    :cst_reuse,
    {
      "stage" => blender.reused_descendants.positive? ? "subtree" : "lexical",
      "reused_tokens" => relexed.reused_count,
      "token_count" => relexed.memo.tokens.length,
      "reused_ratio" => reused_ratio
    }
  )
end

#empty_parse_memo(root) ⇒ ParseMemo

Error trees cannot be reused, but retain position-aligned disposable memo storage.

RBS:

  • (GreenNode root) -> ParseMemo

Parameters:

Returns:



170
171
172
173
174
175
176
177
178
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 170

def empty_parse_memo(root)
  tables = @parser.__send__(:parser_tables) #: Hash[Symbol, untyped]
  ParseMemo.new(
    left_states: Array.new(root.descendant_count),
    grammar_digest: tables.fetch(:grammar_digest),
    state_count: tables.fetch(:state_count),
    production_count: tables.fetch(:production_count)
  )
end

#enforce_memo_budget!void

This method returns an undefined value.

RBS:

  • () -> void



213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 213

def enforce_memo_budget!
  observed = @token_memo.estimated_bytes + @parse_memo.estimated_bytes
  limit = @resource_limits.max_session_memo_bytes
  return unless observed > limit

  @parser.__send__(
    :emit_incremental_event,
    :cst_fallback,
    { "reason" => "memo_budget", "limit" => limit, "observed" => observed }
  )
  @cache.clear
  @token_memo = TokenMemo.from_root(@result.syntax_root)
end

#finish_blended_edit(relexed, blender) ⇒ SyntaxResult

RBS:

  • (RelexResult relexed, Blender blender) -> SyntaxResult

Parameters:

Returns:



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 99

def finish_blended_edit(relexed, blender)
  @last_relex_result = relexed
  @last_blender = blender
  parsed = @parser.__send__(:parse_syntax_token_source, blender, @cache)
  reused_ratio = blender_reused_ratio(blender, parsed.syntax_root.green, relexed)
  @token_memo = relexed.memo
  @parse_memo = @parser.__send__(:syntax_parse_memo) || empty_parse_memo(parsed.syntax_root.green)
  @result = SyntaxResult.new(
    syntax_root: parsed.syntax_root,
    diagnostics: parsed.diagnostics,
    reused_ratio: reused_ratio
  )
  report_blender_fallback(blender)
  enforce_memo_budget!
  emit_reuse_event(blender, relexed, reused_ratio)
  @result
end

#finish_full_fallback(previous_memo, edits, reason) ⇒ SyntaxResult

RBS:

  • (TokenMemo previous_memo, Array[TextEdit] edits, Symbol reason) -> SyntaxResult

Parameters:

Returns:



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 196

def finish_full_fallback(previous_memo, edits, reason)
  fresh_result, fresh_memo, fresh_parse_memo = parse_current(0.0)
  @last_relex_result = Relexer.reconcile(previous_memo, fresh_memo, edits)
  @last_blender = nil
  @token_memo = fresh_memo
  @parse_memo = fresh_parse_memo
  @result = fresh_result
  @parser.__send__(
    :emit_incremental_event,
    :cst_fallback,
    { "reason" => reason.to_s, "limit" => 0, "observed" => 0 }
  )
  enforce_memo_budget!
  @result
end

#observe {|arg0| ... } ⇒ Observation::Subscription

Observe parser and incremental runtime events.

RBS:

  • () { (Event) -> void } -> Observation::Subscription

Yields:

Yield Parameters:

Yield Returns:

  • (void)

Returns:



52
53
54
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 52

def observe(&observer)
  @parser.observe(&observer)
end

#parse_current(reused_ratio) ⇒ [ SyntaxResult, TokenMemo, ParseMemo ]

RBS:

  • (Float reused_ratio) -> [SyntaxResult, TokenMemo, ParseMemo]

Parameters:

  • reused_ratio (Float)

Returns:



155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 155

def parse_current(reused_ratio)
  parsed = @parser.__send__(:parse_syntax_with_cache, @source_text, @cache)
  result = SyntaxResult.new(
    syntax_root: parsed.syntax_root,
    diagnostics: parsed.diagnostics,
    reused_ratio: reused_ratio
  )
  states = @parser.__send__(:syntax_token_states)
  parse_memo = @parser.__send__(:syntax_parse_memo) || empty_parse_memo(result.syntax_root.green)

  [result, TokenMemo.from_root(result.syntax_root, states: states), parse_memo]
end

#report_blender_fallback(blender) ⇒ void

This method returns an undefined value.

RBS:

  • (Blender blender) -> void

Parameters:



181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 181

def report_blender_fallback(blender)
  return unless blender.fallback_reason

  @parser.__send__(
    :emit_incremental_event,
    :cst_fallback,
    {
      "reason" => blender.fallback_reason.to_s,
      "limit" => @resource_limits.max_incremental_decomposed_nodes,
      "observed" => blender.decomposed_nodes
    }
  )
end

#scan_currentLexedSyntax?

RBS:

  • () -> LexedSyntax?

Returns:



80
81
82
83
84
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 80

def scan_current
  @parser.__send__(:scan_syntax_with_cache, @source_text, @cache)
rescue ParseError
  nil
end

#unobserve(subscription) ⇒ Boolean

RBS:

  • (Observation::Subscription subscription) -> bool

Parameters:

Returns:

  • (Boolean)


57
58
59
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 57

def unobserve(subscription)
  @parser.unobserve(subscription)
end

#validate_parser!void

This method returns an undefined value.

RBS:

  • () -> void



139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 139

def validate_parser!
  unless @parser.is_a?(GeneratedLexer)
    raise IncrementalUnsupportedError, "incremental parsing requires a generated lexer"
  end

  tables = @parser.__send__(:parser_tables) #: Hash[Symbol, untyped]
  config = tables[:cst]
  unless tables.fetch(:format_version) >= 6 && config.is_a?(Hash)
    raise IncrementalUnsupportedError, "incremental parsing requires a format-v6 Red/Green CST parser"
  end
  return unless config.fetch(:trivia_policy) == :drop

  raise IncrementalUnsupportedError, "incremental parsing does not support drop trivia policy"
end