flexr 1.0.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 +7 -0
- data/.rubocop.yml +33 -0
- data/CONTRIBUTING.md +39 -0
- data/LICENSE.txt +21 -0
- data/README.md +116 -0
- data/Rakefile +468 -0
- data/benchmark/baselines/json.json +34 -0
- data/benchmark/baselines/json_handwritten.rb +43 -0
- data/benchmark/baselines/json_rexical.rex +25 -0
- data/benchmark/corpora/README.md +11 -0
- data/benchmark/corpora/generate_json.rb +26 -0
- data/benchmark/golden/calculator_lexer.sha256 +1 -0
- data/benchmark/golden/json_lexer.sha256 +1 -0
- data/benchmark/golden/regexp_tokenizer.sha256 +1 -0
- data/benchmark/golden/ruby_subset_lexer.sha256 +1 -0
- data/benchmark/golden/toy_lang_lexer.sha256 +1 -0
- data/benchmark/golden/with_lrama_lexer.sha256 +1 -0
- data/benchmark/golden/with_racc_lexer.sha256 +1 -0
- data/benchmark/run.rb +254 -0
- data/docs/README.md +64 -0
- data/docs/RELEASING.md +30 -0
- data/docs/adr/0001-byte-level-dfa.md +5 -0
- data/docs/adr/0003-leftmost-longest.md +4 -0
- data/docs/adr/0006-accel-not-scanner.md +4 -0
- data/docs/adr/0008-what-pure-ruby-means.md +5 -0
- data/docs/adr/0016-spec-is-plain-ruby.md +4 -0
- data/docs/adr/0017-static-analysis-by-default.md +5 -0
- data/docs/adr/0018-prism-for-generator-only.md +4 -0
- data/docs/adr/0019-measured-performance-floor.md +26 -0
- data/docs/adr/0020-vendored-unicode-contract.md +21 -0
- data/docs/explanation/backends.md +33 -0
- data/docs/explanation/matching-semantics.md +20 -0
- data/docs/explanation/runtime-vs-generated.md +22 -0
- data/docs/explanation/security-model.md +18 -0
- data/docs/explanation/unicode-and-encoding.md +20 -0
- data/docs/how-to/deploy-a-standalone-lexer.md +23 -0
- data/docs/how-to/generate-a-lexer.md +39 -0
- data/docs/how-to/handle-errors.md +32 -0
- data/docs/how-to/integrate-with-lrama.md +21 -0
- data/docs/how-to/integrate-with-racc.md +25 -0
- data/docs/how-to/migrate-from-flex.md +21 -0
- data/docs/how-to/migrate-from-rexical.md +23 -0
- data/docs/how-to/run-a-lexer-at-runtime.md +29 -0
- data/docs/how-to/track-token-locations.md +27 -0
- data/docs/how-to/tune-performance.md +23 -0
- data/docs/how-to/use-states.md +36 -0
- data/docs/how-to/use-trailing-context.md +22 -0
- data/docs/internals/README.md +14 -0
- data/docs/perf-log.md +56 -0
- data/docs/reference/README.md +23 -0
- data/docs/reference/actions.md +47 -0
- data/docs/reference/cli.md +80 -0
- data/docs/reference/compatibility.md +38 -0
- data/docs/reference/diagnostics.md +41 -0
- data/docs/reference/dsl.md +81 -0
- data/docs/reference/errors.md +27 -0
- data/docs/reference/generated-artifacts.md +50 -0
- data/docs/reference/public-api.md +42 -0
- data/docs/reference/regexp.md +39 -0
- data/docs/reference/runtime.md +49 -0
- data/docs/reference/tokens-and-locations.md +33 -0
- data/docs/tutorial/build-a-calculator-lexer.md +96 -0
- data/examples/calculator/README.md +27 -0
- data/examples/calculator/lexer.flexr.rb +17 -0
- data/examples/json/README.md +30 -0
- data/examples/json/lexer.flexr.rb +24 -0
- data/examples/ruby_subset/README.md +17 -0
- data/examples/ruby_subset/lexer.flexr.rb +22 -0
- data/examples/toy_lang/README.md +17 -0
- data/examples/toy_lang/lexer.flexr.rb +18 -0
- data/examples/with_lrama/README.md +17 -0
- data/examples/with_lrama/lexer.flexr.rb +13 -0
- data/examples/with_racc/README.md +17 -0
- data/examples/with_racc/lexer.flexr.rb +13 -0
- data/exe/flexr +7 -0
- data/lib/flexr/automaton/accel.rb +39 -0
- data/lib/flexr/automaton/analysis.rb +38 -0
- data/lib/flexr/automaton/byte_class_set.rb +29 -0
- data/lib/flexr/automaton/compiler.rb +413 -0
- data/lib/flexr/automaton/dfa.rb +103 -0
- data/lib/flexr/automaton/minimizer.rb +70 -0
- data/lib/flexr/automaton/nfa.rb +92 -0
- data/lib/flexr/cli.rb +342 -0
- data/lib/flexr/codegen/base.rb +17 -0
- data/lib/flexr/codegen/direct.rb +52 -0
- data/lib/flexr/codegen/firstmatch.rb +17 -0
- data/lib/flexr/codegen/table.rb +158 -0
- data/lib/flexr/codegen/table_packer.rb +61 -0
- data/lib/flexr/diagnostics.rb +94 -0
- data/lib/flexr/dsl.rb +182 -0
- data/lib/flexr/errors.rb +28 -0
- data/lib/flexr/generated.rb +125 -0
- data/lib/flexr/generator.rb +400 -0
- data/lib/flexr/importer.rb +560 -0
- data/lib/flexr/ir.rb +36 -0
- data/lib/flexr/lexer.rb +10 -0
- data/lib/flexr/options.rb +47 -0
- data/lib/flexr/rake_task.rb +27 -0
- data/lib/flexr/regexp/ast.rb +45 -0
- data/lib/flexr/regexp/char_class.rb +7 -0
- data/lib/flexr/regexp/normalizer.rb +117 -0
- data/lib/flexr/regexp/parser.rb +517 -0
- data/lib/flexr/regexp/tokenizer.flexr.rb +27 -0
- data/lib/flexr/regexp/tokenizer.rb +168 -0
- data/lib/flexr/regexp/unsupported.rb +7 -0
- data/lib/flexr/runtime/buffer.rb +112 -0
- data/lib/flexr/runtime/core.rb +388 -0
- data/lib/flexr/runtime/errors.rb +22 -0
- data/lib/flexr/runtime/interpreter.rb +505 -0
- data/lib/flexr/runtime/location.rb +26 -0
- data/lib/flexr/runtime/token.rb +7 -0
- data/lib/flexr/source/passthrough.rb +31 -0
- data/lib/flexr/source/prism_reader.rb +283 -0
- data/lib/flexr/source/static_eval.rb +145 -0
- data/lib/flexr/unicode/case_fold.rb +45 -0
- data/lib/flexr/unicode/data/LICENSE-UNICODE.txt +5 -0
- data/lib/flexr/unicode/data/UNICODE_VERSION +1 -0
- data/lib/flexr/unicode/data/case_folding.rb +9 -0
- data/lib/flexr/unicode/data/properties.rb +10 -0
- data/lib/flexr/unicode/property.rb +107 -0
- data/lib/flexr/unicode/reference_regexp.rb +102 -0
- data/lib/flexr/unicode/utf8_splitter.rb +109 -0
- data/lib/flexr/version.rb +5 -0
- data/lib/flexr.rb +81 -0
- data/site/README.md +22 -0
- data/site/astro.config.mjs +57 -0
- data/site/package.json +19 -0
- data/site/pnpm-lock.yaml +5029 -0
- data/site/pnpm-workspace.yaml +6 -0
- data/site/public/playground.js +189 -0
- data/site/scripts/verify-site.mjs +42 -0
- data/site/src/content/docs/benchmarks.md +8 -0
- data/site/src/content/docs/concepts/matching-semantics.md +15 -0
- data/site/src/content/docs/concepts/regexp-model.md +18 -0
- data/site/src/content/docs/concepts/runtime-vs-generated.md +15 -0
- data/site/src/content/docs/concepts/security-model.md +15 -0
- data/site/src/content/docs/examples.md +17 -0
- data/site/src/content/docs/learn/generation.md +29 -0
- data/site/src/content/docs/learn/getting-started.md +56 -0
- data/site/src/content/docs/learn/parser-integration.md +27 -0
- data/site/src/content/docs/learn/runtime-mode.md +32 -0
- data/site/src/content/docs/reference/action-context.md +20 -0
- data/site/src/content/docs/reference/cli.md +22 -0
- data/site/src/content/docs/reference/diagnostics.md +16 -0
- data/site/src/content/docs/reference/dsl.md +19 -0
- data/site/src/content/docs/reference/public-api.md +18 -0
- data/site/src/content/docs/reference/regexp.md +16 -0
- data/site/src/content/docs/reference/runtime.md +16 -0
- data/site/src/content/docs/reference/tokens-and-locations.md +16 -0
- data/site/src/content.config.ts +12 -0
- data/site/src/env.d.ts +1 -0
- data/site/src/layouts/SiteLayout.astro +39 -0
- data/site/src/pages/index.astro +174 -0
- data/site/src/pages/playground.astro +64 -0
- data/site/src/styles/custom.css +711 -0
- data/site/tsconfig.json +5 -0
- data/tools/coverage.rb +32 -0
- data/tools/docs_verify.rb +116 -0
- data/tools/gen_unicode_tables.rb +202 -0
- data/tools/regexp_tokenizer_reference.rb +60 -0
- metadata +205 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flexr
|
|
4
|
+
module Unicode
|
|
5
|
+
module ReferenceRegexp
|
|
6
|
+
# rubocop:disable Style/MutableConstant
|
|
7
|
+
CACHE = {}
|
|
8
|
+
# rubocop:enable Style/MutableConstant
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def match(pattern, subject, encoding:, options: 0, unicode: false)
|
|
12
|
+
regexp = compiled(pattern, encoding: encoding, options: options, unicode: unicode)
|
|
13
|
+
subject = subject.dup.force_encoding(regexp.encoding)
|
|
14
|
+
regexp.match(subject, 0)
|
|
15
|
+
rescue RegexpError, ArgumentError, EncodingError
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def compiled(pattern, encoding:, options: nil, unicode: false)
|
|
20
|
+
effective_options = options.nil? ? pattern.options : options
|
|
21
|
+
key = [pattern.source, effective_options, encoding, unicode]
|
|
22
|
+
return CACHE[key] if CACHE.key?(key)
|
|
23
|
+
|
|
24
|
+
parser = Regexp::Parser.new(pattern.source, options: effective_options, encoding: encoding, unicode: unicode)
|
|
25
|
+
source = source_for(parser.parse, ignorecase: effective_options.anybits?(::Regexp::IGNORECASE))
|
|
26
|
+
regexp_options = effective_options & ~::Regexp::IGNORECASE
|
|
27
|
+
CACHE[key] = ::Regexp.new(source, regexp_options).freeze
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def source_for(node, ignorecase: false)
|
|
31
|
+
case node
|
|
32
|
+
when Regexp::AST::Empty, Regexp::AST::Anchor then ""
|
|
33
|
+
when Regexp::AST::ByteRange then byte_class([[node.lo, node.hi]])
|
|
34
|
+
when Regexp::AST::CodepointRange then codepoint_class([[node.lo, node.hi]])
|
|
35
|
+
when Regexp::AST::CharClass
|
|
36
|
+
ranges = node.ranges.flat_map do |range|
|
|
37
|
+
if range.first == Regexp::AST::Property
|
|
38
|
+
property_ranges = Unicode::Property.ranges(range[2])
|
|
39
|
+
property_ranges = casefold_ranges(property_ranges) if ignorecase
|
|
40
|
+
range[1] ? complement(property_ranges) : property_ranges
|
|
41
|
+
else
|
|
42
|
+
[range]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
ranges = complement(ranges) if node.negated
|
|
46
|
+
codepoint_class(ranges)
|
|
47
|
+
when Regexp::AST::Seq then node.children.map { |child| source_for(child, ignorecase: ignorecase) }.join
|
|
48
|
+
when Regexp::AST::Alt
|
|
49
|
+
"(?:#{node.children.map { |child| source_for(child, ignorecase: ignorecase) }.join('|')})"
|
|
50
|
+
when Regexp::AST::Star then "(?:#{source_for(node.child, ignorecase: ignorecase)})*"
|
|
51
|
+
else
|
|
52
|
+
raise CompileError, "unsupported reference AST node: #{node.class}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def byte_class(ranges)
|
|
57
|
+
"[#{ranges.map { |lo, hi| byte_escape(lo, hi) }.join}]"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def byte_escape(lo, hi)
|
|
61
|
+
lo == hi ? format("\\x%<byte>02X", byte: lo) : format("\\x%<lo>02X-\\x%<hi>02X", lo: lo, hi: hi)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def codepoint_class(ranges)
|
|
65
|
+
ranges = scalar_ranges(ranges)
|
|
66
|
+
return "(?!)" if ranges.empty?
|
|
67
|
+
|
|
68
|
+
"[#{ranges.sort_by(&:first).map { |lo, hi| codepoint_escape(lo, hi) }.join}]"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def scalar_ranges(ranges)
|
|
72
|
+
ranges.flat_map do |lo, hi|
|
|
73
|
+
result = []
|
|
74
|
+
result << [lo, [hi, 0xd7ff].min] if lo <= 0xd7ff
|
|
75
|
+
result << [[lo, 0xe000].max, hi] if hi >= 0xe000
|
|
76
|
+
result
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def codepoint_escape(lo, hi)
|
|
81
|
+
first = ::Regexp.escape([lo].pack("U"))
|
|
82
|
+
last = ::Regexp.escape([hi].pack("U"))
|
|
83
|
+
lo == hi ? first : "#{first}-#{last}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def complement(ranges)
|
|
87
|
+
result = []
|
|
88
|
+
cursor = 0
|
|
89
|
+
ranges.sort_by(&:first).each do |lo, hi|
|
|
90
|
+
result << [cursor, lo - 1] if cursor < lo
|
|
91
|
+
cursor = [cursor, hi + 1].max
|
|
92
|
+
end
|
|
93
|
+
result << [cursor, 0x10ffff] if cursor <= 0x10ffff
|
|
94
|
+
result
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def casefold_ranges(ranges)
|
|
98
|
+
CaseFold.merge(ranges.flat_map { |lo, hi| CaseFold.ranges(lo, hi) })
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flexr
|
|
4
|
+
module Unicode
|
|
5
|
+
module Utf8Splitter
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def split(lo, hi)
|
|
9
|
+
raise ArgumentError, "invalid codepoint range" if lo > hi || lo.negative? || hi > 0x10ffff
|
|
10
|
+
return [] if lo.between?(0xd800, 0xdfff) && hi.between?(0xd800, 0xdfff)
|
|
11
|
+
return [encoded(lo).map { |byte| [byte, byte] }] if lo == hi
|
|
12
|
+
|
|
13
|
+
ranges = []
|
|
14
|
+
[[0, 0x7f, 1], [0x80, 0x7ff, 2], [0x800, 0xffff, 3], [0x10000, 0x10ffff, 4]].each do |min, max, length|
|
|
15
|
+
lower = [lo, min].max
|
|
16
|
+
upper = [hi, max].min
|
|
17
|
+
next if lower > upper
|
|
18
|
+
|
|
19
|
+
walk(lower, upper, length, [], ranges)
|
|
20
|
+
end
|
|
21
|
+
ranges
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def walk(lo, hi, length, prefix, output)
|
|
25
|
+
if prefix.length == length
|
|
26
|
+
output << prefix.map { |byte| [byte, byte] }
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if prefix.length == length - 1
|
|
31
|
+
first_byte = prefix.empty? ? encoded(lo)[0] : 0x80
|
|
32
|
+
last_byte = prefix.empty? ? encoded(hi)[0] : 0xbf
|
|
33
|
+
group_start = nil
|
|
34
|
+
first_byte.upto(last_byte) do |byte|
|
|
35
|
+
min_cp, max_cp = prefix_bounds(prefix + [byte], length)
|
|
36
|
+
allowed = min_cp && lo <= min_cp && max_cp <= hi
|
|
37
|
+
if allowed
|
|
38
|
+
group_start ||= byte
|
|
39
|
+
elsif group_start
|
|
40
|
+
output << (prefix.map { |value| [value, value] } + [[group_start, byte - 1]])
|
|
41
|
+
group_start = nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
output << (prefix.map { |value| [value, value] } + [[group_start, last_byte]]) if group_start
|
|
45
|
+
return
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
first_byte = prefix.empty? ? encoded(lo)[0] : 0x80
|
|
49
|
+
last_byte = prefix.empty? ? encoded(hi)[0] : 0xbf
|
|
50
|
+
first_byte.upto(last_byte) do |byte|
|
|
51
|
+
next_prefix = prefix + [byte]
|
|
52
|
+
min_cp, max_cp = prefix_bounds(next_prefix, length)
|
|
53
|
+
next if min_cp.nil? || max_cp < lo || min_cp > hi
|
|
54
|
+
|
|
55
|
+
if lo <= min_cp && max_cp <= hi
|
|
56
|
+
rest = length - next_prefix.length
|
|
57
|
+
output << (next_prefix.map { |value| [value, value] } + Array.new(rest) { [0x80, 0xbf] })
|
|
58
|
+
else
|
|
59
|
+
walk(lo, hi, length, next_prefix, output)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def encoded(codepoint)
|
|
65
|
+
[codepoint].pack("U").bytes
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def prefix_bounds(prefix, length)
|
|
69
|
+
return [nil, nil] if prefix.empty?
|
|
70
|
+
|
|
71
|
+
bytes = prefix + Array.new(length - prefix.length, 0x80)
|
|
72
|
+
high = prefix + Array.new(length - prefix.length, 0xbf)
|
|
73
|
+
if prefix.length == 1
|
|
74
|
+
bytes[1] = 0xa0 if length == 3 && prefix.first == 0xe0
|
|
75
|
+
high[1] = 0x9f if length == 3 && prefix.first == 0xed
|
|
76
|
+
bytes[1] = 0x90 if length == 4 && prefix.first == 0xf0
|
|
77
|
+
high[1] = 0x8f if length == 4 && prefix.first == 0xf4
|
|
78
|
+
end
|
|
79
|
+
return [nil, nil] unless valid_prefix?(bytes, length) && valid_prefix?(high, length)
|
|
80
|
+
|
|
81
|
+
[decode(bytes), decode(high)]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def valid_prefix?(bytes, length)
|
|
85
|
+
first = bytes.first
|
|
86
|
+
expected = if first <= 0x7f then 1 elsif first.between?(0xc2, 0xdf) then 2
|
|
87
|
+
elsif first.between?(0xe0, 0xef) then 3
|
|
88
|
+
elsif first.between?(0xf0, 0xf4) then 4 end
|
|
89
|
+
return false unless expected == length
|
|
90
|
+
return false if bytes[1..].any? { |byte| !byte.between?(0x80, 0xbf) }
|
|
91
|
+
return false if length == 3 && first == 0xe0 && bytes[1] < 0xa0
|
|
92
|
+
return false if length == 3 && first == 0xed && bytes[1] > 0x9f
|
|
93
|
+
return false if length == 4 && first == 0xf0 && bytes[1] < 0x90
|
|
94
|
+
return false if length == 4 && first == 0xf4 && bytes[1] > 0x8f
|
|
95
|
+
true
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def decode(bytes)
|
|
99
|
+
return bytes.first if bytes.length == 1
|
|
100
|
+
case bytes.length
|
|
101
|
+
when 2 then ((bytes[0] & 0x1f) << 6) | (bytes[1] & 0x3f)
|
|
102
|
+
when 3 then ((bytes[0] & 0x0f) << 12) | ((bytes[1] & 0x3f) << 6) | (bytes[2] & 0x3f)
|
|
103
|
+
when 4 then ((bytes[0] & 0x07) << 18) | ((bytes[1] & 0x3f) << 12) |
|
|
104
|
+
((bytes[2] & 0x3f) << 6) | (bytes[3] & 0x3f)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/flexr.rb
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "digest"
|
|
5
|
+
require_relative "flexr/version"
|
|
6
|
+
require_relative "flexr/errors"
|
|
7
|
+
require_relative "flexr/diagnostics"
|
|
8
|
+
require_relative "flexr/ir"
|
|
9
|
+
require_relative "flexr/regexp/ast"
|
|
10
|
+
require_relative "flexr/regexp/parser"
|
|
11
|
+
require_relative "flexr/regexp/normalizer"
|
|
12
|
+
require_relative "flexr/regexp/unsupported"
|
|
13
|
+
require_relative "flexr/regexp/char_class"
|
|
14
|
+
require_relative "flexr/unicode/utf8_splitter"
|
|
15
|
+
require_relative "flexr/unicode/data/properties"
|
|
16
|
+
require_relative "flexr/unicode/data/case_folding"
|
|
17
|
+
require_relative "flexr/unicode/property"
|
|
18
|
+
require_relative "flexr/unicode/reference_regexp"
|
|
19
|
+
require_relative "flexr/unicode/case_fold"
|
|
20
|
+
require_relative "flexr/automaton/byte_class_set"
|
|
21
|
+
require_relative "flexr/automaton/nfa"
|
|
22
|
+
require_relative "flexr/automaton/dfa"
|
|
23
|
+
require_relative "flexr/automaton/compiler"
|
|
24
|
+
require_relative "flexr/automaton/analysis"
|
|
25
|
+
require_relative "flexr/automaton/minimizer"
|
|
26
|
+
require_relative "flexr/automaton/accel"
|
|
27
|
+
require_relative "flexr/codegen/base"
|
|
28
|
+
require_relative "flexr/codegen/table"
|
|
29
|
+
require_relative "flexr/codegen/direct"
|
|
30
|
+
require_relative "flexr/codegen/firstmatch"
|
|
31
|
+
require_relative "flexr/codegen/table_packer"
|
|
32
|
+
require_relative "flexr/runtime/location"
|
|
33
|
+
require_relative "flexr/runtime/token"
|
|
34
|
+
require_relative "flexr/runtime/buffer"
|
|
35
|
+
require_relative "flexr/runtime/errors"
|
|
36
|
+
require_relative "flexr/runtime/interpreter"
|
|
37
|
+
require_relative "flexr/runtime/core"
|
|
38
|
+
require_relative "flexr/dsl"
|
|
39
|
+
require_relative "flexr/lexer"
|
|
40
|
+
require_relative "flexr/generated"
|
|
41
|
+
require_relative "flexr/source/static_eval"
|
|
42
|
+
require_relative "flexr/source/prism_reader"
|
|
43
|
+
require_relative "flexr/source/passthrough"
|
|
44
|
+
require_relative "flexr/importer"
|
|
45
|
+
require_relative "flexr/generator"
|
|
46
|
+
require_relative "flexr/options"
|
|
47
|
+
require_relative "flexr/rake_task"
|
|
48
|
+
require_relative "flexr/cli"
|
|
49
|
+
|
|
50
|
+
module Flexr
|
|
51
|
+
class << self
|
|
52
|
+
def compile_pattern(pattern, options: {})
|
|
53
|
+
regexp = pattern.is_a?(::Regexp) ? pattern : ::Regexp.new(pattern.to_s)
|
|
54
|
+
return Automaton::ReferenceDFA.new(regexp, unicode: options[:unicode] == true) if
|
|
55
|
+
reference_pattern?(regexp, unicode: options[:unicode] == true)
|
|
56
|
+
encoding = regexp.encoding == Encoding::BINARY ? Encoding::BINARY : Encoding::UTF_8
|
|
57
|
+
rule = IR::Rule.new(index: 0, patterns: [regexp], action: :skip, states: [:initial])
|
|
58
|
+
state = IR::State.new(name: :initial, inclusive: true, id: 0)
|
|
59
|
+
spec = IR::Spec.new(
|
|
60
|
+
class_name: "Pattern", backend: :table, token_kind: :array,
|
|
61
|
+
encoding: encoding, options: options, declared_tokens: [],
|
|
62
|
+
states: { initial: state }, rules: [rule], eof_rules: {}, verbatim: nil
|
|
63
|
+
)
|
|
64
|
+
Automaton::Compiler.new(spec).compile.machines.fetch(:initial).dfa
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def reference_pattern?(regexp, unicode: false)
|
|
68
|
+
return true if regexp.source.match?(/\\[pP]\{/) || regexp.source.match?(/\[:(?:\^)?[a-z]+:\]/)
|
|
69
|
+
|
|
70
|
+
unicode && regexp.encoding != Encoding::BINARY && regexp.source.match?(/\\[dDwWsS]/)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def parse_pattern(pattern, options: {})
|
|
74
|
+
regexp = pattern.is_a?(::Regexp) ? pattern : ::Regexp.new(pattern.to_s)
|
|
75
|
+
encoding = regexp.encoding == Encoding::BINARY ? Encoding::BINARY : Encoding::UTF_8
|
|
76
|
+
ast = Regexp::Parser.new(regexp.source, options: regexp.options, encoding: encoding,
|
|
77
|
+
unicode: options[:unicode] == true).parse
|
|
78
|
+
Regexp::Normalizer.new(ast, encoding: encoding, options: regexp.options).normalize
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
data/site/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# flexr product site
|
|
2
|
+
|
|
3
|
+
This directory contains the Astro/Starlight product site for flexr. It is a static GitHub Pages site with a custom landing page, documentation routes, and a fixture-backed playground preview.
|
|
4
|
+
|
|
5
|
+
## Local development
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
cd site
|
|
9
|
+
pnpm install
|
|
10
|
+
pnpm dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Run the same checks used by the Pages workflow:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
pnpm verify
|
|
17
|
+
pnpm build
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The current playground does not execute arbitrary Ruby. It demonstrates the matching decision model from fixed fixtures and keeps `--eval` out of the browser. A future Ruby WASM worker must preserve that security boundary and add runtime/generated equivalence tests before the UI claims full execution.
|
|
21
|
+
|
|
22
|
+
The repository examples, CLI definitions, and Ruby implementation remain the behavioral source of truth. Site copy links to those artifacts where a summary would otherwise drift.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { defineConfig } from 'astro/config';
|
|
2
|
+
import starlight from '@astrojs/starlight';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
site: 'https://ydah.github.io',
|
|
6
|
+
base: '/flexr',
|
|
7
|
+
integrations: [
|
|
8
|
+
starlight({
|
|
9
|
+
title: 'flexr',
|
|
10
|
+
description: 'A Ruby-native lexer generator for parser authors.',
|
|
11
|
+
customCss: ['./src/styles/custom.css'],
|
|
12
|
+
sidebar: [
|
|
13
|
+
{
|
|
14
|
+
label: 'Learn',
|
|
15
|
+
items: [
|
|
16
|
+
{ label: 'Getting started', slug: 'learn/getting-started' },
|
|
17
|
+
{ label: 'Runtime mode', slug: 'learn/runtime-mode' },
|
|
18
|
+
{ label: 'Generation', slug: 'learn/generation' },
|
|
19
|
+
{ label: 'Parser integration', slug: 'learn/parser-integration' }
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
label: 'Concepts',
|
|
24
|
+
items: [
|
|
25
|
+
{ label: 'Matching semantics', slug: 'concepts/matching-semantics' },
|
|
26
|
+
{ label: 'Runtime vs generated', slug: 'concepts/runtime-vs-generated' },
|
|
27
|
+
{ label: 'Regexp model', slug: 'concepts/regexp-model' },
|
|
28
|
+
{ label: 'Security model', slug: 'concepts/security-model' }
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: 'Reference',
|
|
33
|
+
items: [
|
|
34
|
+
{ label: 'DSL', slug: 'reference/dsl' },
|
|
35
|
+
{ label: 'Action context', slug: 'reference/action-context' },
|
|
36
|
+
{ label: 'Runtime', slug: 'reference/runtime' },
|
|
37
|
+
{ label: 'Tokens and locations', slug: 'reference/tokens-and-locations' },
|
|
38
|
+
{ label: 'CLI', slug: 'reference/cli' },
|
|
39
|
+
{ label: 'Regexp compatibility', slug: 'reference/regexp' },
|
|
40
|
+
{ label: 'Diagnostics', slug: 'reference/diagnostics' },
|
|
41
|
+
{ label: 'Public API', slug: 'reference/public-api' }
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: 'Project',
|
|
46
|
+
items: [
|
|
47
|
+
{ label: 'Examples', slug: 'examples' },
|
|
48
|
+
{ label: 'Benchmarks', slug: 'benchmarks' }
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
social: [
|
|
53
|
+
{ icon: 'github', label: 'GitHub', href: 'https://github.com/ydah/flexr' }
|
|
54
|
+
]
|
|
55
|
+
})
|
|
56
|
+
]
|
|
57
|
+
});
|
data/site/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flexr-site",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "astro dev",
|
|
7
|
+
"build": "astro check && astro build",
|
|
8
|
+
"preview": "astro preview",
|
|
9
|
+
"verify": "node scripts/verify-site.mjs"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@astrojs/check": "^0.9.4",
|
|
13
|
+
"typescript": "^5.7.3"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@astrojs/starlight": "^0.36.0",
|
|
17
|
+
"astro": "^5.14.1"
|
|
18
|
+
}
|
|
19
|
+
}
|