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/automaton/dfa.rb
CHANGED
|
@@ -2,42 +2,81 @@
|
|
|
2
2
|
|
|
3
3
|
module Flexr
|
|
4
4
|
module Automaton
|
|
5
|
+
class TransitionRows
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
attr_reader :length
|
|
9
|
+
|
|
10
|
+
def initialize(length, &loader)
|
|
11
|
+
@length = length
|
|
12
|
+
@loader = loader
|
|
13
|
+
@rows = Array.new(length)
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
alias size length
|
|
18
|
+
|
|
19
|
+
def [](state)
|
|
20
|
+
return unless state&.between?(0, length - 1)
|
|
21
|
+
|
|
22
|
+
@rows[state] ||= @loader.call(state).freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fetch(state)
|
|
26
|
+
return self[state] if state.between?(0, length - 1)
|
|
27
|
+
|
|
28
|
+
raise IndexError, "transition row #{state} is out of bounds"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def each
|
|
32
|
+
return enum_for(__method__) unless block_given?
|
|
33
|
+
|
|
34
|
+
length.times { |state| yield self[state] }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def each_index(&)
|
|
38
|
+
return enum_for(__method__) unless block_given?
|
|
39
|
+
|
|
40
|
+
length.times(&)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def materialized_count
|
|
44
|
+
@rows.count { |row| !row.nil? }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
5
48
|
class DFA
|
|
6
49
|
attr_reader :transitions, :accepts, :ec, :class_count, :start, :states, :rule_ids, :packed, :direct
|
|
7
50
|
|
|
8
|
-
def initialize(
|
|
9
|
-
|
|
51
|
+
def initialize(accepts:, ec:, class_count:, start:, rule_ids:, transitions: nil, packed: nil, direct: nil,
|
|
52
|
+
state_count: nil)
|
|
53
|
+
@packed = freeze_representation(packed)
|
|
54
|
+
@direct = freeze_representation(direct)
|
|
55
|
+
@states = state_count || transitions&.length || direct_state_count
|
|
56
|
+
raise ArgumentError, "state_count is required without dense transitions" unless @states
|
|
57
|
+
|
|
58
|
+
@transitions = transition_rows(transitions)
|
|
10
59
|
@accepts = accepts.map do |rules|
|
|
11
60
|
rules.map do |acceptance|
|
|
12
|
-
next acceptance if acceptance.is_a?(Acceptance)
|
|
61
|
+
next acceptance.freeze if acceptance.is_a?(Acceptance)
|
|
13
62
|
|
|
14
63
|
Acceptance.new(rule_index: acceptance[0], pattern_index: acceptance[1],
|
|
15
|
-
bol_only: acceptance[2], end_anchor: acceptance[3])
|
|
64
|
+
bol_only: acceptance[2], end_anchor: acceptance[3]).freeze
|
|
16
65
|
end.freeze
|
|
17
66
|
end.freeze
|
|
18
67
|
@ec = ec.freeze
|
|
19
68
|
@class_count = class_count
|
|
20
69
|
@start = start
|
|
21
|
-
@states = transitions.length
|
|
22
70
|
@rule_ids = rule_ids.map { |id| id.respond_to?(:rule_index) ? id.rule_index : id }.uniq.sort.freeze
|
|
23
|
-
|
|
24
|
-
@direct = direct
|
|
71
|
+
freeze
|
|
25
72
|
end
|
|
26
73
|
|
|
27
74
|
def transition(state, byte)
|
|
28
75
|
class_id = @ec[byte]
|
|
76
|
+
return direct_transition(state, class_id) if @direct
|
|
29
77
|
return @transitions[state][class_id] unless @packed
|
|
30
78
|
|
|
31
|
-
|
|
32
|
-
loop do
|
|
33
|
-
index = @packed.fetch(:base).fetch(cursor) + class_id
|
|
34
|
-
return @packed.fetch(:next).fetch(index) if @packed.fetch(:check)[index] == cursor
|
|
35
|
-
|
|
36
|
-
fallback = @packed[:fallback]&.fetch(cursor)
|
|
37
|
-
return @packed.fetch(:default).fetch(cursor) unless fallback
|
|
38
|
-
|
|
39
|
-
cursor = fallback
|
|
40
|
-
end
|
|
79
|
+
packed_transition(state, class_id)
|
|
41
80
|
end
|
|
42
81
|
|
|
43
82
|
# Generated direct lexers use a flattened dispatch representation. The
|
|
@@ -45,8 +84,7 @@ module Flexr
|
|
|
45
84
|
def transition_direct(state, byte)
|
|
46
85
|
if @direct
|
|
47
86
|
class_id = @ec[byte]
|
|
48
|
-
|
|
49
|
-
return value >= 0 ? value : nil
|
|
87
|
+
return direct_transition(state, class_id)
|
|
50
88
|
end
|
|
51
89
|
|
|
52
90
|
@transitions[state][@ec[byte]]
|
|
@@ -65,6 +103,55 @@ module Flexr
|
|
|
65
103
|
def stats
|
|
66
104
|
{ states: states, classes: class_count, accepting_states: accepts.count { |rules| !rules.empty? } }
|
|
67
105
|
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def transition_rows(transitions)
|
|
110
|
+
return transitions.map(&:freeze).freeze if transitions
|
|
111
|
+
if @packed
|
|
112
|
+
return TransitionRows.new(states) do |state|
|
|
113
|
+
Array.new(class_count) { |class_id| packed_transition(state, class_id) }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
if @direct
|
|
117
|
+
return TransitionRows.new(states) do |state|
|
|
118
|
+
Array.new(class_count) { |class_id| direct_transition(state, class_id) }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
raise ArgumentError, "a transition representation is required"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def packed_transition(state, class_id)
|
|
126
|
+
cursor = state
|
|
127
|
+
loop do
|
|
128
|
+
index = @packed.fetch(:base).fetch(cursor) + class_id
|
|
129
|
+
return @packed.fetch(:next).fetch(index) if @packed.fetch(:check)[index] == cursor
|
|
130
|
+
|
|
131
|
+
fallback = @packed[:fallback]&.fetch(cursor)
|
|
132
|
+
return @packed.fetch(:default).fetch(cursor) unless fallback
|
|
133
|
+
|
|
134
|
+
cursor = fallback
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def direct_transition(state, class_id)
|
|
139
|
+
value = @direct.fetch(:nxt).fetch((state * @direct.fetch(:classes)) + class_id)
|
|
140
|
+
value >= 0 ? value : nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def direct_state_count
|
|
144
|
+
return unless @direct
|
|
145
|
+
|
|
146
|
+
@direct.fetch(:nxt).length / @direct.fetch(:classes)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def freeze_representation(representation)
|
|
150
|
+
return unless representation
|
|
151
|
+
|
|
152
|
+
representation.each_value { |value| value.freeze if value.respond_to?(:freeze) }
|
|
153
|
+
representation.freeze
|
|
154
|
+
end
|
|
68
155
|
end
|
|
69
156
|
|
|
70
157
|
class ReferenceDFA
|
|
@@ -6,65 +6,113 @@ module Flexr
|
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
8
|
def minimize(dfa)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
dead = dfa.states
|
|
10
|
+
rows = dfa.transitions.map { |row| row.map { |destination| destination.nil? ? dead : destination } }
|
|
11
|
+
rows << Array.new(dfa.class_count, dead)
|
|
12
|
+
partitions = initial_partitions(dfa, dead)
|
|
13
|
+
predecessors = predecessor_index(rows, dfa.class_count)
|
|
14
|
+
partitions = refine(partitions, predecessors, dfa.class_count)
|
|
15
|
+
rebuild(dfa, partitions, dead)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initial_partitions(dfa, dead)
|
|
19
|
+
(0..dead).group_by { |state| state == dead ? [] : dfa.accepts[state] }.values
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def predecessor_index(rows, class_count)
|
|
23
|
+
Array.new(class_count) { Array.new(rows.length) { [] } }.tap do |index|
|
|
24
|
+
rows.each_with_index do |row, state|
|
|
25
|
+
row.each_with_index { |destination, class_id| index[class_id][destination] << state }
|
|
16
26
|
end
|
|
17
|
-
|
|
27
|
+
end
|
|
28
|
+
end
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
def refine(partitions, predecessors, class_count)
|
|
31
|
+
worklist = partitions.dup
|
|
32
|
+
pending = {}.compare_by_identity
|
|
33
|
+
partitions.each { |partition| pending[partition] = true }
|
|
34
|
+
cursor = 0
|
|
35
|
+
while cursor < worklist.length
|
|
36
|
+
splitter = worklist.fetch(cursor)
|
|
37
|
+
cursor += 1
|
|
38
|
+
next unless pending.delete(splitter)
|
|
39
|
+
|
|
40
|
+
class_count.times do |class_id|
|
|
41
|
+
marked = splitter.each_with_object({}) do |state, result|
|
|
42
|
+
predecessors[class_id][state].each { |predecessor| result[predecessor] = true }
|
|
43
|
+
end
|
|
44
|
+
next if marked.empty?
|
|
45
|
+
|
|
46
|
+
partitions = split_partitions(partitions, marked, worklist, pending)
|
|
47
|
+
end
|
|
20
48
|
end
|
|
21
|
-
|
|
49
|
+
partitions
|
|
22
50
|
end
|
|
23
51
|
|
|
24
|
-
def
|
|
25
|
-
|
|
52
|
+
def split_partitions(partitions, marked, worklist, pending)
|
|
53
|
+
partitions.flat_map do |partition|
|
|
54
|
+
inside, outside = partition.partition { |state| marked[state] }
|
|
55
|
+
next [partition] if inside.empty? || outside.empty?
|
|
56
|
+
|
|
57
|
+
if pending.delete(partition)
|
|
58
|
+
enqueue(inside, worklist, pending)
|
|
59
|
+
enqueue(outside, worklist, pending)
|
|
60
|
+
else
|
|
61
|
+
enqueue(inside.length <= outside.length ? inside : outside, worklist, pending)
|
|
62
|
+
end
|
|
63
|
+
[inside, outside]
|
|
64
|
+
end
|
|
26
65
|
end
|
|
27
66
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
ids
|
|
67
|
+
def enqueue(partition, worklist, pending)
|
|
68
|
+
worklist << partition
|
|
69
|
+
pending[partition] = true
|
|
32
70
|
end
|
|
33
71
|
|
|
34
|
-
def rebuild(dfa, partitions)
|
|
72
|
+
def rebuild(dfa, partitions, dead)
|
|
35
73
|
groups = group_ids(partitions)
|
|
74
|
+
dead_group = groups.fetch(dead)
|
|
36
75
|
start_group = groups.fetch(dfa.start)
|
|
37
|
-
|
|
76
|
+
representatives = partitions.map { |partition| partition.find { |state| state != dead } }
|
|
77
|
+
order = reachable_groups(dfa, groups, representatives, start_group, dead_group)
|
|
38
78
|
index = order.each_with_index.to_h
|
|
39
79
|
transitions = order.map do |group|
|
|
40
|
-
representative =
|
|
41
|
-
dfa.transitions
|
|
80
|
+
representative = representatives.fetch(group)
|
|
81
|
+
dfa.transitions.fetch(representative).map do |destination|
|
|
82
|
+
next nil if destination.nil? || groups.fetch(destination) == dead_group
|
|
83
|
+
|
|
84
|
+
index.fetch(groups.fetch(destination))
|
|
85
|
+
end
|
|
42
86
|
end
|
|
43
|
-
accepts = order.map { |group| dfa.accepts
|
|
87
|
+
accepts = order.map { |group| dfa.accepts.fetch(representatives.fetch(group)) }
|
|
44
88
|
rule_ids = accepts.flatten.map(&:rule_index).uniq.sort
|
|
45
89
|
DFA.new(transitions: transitions, accepts: accepts, ec: dfa.ec, class_count: dfa.class_count,
|
|
46
90
|
start: 0, rule_ids: rule_ids)
|
|
47
91
|
end
|
|
48
92
|
|
|
49
|
-
def
|
|
93
|
+
def group_ids(partitions)
|
|
94
|
+
partitions.each_with_index.with_object({}) do |(partition, group), result|
|
|
95
|
+
partition.each { |state| result[state] = group }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def reachable_groups(dfa, groups, representatives, start_group, dead_group)
|
|
50
100
|
result = []
|
|
51
101
|
queue = [start_group]
|
|
52
102
|
seen = {}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
103
|
+
cursor = 0
|
|
104
|
+
while cursor < queue.length
|
|
105
|
+
group = queue.fetch(cursor)
|
|
106
|
+
cursor += 1
|
|
107
|
+
next if seen[group] || (group == dead_group && group != start_group)
|
|
56
108
|
|
|
57
109
|
seen[group] = true
|
|
58
110
|
result << group
|
|
59
|
-
representative =
|
|
60
|
-
representative.compact.each { |destination| queue << groups
|
|
111
|
+
representative = representatives.fetch(group)
|
|
112
|
+
dfa.transitions.fetch(representative).compact.each { |destination| queue << groups.fetch(destination) }
|
|
61
113
|
end
|
|
62
114
|
result
|
|
63
115
|
end
|
|
64
|
-
|
|
65
|
-
def group_members(dfa, groups, group)
|
|
66
|
-
(0...dfa.states).select { |state| groups[state] == group }
|
|
67
|
-
end
|
|
68
116
|
end
|
|
69
117
|
end
|
|
70
118
|
end
|
data/lib/flexr/automaton/nfa.rb
CHANGED
|
@@ -4,11 +4,6 @@ module Flexr
|
|
|
4
4
|
module Automaton
|
|
5
5
|
NFAState = Struct.new(:epsilon, :transitions, :accepts, keyword_init: true)
|
|
6
6
|
NFATransition = Struct.new(:lo, :hi, :to, keyword_init: true)
|
|
7
|
-
Acceptance = Struct.new(:rule_index, :pattern_index, :bol_only, :end_anchor, keyword_init: true) do
|
|
8
|
-
def inspect
|
|
9
|
-
[rule_index, pattern_index, bol_only, end_anchor].inspect
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
7
|
|
|
13
8
|
class NFA
|
|
14
9
|
attr_reader :states, :start, :byte_classes
|
|
@@ -56,6 +51,8 @@ module Flexr
|
|
|
56
51
|
when Regexp::AST::Empty
|
|
57
52
|
state = @nfa.new_state
|
|
58
53
|
[state, state]
|
|
54
|
+
when Regexp::AST::Fail
|
|
55
|
+
[@nfa.new_state, @nfa.new_state]
|
|
59
56
|
when Regexp::AST::ByteRange
|
|
60
57
|
from = @nfa.new_state
|
|
61
58
|
to = @nfa.new_state
|
|
@@ -75,18 +72,53 @@ module Flexr
|
|
|
75
72
|
end
|
|
76
73
|
[from, to]
|
|
77
74
|
when Regexp::AST::Star
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
@nfa.epsilon(from, to)
|
|
82
|
-
@nfa.epsilon(from, child_start)
|
|
83
|
-
@nfa.epsilon(child_end, child_start)
|
|
84
|
-
@nfa.epsilon(child_end, to)
|
|
85
|
-
[from, to]
|
|
75
|
+
repeat_fragment(node.child, 0, nil)
|
|
76
|
+
when Regexp::AST::Repeat
|
|
77
|
+
repeat_fragment(node.child, node.minimum, node.maximum)
|
|
86
78
|
else
|
|
87
79
|
raise CompileError, "cannot build NFA from #{node.class}"
|
|
88
80
|
end
|
|
89
81
|
end
|
|
82
|
+
|
|
83
|
+
def repeat_fragment(child, minimum, maximum)
|
|
84
|
+
return fragment(Regexp::AST::Empty.new(loc: nil)) if maximum&.zero?
|
|
85
|
+
return star_fragment(child) if minimum.zero? && maximum.nil?
|
|
86
|
+
|
|
87
|
+
from = @nfa.new_state
|
|
88
|
+
cursor = from
|
|
89
|
+
last_start = nil
|
|
90
|
+
minimum.times do
|
|
91
|
+
child_start, child_end = fragment(child)
|
|
92
|
+
@nfa.epsilon(cursor, child_start)
|
|
93
|
+
cursor = child_end
|
|
94
|
+
last_start = child_start
|
|
95
|
+
end
|
|
96
|
+
if maximum.nil?
|
|
97
|
+
@nfa.epsilon(cursor, last_start)
|
|
98
|
+
return [from, cursor]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
(maximum - minimum).times do
|
|
102
|
+
child_start, child_end = fragment(child)
|
|
103
|
+
next_cursor = @nfa.new_state
|
|
104
|
+
@nfa.epsilon(cursor, next_cursor)
|
|
105
|
+
@nfa.epsilon(cursor, child_start)
|
|
106
|
+
@nfa.epsilon(child_end, next_cursor)
|
|
107
|
+
cursor = next_cursor
|
|
108
|
+
end
|
|
109
|
+
[from, cursor]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def star_fragment(child)
|
|
113
|
+
from = @nfa.new_state
|
|
114
|
+
to = @nfa.new_state
|
|
115
|
+
child_start, child_end = fragment(child)
|
|
116
|
+
@nfa.epsilon(from, to)
|
|
117
|
+
@nfa.epsilon(from, child_start)
|
|
118
|
+
@nfa.epsilon(child_end, child_start)
|
|
119
|
+
@nfa.epsilon(child_end, to)
|
|
120
|
+
[from, to]
|
|
121
|
+
end
|
|
90
122
|
end
|
|
91
123
|
end
|
|
92
124
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flexr
|
|
4
|
+
module Automaton
|
|
5
|
+
Acceptance = Struct.new(:rule_index, :pattern_index, :bol_only, :end_anchor, keyword_init: true) do
|
|
6
|
+
def inspect
|
|
7
|
+
[rule_index, pattern_index, bol_only, end_anchor].inspect
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
CompiledSpec = Struct.new(:machines, :rules, :states, :stats, :diagnostics, keyword_init: true)
|
|
12
|
+
Machine = Struct.new(:dfa, :state_name, keyword_init: true)
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/flexr/cli.rb
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "runtime" unless defined?(Flexr::Lexer)
|
|
4
|
+
require_relative "artifact_writer"
|
|
5
|
+
require_relative "generator"
|
|
6
|
+
require_relative "importer"
|
|
7
|
+
require_relative "options"
|
|
8
|
+
|
|
3
9
|
module Flexr
|
|
4
10
|
module CLI
|
|
5
11
|
COMMANDS = %w[check stats tokens dot explain trace bench import].freeze
|
|
@@ -121,13 +127,13 @@ module Flexr
|
|
|
121
127
|
return EXIT_FAILURE unless result.complete?
|
|
122
128
|
|
|
123
129
|
if output
|
|
124
|
-
|
|
130
|
+
ArtifactWriter.write!(output, result.source, source_path: spec)
|
|
125
131
|
else
|
|
126
132
|
out.write(result.source)
|
|
127
133
|
end
|
|
128
134
|
EXIT_OK
|
|
129
135
|
when :generate
|
|
130
|
-
target = output ||
|
|
136
|
+
target = output || ArtifactWriter.default_generated_path(spec)
|
|
131
137
|
Generator.new(spec, output: target, eval_mode: options.eval_mode,
|
|
132
138
|
options: options.generator_options).generate
|
|
133
139
|
EXIT_OK
|
|
@@ -142,7 +148,7 @@ module Flexr
|
|
|
142
148
|
else
|
|
143
149
|
compile(read_spec(spec), overrides: options.overrides)
|
|
144
150
|
end
|
|
145
|
-
diagnostics = compiled.is_a?(Array) ? compiled : Array(compiled&.diagnostics)
|
|
151
|
+
diagnostics = (compiled.is_a?(Array) ? compiled : Array(compiled&.diagnostics)).dup
|
|
146
152
|
diagnostics.select! do |diagnostic|
|
|
147
153
|
options.warn_level == :all || (options.warn_level == :default && diagnostic.code != "FLEXR-W016")
|
|
148
154
|
end
|
|
@@ -275,8 +281,8 @@ module Flexr
|
|
|
275
281
|
klass.encoding(config.fetch(:encoding, Encoding::UTF_8))
|
|
276
282
|
Array(config[:declared_tokens]).each { |token| klass.emits(token) }
|
|
277
283
|
config_options = config.fetch(:options, {}).merge(overrides.slice(:experimental, :allow_empty_match))
|
|
278
|
-
config_options.each do |name, value|
|
|
279
|
-
|
|
284
|
+
config_options.slice(*Configuration::BOOLEAN_OPTIONS).each do |name, value|
|
|
285
|
+
klass.option(name) if value
|
|
280
286
|
end
|
|
281
287
|
klass.__flexr_config.options[:max_dfa_states] = overrides[:max_dfa_states] if overrides[:max_dfa_states]
|
|
282
288
|
accel = overrides.fetch(:accel, config_options[:accel])
|
data/lib/flexr/codegen/direct.rb
CHANGED
|
@@ -4,49 +4,13 @@ module Flexr
|
|
|
4
4
|
module Codegen
|
|
5
5
|
class Direct < Table
|
|
6
6
|
def generate(state: :initial)
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
dfa = compiled.machines.fetch(state).dfa
|
|
8
|
+
{
|
|
9
|
+
nxt: dfa.transitions.flatten.map { |value| value || -1 }.freeze,
|
|
10
|
+
classes: dfa.class_count
|
|
11
|
+
}.freeze
|
|
9
12
|
end
|
|
10
13
|
|
|
11
|
-
def source(indent: " ")
|
|
12
|
-
lines = []
|
|
13
|
-
lines << "#{indent}def self.__flexr_generated_direct_transition(state_name, state, byte)"
|
|
14
|
-
lines << "#{indent} case state_name"
|
|
15
|
-
compiled.machines.each do |state_name, machine|
|
|
16
|
-
lines << "#{indent} when #{state_name.inspect}"
|
|
17
|
-
lines << "#{indent} class_id = case byte"
|
|
18
|
-
byte_classes(machine.dfa.ec).each do |first, last, class_id|
|
|
19
|
-
selector = first == last ? first.to_s : "#{first}..#{last}"
|
|
20
|
-
lines << "#{indent} when #{selector} then #{class_id}"
|
|
21
|
-
end
|
|
22
|
-
lines << "#{indent} end"
|
|
23
|
-
lines << "#{indent} case state"
|
|
24
|
-
machine.dfa.transitions.each_with_index do |row, state|
|
|
25
|
-
lines << "#{indent} when #{state}"
|
|
26
|
-
lines << "#{indent} case class_id"
|
|
27
|
-
row.each_with_index do |destination, class_id|
|
|
28
|
-
value = destination.nil? ? "nil" : destination
|
|
29
|
-
lines << "#{indent} when #{class_id} then #{value}"
|
|
30
|
-
end
|
|
31
|
-
lines << "#{indent} else nil"
|
|
32
|
-
lines << "#{indent} end"
|
|
33
|
-
end
|
|
34
|
-
lines << "#{indent} else nil"
|
|
35
|
-
lines << "#{indent} end"
|
|
36
|
-
end
|
|
37
|
-
lines << "#{indent} else nil"
|
|
38
|
-
lines << "#{indent} end"
|
|
39
|
-
lines << "#{indent}end"
|
|
40
|
-
"#{lines.join("\n")}\n"
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def byte_classes(ec)
|
|
46
|
-
ec.each_with_index.chunk_while { |left, right| right[0] == left[0] }.map do |run|
|
|
47
|
-
[run.first[1], run.last[1], run.first[0]]
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
14
|
end
|
|
51
15
|
end
|
|
52
16
|
end
|