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/codegen/table.rb
CHANGED
|
@@ -28,56 +28,139 @@ module Flexr
|
|
|
28
28
|
def source(indent: " ")
|
|
29
29
|
lines = []
|
|
30
30
|
lines << "#{indent}def scan_one"
|
|
31
|
-
lines << "#{indent}
|
|
32
|
-
lines << "#{indent}
|
|
33
|
-
lines << "#{indent}
|
|
31
|
+
lines << "#{indent} if @__flexr_generated_scan_state != @state"
|
|
32
|
+
lines << "#{indent} machine = self.class.__flexr_compiled.machines.fetch(@state)"
|
|
33
|
+
lines << "#{indent} @__flexr_generated_scan_state = @state"
|
|
34
|
+
lines << "#{indent} @__flexr_generated_scan_dfa = machine.dfa"
|
|
35
|
+
lines << "#{indent} @__flexr_generated_scan_fast = __flexr_generated_fast_path?(@__flexr_generated_scan_dfa)"
|
|
36
|
+
lines << "#{indent} @__flexr_generated_scan_simple = __flexr_generated_simple_fast_path?(@__flexr_generated_scan_dfa)"
|
|
37
|
+
lines << "#{indent} end"
|
|
38
|
+
lines << "#{indent} dfa = @__flexr_generated_scan_dfa"
|
|
39
|
+
lines << "#{indent} return Flexr::Runtime::Interpreter.new(self).scan unless @__flexr_generated_scan_fast"
|
|
34
40
|
lines << "#{indent} position = byte_pos"
|
|
35
41
|
lines << "#{indent} return nil unless valid_utf8_at?(position)"
|
|
42
|
+
lines << "#{indent} return __flexr_generated_scan_simple(dfa, position) if @__flexr_generated_scan_simple"
|
|
36
43
|
lines << "#{indent} cursor = position"
|
|
37
44
|
lines << "#{indent} current = dfa.start"
|
|
38
45
|
lines << "#{indent} direct = dfa.direct"
|
|
39
|
-
lines << "#{indent}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
lines << "#{indent}
|
|
48
|
-
lines << "#{indent}
|
|
46
|
+
lines << "#{indent} direct_nxt = direct&.fetch(:nxt)"
|
|
47
|
+
lines << "#{indent} direct_classes = direct&.fetch(:classes)"
|
|
48
|
+
lines << "#{indent} ec = dfa.ec"
|
|
49
|
+
lines << "#{indent} source = buffer.stable_source"
|
|
50
|
+
lines << "#{indent} guarded_steps = scan_steps_guarded?"
|
|
51
|
+
lines << "#{indent} token_start = more_text_start || position"
|
|
52
|
+
lines << "#{indent} token_limit = max_token_size"
|
|
53
|
+
lines << "#{indent} check_token_limit = token_limit_required?"
|
|
54
|
+
lines << "#{indent} best = nil"
|
|
55
|
+
lines << "#{indent} accel_mode = @accel_mode"
|
|
56
|
+
lines << "#{indent} accelerate = accel_mode != :none && !(accel_mode == :auto && @__flexr_generated_auto_accel_off)"
|
|
57
|
+
lines << "#{indent} while cursor < (source ? source.bytesize : buffer.bytesize) || buffer.ensure_available?(cursor + 1)"
|
|
49
58
|
lines << "#{indent} if accelerate"
|
|
50
59
|
lines << "#{indent} region = __flexr_generated_acceleration_region(dfa, current)"
|
|
51
|
-
lines << "#{indent}
|
|
60
|
+
lines << "#{indent} acceptance = dfa.accepts[current].first"
|
|
61
|
+
lines << "#{indent} token_rule = acceptance&.rule_index"
|
|
62
|
+
lines << "#{indent} accelerated_end = __flexr_generated_accelerate(region, cursor, token_rule, position) if region"
|
|
52
63
|
lines << "#{indent} if accelerated_end && accelerated_end > cursor"
|
|
53
64
|
lines << "#{indent} cursor = accelerated_end"
|
|
54
65
|
lines << "#{indent} best = __flexr_generated_acceptance(dfa, current, position, cursor, best)"
|
|
55
66
|
lines << "#{indent} next"
|
|
56
67
|
lines << "#{indent} end"
|
|
57
68
|
lines << "#{indent} end"
|
|
58
|
-
lines << "#{indent} byte = source.getbyte(cursor)"
|
|
59
|
-
lines << "#{indent}
|
|
60
|
-
lines << "#{indent}
|
|
61
|
-
lines << "#{indent}
|
|
69
|
+
lines << "#{indent} byte = source ? source.getbyte(cursor) : buffer.getbyte(cursor)"
|
|
70
|
+
lines << "#{indent} consume_step! if guarded_steps"
|
|
71
|
+
lines << "#{indent} current = if direct_nxt"
|
|
72
|
+
lines << "#{indent} class_id = ec[byte]"
|
|
73
|
+
lines << "#{indent} value = direct_nxt[(current * direct_classes) + class_id]"
|
|
62
74
|
lines << "#{indent} value >= 0 ? value : nil"
|
|
63
75
|
lines << "#{indent} else"
|
|
64
76
|
lines << "#{indent} dfa.transition(current, byte)"
|
|
65
77
|
lines << "#{indent} end"
|
|
66
78
|
lines << "#{indent} break unless current"
|
|
67
79
|
lines << "#{indent} cursor += 1"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
lines << "#{indent} resource_acceptance = dfa.accepts[current].first"
|
|
81
|
+
lines << "#{indent} resource_rule = resource_acceptance&.rule_index"
|
|
82
|
+
lines << "#{indent} if check_token_limit"
|
|
83
|
+
lines << "#{indent} size = cursor - token_start"
|
|
84
|
+
lines << "#{indent} defer_token_size_check!(size, rule: resource_rule) if size > token_limit"
|
|
85
|
+
lines << "#{indent} end"
|
|
86
|
+
lines << "#{indent} best = __flexr_generated_acceptance(dfa, current, position, cursor, best)"
|
|
87
|
+
lines << "#{indent} end"
|
|
88
|
+
lines << "#{indent} best"
|
|
89
|
+
lines << "#{indent}end"
|
|
90
|
+
lines << "#{indent}def __flexr_generated_scan_simple(dfa, position)"
|
|
91
|
+
lines << "#{indent} current = dfa.start"
|
|
92
|
+
lines << "#{indent} direct = dfa.direct"
|
|
93
|
+
lines << "#{indent} direct_nxt = direct&.fetch(:nxt)"
|
|
94
|
+
lines << "#{indent} direct_classes = direct&.fetch(:classes)"
|
|
95
|
+
lines << "#{indent} ec = dfa.ec"
|
|
96
|
+
lines << "#{indent} accepts = dfa.accepts"
|
|
97
|
+
lines << "#{indent} rules = self.class.__flexr_rules"
|
|
98
|
+
lines << "#{indent} source = buffer.stable_source"
|
|
99
|
+
lines << "#{indent} guarded_steps = scan_steps_guarded?"
|
|
100
|
+
lines << "#{indent} token_start = more_text_start || position"
|
|
101
|
+
lines << "#{indent} token_limit = max_token_size"
|
|
102
|
+
lines << "#{indent} check_token_limit = token_limit_required?"
|
|
103
|
+
lines << "#{indent} cursor = position"
|
|
104
|
+
lines << "#{indent} best = nil"
|
|
105
|
+
lines << "#{indent} accel_mode = @accel_mode"
|
|
106
|
+
lines << "#{indent} accelerate = accel_mode != :none && !(accel_mode == :auto && @__flexr_generated_auto_accel_off)"
|
|
107
|
+
lines << "#{indent} if source && !guarded_steps && !check_token_limit && !accelerate"
|
|
108
|
+
lines << "#{indent} while cursor < source.bytesize"
|
|
109
|
+
lines << "#{indent} byte = source.getbyte(cursor)"
|
|
110
|
+
lines << "#{indent} current = if direct_nxt"
|
|
111
|
+
lines << "#{indent} class_id = ec[byte]"
|
|
112
|
+
lines << "#{indent} value = direct_nxt[(current * direct_classes) + class_id]"
|
|
113
|
+
lines << "#{indent} value >= 0 ? value : nil"
|
|
114
|
+
lines << "#{indent} else"
|
|
115
|
+
lines << "#{indent} dfa.transition(current, byte)"
|
|
116
|
+
lines << "#{indent} end"
|
|
117
|
+
lines << "#{indent} break unless current"
|
|
118
|
+
lines << "#{indent} cursor += 1"
|
|
119
|
+
lines << "#{indent} acceptance = accepts[current].first"
|
|
120
|
+
lines << "#{indent} next unless acceptance"
|
|
121
|
+
lines << "#{indent} rule = rules.fetch(acceptance.rule_index)"
|
|
122
|
+
lines << "#{indent} best ||= (@__flexr_generated_match ||= Flexr::Runtime::Match.new)"
|
|
123
|
+
lines << "#{indent} best.rule = rule"
|
|
124
|
+
lines << "#{indent} best.start_pos = position"
|
|
125
|
+
lines << "#{indent} best.end_pos = cursor"
|
|
126
|
+
lines << "#{indent} best.total_end_pos = cursor"
|
|
127
|
+
lines << "#{indent} end"
|
|
128
|
+
lines << "#{indent} return best"
|
|
129
|
+
lines << "#{indent} end"
|
|
130
|
+
lines << "#{indent} while cursor < (source ? source.bytesize : buffer.bytesize) || buffer.ensure_available?(cursor + 1)"
|
|
131
|
+
lines << "#{indent} if accelerate"
|
|
132
|
+
lines << "#{indent} region = __flexr_generated_acceleration_region(dfa, current)"
|
|
133
|
+
lines << "#{indent} acceptance = accepts[current].first"
|
|
134
|
+
lines << "#{indent} accelerated_end = __flexr_generated_accelerate(region, cursor, acceptance&.rule_index, position) if region"
|
|
135
|
+
lines << "#{indent} if accelerated_end && accelerated_end > cursor"
|
|
136
|
+
lines << "#{indent} cursor = accelerated_end"
|
|
137
|
+
lines << "#{indent} best = __flexr_generated_acceptance(dfa, current, position, cursor, best)"
|
|
138
|
+
lines << "#{indent} next"
|
|
139
|
+
lines << "#{indent} end"
|
|
140
|
+
lines << "#{indent} end"
|
|
141
|
+
lines << "#{indent} byte = source ? source.getbyte(cursor) : buffer.getbyte(cursor)"
|
|
142
|
+
lines << "#{indent} consume_step! if guarded_steps"
|
|
143
|
+
lines << "#{indent} current = if direct_nxt"
|
|
144
|
+
lines << "#{indent} class_id = ec[byte]"
|
|
145
|
+
lines << "#{indent} value = direct_nxt[(current * direct_classes) + class_id]"
|
|
146
|
+
lines << "#{indent} value >= 0 ? value : nil"
|
|
147
|
+
lines << "#{indent} else"
|
|
148
|
+
lines << "#{indent} dfa.transition(current, byte)"
|
|
149
|
+
lines << "#{indent} end"
|
|
150
|
+
lines << "#{indent} break unless current"
|
|
151
|
+
lines << "#{indent} cursor += 1"
|
|
152
|
+
lines << "#{indent} acceptance = accepts[current].first"
|
|
153
|
+
lines << "#{indent} if check_token_limit"
|
|
154
|
+
lines << "#{indent} size = cursor - token_start"
|
|
155
|
+
lines << "#{indent} defer_token_size_check!(size, rule: acceptance&.rule_index) if size > token_limit"
|
|
156
|
+
lines << "#{indent} end"
|
|
157
|
+
lines << "#{indent} next unless acceptance"
|
|
158
|
+
lines << "#{indent} rule = rules.fetch(acceptance.rule_index)"
|
|
159
|
+
lines << "#{indent} best ||= (@__flexr_generated_match ||= Flexr::Runtime::Match.new)"
|
|
160
|
+
lines << "#{indent} best.rule = rule"
|
|
161
|
+
lines << "#{indent} best.start_pos = position"
|
|
162
|
+
lines << "#{indent} best.end_pos = cursor"
|
|
163
|
+
lines << "#{indent} best.total_end_pos = cursor"
|
|
81
164
|
lines << "#{indent} end"
|
|
82
165
|
lines << "#{indent} best"
|
|
83
166
|
lines << "#{indent}end"
|
|
@@ -86,44 +169,75 @@ module Flexr
|
|
|
86
169
|
lines << "#{indent} regions = (@__flexr_generated_accel_regions[dfa] ||= Flexr::Automaton::Accel.extract(dfa).to_h { |region| [region.state, region] })"
|
|
87
170
|
lines << "#{indent} region = regions[state]"
|
|
88
171
|
lines << "#{indent} return unless region"
|
|
172
|
+
lines << "#{indent} return if @__flexr_generated_disabled_auto_accel&.key?(region)"
|
|
173
|
+
lines << "#{indent} return if utf8_input? && region.bytes.any? { |byte| byte >= 128 }"
|
|
89
174
|
lines << "#{indent} return if dfa.accepts[state].any? do |acceptance|"
|
|
90
175
|
lines << "#{indent} rule = self.class.__flexr_rules.fetch(acceptance.rule_index)"
|
|
91
176
|
lines << "#{indent} acceptance.bol_only || acceptance.end_anchor || rule.trailing"
|
|
92
177
|
lines << "#{indent} end"
|
|
93
178
|
lines << "#{indent} region"
|
|
94
179
|
lines << "#{indent}end"
|
|
95
|
-
lines << "#{indent}def __flexr_generated_accelerate(region, position)"
|
|
96
|
-
lines << "#{indent}
|
|
97
|
-
lines << "#{indent}
|
|
98
|
-
lines << "#{indent}
|
|
99
|
-
lines << "#{indent}
|
|
100
|
-
lines << "#{indent}
|
|
101
|
-
lines << "#{indent}
|
|
102
|
-
lines << "#{indent}
|
|
103
|
-
lines << "#{indent}
|
|
104
|
-
lines << "#{indent}
|
|
105
|
-
lines << "#{indent}
|
|
106
|
-
lines << "#{indent}
|
|
107
|
-
lines << "#{indent}
|
|
108
|
-
lines << "#{indent}
|
|
109
|
-
lines << "#{indent}
|
|
110
|
-
lines << "#{indent}
|
|
111
|
-
lines << "#{indent}
|
|
112
|
-
lines << "#{indent}
|
|
113
|
-
lines << "#{indent}
|
|
114
|
-
lines << "#{indent}
|
|
115
|
-
lines << "#{indent}
|
|
116
|
-
lines << "#{indent}
|
|
117
|
-
lines << "#{indent}
|
|
118
|
-
lines << "#{indent}
|
|
119
|
-
lines << "#{indent}
|
|
120
|
-
lines << "#{indent}
|
|
121
|
-
lines << "#{indent} !rule.trailing.nil? || rule.patterns.any? do |pattern|"
|
|
122
|
-
lines << "#{indent} next false unless pattern.is_a?(Regexp)"
|
|
123
|
-
lines << "#{indent} next true if pattern.source.match?(/\\\\[pP]\\\\{/) || pattern.source.match?(/\\[:(?:\\^)?[a-z]+:\\]/)"
|
|
124
|
-
lines << "#{indent} self.class.__flexr_config.options[:unicode] == true && utf8_input? && pattern.source.match?(/\\\\[dDwWsS]/)"
|
|
180
|
+
lines << "#{indent}def __flexr_generated_accelerate(region, position, token_rule, token_start)"
|
|
181
|
+
lines << "#{indent} mode = @accel_mode"
|
|
182
|
+
lines << "#{indent} cursor = position"
|
|
183
|
+
lines << "#{indent} matched = false"
|
|
184
|
+
lines << "#{indent} loop do"
|
|
185
|
+
lines << "#{indent} if cursor >= buffer.bytesize"
|
|
186
|
+
lines << "#{indent} return matched ? cursor : nil if buffer.eof_loaded?"
|
|
187
|
+
lines << "#{indent} return matched ? cursor : nil unless buffer.ensure_available?(cursor + 1)"
|
|
188
|
+
lines << "#{indent} end"
|
|
189
|
+
lines << "#{indent} segment = buffer.source"
|
|
190
|
+
lines << "#{indent} segment_position = cursor - buffer.base_offset"
|
|
191
|
+
lines << "#{indent} if segment.encoding == ::Encoding::UTF_8 && region.utf8_regexp"
|
|
192
|
+
lines << "#{indent} regexp = region.utf8_regexp"
|
|
193
|
+
lines << "#{indent} else"
|
|
194
|
+
lines << "#{indent} segment = segment.b unless segment.encoding == ::Encoding::BINARY"
|
|
195
|
+
lines << "#{indent} regexp = region.regexp"
|
|
196
|
+
lines << "#{indent} end"
|
|
197
|
+
lines << "#{indent} length = begin"
|
|
198
|
+
lines << "#{indent} if %i[strscan auto].include?(mode) && defined?(::StringScanner)"
|
|
199
|
+
lines << "#{indent} @__flexr_generated_accel_scanner ||= ::StringScanner.new(\"\".b)"
|
|
200
|
+
lines << "#{indent} @__flexr_generated_accel_scanner.string = segment"
|
|
201
|
+
lines << "#{indent} @__flexr_generated_accel_scanner.pos = segment_position"
|
|
202
|
+
lines << "#{indent} @__flexr_generated_accel_scanner.skip(regexp)"
|
|
203
|
+
lines << "#{indent} else"
|
|
204
|
+
lines << "#{indent} match = regexp.match(segment, segment_position)"
|
|
205
|
+
lines << "#{indent} match && match.begin(0) == segment_position ? match.end(0) - segment_position : nil"
|
|
125
206
|
lines << "#{indent} end"
|
|
207
|
+
lines << "#{indent} rescue ArgumentError"
|
|
208
|
+
lines << "#{indent} nil"
|
|
126
209
|
lines << "#{indent} end"
|
|
210
|
+
lines << "#{indent} __flexr_generated_record_auto_accel_miss(region) if mode == :auto && length.to_i < 8"
|
|
211
|
+
lines << "#{indent} return matched ? cursor : nil unless length&.positive?"
|
|
212
|
+
lines << "#{indent} matched = true"
|
|
213
|
+
lines << "#{indent} cursor += length"
|
|
214
|
+
lines << "#{indent} consume_step!(length)"
|
|
215
|
+
lines << "#{indent} defer_token_size_check!(cursor - (more_text_start || token_start), rule: token_rule)"
|
|
216
|
+
lines << "#{indent} return cursor if cursor < buffer.bytesize || buffer.eof_loaded?"
|
|
217
|
+
lines << "#{indent} end"
|
|
218
|
+
lines << "#{indent}end"
|
|
219
|
+
lines << "#{indent}def __flexr_generated_record_auto_accel_miss(region)"
|
|
220
|
+
lines << "#{indent} @__flexr_generated_auto_accel_misses ||= Hash.new(0)"
|
|
221
|
+
lines << "#{indent} @__flexr_generated_auto_accel_misses[region] += 1"
|
|
222
|
+
lines << "#{indent} @__flexr_generated_auto_accel_miss_total = @__flexr_generated_auto_accel_miss_total.to_i + 1"
|
|
223
|
+
lines << "#{indent} @__flexr_generated_auto_accel_off = true if @__flexr_generated_auto_accel_miss_total >= 9"
|
|
224
|
+
lines << "#{indent} return if @__flexr_generated_auto_accel_misses[region] < 3"
|
|
225
|
+
lines << "#{indent} @__flexr_generated_disabled_auto_accel ||= {}"
|
|
226
|
+
lines << "#{indent} @__flexr_generated_disabled_auto_accel[region] = true"
|
|
227
|
+
lines << "#{indent}end"
|
|
228
|
+
lines << "#{indent}def __flexr_generated_fast_path?(dfa)"
|
|
229
|
+
lines << "#{indent} @__flexr_generated_fast_paths ||= {}"
|
|
230
|
+
lines << "#{indent} return @__flexr_generated_fast_paths[dfa] if @__flexr_generated_fast_paths.key?(dfa)"
|
|
231
|
+
lines << "#{indent} rules = dfa.rule_ids.map { |rule_index| self.class.__flexr_rules.fetch(rule_index) }"
|
|
232
|
+
lines << "#{indent} @__flexr_generated_fast_paths[dfa] = self.class.__flexr_config.backend != :firstmatch &&"
|
|
233
|
+
lines << "#{indent} self.class.__flexr_config.options[:allow_empty_match] != true && rules.none?(&:trailing)"
|
|
234
|
+
lines << "#{indent}end"
|
|
235
|
+
lines << "#{indent}def __flexr_generated_simple_fast_path?(dfa)"
|
|
236
|
+
lines << "#{indent} @__flexr_generated_simple_fast_paths ||= {}"
|
|
237
|
+
lines << "#{indent} return @__flexr_generated_simple_fast_paths[dfa] if @__flexr_generated_simple_fast_paths.key?(dfa)"
|
|
238
|
+
lines << "#{indent} rules = dfa.rule_ids.map { |rule_index| self.class.__flexr_rules.fetch(rule_index) }"
|
|
239
|
+
lines << "#{indent} @__flexr_generated_simple_fast_paths[dfa] = rules.all? do |rule|"
|
|
240
|
+
lines << "#{indent} rule.pattern_conditions.all? { |condition| condition && !condition.bol_only && !condition.end_anchor }"
|
|
127
241
|
lines << "#{indent} end"
|
|
128
242
|
lines << "#{indent}end"
|
|
129
243
|
lines << "#{indent}def __flexr_generated_acceptance(dfa, state, start_position, cursor, best)"
|
|
@@ -131,7 +245,6 @@ module Flexr
|
|
|
131
245
|
lines << "#{indent} next if acceptance.bol_only && !beginning_of_line?"
|
|
132
246
|
lines << "#{indent} next if acceptance.end_anchor && !(buffer.eof?(cursor) || buffer.getbyte(cursor) == 0x0a)"
|
|
133
247
|
lines << "#{indent} rule = self.class.__flexr_rules.fetch(acceptance.rule_index)"
|
|
134
|
-
lines << "#{indent} defer_token_size_check!(cursor - start_position)"
|
|
135
248
|
lines << "#{indent} next if best && cursor < best.total_end_pos"
|
|
136
249
|
lines << "#{indent} next if best && cursor == best.total_end_pos &&"
|
|
137
250
|
lines << "#{indent} rule.index > best.rule.index"
|
|
@@ -146,13 +259,6 @@ module Flexr
|
|
|
146
259
|
"#{lines.join("\n")}\n"
|
|
147
260
|
end
|
|
148
261
|
|
|
149
|
-
def simple_fast_path?
|
|
150
|
-
compiled.rules.all? do |rule|
|
|
151
|
-
rule.trailing.nil? && rule.pattern_conditions.all? do |condition|
|
|
152
|
-
condition && !condition.bol_only && !condition.end_anchor
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
262
|
end
|
|
157
263
|
end
|
|
158
264
|
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "runtime" unless defined?(Flexr::Lexer)
|
|
4
|
+
require_relative "codegen/base"
|
|
5
|
+
require_relative "codegen/table"
|
|
6
|
+
require_relative "codegen/direct"
|
|
7
|
+
require_relative "codegen/firstmatch"
|
|
8
|
+
require_relative "codegen/table_packer"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flexr
|
|
4
|
+
module Configuration
|
|
5
|
+
BACKENDS = %i[table direct firstmatch auto].freeze
|
|
6
|
+
TOKEN_KINDS = %i[array struct yield].freeze
|
|
7
|
+
ACCELERATORS = %i[auto strscan regexp none].freeze
|
|
8
|
+
BOOLEAN_OPTIONS = %i[unicode eager_columns allow_empty_match experimental standalone].freeze
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def backend!(value)
|
|
13
|
+
validated_symbol!(value, BACKENDS, :backend)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def token_kind!(value)
|
|
17
|
+
validated_symbol!(value, TOKEN_KINDS, :token_kind)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def accelerator!(value)
|
|
21
|
+
validated_symbol!(value, ACCELERATORS, :accel)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def option!(value)
|
|
25
|
+
validated_symbol!(value, BOOLEAN_OPTIONS, :option)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def validated_symbol!(value, allowed, name)
|
|
29
|
+
symbol = value.to_sym
|
|
30
|
+
return symbol if allowed.include?(symbol)
|
|
31
|
+
|
|
32
|
+
raise ArgumentError, "unsupported #{name}: #{value.inspect}"
|
|
33
|
+
rescue NoMethodError
|
|
34
|
+
raise ArgumentError, "unsupported #{name}: #{value.inspect}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/flexr/dsl.rb
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
module Flexr
|
|
4
4
|
module DSL
|
|
5
5
|
DSL_METHODS = %i[rule state all_states on_eof emits backend token_kind encoding option accel].freeze
|
|
6
|
-
AUTO_DIRECT_CELL_THRESHOLD = 100_000
|
|
7
|
-
|
|
8
6
|
def inherited(child)
|
|
9
7
|
super
|
|
10
8
|
child.__flexr_reset!
|
|
@@ -21,23 +19,23 @@ module Flexr
|
|
|
21
19
|
)
|
|
22
20
|
@__flexr_compiled = nil
|
|
23
21
|
@__flexr_generated = false
|
|
24
|
-
@
|
|
22
|
+
@__flexr_generated_actions = []
|
|
23
|
+
@__flexr_specification_frozen = false
|
|
24
|
+
@__flexr_compile_mutex = Monitor.new
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def rule(pattern, skip: false, emit: nil, followed_by: nil, &action)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
__flexr_mutate! do
|
|
29
|
+
patterns = normalize_patterns(pattern)
|
|
30
|
+
rule_action = ActionResolver.resolve(
|
|
31
|
+
skip: skip, emit: emit, block: action, default: proc { emit(nil, text) }
|
|
32
|
+
)
|
|
33
|
+
states = @__flexr_state_stack.empty? ? [:initial] : @__flexr_state_stack.dup
|
|
34
|
+
@__flexr_rules << IR::Rule.new(
|
|
35
|
+
index: @__flexr_rules.length, patterns: patterns, trailing: normalize_trailing(followed_by),
|
|
36
|
+
action: rule_action, states: states, bol_only: false, end_anchor: nil
|
|
37
|
+
)
|
|
35
38
|
end
|
|
36
|
-
states = @__flexr_state_stack.empty? ? [:initial] : @__flexr_state_stack.dup
|
|
37
|
-
@__flexr_rules << IR::Rule.new(
|
|
38
|
-
index: @__flexr_rules.length, patterns: patterns, trailing: normalize_trailing(followed_by),
|
|
39
|
-
action: rule_action, states: states, bol_only: false, end_anchor: nil
|
|
40
|
-
)
|
|
41
39
|
nil
|
|
42
40
|
end
|
|
43
41
|
|
|
@@ -45,38 +43,56 @@ module Flexr
|
|
|
45
43
|
raise ArgumentError, "state requires a block" unless block
|
|
46
44
|
raise ArgumentError, "state requires a name" if names.empty?
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
__flexr_mutate! do
|
|
47
|
+
names.each do |name|
|
|
48
|
+
symbol = name.to_sym
|
|
49
|
+
existing = @__flexr_states[symbol]
|
|
50
|
+
raise ArgumentError, "state #{symbol.inspect} already declared with inclusive: #{existing.inclusive}" if
|
|
51
|
+
existing && existing.inclusive != inclusive
|
|
52
|
+
@__flexr_states[symbol] ||= IR::State.new(
|
|
53
|
+
name: symbol, inclusive: inclusive, id: @__flexr_states.length
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
@__flexr_state_stack.concat(names.map(&:to_sym))
|
|
57
|
+
begin
|
|
58
|
+
class_eval(&block)
|
|
59
|
+
ensure
|
|
60
|
+
names.length.times { @__flexr_state_stack.pop }
|
|
61
|
+
end
|
|
51
62
|
end
|
|
52
|
-
@__flexr_state_stack.concat(names.map(&:to_sym))
|
|
53
|
-
class_eval(&block)
|
|
54
|
-
ensure
|
|
55
|
-
names&.length&.times { @__flexr_state_stack.pop }
|
|
56
63
|
end
|
|
57
64
|
|
|
58
65
|
def all_states(&)
|
|
59
|
-
|
|
66
|
+
raise ArgumentError, "all_states requires a block" unless block_given?
|
|
67
|
+
|
|
68
|
+
__flexr_mutate! do
|
|
69
|
+
names = @__flexr_states.keys
|
|
70
|
+
@__flexr_state_stack.concat(names)
|
|
71
|
+
begin
|
|
72
|
+
class_eval(&)
|
|
73
|
+
ensure
|
|
74
|
+
names.length.times { @__flexr_state_stack.pop }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
60
77
|
end
|
|
61
78
|
|
|
62
79
|
def on_eof(&action)
|
|
63
|
-
|
|
64
|
-
|
|
80
|
+
__flexr_mutate! do
|
|
81
|
+
state_name = @__flexr_state_stack.last || :initial
|
|
82
|
+
@__flexr_eof_rules[state_name] = action
|
|
83
|
+
end
|
|
65
84
|
end
|
|
66
85
|
|
|
67
86
|
def emits(*tokens)
|
|
68
|
-
@__flexr_config.declared_tokens.concat(tokens.flatten.map(&:to_sym)).uniq!
|
|
87
|
+
__flexr_mutate! { @__flexr_config.declared_tokens.concat(tokens.flatten.map(&:to_sym)).uniq! }
|
|
69
88
|
end
|
|
70
89
|
|
|
71
90
|
def backend(name)
|
|
72
|
-
@__flexr_config.backend = name
|
|
91
|
+
__flexr_mutate! { @__flexr_config.backend = Configuration.backend!(name) }
|
|
73
92
|
end
|
|
74
93
|
|
|
75
94
|
def token_kind(name)
|
|
76
|
-
|
|
77
|
-
raise ArgumentError, "unsupported token_kind: #{name}" unless %i[array struct yield].include?(value)
|
|
78
|
-
|
|
79
|
-
@__flexr_config.token_kind = value
|
|
95
|
+
__flexr_mutate! { @__flexr_config.token_kind = Configuration.token_kind!(name) }
|
|
80
96
|
end
|
|
81
97
|
|
|
82
98
|
def encoding(value)
|
|
@@ -86,15 +102,17 @@ module Flexr
|
|
|
86
102
|
raise CompileError.new(diagnostic.message, diagnostic: diagnostic)
|
|
87
103
|
end
|
|
88
104
|
|
|
89
|
-
@__flexr_config.encoding = encoding
|
|
105
|
+
__flexr_mutate! { @__flexr_config.encoding = encoding }
|
|
90
106
|
end
|
|
91
107
|
|
|
92
108
|
def option(*values)
|
|
93
|
-
|
|
109
|
+
__flexr_mutate! do
|
|
110
|
+
values.each { |value| @__flexr_config.options[Configuration.option!(value)] = true }
|
|
111
|
+
end
|
|
94
112
|
end
|
|
95
113
|
|
|
96
114
|
def accel(value)
|
|
97
|
-
@__flexr_config.options[:accel] = value
|
|
115
|
+
__flexr_mutate! { @__flexr_config.options[:accel] = Configuration.accelerator!(value) }
|
|
98
116
|
end
|
|
99
117
|
|
|
100
118
|
def compile!
|
|
@@ -104,6 +122,8 @@ module Flexr
|
|
|
104
122
|
@__flexr_compiled ||= begin
|
|
105
123
|
compiled = Automaton::Compiler.new(__flexr_spec).compile
|
|
106
124
|
@__flexr_config.backend = auto_direct?(compiled) ? :direct : :table if @__flexr_config.backend == :auto
|
|
125
|
+
freeze_specification!
|
|
126
|
+
freeze_compiled!(compiled)
|
|
107
127
|
compiled
|
|
108
128
|
end
|
|
109
129
|
# rubocop:enable Naming/MemoizedInstanceVariableName
|
|
@@ -131,26 +151,73 @@ module Flexr
|
|
|
131
151
|
)
|
|
132
152
|
end
|
|
133
153
|
|
|
134
|
-
attr_reader :__flexr_rules, :__flexr_states, :__flexr_config, :__flexr_compiled
|
|
154
|
+
attr_reader :__flexr_rules, :__flexr_states, :__flexr_config, :__flexr_compiled, :__flexr_generated_actions
|
|
135
155
|
|
|
136
156
|
def __flexr_add_generated_eof(state, action)
|
|
137
|
-
@__flexr_eof_rules[state.to_sym] = action
|
|
157
|
+
__flexr_mutate! { @__flexr_eof_rules[state.to_sym] = action }
|
|
138
158
|
end
|
|
139
159
|
|
|
140
160
|
def __flexr_set_compiled!(compiled)
|
|
141
|
-
@
|
|
161
|
+
@__flexr_compile_mutex.synchronize do
|
|
162
|
+
@__flexr_compiled = freeze_compiled!(compiled)
|
|
163
|
+
freeze_specification!
|
|
164
|
+
end
|
|
142
165
|
end
|
|
143
166
|
|
|
144
167
|
def __flexr_mark_generated!
|
|
145
168
|
@__flexr_generated = true
|
|
146
169
|
end
|
|
147
170
|
|
|
171
|
+
def __flexr_bind_generated_action(index, action)
|
|
172
|
+
raise FrozenSpecificationError, "generated actions can only be bound on a generated lexer" unless @__flexr_generated
|
|
173
|
+
|
|
174
|
+
@__flexr_generated_actions[index] = action
|
|
175
|
+
end
|
|
176
|
+
|
|
148
177
|
def __flexr_generated?
|
|
149
178
|
@__flexr_generated == true
|
|
150
179
|
end
|
|
151
180
|
|
|
152
181
|
private
|
|
153
182
|
|
|
183
|
+
def __flexr_mutate!
|
|
184
|
+
@__flexr_compile_mutex.synchronize do
|
|
185
|
+
raise FrozenSpecificationError, "lexer specification is immutable after compilation" if
|
|
186
|
+
@__flexr_specification_frozen
|
|
187
|
+
|
|
188
|
+
yield
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def freeze_specification!
|
|
193
|
+
@__flexr_rules.each do |rule|
|
|
194
|
+
rule.patterns.freeze
|
|
195
|
+
rule.states.freeze
|
|
196
|
+
rule.pattern_conditions&.each(&:freeze)
|
|
197
|
+
rule.pattern_conditions&.freeze
|
|
198
|
+
rule.freeze
|
|
199
|
+
end
|
|
200
|
+
@__flexr_states.each_value(&:freeze)
|
|
201
|
+
@__flexr_eof_rules.freeze
|
|
202
|
+
@__flexr_rules.freeze
|
|
203
|
+
@__flexr_states.freeze
|
|
204
|
+
@__flexr_config.options.freeze
|
|
205
|
+
@__flexr_config.declared_tokens.freeze
|
|
206
|
+
@__flexr_config.freeze
|
|
207
|
+
@__flexr_specification_frozen = true
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def freeze_compiled!(compiled)
|
|
211
|
+
compiled.machines.each_value(&:freeze)
|
|
212
|
+
compiled.machines.freeze
|
|
213
|
+
compiled.states.freeze
|
|
214
|
+
compiled.stats.each_value(&:freeze)
|
|
215
|
+
compiled.stats.freeze
|
|
216
|
+
Array(compiled.diagnostics).each(&:freeze)
|
|
217
|
+
compiled.diagnostics.freeze
|
|
218
|
+
compiled.freeze
|
|
219
|
+
end
|
|
220
|
+
|
|
154
221
|
def normalize_patterns(pattern)
|
|
155
222
|
values = pattern.is_a?(Array) ? pattern : [pattern]
|
|
156
223
|
values.each do |value|
|
|
@@ -175,8 +242,7 @@ module Flexr
|
|
|
175
242
|
end
|
|
176
243
|
|
|
177
244
|
def auto_direct?(compiled)
|
|
178
|
-
|
|
179
|
-
cells > AUTO_DIRECT_CELL_THRESHOLD
|
|
245
|
+
Automaton::BackendCostModel.choose(compiled) == :direct
|
|
180
246
|
end
|
|
181
247
|
end
|
|
182
248
|
end
|