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
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# Generated by flexr. DO NOT EDIT.
|
|
3
3
|
# source: lib/flexr/regexp/tokenizer.flexr.rb
|
|
4
|
-
# spec-digest: sha256:
|
|
4
|
+
# spec-digest: sha256:ef176b30fa1aec18f8474cdc44dfc9004802e14e4d7ed89e9a20890930811c63
|
|
5
|
+
# artifact-schema: 1
|
|
6
|
+
# runtime-abi: 1
|
|
5
7
|
# unicode: 15.1.0
|
|
6
8
|
# backend: table
|
|
7
9
|
# compiled: true
|
|
@@ -15,48 +17,141 @@ module Flexr
|
|
|
15
17
|
# This specification is intentionally small: it is a dogfood target for
|
|
16
18
|
# the source lexer path, not the parser's implementation.
|
|
17
19
|
class SourceLexer < Flexr::Lexer
|
|
18
|
-
Flexr::Generated.install_compiled!(self, { rules: [{ index: 0, patterns: [/[ \t\r\n]+/], pattern_conditions: [[0, 0, false, false]], trailing: nil, action: :skip, states: [:initial], bol_only: false, end_anchor: nil }, { index: 1, patterns: [/\\p\{[A-Za-z_][A-Za-z0-9_]*\}/], pattern_conditions: [[1, 0, false, false]], trailing: nil, action: proc{ emit :PROPERTY, text.byteslice(3...-1) }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 2, patterns: [/\\./], pattern_conditions: [[2, 0, false, false]], trailing: nil, action: proc{ emit :ESCAPE, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 3, patterns: [/\[[^\]\n]*\]/], pattern_conditions: [[3, 0, false, false]], trailing: nil, action: proc{ emit :CHAR_CLASS, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 4, patterns: [/[?*+]|\{[0-9]+(?:,[0-9]*)?\}/], pattern_conditions: [[4, 0, false, false]], trailing: nil, action: proc{ emit :QUANTIFIER, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 5, patterns: [/\|/], pattern_conditions: [[5, 0, false, false]], trailing: nil, action: proc{ emit :ALTERNATION, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 6, patterns: [/\(/], pattern_conditions: [[6, 0, false, false]], trailing: nil, action: proc{ emit :GROUP_OPEN, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 7, patterns: [/\)/], pattern_conditions: [[7, 0, false, false]], trailing: nil, action: proc{ emit :GROUP_CLOSE, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 8, patterns: [/\^|\$/], pattern_conditions: [[8, 0, false, false]], trailing: nil, action: proc{ emit :ANCHOR, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 9, patterns: [/\./], pattern_conditions: [[9, 0, false, false]], trailing: nil, action: proc{ emit :DOT, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 10, patterns: [/[^\\\[\]().|?*+{}^$ \t\r\n]/], pattern_conditions: [[10, 0, false, false]], trailing: nil, action: proc{ emit :LITERAL, text }, states: [:initial], bol_only: false, end_anchor: nil }], backend: :table, token_kind: :array, encoding: Encoding::BINARY, declared_tokens: [:PROPERTY, :ESCAPE, :CHAR_CLASS, :QUANTIFIER, :ALTERNATION, :GROUP_OPEN, :GROUP_CLOSE, :ANCHOR, :DOT, :LITERAL], options: {}, eof_rules: { }, states: [], inclusive_states: {initial: true}, compiled: { machines: { :initial => { state_name: :initial, dfa: {accepts: [[], [[10, 0, false, false]], [[0, 0, false, false]], [[8, 0, false, false]], [[6, 0, false, false]], [[7, 0, false, false]], [[4, 0, false, false]], [[9, 0, false, false]], [], [], [], [[5, 0, false, false]], [[3, 0, false, false]], [[2, 0, false, false]], [[2, 0, false, false]], [], [], [], [], [[1, 0, false, false]]], ec: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 7, 7, 8, 9, 9, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 19, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 23, 24, 25, 26, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31, 32, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34], class_count: 35, state_count: 20, start: 0, rule_ids: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], packed: {base: [0, 0, 12, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 3, 22, 15, 4, 21, 0], default: [1, nil, nil, nil, nil, nil, nil, nil, 8, 13, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil], next: [nil, 2, 2, nil, 2, nil, 2, nil, 3, nil, 4, 5, 6, 2, 2, 7, 2, 15, 2, 6, nil, 17, 8, 9, nil, 3, nil, 12, nil, nil, 14, 10, 11, nil, 16, 17, 18, 6, 18, 15, nil, 18, 18, 18, 18, 18, nil, 18, nil, 18, 18, 18, nil, nil, 19, 6], check: [nil, 0, 0, 9, 0, 8, 0, nil, 0, nil, 0, 0, 0, 2, 2, 0, 2, 10, 2, 0, nil, 17, 0, 0, 0, 0, nil, 8, nil, nil, 9, 0, 0, 0, 14, 15, 16, 17, 18, 15, nil, 16, 18, 16, 16, 16, nil, 18, nil, 18, 18, 18, nil, nil, 18, 15]}} } }, states: [:initial], stats: {initial: {states: 20, classes: 35, accepting_states: 12}}, diagnostics: [] } })
|
|
20
|
+
Flexr::Generated.install_compiled!(self, { artifact: {schema_version: 1, compiler_version: "1.1.0", runtime_abi_version: 1, unicode_version: "15.1.0"}, rules: [{ index: 0, patterns: [/[ \t\r\n]+/], pattern_conditions: [[0, 0, false, false]], trailing: nil, action: :skip, states: [:initial], bol_only: false, end_anchor: nil }, { index: 1, patterns: [/\\p\{[A-Za-z_][A-Za-z0-9_]*\}/], pattern_conditions: [[1, 0, false, false]], trailing: nil, action: proc{ emit :PROPERTY, text.byteslice(3...-1) }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 2, patterns: [/\\./], pattern_conditions: [[2, 0, false, false]], trailing: nil, action: proc{ emit :ESCAPE, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 3, patterns: [/\[[^\]\n]*\]/], pattern_conditions: [[3, 0, false, false]], trailing: nil, action: proc{ emit :CHAR_CLASS, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 4, patterns: [/[?*+]|\{[0-9]+(?:,[0-9]*)?\}/], pattern_conditions: [[4, 0, false, false]], trailing: nil, action: proc{ emit :QUANTIFIER, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 5, patterns: [/\|/], pattern_conditions: [[5, 0, false, false]], trailing: nil, action: proc{ emit :ALTERNATION, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 6, patterns: [/\(/], pattern_conditions: [[6, 0, false, false]], trailing: nil, action: proc{ emit :GROUP_OPEN, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 7, patterns: [/\)/], pattern_conditions: [[7, 0, false, false]], trailing: nil, action: proc{ emit :GROUP_CLOSE, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 8, patterns: [/\^|\$/], pattern_conditions: [[8, 0, false, false]], trailing: nil, action: proc{ emit :ANCHOR, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 9, patterns: [/\./], pattern_conditions: [[9, 0, false, false]], trailing: nil, action: proc{ emit :DOT, text }, states: [:initial], bol_only: false, end_anchor: nil }, { index: 10, patterns: [/[^\\\[\]().|?*+{}^$ \t\r\n]/], pattern_conditions: [[10, 0, false, false]], trailing: nil, action: proc{ emit :LITERAL, text }, states: [:initial], bol_only: false, end_anchor: nil }], backend: :table, token_kind: :array, encoding: Encoding::BINARY, declared_tokens: [:PROPERTY, :ESCAPE, :CHAR_CLASS, :QUANTIFIER, :ALTERNATION, :GROUP_OPEN, :GROUP_CLOSE, :ANCHOR, :DOT, :LITERAL], options: {}, eof_rules: { }, states: [], inclusive_states: {initial: true}, compiled: { machines: { :initial => { state_name: :initial, dfa: {accepts: [[], [[10, 0, false, false]], [[0, 0, false, false]], [[8, 0, false, false]], [[6, 0, false, false]], [[7, 0, false, false]], [[4, 0, false, false]], [[9, 0, false, false]], [], [], [], [[5, 0, false, false]], [[3, 0, false, false]], [[2, 0, false, false]], [[2, 0, false, false]], [], [], [], [], [[1, 0, false, false]]], ec: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 7, 7, 8, 9, 9, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 19, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 23, 24, 25, 26, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31, 32, 33, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34], class_count: 35, state_count: 20, start: 0, rule_ids: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], packed: {base: [0, 0, 12, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 3, 22, 15, 4, 21, 0], default: [1, nil, nil, nil, nil, nil, nil, nil, 8, 13, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil], next: [nil, 2, 2, nil, 2, nil, 2, nil, 3, nil, 4, 5, 6, 2, 2, 7, 2, 15, 2, 6, nil, 17, 8, 9, nil, 3, nil, 12, nil, nil, 14, 10, 11, nil, 16, 17, 18, 6, 18, 15, nil, 18, 18, 18, 18, 18, nil, 18, nil, 18, 18, 18, nil, nil, 19, 6], check: [nil, 0, 0, 9, 0, 8, 0, nil, 0, nil, 0, 0, 0, 2, 2, 0, 2, 10, 2, 0, nil, 17, 0, 0, 0, 0, nil, 8, nil, nil, 9, 0, 0, 0, 14, 15, 16, 17, 18, 15, nil, 16, 18, 16, 16, 16, nil, 18, nil, 18, 18, 18, nil, nil, 18, 15]}} } }, states: [:initial], stats: {initial: {states: 20, classes: 35, accepting_states: 12}}, diagnostics: [] } })
|
|
19
21
|
def scan_one
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
if @__flexr_generated_scan_state != @state
|
|
23
|
+
machine = self.class.__flexr_compiled.machines.fetch(@state)
|
|
24
|
+
@__flexr_generated_scan_state = @state
|
|
25
|
+
@__flexr_generated_scan_dfa = machine.dfa
|
|
26
|
+
@__flexr_generated_scan_fast = __flexr_generated_fast_path?(@__flexr_generated_scan_dfa)
|
|
27
|
+
@__flexr_generated_scan_simple = __flexr_generated_simple_fast_path?(@__flexr_generated_scan_dfa)
|
|
28
|
+
end
|
|
29
|
+
dfa = @__flexr_generated_scan_dfa
|
|
30
|
+
return Flexr::Runtime::Interpreter.new(self).scan unless @__flexr_generated_scan_fast
|
|
23
31
|
position = byte_pos
|
|
24
32
|
return nil unless valid_utf8_at?(position)
|
|
33
|
+
return __flexr_generated_scan_simple(dfa, position) if @__flexr_generated_scan_simple
|
|
25
34
|
cursor = position
|
|
26
35
|
current = dfa.start
|
|
27
36
|
direct = dfa.direct
|
|
28
|
-
|
|
37
|
+
direct_nxt = direct&.fetch(:nxt)
|
|
38
|
+
direct_classes = direct&.fetch(:classes)
|
|
39
|
+
ec = dfa.ec
|
|
40
|
+
source = buffer.stable_source
|
|
41
|
+
guarded_steps = scan_steps_guarded?
|
|
42
|
+
token_start = more_text_start || position
|
|
43
|
+
token_limit = max_token_size
|
|
44
|
+
check_token_limit = token_limit_required?
|
|
29
45
|
best = nil
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
accel_mode = @accel_mode
|
|
47
|
+
accelerate = accel_mode != :none && !(accel_mode == :auto && @__flexr_generated_auto_accel_off)
|
|
48
|
+
while cursor < (source ? source.bytesize : buffer.bytesize) || buffer.ensure_available?(cursor + 1)
|
|
32
49
|
if accelerate
|
|
33
50
|
region = __flexr_generated_acceleration_region(dfa, current)
|
|
34
|
-
|
|
51
|
+
acceptance = dfa.accepts[current].first
|
|
52
|
+
token_rule = acceptance&.rule_index
|
|
53
|
+
accelerated_end = __flexr_generated_accelerate(region, cursor, token_rule, position) if region
|
|
35
54
|
if accelerated_end && accelerated_end > cursor
|
|
36
55
|
cursor = accelerated_end
|
|
37
56
|
best = __flexr_generated_acceptance(dfa, current, position, cursor, best)
|
|
38
57
|
next
|
|
39
58
|
end
|
|
40
59
|
end
|
|
41
|
-
byte = source.getbyte(cursor)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
byte = source ? source.getbyte(cursor) : buffer.getbyte(cursor)
|
|
61
|
+
consume_step! if guarded_steps
|
|
62
|
+
current = if direct_nxt
|
|
63
|
+
class_id = ec[byte]
|
|
64
|
+
value = direct_nxt[(current * direct_classes) + class_id]
|
|
45
65
|
value >= 0 ? value : nil
|
|
46
66
|
else
|
|
47
67
|
dfa.transition(current, byte)
|
|
48
68
|
end
|
|
49
69
|
break unless current
|
|
50
70
|
cursor += 1
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
resource_acceptance = dfa.accepts[current].first
|
|
72
|
+
resource_rule = resource_acceptance&.rule_index
|
|
73
|
+
if check_token_limit
|
|
74
|
+
size = cursor - token_start
|
|
75
|
+
defer_token_size_check!(size, rule: resource_rule) if size > token_limit
|
|
76
|
+
end
|
|
77
|
+
best = __flexr_generated_acceptance(dfa, current, position, cursor, best)
|
|
78
|
+
end
|
|
79
|
+
best
|
|
80
|
+
end
|
|
81
|
+
def __flexr_generated_scan_simple(dfa, position)
|
|
82
|
+
current = dfa.start
|
|
83
|
+
direct = dfa.direct
|
|
84
|
+
direct_nxt = direct&.fetch(:nxt)
|
|
85
|
+
direct_classes = direct&.fetch(:classes)
|
|
86
|
+
ec = dfa.ec
|
|
87
|
+
accepts = dfa.accepts
|
|
88
|
+
rules = self.class.__flexr_rules
|
|
89
|
+
source = buffer.stable_source
|
|
90
|
+
guarded_steps = scan_steps_guarded?
|
|
91
|
+
token_start = more_text_start || position
|
|
92
|
+
token_limit = max_token_size
|
|
93
|
+
check_token_limit = token_limit_required?
|
|
94
|
+
cursor = position
|
|
95
|
+
best = nil
|
|
96
|
+
accel_mode = @accel_mode
|
|
97
|
+
accelerate = accel_mode != :none && !(accel_mode == :auto && @__flexr_generated_auto_accel_off)
|
|
98
|
+
if source && !guarded_steps && !check_token_limit && !accelerate
|
|
99
|
+
while cursor < source.bytesize
|
|
100
|
+
byte = source.getbyte(cursor)
|
|
101
|
+
current = if direct_nxt
|
|
102
|
+
class_id = ec[byte]
|
|
103
|
+
value = direct_nxt[(current * direct_classes) + class_id]
|
|
104
|
+
value >= 0 ? value : nil
|
|
105
|
+
else
|
|
106
|
+
dfa.transition(current, byte)
|
|
107
|
+
end
|
|
108
|
+
break unless current
|
|
109
|
+
cursor += 1
|
|
110
|
+
acceptance = accepts[current].first
|
|
111
|
+
next unless acceptance
|
|
112
|
+
rule = rules.fetch(acceptance.rule_index)
|
|
54
113
|
best ||= (@__flexr_generated_match ||= Flexr::Runtime::Match.new)
|
|
55
114
|
best.rule = rule
|
|
56
115
|
best.start_pos = position
|
|
57
116
|
best.end_pos = cursor
|
|
58
117
|
best.total_end_pos = cursor
|
|
59
118
|
end
|
|
119
|
+
return best
|
|
120
|
+
end
|
|
121
|
+
while cursor < (source ? source.bytesize : buffer.bytesize) || buffer.ensure_available?(cursor + 1)
|
|
122
|
+
if accelerate
|
|
123
|
+
region = __flexr_generated_acceleration_region(dfa, current)
|
|
124
|
+
acceptance = accepts[current].first
|
|
125
|
+
accelerated_end = __flexr_generated_accelerate(region, cursor, acceptance&.rule_index, position) if region
|
|
126
|
+
if accelerated_end && accelerated_end > cursor
|
|
127
|
+
cursor = accelerated_end
|
|
128
|
+
best = __flexr_generated_acceptance(dfa, current, position, cursor, best)
|
|
129
|
+
next
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
byte = source ? source.getbyte(cursor) : buffer.getbyte(cursor)
|
|
133
|
+
consume_step! if guarded_steps
|
|
134
|
+
current = if direct_nxt
|
|
135
|
+
class_id = ec[byte]
|
|
136
|
+
value = direct_nxt[(current * direct_classes) + class_id]
|
|
137
|
+
value >= 0 ? value : nil
|
|
138
|
+
else
|
|
139
|
+
dfa.transition(current, byte)
|
|
140
|
+
end
|
|
141
|
+
break unless current
|
|
142
|
+
cursor += 1
|
|
143
|
+
acceptance = accepts[current].first
|
|
144
|
+
if check_token_limit
|
|
145
|
+
size = cursor - token_start
|
|
146
|
+
defer_token_size_check!(size, rule: acceptance&.rule_index) if size > token_limit
|
|
147
|
+
end
|
|
148
|
+
next unless acceptance
|
|
149
|
+
rule = rules.fetch(acceptance.rule_index)
|
|
150
|
+
best ||= (@__flexr_generated_match ||= Flexr::Runtime::Match.new)
|
|
151
|
+
best.rule = rule
|
|
152
|
+
best.start_pos = position
|
|
153
|
+
best.end_pos = cursor
|
|
154
|
+
best.total_end_pos = cursor
|
|
60
155
|
end
|
|
61
156
|
best
|
|
62
157
|
end
|
|
@@ -65,44 +160,75 @@ module Flexr
|
|
|
65
160
|
regions = (@__flexr_generated_accel_regions[dfa] ||= Flexr::Automaton::Accel.extract(dfa).to_h { |region| [region.state, region] })
|
|
66
161
|
region = regions[state]
|
|
67
162
|
return unless region
|
|
163
|
+
return if @__flexr_generated_disabled_auto_accel&.key?(region)
|
|
164
|
+
return if utf8_input? && region.bytes.any? { |byte| byte >= 128 }
|
|
68
165
|
return if dfa.accepts[state].any? do |acceptance|
|
|
69
166
|
rule = self.class.__flexr_rules.fetch(acceptance.rule_index)
|
|
70
167
|
acceptance.bol_only || acceptance.end_anchor || rule.trailing
|
|
71
168
|
end
|
|
72
169
|
region
|
|
73
170
|
end
|
|
74
|
-
def __flexr_generated_accelerate(region, position)
|
|
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
|
-
|
|
100
|
-
!rule.trailing.nil? || rule.patterns.any? do |pattern|
|
|
101
|
-
next false unless pattern.is_a?(Regexp)
|
|
102
|
-
next true if pattern.source.match?(/\\[pP]\\{/) || pattern.source.match?(/\[:(?:\^)?[a-z]+:\]/)
|
|
103
|
-
self.class.__flexr_config.options[:unicode] == true && utf8_input? && pattern.source.match?(/\\[dDwWsS]/)
|
|
171
|
+
def __flexr_generated_accelerate(region, position, token_rule, token_start)
|
|
172
|
+
mode = @accel_mode
|
|
173
|
+
cursor = position
|
|
174
|
+
matched = false
|
|
175
|
+
loop do
|
|
176
|
+
if cursor >= buffer.bytesize
|
|
177
|
+
return matched ? cursor : nil if buffer.eof_loaded?
|
|
178
|
+
return matched ? cursor : nil unless buffer.ensure_available?(cursor + 1)
|
|
179
|
+
end
|
|
180
|
+
segment = buffer.source
|
|
181
|
+
segment_position = cursor - buffer.base_offset
|
|
182
|
+
if segment.encoding == ::Encoding::UTF_8 && region.utf8_regexp
|
|
183
|
+
regexp = region.utf8_regexp
|
|
184
|
+
else
|
|
185
|
+
segment = segment.b unless segment.encoding == ::Encoding::BINARY
|
|
186
|
+
regexp = region.regexp
|
|
187
|
+
end
|
|
188
|
+
length = begin
|
|
189
|
+
if %i[strscan auto].include?(mode) && defined?(::StringScanner)
|
|
190
|
+
@__flexr_generated_accel_scanner ||= ::StringScanner.new("".b)
|
|
191
|
+
@__flexr_generated_accel_scanner.string = segment
|
|
192
|
+
@__flexr_generated_accel_scanner.pos = segment_position
|
|
193
|
+
@__flexr_generated_accel_scanner.skip(regexp)
|
|
194
|
+
else
|
|
195
|
+
match = regexp.match(segment, segment_position)
|
|
196
|
+
match && match.begin(0) == segment_position ? match.end(0) - segment_position : nil
|
|
104
197
|
end
|
|
198
|
+
rescue ArgumentError
|
|
199
|
+
nil
|
|
105
200
|
end
|
|
201
|
+
__flexr_generated_record_auto_accel_miss(region) if mode == :auto && length.to_i < 8
|
|
202
|
+
return matched ? cursor : nil unless length&.positive?
|
|
203
|
+
matched = true
|
|
204
|
+
cursor += length
|
|
205
|
+
consume_step!(length)
|
|
206
|
+
defer_token_size_check!(cursor - (more_text_start || token_start), rule: token_rule)
|
|
207
|
+
return cursor if cursor < buffer.bytesize || buffer.eof_loaded?
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
def __flexr_generated_record_auto_accel_miss(region)
|
|
211
|
+
@__flexr_generated_auto_accel_misses ||= Hash.new(0)
|
|
212
|
+
@__flexr_generated_auto_accel_misses[region] += 1
|
|
213
|
+
@__flexr_generated_auto_accel_miss_total = @__flexr_generated_auto_accel_miss_total.to_i + 1
|
|
214
|
+
@__flexr_generated_auto_accel_off = true if @__flexr_generated_auto_accel_miss_total >= 9
|
|
215
|
+
return if @__flexr_generated_auto_accel_misses[region] < 3
|
|
216
|
+
@__flexr_generated_disabled_auto_accel ||= {}
|
|
217
|
+
@__flexr_generated_disabled_auto_accel[region] = true
|
|
218
|
+
end
|
|
219
|
+
def __flexr_generated_fast_path?(dfa)
|
|
220
|
+
@__flexr_generated_fast_paths ||= {}
|
|
221
|
+
return @__flexr_generated_fast_paths[dfa] if @__flexr_generated_fast_paths.key?(dfa)
|
|
222
|
+
rules = dfa.rule_ids.map { |rule_index| self.class.__flexr_rules.fetch(rule_index) }
|
|
223
|
+
@__flexr_generated_fast_paths[dfa] = self.class.__flexr_config.backend != :firstmatch &&
|
|
224
|
+
self.class.__flexr_config.options[:allow_empty_match] != true && rules.none?(&:trailing)
|
|
225
|
+
end
|
|
226
|
+
def __flexr_generated_simple_fast_path?(dfa)
|
|
227
|
+
@__flexr_generated_simple_fast_paths ||= {}
|
|
228
|
+
return @__flexr_generated_simple_fast_paths[dfa] if @__flexr_generated_simple_fast_paths.key?(dfa)
|
|
229
|
+
rules = dfa.rule_ids.map { |rule_index| self.class.__flexr_rules.fetch(rule_index) }
|
|
230
|
+
@__flexr_generated_simple_fast_paths[dfa] = rules.all? do |rule|
|
|
231
|
+
rule.pattern_conditions.all? { |condition| condition && !condition.bol_only && !condition.end_anchor }
|
|
106
232
|
end
|
|
107
233
|
end
|
|
108
234
|
def __flexr_generated_acceptance(dfa, state, start_position, cursor, best)
|
|
@@ -110,7 +236,6 @@ module Flexr
|
|
|
110
236
|
next if acceptance.bol_only && !beginning_of_line?
|
|
111
237
|
next if acceptance.end_anchor && !(buffer.eof?(cursor) || buffer.getbyte(cursor) == 0x0a)
|
|
112
238
|
rule = self.class.__flexr_rules.fetch(acceptance.rule_index)
|
|
113
|
-
defer_token_size_check!(cursor - start_position)
|
|
114
239
|
next if best && cursor < best.total_end_pos
|
|
115
240
|
next if best && cursor == best.total_end_pos &&
|
|
116
241
|
rule.index > best.rule.index
|
|
@@ -126,25 +251,25 @@ module Flexr
|
|
|
126
251
|
case rule.index
|
|
127
252
|
when 0
|
|
128
253
|
when 1
|
|
129
|
-
|
|
254
|
+
instance_exec(&self.class.__flexr_generated_actions.fetch(rule.index))
|
|
130
255
|
when 2
|
|
131
|
-
|
|
256
|
+
instance_exec(&self.class.__flexr_generated_actions.fetch(rule.index))
|
|
132
257
|
when 3
|
|
133
|
-
|
|
258
|
+
instance_exec(&self.class.__flexr_generated_actions.fetch(rule.index))
|
|
134
259
|
when 4
|
|
135
|
-
|
|
260
|
+
instance_exec(&self.class.__flexr_generated_actions.fetch(rule.index))
|
|
136
261
|
when 5
|
|
137
|
-
|
|
262
|
+
instance_exec(&self.class.__flexr_generated_actions.fetch(rule.index))
|
|
138
263
|
when 6
|
|
139
|
-
|
|
264
|
+
instance_exec(&self.class.__flexr_generated_actions.fetch(rule.index))
|
|
140
265
|
when 7
|
|
141
|
-
|
|
266
|
+
instance_exec(&self.class.__flexr_generated_actions.fetch(rule.index))
|
|
142
267
|
when 8
|
|
143
|
-
|
|
268
|
+
instance_exec(&self.class.__flexr_generated_actions.fetch(rule.index))
|
|
144
269
|
when 9
|
|
145
|
-
|
|
270
|
+
instance_exec(&self.class.__flexr_generated_actions.fetch(rule.index))
|
|
146
271
|
when 10
|
|
147
|
-
|
|
272
|
+
instance_exec(&self.class.__flexr_generated_actions.fetch(rule.index))
|
|
148
273
|
else
|
|
149
274
|
instance_exec(&rule.action)
|
|
150
275
|
end
|
|
@@ -153,16 +278,16 @@ module Flexr
|
|
|
153
278
|
|
|
154
279
|
|
|
155
280
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
281
|
+
__flexr_bind_generated_action(1, proc{ emit :PROPERTY, text.byteslice(3...-1) })
|
|
282
|
+
__flexr_bind_generated_action(2, proc{ emit :ESCAPE, text })
|
|
283
|
+
__flexr_bind_generated_action(3, proc{ emit :CHAR_CLASS, text })
|
|
284
|
+
__flexr_bind_generated_action(4, proc{ emit :QUANTIFIER, text })
|
|
285
|
+
__flexr_bind_generated_action(5, proc{ emit :ALTERNATION, text })
|
|
286
|
+
__flexr_bind_generated_action(6, proc{ emit :GROUP_OPEN, text })
|
|
287
|
+
__flexr_bind_generated_action(7, proc{ emit :GROUP_CLOSE, text })
|
|
288
|
+
__flexr_bind_generated_action(8, proc{ emit :ANCHOR, text })
|
|
289
|
+
__flexr_bind_generated_action(9, proc{ emit :DOT, text })
|
|
290
|
+
__flexr_bind_generated_action(10, proc{ emit :LITERAL, text })
|
|
166
291
|
end
|
|
167
292
|
end
|
|
168
293
|
end
|
data/lib/flexr/runtime/buffer.rb
CHANGED
|
@@ -5,31 +5,65 @@ module Flexr
|
|
|
5
5
|
class Buffer
|
|
6
6
|
DEFAULT_CHUNK_SIZE = 64 * 1024
|
|
7
7
|
|
|
8
|
-
attr_reader :
|
|
8
|
+
attr_reader :base_offset, :max_buffer_size
|
|
9
9
|
|
|
10
|
-
def initialize(input, chunk_size: DEFAULT_CHUNK_SIZE
|
|
10
|
+
def initialize(input, chunk_size: DEFAULT_CHUNK_SIZE, max_buffer_size: 64 * 1024 * 1024,
|
|
11
|
+
retain_input: true, filename: nil)
|
|
11
12
|
raise ArgumentError, "chunk_size must be positive" unless chunk_size.to_i.positive?
|
|
13
|
+
raise ArgumentError, "max_buffer_size must be non-negative" if max_buffer_size.to_i.negative?
|
|
12
14
|
|
|
13
15
|
@chunk_size = chunk_size.to_i
|
|
16
|
+
@max_buffer_size = max_buffer_size.to_i
|
|
17
|
+
@retain_input = retain_input
|
|
18
|
+
@filename = filename
|
|
19
|
+
@base_offset = 0
|
|
20
|
+
@window_start = 0
|
|
14
21
|
@io = input.is_a?(String) ? nil : input
|
|
15
22
|
raise ArgumentError, "input must be a String or IO" if @io && !@io.respond_to?(:read)
|
|
16
23
|
|
|
17
24
|
@source = input.is_a?(String) ? input : String.new(encoding: Encoding::BINARY)
|
|
18
25
|
@eof = @io.nil?
|
|
26
|
+
ensure_buffer_size!(@source.bytesize)
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
def bytesize
|
|
22
|
-
source.bytesize
|
|
30
|
+
return @source.bytesize if @window_start.zero? && base_offset.zero?
|
|
31
|
+
|
|
32
|
+
base_offset + retained_bytesize
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def retained_bytesize
|
|
36
|
+
@source.bytesize - @window_start
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def source
|
|
40
|
+
return @source if @window_start.zero?
|
|
41
|
+
|
|
42
|
+
@source.byteslice(@window_start..).to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def stable_source
|
|
46
|
+
@source if @retain_input && @window_start.zero? && base_offset.zero?
|
|
23
47
|
end
|
|
24
48
|
|
|
25
49
|
def getbyte(position)
|
|
50
|
+
return @source.getbyte(position) if @window_start.zero? && base_offset.zero? && position < @source.bytesize
|
|
51
|
+
|
|
26
52
|
ensure_available?(position + 1)
|
|
27
|
-
source.getbyte(position)
|
|
53
|
+
@source.getbyte(storage_position(position))
|
|
28
54
|
end
|
|
29
55
|
|
|
30
56
|
def byteslice(range, length = nil)
|
|
31
57
|
ensure_range(range, length)
|
|
32
|
-
|
|
58
|
+
if @window_start.zero? && base_offset.zero?
|
|
59
|
+
return length ? @source.byteslice(range, length) : @source.byteslice(range)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if length
|
|
63
|
+
@source.byteslice(storage_position(range), length)
|
|
64
|
+
else
|
|
65
|
+
@source.byteslice(storage_range(range))
|
|
66
|
+
end
|
|
33
67
|
end
|
|
34
68
|
|
|
35
69
|
def ensure_available?(end_position)
|
|
@@ -37,7 +71,7 @@ module Flexr
|
|
|
37
71
|
return false if @eof
|
|
38
72
|
|
|
39
73
|
while bytesize < end_position && !@eof
|
|
40
|
-
chunk =
|
|
74
|
+
chunk = read_chunk
|
|
41
75
|
if chunk.nil? || chunk.empty?
|
|
42
76
|
@eof = true
|
|
43
77
|
break
|
|
@@ -63,11 +97,11 @@ module Flexr
|
|
|
63
97
|
end
|
|
64
98
|
|
|
65
99
|
def utf8_boundary?(position)
|
|
66
|
-
return false if position
|
|
67
|
-
return true if position
|
|
100
|
+
return false if position < base_offset
|
|
101
|
+
return true if position == base_offset
|
|
68
102
|
return true unless ensure_available?(position)
|
|
69
103
|
|
|
70
|
-
following =
|
|
104
|
+
following = getbyte(position)
|
|
71
105
|
following.nil? || (following & 0xc0) != 0x80
|
|
72
106
|
end
|
|
73
107
|
|
|
@@ -89,6 +123,31 @@ module Flexr
|
|
|
89
123
|
byteslice(position, length).dup.force_encoding(Encoding::UTF_8).valid_encoding?
|
|
90
124
|
end
|
|
91
125
|
|
|
126
|
+
def utf8_character_length(position)
|
|
127
|
+
first = getbyte(position)
|
|
128
|
+
return 0 unless first
|
|
129
|
+
return 1 if first <= 0x7f
|
|
130
|
+
|
|
131
|
+
length = if first <= 0xdf
|
|
132
|
+
2
|
|
133
|
+
elsif first <= 0xef
|
|
134
|
+
3
|
|
135
|
+
else
|
|
136
|
+
4
|
|
137
|
+
end
|
|
138
|
+
valid_utf8_at?(position) ? length : 1
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def discard_before(position)
|
|
142
|
+
return if @retain_input || position <= base_offset
|
|
143
|
+
|
|
144
|
+
ending = [position, bytesize].min
|
|
145
|
+
removed = ending - base_offset
|
|
146
|
+
@window_start += removed
|
|
147
|
+
@base_offset = ending
|
|
148
|
+
compact_storage!
|
|
149
|
+
end
|
|
150
|
+
|
|
92
151
|
private
|
|
93
152
|
|
|
94
153
|
def ensure_range(range, length)
|
|
@@ -107,6 +166,53 @@ module Flexr
|
|
|
107
166
|
ending = range.end + (range.exclude_end? ? 0 : 1)
|
|
108
167
|
ensure_available?(ending)
|
|
109
168
|
end
|
|
169
|
+
|
|
170
|
+
def storage_position(position)
|
|
171
|
+
local = position - base_offset
|
|
172
|
+
return @window_start + local unless local.negative?
|
|
173
|
+
|
|
174
|
+
raise RangeError, "byte #{position} has been discarded from the streaming buffer"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def storage_range(range)
|
|
178
|
+
return storage_position(range) unless range.is_a?(Range)
|
|
179
|
+
|
|
180
|
+
ending = range.end
|
|
181
|
+
Range.new(storage_position(range.begin), ending.nil? ? nil : storage_position(ending), range.exclude_end?)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def ensure_buffer_size!(size)
|
|
185
|
+
return if size <= max_buffer_size
|
|
186
|
+
|
|
187
|
+
raise BufferTooLargeError.new(filename: @filename, byte_pos: bytesize)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def read_chunk
|
|
191
|
+
capacity = max_buffer_size - retained_bytesize
|
|
192
|
+
if capacity.zero?
|
|
193
|
+
probe = @io.read(1)
|
|
194
|
+
if probe.nil? || probe.empty?
|
|
195
|
+
@eof = true
|
|
196
|
+
return nil
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
raise BufferTooLargeError.new(filename: @filename, byte_pos: bytesize)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
@io.read([@chunk_size, capacity].min)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def compact_storage!
|
|
206
|
+
return if @window_start.zero?
|
|
207
|
+
|
|
208
|
+
if @window_start == @source.bytesize
|
|
209
|
+
@source = String.new(encoding: @source.encoding)
|
|
210
|
+
@window_start = 0
|
|
211
|
+
elsif @window_start >= @chunk_size || @window_start * 2 >= @source.bytesize
|
|
212
|
+
@source = @source.byteslice(@window_start..)
|
|
213
|
+
@window_start = 0
|
|
214
|
+
end
|
|
215
|
+
end
|
|
110
216
|
end
|
|
111
217
|
end
|
|
112
218
|
end
|