flexr 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +74 -28
- data/benchmark/golden/calculator_lexer.sha256 +1 -1
- data/benchmark/golden/json_lexer.sha256 +1 -1
- data/benchmark/golden/regexp_tokenizer.sha256 +1 -1
- data/benchmark/golden/ruby_subset_lexer.sha256 +1 -1
- data/benchmark/golden/toy_lang_lexer.sha256 +1 -1
- data/benchmark/golden/with_lrama_lexer.sha256 +1 -1
- data/benchmark/golden/with_racc_lexer.sha256 +1 -1
- data/docs/README.md +2 -1
- data/docs/explanation/backends.md +13 -10
- data/docs/explanation/matching-semantics.md +4 -2
- data/docs/explanation/security-model.md +10 -3
- data/docs/explanation/unicode-and-encoding.md +3 -1
- data/docs/how-to/deploy-a-standalone-lexer.md +5 -3
- data/docs/how-to/generate-a-lexer.md +3 -3
- data/docs/how-to/handle-errors.md +4 -1
- data/docs/how-to/run-a-lexer-at-runtime.md +5 -0
- data/docs/how-to/track-token-locations.md +2 -1
- data/docs/how-to/tune-performance.md +3 -3
- data/docs/perf-log.md +16 -0
- data/docs/reference/actions.md +5 -2
- data/docs/reference/diagnostics.md +8 -0
- data/docs/reference/dsl.md +19 -5
- data/docs/reference/errors.md +6 -3
- data/docs/reference/generated-artifacts.md +29 -2
- data/docs/reference/public-api.md +7 -2
- data/docs/reference/regexp.md +8 -6
- data/docs/reference/runtime.md +20 -3
- data/docs/reference/tokens-and-locations.md +6 -4
- data/docs/releases/v1.1.0.md +44 -0
- data/lib/flexr/action_resolver.rb +15 -0
- data/lib/flexr/artifact_writer.rb +61 -0
- data/lib/flexr/automaton/accel.rb +9 -4
- data/lib/flexr/automaton/analysis.rb +47 -6
- data/lib/flexr/automaton/backend_cost_model.rb +40 -0
- data/lib/flexr/automaton/compiler.rb +50 -71
- data/lib/flexr/automaton/dfa.rb +106 -19
- data/lib/flexr/automaton/minimizer.rb +79 -31
- data/lib/flexr/automaton/nfa.rb +45 -13
- data/lib/flexr/automaton/types.rb +14 -0
- data/lib/flexr/cli.rb +11 -5
- data/lib/flexr/codegen/direct.rb +5 -41
- data/lib/flexr/codegen/table.rb +175 -69
- data/lib/flexr/codegen.rb +8 -0
- data/lib/flexr/configuration.rb +37 -0
- data/lib/flexr/dsl.rb +105 -39
- data/lib/flexr/errors.rb +1 -0
- data/lib/flexr/generated.rb +61 -35
- data/lib/flexr/generator.rb +47 -82
- data/lib/flexr/importer.rb +4 -40
- data/lib/flexr/options.rb +5 -3
- data/lib/flexr/rake_task.rb +6 -1
- data/lib/flexr/regexp/ast.rb +5 -2
- data/lib/flexr/regexp/normalizer.rb +62 -16
- data/lib/flexr/regexp/parser.rb +121 -46
- data/lib/flexr/regexp/tokenizer.rb +192 -67
- data/lib/flexr/runtime/buffer.rb +115 -9
- data/lib/flexr/runtime/core.rb +214 -47
- data/lib/flexr/runtime/errors.rb +64 -4
- data/lib/flexr/runtime/interpreter.rb +178 -71
- data/lib/flexr/runtime.rb +70 -0
- data/lib/flexr/source/passthrough.rb +62 -9
- data/lib/flexr/source/prism_reader.rb +175 -46
- data/lib/flexr/source/static_eval.rb +40 -4
- data/lib/flexr/source.rb +6 -0
- data/lib/flexr/unicode/data/properties.rb +1 -1
- data/lib/flexr/unicode/data.rb +11 -0
- data/lib/flexr/unicode/property.rb +3 -5
- data/lib/flexr/unicode/reference_regexp.rb +4 -0
- data/lib/flexr/unicode/version.rb +7 -0
- data/lib/flexr/version.rb +1 -1
- data/lib/flexr.rb +9 -76
- data/tools/coverage.rb +6 -1
- metadata +12 -1
data/lib/flexr/runtime/core.rb
CHANGED
|
@@ -3,20 +3,45 @@
|
|
|
3
3
|
module Flexr
|
|
4
4
|
module Runtime
|
|
5
5
|
def initialize(input, filename: nil, error_mode: :raise, max_token_size: 16 * 1024 * 1024,
|
|
6
|
-
|
|
6
|
+
max_lookahead_size: nil, max_buffer_size: 64 * 1024 * 1024,
|
|
7
|
+
max_state_stack: 1024, max_steps: nil, cancellation: nil, retain_input: true,
|
|
8
|
+
chunk_size: Runtime::Buffer::DEFAULT_CHUNK_SIZE)
|
|
7
9
|
raise ArgumentError, "max_token_size must be non-negative" if max_token_size.to_i.negative?
|
|
10
|
+
raise ArgumentError, "max_lookahead_size must be non-negative" if max_lookahead_size&.to_i&.negative?
|
|
11
|
+
raise ArgumentError, "max_buffer_size must be non-negative" if max_buffer_size.to_i.negative?
|
|
8
12
|
raise ArgumentError, "max_state_stack must be non-negative" if max_state_stack.to_i.negative?
|
|
13
|
+
raise ArgumentError, "max_steps must be non-negative" if max_steps&.to_i&.negative?
|
|
14
|
+
raise ArgumentError, "cancellation must respond to call" if cancellation && !cancellation.respond_to?(:call)
|
|
9
15
|
|
|
10
16
|
self.class.compile!
|
|
11
|
-
|
|
12
|
-
@
|
|
13
|
-
|
|
17
|
+
config = self.class.__flexr_config
|
|
18
|
+
@buffer = Runtime::Buffer.new(
|
|
19
|
+
input, chunk_size: chunk_size, max_buffer_size: max_buffer_size,
|
|
20
|
+
retain_input: retain_input, filename: filename
|
|
21
|
+
)
|
|
22
|
+
@string_input = input.is_a?(String)
|
|
23
|
+
@stable_input_end = input.bytesize if @string_input && input.frozen?
|
|
24
|
+
@valid_utf8_input = @string_input && input.frozen? && (config.encoding != Encoding::UTF_8 || begin
|
|
25
|
+
bytes = input.encoding == Encoding::UTF_8 ? input : input.dup.force_encoding(Encoding::UTF_8)
|
|
26
|
+
bytes.valid_encoding?
|
|
27
|
+
end)
|
|
28
|
+
@simple_location_input = @string_input && input.frozen? && retain_input &&
|
|
29
|
+
input.ascii_only? && !input.include?("\n")
|
|
30
|
+
@retain_input = retain_input
|
|
14
31
|
@filename = filename
|
|
15
32
|
@error_mode = error_mode
|
|
33
|
+
@token_kind = config.token_kind
|
|
34
|
+
@accel_mode = config.options.fetch(:accel, :auto)
|
|
16
35
|
@max_token_size = max_token_size.to_i
|
|
36
|
+
@token_limit_required = !@string_input || !input.frozen? || input.bytesize > @max_token_size
|
|
37
|
+
@max_lookahead_size = (max_lookahead_size || max_token_size).to_i
|
|
17
38
|
@max_state_stack = max_state_stack.to_i
|
|
39
|
+
@max_steps = max_steps&.to_i
|
|
40
|
+
@cancellation = cancellation
|
|
41
|
+
@steps = 0
|
|
18
42
|
@position = 0
|
|
19
43
|
@line = 1
|
|
44
|
+
@column = 1
|
|
20
45
|
@state = :initial
|
|
21
46
|
@state_stack = []
|
|
22
47
|
@pending = nil
|
|
@@ -25,18 +50,22 @@ module Flexr
|
|
|
25
50
|
@match_end = 0
|
|
26
51
|
@text_start = 0
|
|
27
52
|
@text_line = 1
|
|
53
|
+
@text_column = 1
|
|
28
54
|
@bol = true
|
|
29
55
|
@more_start = nil
|
|
30
56
|
@more_line = nil
|
|
57
|
+
@more_column = nil
|
|
31
58
|
@more_requested = false
|
|
32
|
-
@
|
|
59
|
+
@non_progress_signatures = {}
|
|
60
|
+
@active_rule = nil
|
|
33
61
|
@eof_fired_states = {}
|
|
34
62
|
@on_error = nil
|
|
35
63
|
@halted = false
|
|
36
64
|
@interpreter = nil
|
|
65
|
+
@generated_scanner = generated_runtime? && respond_to?(:scan_one, true)
|
|
37
66
|
end
|
|
38
67
|
|
|
39
|
-
attr_reader :filename, :error_mode, :buffer, :max_token_size
|
|
68
|
+
attr_reader :filename, :error_mode, :buffer, :max_token_size, :max_lookahead_size, :steps
|
|
40
69
|
|
|
41
70
|
attr_writer :on_error
|
|
42
71
|
|
|
@@ -49,6 +78,7 @@ module Flexr
|
|
|
49
78
|
|
|
50
79
|
loop do
|
|
51
80
|
return nil if @halted
|
|
81
|
+
consume_step! if scan_steps_guarded?
|
|
52
82
|
|
|
53
83
|
if eof? && @pending.nil?
|
|
54
84
|
eof_action = self.class.__flexr_spec.eof_rules[@state]
|
|
@@ -58,6 +88,7 @@ module Flexr
|
|
|
58
88
|
@match_end = @position
|
|
59
89
|
@text_start = @position
|
|
60
90
|
@text_line = @line
|
|
91
|
+
@text_column = @column
|
|
61
92
|
@matched = nil
|
|
62
93
|
instance_exec(&eof_action)
|
|
63
94
|
token = @pending
|
|
@@ -68,7 +99,8 @@ module Flexr
|
|
|
68
99
|
return nil
|
|
69
100
|
end
|
|
70
101
|
|
|
71
|
-
|
|
102
|
+
@active_rule = nil
|
|
103
|
+
match = if @generated_scanner
|
|
72
104
|
scan_one
|
|
73
105
|
else
|
|
74
106
|
(@interpreter ||= Runtime::Interpreter.new(self)).scan
|
|
@@ -84,22 +116,28 @@ module Flexr
|
|
|
84
116
|
end
|
|
85
117
|
@match_start = match.start_pos
|
|
86
118
|
@match_end = match.end_pos
|
|
119
|
+
scan_state = @state
|
|
87
120
|
if @more_start
|
|
88
121
|
@text_start = @more_start
|
|
89
122
|
@text_line = @more_line
|
|
123
|
+
@text_column = @more_column
|
|
90
124
|
else
|
|
91
125
|
@text_start = @match_start
|
|
92
126
|
@text_line = @line
|
|
127
|
+
@text_column = @column
|
|
93
128
|
end
|
|
94
129
|
@matched = nil
|
|
95
130
|
@more_requested = false
|
|
96
131
|
@position = match.end_pos
|
|
97
132
|
empty_match = match.end_pos == match.start_pos
|
|
133
|
+
@active_rule = match.rule
|
|
98
134
|
execute(match.rule)
|
|
99
135
|
finalize_more
|
|
100
|
-
|
|
101
|
-
|
|
136
|
+
ensure_progress!(match, scan_state, empty_match: empty_match) unless
|
|
137
|
+
@position > match.start_pos && @non_progress_signatures.empty?
|
|
138
|
+
ensure_token_size! if @token_limit_required
|
|
102
139
|
update_position
|
|
140
|
+
discard_consumed_input! unless @retain_input
|
|
103
141
|
token = @pending
|
|
104
142
|
@pending = nil
|
|
105
143
|
return token if token
|
|
@@ -113,7 +151,7 @@ module Flexr
|
|
|
113
151
|
token = next_token
|
|
114
152
|
break unless token
|
|
115
153
|
|
|
116
|
-
if
|
|
154
|
+
if @token_kind == :yield
|
|
117
155
|
yield(*token)
|
|
118
156
|
else
|
|
119
157
|
yield token
|
|
@@ -153,8 +191,46 @@ module Flexr
|
|
|
153
191
|
@more_start
|
|
154
192
|
end
|
|
155
193
|
|
|
156
|
-
def defer_token_size_check!(size)
|
|
157
|
-
|
|
194
|
+
def defer_token_size_check!(size, rule: nil)
|
|
195
|
+
return if size <= @max_token_size
|
|
196
|
+
|
|
197
|
+
raise Runtime::TokenTooLargeError.new(
|
|
198
|
+
filename: @filename, byte_pos: @more_start || @position, line: @line,
|
|
199
|
+
rule: rule_index(rule || @active_rule)
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def ensure_lookahead_size!(size, rule: nil)
|
|
204
|
+
return if size <= @max_lookahead_size
|
|
205
|
+
|
|
206
|
+
raise Runtime::LookaheadTooLargeError.new(
|
|
207
|
+
filename: @filename, byte_pos: @position, line: @line,
|
|
208
|
+
rule: rule_index(rule || @active_rule)
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def consume_step!(count = 1)
|
|
213
|
+
return unless scan_steps_guarded?
|
|
214
|
+
|
|
215
|
+
@steps += count
|
|
216
|
+
if @max_steps && @steps > @max_steps
|
|
217
|
+
raise Runtime::StepLimitError.new(
|
|
218
|
+
filename: @filename, byte_pos: @position, line: @line, rule: rule_index(@active_rule)
|
|
219
|
+
)
|
|
220
|
+
end
|
|
221
|
+
return unless cancelled?
|
|
222
|
+
|
|
223
|
+
raise Runtime::CancelledError.new(
|
|
224
|
+
filename: @filename, byte_pos: @position, line: @line, rule: rule_index(@active_rule)
|
|
225
|
+
)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def scan_steps_guarded?
|
|
229
|
+
!@max_steps.nil? || !@cancellation.nil?
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def token_limit_required?
|
|
233
|
+
@token_limit_required
|
|
158
234
|
end
|
|
159
235
|
|
|
160
236
|
def utf8_input?
|
|
@@ -162,7 +238,7 @@ module Flexr
|
|
|
162
238
|
end
|
|
163
239
|
|
|
164
240
|
def valid_utf8_at?(position)
|
|
165
|
-
!utf8_input? || @buffer.valid_utf8_at?(position)
|
|
241
|
+
@valid_utf8_input || !utf8_input? || @buffer.valid_utf8_at?(position)
|
|
166
242
|
end
|
|
167
243
|
|
|
168
244
|
def utf8_boundary?(position)
|
|
@@ -184,11 +260,12 @@ module Flexr
|
|
|
184
260
|
end
|
|
185
261
|
|
|
186
262
|
def binary_input
|
|
187
|
-
@buffer.source
|
|
263
|
+
source = @buffer.source
|
|
264
|
+
source.encoding == ::Encoding::BINARY ? source : source.b
|
|
188
265
|
end
|
|
189
266
|
|
|
190
267
|
def emit(type, value = text)
|
|
191
|
-
@pending = case
|
|
268
|
+
@pending = case @token_kind
|
|
192
269
|
when :struct
|
|
193
270
|
Runtime::Token.new(type: type, value: value, location: last_location)
|
|
194
271
|
else
|
|
@@ -201,7 +278,7 @@ module Flexr
|
|
|
201
278
|
end
|
|
202
279
|
|
|
203
280
|
def error!(message)
|
|
204
|
-
error = LexError.new(message, filename: @filename, byte_pos: @match_start, line: @
|
|
281
|
+
error = LexError.new(message, filename: @filename, byte_pos: @match_start, line: @text_line, text: text)
|
|
205
282
|
if @on_error
|
|
206
283
|
action = @on_error.call(error)
|
|
207
284
|
return @pending = nil if action == :skip
|
|
@@ -211,6 +288,11 @@ module Flexr
|
|
|
211
288
|
return @pending = nil
|
|
212
289
|
end
|
|
213
290
|
return emit(:error, text) if action == :token
|
|
291
|
+
|
|
292
|
+
raise Runtime::InvalidRecoveryActionError.new(
|
|
293
|
+
action: action, filename: @filename, byte_pos: @match_start, line: @text_line,
|
|
294
|
+
text: text, rule: rule_index(@active_rule)
|
|
295
|
+
)
|
|
214
296
|
end
|
|
215
297
|
case @error_mode
|
|
216
298
|
when :token
|
|
@@ -239,7 +321,8 @@ module Flexr
|
|
|
239
321
|
if @state_stack.length >= @max_state_stack
|
|
240
322
|
raise Runtime::StateStackOverflowError.new(
|
|
241
323
|
"state stack exceeds max_state_stack (#{@max_state_stack})",
|
|
242
|
-
filename: @filename, byte_pos: @position, line: @line, text: text
|
|
324
|
+
filename: @filename, byte_pos: @position, line: @line, text: text,
|
|
325
|
+
rule: rule_index(@active_rule)
|
|
243
326
|
)
|
|
244
327
|
end
|
|
245
328
|
|
|
@@ -266,7 +349,11 @@ module Flexr
|
|
|
266
349
|
matched_bytes = @match_end - @match_start
|
|
267
350
|
raise ArgumentError, "less must not exceed matched bytes" if count.negative? || count > matched_bytes
|
|
268
351
|
|
|
269
|
-
|
|
352
|
+
new_position = @match_start + count
|
|
353
|
+
raise ArgumentError, "less must end at a UTF-8 codepoint boundary" if
|
|
354
|
+
utf8_input? && !@buffer.utf8_boundary?(new_position)
|
|
355
|
+
|
|
356
|
+
@position = new_position
|
|
270
357
|
@match_end = @position
|
|
271
358
|
@matched = nil
|
|
272
359
|
end
|
|
@@ -277,10 +364,14 @@ module Flexr
|
|
|
277
364
|
|
|
278
365
|
def last_location
|
|
279
366
|
line_begin = @text_line || @line
|
|
367
|
+
column_begin = @text_column || @column
|
|
368
|
+
line_end, column_end = location_after(
|
|
369
|
+
@text_start, @match_end, line: line_begin, column: column_begin
|
|
370
|
+
)
|
|
280
371
|
Runtime::Location.new(
|
|
281
372
|
filename: @filename, byte_begin: @text_start, byte_end: @match_end,
|
|
282
|
-
line_begin: line_begin, line_end:
|
|
283
|
-
column_values: [
|
|
373
|
+
line_begin: line_begin, line_end: line_end,
|
|
374
|
+
column_values: [column_begin, column_end],
|
|
284
375
|
eager_columns: self.class.__flexr_config.options[:eager_columns] == true
|
|
285
376
|
)
|
|
286
377
|
end
|
|
@@ -304,9 +395,11 @@ module Flexr
|
|
|
304
395
|
if @more_requested
|
|
305
396
|
@more_start = @text_start
|
|
306
397
|
@more_line = @text_line
|
|
398
|
+
@more_column = @text_column
|
|
307
399
|
else
|
|
308
400
|
@more_start = nil
|
|
309
401
|
@more_line = nil
|
|
402
|
+
@more_column = nil
|
|
310
403
|
end
|
|
311
404
|
@more_requested = false
|
|
312
405
|
end
|
|
@@ -316,34 +409,31 @@ module Flexr
|
|
|
316
409
|
end
|
|
317
410
|
|
|
318
411
|
def ensure_token_size!
|
|
319
|
-
|
|
320
|
-
@candidate_token_size = 0
|
|
321
|
-
return if actual_size <= @max_token_size
|
|
412
|
+
return unless @token_limit_required
|
|
322
413
|
|
|
323
|
-
|
|
414
|
+
actual_size = @match_end - @text_start
|
|
415
|
+
defer_token_size_check!(actual_size, rule: @active_rule)
|
|
324
416
|
end
|
|
325
417
|
|
|
326
418
|
def force_empty_match_progress!
|
|
327
419
|
return if eof?
|
|
328
420
|
|
|
329
|
-
|
|
330
|
-
@position
|
|
331
|
-
@
|
|
332
|
-
@
|
|
421
|
+
length = utf8_input? ? @buffer.utf8_character_length(@position) : 1
|
|
422
|
+
ending = @position + length
|
|
423
|
+
advance_location!(@position, ending)
|
|
424
|
+
@position = ending
|
|
425
|
+
@bol = @buffer.getbyte(@position - 1) == 0x0a
|
|
333
426
|
end
|
|
334
427
|
|
|
335
428
|
def update_position
|
|
336
|
-
if @
|
|
337
|
-
|
|
338
|
-
@
|
|
339
|
-
|
|
340
|
-
elsif @line_tracking_needed && (newline = @buffer.source.index("\n", @match_start)) && newline < @match_end
|
|
341
|
-
@buffer.byteslice(@match_start, length).to_s.count("\n")
|
|
342
|
-
else
|
|
343
|
-
0
|
|
344
|
-
end
|
|
429
|
+
if @simple_location_input
|
|
430
|
+
@column += @match_end - @match_start
|
|
431
|
+
@bol = @match_end.zero?
|
|
432
|
+
return
|
|
345
433
|
end
|
|
346
|
-
|
|
434
|
+
|
|
435
|
+
advance_location!(@match_start, @match_end)
|
|
436
|
+
@bol = @match_end.zero? || @buffer.getbyte(@match_end - 1) == 0x0a
|
|
347
437
|
end
|
|
348
438
|
|
|
349
439
|
def handle_unmatched_byte
|
|
@@ -352,17 +442,21 @@ module Flexr
|
|
|
352
442
|
@match_end = @position + 1
|
|
353
443
|
@text_start = @match_start
|
|
354
444
|
@text_line = @line
|
|
445
|
+
@text_column = @column
|
|
355
446
|
@position += 1
|
|
356
|
-
@
|
|
447
|
+
@non_progress_signatures.clear
|
|
448
|
+
advance_location!(@match_start, @match_end)
|
|
357
449
|
@bol = bad.to_s.b == "\n"
|
|
358
450
|
error!("unexpected byte #{bad.inspect}")
|
|
359
451
|
token = @pending
|
|
360
452
|
@pending = nil
|
|
453
|
+
discard_consumed_input! unless @retain_input
|
|
361
454
|
token
|
|
362
455
|
end
|
|
363
456
|
|
|
364
457
|
def eof?
|
|
365
|
-
return @position >= @
|
|
458
|
+
return @position >= @stable_input_end if @stable_input_end
|
|
459
|
+
return @position >= @buffer.bytesize if @string_input
|
|
366
460
|
|
|
367
461
|
@buffer.eof?(@position)
|
|
368
462
|
end
|
|
@@ -374,15 +468,88 @@ module Flexr
|
|
|
374
468
|
raise CompileError.new(diagnostic.message, diagnostic: diagnostic)
|
|
375
469
|
end
|
|
376
470
|
|
|
377
|
-
def
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
471
|
+
def ensure_progress!(match, scan_state, empty_match:)
|
|
472
|
+
if @position > match.start_pos
|
|
473
|
+
@non_progress_signatures.clear
|
|
474
|
+
return
|
|
475
|
+
end
|
|
476
|
+
return if @halted
|
|
477
|
+
|
|
478
|
+
if empty_match && @state == scan_state && self.class.__flexr_config.options[:allow_empty_match]
|
|
479
|
+
force_empty_match_progress!
|
|
480
|
+
@non_progress_signatures.clear
|
|
481
|
+
return
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
signature = [match.start_pos, scan_state, match.rule.index]
|
|
485
|
+
repeated = @non_progress_signatures.key?(signature)
|
|
486
|
+
@non_progress_signatures[signature] = true
|
|
487
|
+
return if @state != scan_state && !repeated
|
|
488
|
+
|
|
489
|
+
raise Runtime::NonProgressError.new(
|
|
490
|
+
filename: @filename, byte_pos: match.start_pos, line: @line,
|
|
491
|
+
text: text, rule: match.rule.index
|
|
492
|
+
)
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def advance_location!(starting, ending)
|
|
496
|
+
return if ending <= starting
|
|
497
|
+
|
|
498
|
+
if @simple_location_input
|
|
499
|
+
@column += ending - starting
|
|
500
|
+
return
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
value = location_value(starting, ending)
|
|
504
|
+
location_bytes = value.valid_encoding? ? value : value.b
|
|
505
|
+
newline_count = location_bytes.count("\n")
|
|
506
|
+
if newline_count.zero?
|
|
507
|
+
@column += display_length(value)
|
|
383
508
|
else
|
|
384
|
-
|
|
509
|
+
tail = value.byteslice(((location_bytes.rindex("\n") || -1) + 1)..).to_s
|
|
510
|
+
@line += newline_count
|
|
511
|
+
@column = display_length(tail) + 1
|
|
385
512
|
end
|
|
386
513
|
end
|
|
514
|
+
|
|
515
|
+
def location_after(starting, ending, line:, column:)
|
|
516
|
+
return [line, column] if ending <= starting
|
|
517
|
+
|
|
518
|
+
value = location_value(starting, ending)
|
|
519
|
+
location_bytes = value.valid_encoding? ? value : value.b
|
|
520
|
+
newline_count = location_bytes.count("\n")
|
|
521
|
+
return [line, column + display_length(value)] if newline_count.zero?
|
|
522
|
+
|
|
523
|
+
tail = value.byteslice(((location_bytes.rindex("\n") || -1) + 1)..).to_s
|
|
524
|
+
[line + newline_count, display_length(tail) + 1]
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def display_length(value)
|
|
528
|
+
return value.bytesize unless utf8_input?
|
|
529
|
+
return value.bytesize if value.ascii_only?
|
|
530
|
+
|
|
531
|
+
utf8 = value.encoding == Encoding::UTF_8 ? value : value.dup.force_encoding(Encoding::UTF_8)
|
|
532
|
+
utf8.valid_encoding? ? utf8.length : value.bytesize
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def location_value(starting, ending)
|
|
536
|
+
return @matched if @matched && starting == @text_start && ending == @match_end
|
|
537
|
+
|
|
538
|
+
@buffer.byteslice(starting...ending).to_s
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def discard_consumed_input!
|
|
542
|
+
@buffer.discard_before(@more_start || @position)
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def cancelled?
|
|
546
|
+
return false unless @cancellation
|
|
547
|
+
|
|
548
|
+
@cancellation.arity.zero? ? @cancellation.call : @cancellation.call(self)
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def rule_index(rule)
|
|
552
|
+
rule.respond_to?(:index) ? rule.index : rule
|
|
553
|
+
end
|
|
387
554
|
end
|
|
388
555
|
end
|
data/lib/flexr/runtime/errors.rb
CHANGED
|
@@ -5,18 +5,78 @@ module Flexr
|
|
|
5
5
|
class TokenTooLargeError < LexError
|
|
6
6
|
CODE = "FLEXR-E012"
|
|
7
7
|
|
|
8
|
-
attr_reader :code
|
|
8
|
+
attr_reader :code, :rule
|
|
9
9
|
|
|
10
|
-
def initialize(message = "token exceeds max_token_size", filename: nil, byte_pos: nil, line: nil, text: nil
|
|
10
|
+
def initialize(message = "token exceeds max_token_size", filename: nil, byte_pos: nil, line: nil, text: nil,
|
|
11
|
+
rule: nil)
|
|
11
12
|
@code = CODE
|
|
13
|
+
@rule = rule
|
|
12
14
|
diagnostic = Diagnostics.error(
|
|
13
15
|
CODE,
|
|
14
16
|
message,
|
|
15
|
-
help: "increase max_token_size or split the input token"
|
|
17
|
+
help: "increase max_token_size or split the input token",
|
|
18
|
+
note: rule.nil? ? nil : "rule #{rule} at byte #{byte_pos}"
|
|
16
19
|
)
|
|
17
20
|
super(message, filename: filename, byte_pos: byte_pos, line: line, text: text, diagnostic: diagnostic)
|
|
18
21
|
end
|
|
19
22
|
end
|
|
20
|
-
|
|
23
|
+
|
|
24
|
+
class ResourceLimitError < LexError
|
|
25
|
+
attr_reader :code, :rule
|
|
26
|
+
|
|
27
|
+
def initialize(code, message, help:, filename: nil, byte_pos: nil, line: nil, text: nil, rule: nil)
|
|
28
|
+
@code = code
|
|
29
|
+
@rule = rule
|
|
30
|
+
diagnostic = Diagnostics.error(code, message, help: help, note: rule.nil? ? nil : "rule #{rule}")
|
|
31
|
+
super(message, filename: filename, byte_pos: byte_pos, line: line, text: text, diagnostic: diagnostic)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class NonProgressError < ResourceLimitError
|
|
36
|
+
def initialize(**context)
|
|
37
|
+
super("FLEXR-E021", "lexer action did not make progress",
|
|
38
|
+
help: "consume input or change to a state that can consume it", **context)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class BufferTooLargeError < ResourceLimitError
|
|
43
|
+
def initialize(**context)
|
|
44
|
+
super("FLEXR-E022", "streaming buffer exceeds max_buffer_size",
|
|
45
|
+
help: "increase max_buffer_size, shorten tokens, or use retain_input: false", **context)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class StepLimitError < ResourceLimitError
|
|
50
|
+
def initialize(**context)
|
|
51
|
+
super("FLEXR-E023", "lexer exceeds max_steps",
|
|
52
|
+
help: "increase max_steps or simplify the specification", **context)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class LookaheadTooLargeError < ResourceLimitError
|
|
57
|
+
def initialize(**context)
|
|
58
|
+
super("FLEXR-E024", "trailing context exceeds max_lookahead_size",
|
|
59
|
+
help: "increase max_lookahead_size or bound the trailing context", **context)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class StateStackOverflowError < ResourceLimitError
|
|
64
|
+
def initialize(message = "state stack exceeds max_state_stack", **context)
|
|
65
|
+
super("FLEXR-E025", message, help: "increase max_state_stack or remove recursive pushes", **context)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class CancelledError < ResourceLimitError
|
|
70
|
+
def initialize(**context)
|
|
71
|
+
super("FLEXR-E026", "lexing was cancelled", help: "resume with a new lexer when ready", **context)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class InvalidRecoveryActionError < ResourceLimitError
|
|
76
|
+
def initialize(action:, **context)
|
|
77
|
+
super("FLEXR-E027", "on_error returned unsupported action #{action.inspect}",
|
|
78
|
+
help: "return :skip, :raise, :halt, or :token", **context)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
21
81
|
end
|
|
22
82
|
end
|