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, event_observer: nil) ⇒ IncrementalParseSession

Returns a new instance of IncrementalParseSession.

RBS:

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

Parameters:

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


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

def initialize(parser_class, source_text, resource_limits: nil, blender: true, event_observer: nil)
  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!
  @parser.observe { |event| event_observer.call(event, @parser) } if event_observer
  @cache = NodeCache.new #: NodeCache
  @source_text = source_text
  @last_relex_result = nil #: RelexResult?
  @last_blender = nil #: Blender?
  @last_full_fallback = false #: bool
  @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)


141
142
143
144
145
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 141

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:



109
110
111
112
113
114
115
116
117
118
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 109

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:



49
50
51
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 49

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

#edit_locked(edits) ⇒ SyntaxResult

RBS:

  • (Array[TextEdit] edits) -> SyntaxResult

Parameters:

Returns:



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 72

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

  previous_source = @source_text
  previous_memo = @token_memo
  previous_root = @result.syntax_root
  previous_parse_memo = @parse_memo
  previous_result = @result
  previous_relex_result = @last_relex_result
  previous_blender = @last_blender
  previous_full_fallback = @last_full_fallback
  @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)
rescue StandardError
  @source_text = previous_source
  @token_memo = previous_memo
  @parse_memo = previous_parse_memo
  @result = previous_result
  @last_relex_result = previous_relex_result
  @last_blender = previous_blender
  @last_full_fallback = previous_full_fallback
  raise
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:



148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 148

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:



193
194
195
196
197
198
199
200
201
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 193

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



237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 237

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:



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

def finish_blended_edit(relexed, blender)
  @last_relex_result = relexed
  @last_blender = blender
  @last_full_fallback = false
  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:



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 219

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
  @last_full_fallback = true
  @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

#last_full_fallback?Boolean

Whether the last completed edit discarded all incremental reuse and reparsed the current source from a fresh token stream.

RBS:

  • () -> bool

Returns:

  • (Boolean)


67
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 67

def last_full_fallback? = @last_full_fallback

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

Observe parser and incremental runtime events.

RBS:

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

Yields:

Yield Parameters:

Yield Returns:

  • (void)

Returns:



55
56
57
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 55

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:



178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 178

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:



204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 204

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:



102
103
104
105
106
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 102

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)


60
61
62
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 60

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

#validate_parser!void

This method returns an undefined value.

RBS:

  • () -> void



162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/ibex/runtime/cst/incremental/session.rb', line 162

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