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.
Files changed (161) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +33 -0
  3. data/CONTRIBUTING.md +39 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +116 -0
  6. data/Rakefile +468 -0
  7. data/benchmark/baselines/json.json +34 -0
  8. data/benchmark/baselines/json_handwritten.rb +43 -0
  9. data/benchmark/baselines/json_rexical.rex +25 -0
  10. data/benchmark/corpora/README.md +11 -0
  11. data/benchmark/corpora/generate_json.rb +26 -0
  12. data/benchmark/golden/calculator_lexer.sha256 +1 -0
  13. data/benchmark/golden/json_lexer.sha256 +1 -0
  14. data/benchmark/golden/regexp_tokenizer.sha256 +1 -0
  15. data/benchmark/golden/ruby_subset_lexer.sha256 +1 -0
  16. data/benchmark/golden/toy_lang_lexer.sha256 +1 -0
  17. data/benchmark/golden/with_lrama_lexer.sha256 +1 -0
  18. data/benchmark/golden/with_racc_lexer.sha256 +1 -0
  19. data/benchmark/run.rb +254 -0
  20. data/docs/README.md +64 -0
  21. data/docs/RELEASING.md +30 -0
  22. data/docs/adr/0001-byte-level-dfa.md +5 -0
  23. data/docs/adr/0003-leftmost-longest.md +4 -0
  24. data/docs/adr/0006-accel-not-scanner.md +4 -0
  25. data/docs/adr/0008-what-pure-ruby-means.md +5 -0
  26. data/docs/adr/0016-spec-is-plain-ruby.md +4 -0
  27. data/docs/adr/0017-static-analysis-by-default.md +5 -0
  28. data/docs/adr/0018-prism-for-generator-only.md +4 -0
  29. data/docs/adr/0019-measured-performance-floor.md +26 -0
  30. data/docs/adr/0020-vendored-unicode-contract.md +21 -0
  31. data/docs/explanation/backends.md +33 -0
  32. data/docs/explanation/matching-semantics.md +20 -0
  33. data/docs/explanation/runtime-vs-generated.md +22 -0
  34. data/docs/explanation/security-model.md +18 -0
  35. data/docs/explanation/unicode-and-encoding.md +20 -0
  36. data/docs/how-to/deploy-a-standalone-lexer.md +23 -0
  37. data/docs/how-to/generate-a-lexer.md +39 -0
  38. data/docs/how-to/handle-errors.md +32 -0
  39. data/docs/how-to/integrate-with-lrama.md +21 -0
  40. data/docs/how-to/integrate-with-racc.md +25 -0
  41. data/docs/how-to/migrate-from-flex.md +21 -0
  42. data/docs/how-to/migrate-from-rexical.md +23 -0
  43. data/docs/how-to/run-a-lexer-at-runtime.md +29 -0
  44. data/docs/how-to/track-token-locations.md +27 -0
  45. data/docs/how-to/tune-performance.md +23 -0
  46. data/docs/how-to/use-states.md +36 -0
  47. data/docs/how-to/use-trailing-context.md +22 -0
  48. data/docs/internals/README.md +14 -0
  49. data/docs/perf-log.md +56 -0
  50. data/docs/reference/README.md +23 -0
  51. data/docs/reference/actions.md +47 -0
  52. data/docs/reference/cli.md +80 -0
  53. data/docs/reference/compatibility.md +38 -0
  54. data/docs/reference/diagnostics.md +41 -0
  55. data/docs/reference/dsl.md +81 -0
  56. data/docs/reference/errors.md +27 -0
  57. data/docs/reference/generated-artifacts.md +50 -0
  58. data/docs/reference/public-api.md +42 -0
  59. data/docs/reference/regexp.md +39 -0
  60. data/docs/reference/runtime.md +49 -0
  61. data/docs/reference/tokens-and-locations.md +33 -0
  62. data/docs/tutorial/build-a-calculator-lexer.md +96 -0
  63. data/examples/calculator/README.md +27 -0
  64. data/examples/calculator/lexer.flexr.rb +17 -0
  65. data/examples/json/README.md +30 -0
  66. data/examples/json/lexer.flexr.rb +24 -0
  67. data/examples/ruby_subset/README.md +17 -0
  68. data/examples/ruby_subset/lexer.flexr.rb +22 -0
  69. data/examples/toy_lang/README.md +17 -0
  70. data/examples/toy_lang/lexer.flexr.rb +18 -0
  71. data/examples/with_lrama/README.md +17 -0
  72. data/examples/with_lrama/lexer.flexr.rb +13 -0
  73. data/examples/with_racc/README.md +17 -0
  74. data/examples/with_racc/lexer.flexr.rb +13 -0
  75. data/exe/flexr +7 -0
  76. data/lib/flexr/automaton/accel.rb +39 -0
  77. data/lib/flexr/automaton/analysis.rb +38 -0
  78. data/lib/flexr/automaton/byte_class_set.rb +29 -0
  79. data/lib/flexr/automaton/compiler.rb +413 -0
  80. data/lib/flexr/automaton/dfa.rb +103 -0
  81. data/lib/flexr/automaton/minimizer.rb +70 -0
  82. data/lib/flexr/automaton/nfa.rb +92 -0
  83. data/lib/flexr/cli.rb +342 -0
  84. data/lib/flexr/codegen/base.rb +17 -0
  85. data/lib/flexr/codegen/direct.rb +52 -0
  86. data/lib/flexr/codegen/firstmatch.rb +17 -0
  87. data/lib/flexr/codegen/table.rb +158 -0
  88. data/lib/flexr/codegen/table_packer.rb +61 -0
  89. data/lib/flexr/diagnostics.rb +94 -0
  90. data/lib/flexr/dsl.rb +182 -0
  91. data/lib/flexr/errors.rb +28 -0
  92. data/lib/flexr/generated.rb +125 -0
  93. data/lib/flexr/generator.rb +400 -0
  94. data/lib/flexr/importer.rb +560 -0
  95. data/lib/flexr/ir.rb +36 -0
  96. data/lib/flexr/lexer.rb +10 -0
  97. data/lib/flexr/options.rb +47 -0
  98. data/lib/flexr/rake_task.rb +27 -0
  99. data/lib/flexr/regexp/ast.rb +45 -0
  100. data/lib/flexr/regexp/char_class.rb +7 -0
  101. data/lib/flexr/regexp/normalizer.rb +117 -0
  102. data/lib/flexr/regexp/parser.rb +517 -0
  103. data/lib/flexr/regexp/tokenizer.flexr.rb +27 -0
  104. data/lib/flexr/regexp/tokenizer.rb +168 -0
  105. data/lib/flexr/regexp/unsupported.rb +7 -0
  106. data/lib/flexr/runtime/buffer.rb +112 -0
  107. data/lib/flexr/runtime/core.rb +388 -0
  108. data/lib/flexr/runtime/errors.rb +22 -0
  109. data/lib/flexr/runtime/interpreter.rb +505 -0
  110. data/lib/flexr/runtime/location.rb +26 -0
  111. data/lib/flexr/runtime/token.rb +7 -0
  112. data/lib/flexr/source/passthrough.rb +31 -0
  113. data/lib/flexr/source/prism_reader.rb +283 -0
  114. data/lib/flexr/source/static_eval.rb +145 -0
  115. data/lib/flexr/unicode/case_fold.rb +45 -0
  116. data/lib/flexr/unicode/data/LICENSE-UNICODE.txt +5 -0
  117. data/lib/flexr/unicode/data/UNICODE_VERSION +1 -0
  118. data/lib/flexr/unicode/data/case_folding.rb +9 -0
  119. data/lib/flexr/unicode/data/properties.rb +10 -0
  120. data/lib/flexr/unicode/property.rb +107 -0
  121. data/lib/flexr/unicode/reference_regexp.rb +102 -0
  122. data/lib/flexr/unicode/utf8_splitter.rb +109 -0
  123. data/lib/flexr/version.rb +5 -0
  124. data/lib/flexr.rb +81 -0
  125. data/site/README.md +22 -0
  126. data/site/astro.config.mjs +57 -0
  127. data/site/package.json +19 -0
  128. data/site/pnpm-lock.yaml +5029 -0
  129. data/site/pnpm-workspace.yaml +6 -0
  130. data/site/public/playground.js +189 -0
  131. data/site/scripts/verify-site.mjs +42 -0
  132. data/site/src/content/docs/benchmarks.md +8 -0
  133. data/site/src/content/docs/concepts/matching-semantics.md +15 -0
  134. data/site/src/content/docs/concepts/regexp-model.md +18 -0
  135. data/site/src/content/docs/concepts/runtime-vs-generated.md +15 -0
  136. data/site/src/content/docs/concepts/security-model.md +15 -0
  137. data/site/src/content/docs/examples.md +17 -0
  138. data/site/src/content/docs/learn/generation.md +29 -0
  139. data/site/src/content/docs/learn/getting-started.md +56 -0
  140. data/site/src/content/docs/learn/parser-integration.md +27 -0
  141. data/site/src/content/docs/learn/runtime-mode.md +32 -0
  142. data/site/src/content/docs/reference/action-context.md +20 -0
  143. data/site/src/content/docs/reference/cli.md +22 -0
  144. data/site/src/content/docs/reference/diagnostics.md +16 -0
  145. data/site/src/content/docs/reference/dsl.md +19 -0
  146. data/site/src/content/docs/reference/public-api.md +18 -0
  147. data/site/src/content/docs/reference/regexp.md +16 -0
  148. data/site/src/content/docs/reference/runtime.md +16 -0
  149. data/site/src/content/docs/reference/tokens-and-locations.md +16 -0
  150. data/site/src/content.config.ts +12 -0
  151. data/site/src/env.d.ts +1 -0
  152. data/site/src/layouts/SiteLayout.astro +39 -0
  153. data/site/src/pages/index.astro +174 -0
  154. data/site/src/pages/playground.astro +64 -0
  155. data/site/src/styles/custom.css +711 -0
  156. data/site/tsconfig.json +5 -0
  157. data/tools/coverage.rb +32 -0
  158. data/tools/docs_verify.rb +116 -0
  159. data/tools/gen_unicode_tables.rb +202 -0
  160. data/tools/regexp_tokenizer_reference.rb +60 -0
  161. 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flexr
4
+ VERSION = "1.0.0"
5
+ 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
+ }