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,41 @@
|
|
|
1
|
+
# Diagnostics catalog
|
|
2
|
+
|
|
3
|
+
Diagnostics have a code, severity, message, optional location, help, and note.
|
|
4
|
+
Use `flexr check SPEC --format json` to consume the structured form.
|
|
5
|
+
|
|
6
|
+
## Errors
|
|
7
|
+
|
|
8
|
+
| Code | Meaning | First fix to try |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `FLEXR-E001` | Regexp syntax is malformed | Correct the regexp syntax |
|
|
11
|
+
| `FLEXR-E003` | A state name is undefined | Declare the state before entering it |
|
|
12
|
+
| `FLEXR-E005` | A rule can match an empty string | Make it consuming or opt into `allow_empty_match` |
|
|
13
|
+
| `FLEXR-E006` | DFA state limit exceeded | Raise `max_dfa_states` or split rules |
|
|
14
|
+
| `FLEXR-E007` | Bounded repetition exceeds 1000 | Use a smaller bound or split the rule |
|
|
15
|
+
| `FLEXR-E009` | Anchor is not at an outermost boundary | Move `^` / `$` or split alternatives |
|
|
16
|
+
| `FLEXR-E010` | Prism reported source syntax errors | Fix Ruby syntax in the specification |
|
|
17
|
+
| `FLEXR-E011` | Encoding is not UTF-8 or binary | Change `encoding` |
|
|
18
|
+
| `FLEXR-E012` | Runtime token exceeded the size limit | Raise `max_token_size` or split the token |
|
|
19
|
+
| `FLEXR-E013` | `reject` is not supported | Use states and `less` |
|
|
20
|
+
| `FLEXR-E014` | Regexp construct is not DFA-compatible | Follow the diagnostic alternative |
|
|
21
|
+
| `FLEXR-E017` | Pattern is not statically resolvable | Rewrite as a static expression or use trusted `--eval` |
|
|
22
|
+
| `FLEXR-E018` | Rule pattern or trailing context has an invalid type | Use a regexp, string, or valid array |
|
|
23
|
+
| `FLEXR-E019` | Prism is unavailable for generation | Install the generator dependencies |
|
|
24
|
+
|
|
25
|
+
## Warnings
|
|
26
|
+
|
|
27
|
+
| Code | Meaning | First fix to try |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `FLEXR-W001` | Rule is unreachable or shadowed | Remove, reorder, or distinguish it |
|
|
30
|
+
| `FLEXR-W002` | Named state has no rules | Add rules or remove the state |
|
|
31
|
+
| `FLEXR-W003` | Trailing context has variable length | Make both parts fixed length |
|
|
32
|
+
| `FLEXR-W010` | `firstmatch` overlap may change semantics | Use the stable table backend |
|
|
33
|
+
| `FLEXR-W011` | Transition table is large | Use direct backend, compression, or split rules |
|
|
34
|
+
| `FLEXR-W012` | Trailing context cannot use region acceleration | Remove context or set `accel :none` |
|
|
35
|
+
| `FLEXR-W013` | Capturing group is treated as non-capturing | Use `(?:...)` |
|
|
36
|
+
| `FLEXR-W014` | Emitted token is not declared | Add it to `emits` or accept the warning |
|
|
37
|
+
| `FLEXR-W016` | DFA construction took more than 0.5 seconds | Use generated mode for startup |
|
|
38
|
+
|
|
39
|
+
Warnings are reported by `check` according to `--warn`. The default omits the
|
|
40
|
+
wall-clock `FLEXR-W016`; `--warn all` includes it, and `--warn none` suppresses
|
|
41
|
+
warnings. `--warn-as-error` turns selected warnings into a failed command.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# DSL reference
|
|
2
|
+
|
|
3
|
+
The DSL is extended onto a subclass of `Flexr::Lexer`. Class bodies are normal
|
|
4
|
+
Ruby and may contain constants, methods, requires, comments, and ordinary
|
|
5
|
+
expressions.
|
|
6
|
+
|
|
7
|
+
## `rule(pattern, skip: false, emit: nil, followed_by: nil, &action)`
|
|
8
|
+
|
|
9
|
+
Registers one rule. `pattern` is a `Regexp`, `String`, or an array containing
|
|
10
|
+
those values. A string is matched literally. Array members are alternatives of
|
|
11
|
+
one rule and share its action.
|
|
12
|
+
|
|
13
|
+
`skip: true` discards the match and takes precedence over `emit` and the block.
|
|
14
|
+
`emit: :TOKEN` installs a simple action that emits the token with the matched
|
|
15
|
+
text as its value. Without either option, the block runs in the lexer instance;
|
|
16
|
+
if no block is given, the default action emits `[nil, text]`.
|
|
17
|
+
|
|
18
|
+
`followed_by:` accepts a `Regexp` or `String`. It participates in rule
|
|
19
|
+
selection but is not consumed. The method returns `nil`. Rules are numbered in
|
|
20
|
+
source order starting at zero.
|
|
21
|
+
|
|
22
|
+
## `state(*names, inclusive: false, &block)`
|
|
23
|
+
|
|
24
|
+
Creates one or more named states and registers rules in the block for those
|
|
25
|
+
states. The block is required. Named states are exclusive by default. An
|
|
26
|
+
inclusive state also sees rules registered for `:initial`.
|
|
27
|
+
|
|
28
|
+
## `all_states(&block)`
|
|
29
|
+
|
|
30
|
+
Registers the block for every state known when the call is evaluated, including
|
|
31
|
+
`:initial`. It has the same rule-registration behavior as `state`.
|
|
32
|
+
|
|
33
|
+
## `on_eof(&action)`
|
|
34
|
+
|
|
35
|
+
Installs an action for EOF in the current state. If called outside a state block,
|
|
36
|
+
the action belongs to `:initial`. The action can emit a final token, change
|
|
37
|
+
state, or do nothing. It runs at most once per state for a lexer instance.
|
|
38
|
+
|
|
39
|
+
## `emits(*tokens)`
|
|
40
|
+
|
|
41
|
+
Declares token names for diagnostics and parser integration. Arguments are
|
|
42
|
+
flattened and converted to symbols. Duplicate names are removed. The method
|
|
43
|
+
does not restrict `emit` at runtime.
|
|
44
|
+
|
|
45
|
+
## `backend(name)`
|
|
46
|
+
|
|
47
|
+
Selects `:table`, `:direct`, `:auto`, or the experimental `:firstmatch`
|
|
48
|
+
backend. `:table`, `:direct`, and `:auto` preserve the stable longest-match
|
|
49
|
+
contract. `:firstmatch` requires `option :experimental` and can change the
|
|
50
|
+
meaning of overlapping rules.
|
|
51
|
+
|
|
52
|
+
## `token_kind(name)`
|
|
53
|
+
|
|
54
|
+
Selects `:array`, `:struct`, or `:yield`. The default is `:array`. See the
|
|
55
|
+
[token contract](tokens-and-locations.md) for the exact shapes.
|
|
56
|
+
|
|
57
|
+
## `encoding(value)`
|
|
58
|
+
|
|
59
|
+
Accepts `Encoding::UTF_8`, `Encoding::BINARY`, or a name resolving to one of
|
|
60
|
+
them. Other encodings raise `FLEXR-E011`. The regexp encoding can override the
|
|
61
|
+
specification encoding for an individual pattern only when the resulting byte
|
|
62
|
+
model is valid.
|
|
63
|
+
|
|
64
|
+
## `option(*values)`
|
|
65
|
+
|
|
66
|
+
Adds boolean options to the specification. Stable options include
|
|
67
|
+
`:unicode`, `:eager_columns`, and `:allow_empty_match`. `:experimental` is a
|
|
68
|
+
capability opt-in for experimental behavior such as `:firstmatch`. Unknown
|
|
69
|
+
options are retained in generated configuration but may have no effect.
|
|
70
|
+
|
|
71
|
+
## `accel(value)`
|
|
72
|
+
|
|
73
|
+
Selects `:auto`, `:strscan`, `:regexp`, or `:none` for runtime region
|
|
74
|
+
acceleration. Acceleration is an optimization; the DFA remains the semantic
|
|
75
|
+
source of truth. Affected trailing-context rules cannot use region acceleration.
|
|
76
|
+
|
|
77
|
+
## Compilation helpers
|
|
78
|
+
|
|
79
|
+
`compile!` and `dfa` exist for diagnostics and integration internals. They are
|
|
80
|
+
not required for normal use and are not compatibility-stable; use the lexer
|
|
81
|
+
constructor and token methods instead. See [public API](public-api.md).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Errors and recovery
|
|
2
|
+
|
|
3
|
+
## Specification errors
|
|
4
|
+
|
|
5
|
+
Compile-time failures raise `Flexr::CompileError` or its
|
|
6
|
+
`Flexr::UnsupportedRegexpError` subclass. Both expose a structured
|
|
7
|
+
`diagnostic`. Static generation can additionally raise
|
|
8
|
+
`Flexr::StaticResolutionError` with `FLEXR-E017`.
|
|
9
|
+
|
|
10
|
+
## Input errors
|
|
11
|
+
|
|
12
|
+
`Flexr::LexError` records the message, filename, byte position, line, matched
|
|
13
|
+
text, and optional diagnostic. `Flexr::Runtime::TokenTooLargeError` is a
|
|
14
|
+
`LexError` with code `FLEXR-E012`. `StateStackOverflowError` is raised when a
|
|
15
|
+
`push` would exceed `max_state_stack`.
|
|
16
|
+
|
|
17
|
+
The default `error_mode: :raise` raises the lexical error. `:token` queues
|
|
18
|
+
`[:error, text]`; `:panic` discards the unmatched byte and continues. An
|
|
19
|
+
`on_error` callback can return `:skip`, `:raise`, `:token`, or `:halt` for
|
|
20
|
+
instance-specific control.
|
|
21
|
+
|
|
22
|
+
## Import failures
|
|
23
|
+
|
|
24
|
+
`flexr import` reports untranslated actions as `FLEXR-TODO`, returns status 1,
|
|
25
|
+
and never writes incomplete output. This is intentionally stricter than a
|
|
26
|
+
best-effort migration because a partially translated lexer can silently change
|
|
27
|
+
language behavior.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Generated artifacts
|
|
2
|
+
|
|
3
|
+
## Dependency matrix
|
|
4
|
+
|
|
5
|
+
| Use | Build-time requirement | Runtime requirement |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| Runtime mode | None beyond the flexr gem | `flexr` gem |
|
|
8
|
+
| Normal generated mode | flexr gem and Prism on Ruby 3.3+ | `flexr` gem |
|
|
9
|
+
| Standalone generated mode | flexr gem and Prism on Ruby 3.3+ | Generated file and Ruby standard library |
|
|
10
|
+
|
|
11
|
+
The runtime gem supports Ruby 3.1 and newer. The generator's Prism path is
|
|
12
|
+
supported on Ruby 3.3 and newer. A generated file can be loaded by a compatible
|
|
13
|
+
runtime without the generator installed.
|
|
14
|
+
|
|
15
|
+
## Static evaluation boundary
|
|
16
|
+
|
|
17
|
+
The default generator resolves:
|
|
18
|
+
|
|
19
|
+
- regexp, string, numeric, symbol, and range literals;
|
|
20
|
+
- arrays and hashes made from supported values;
|
|
21
|
+
- constants in lexical and qualified scope;
|
|
22
|
+
- interpolation whose expressions resolve statically;
|
|
23
|
+
- `.freeze` on a static value; and
|
|
24
|
+
- `Regexp.union` with static arguments.
|
|
25
|
+
|
|
26
|
+
Methods with runtime-dependent results, `Time`, IO, environment reads, and
|
|
27
|
+
arbitrary dynamic calls produce `FLEXR-E017`. `--eval` is the explicit escape
|
|
28
|
+
hatch and executes the complete specification.
|
|
29
|
+
|
|
30
|
+
## Source transformation
|
|
31
|
+
|
|
32
|
+
The generator removes DSL call spans, inserts `Flexr::Generated.install_compiled!`,
|
|
33
|
+
and adds generated scanner methods. User actions remain Ruby source. A generated
|
|
34
|
+
header records:
|
|
35
|
+
|
|
36
|
+
- source path;
|
|
37
|
+
- SHA-256 digest of the generated payload;
|
|
38
|
+
- vendored Unicode version;
|
|
39
|
+
- effective backend;
|
|
40
|
+
- whether compilation used eval; and
|
|
41
|
+
- whether the runtime is standalone.
|
|
42
|
+
|
|
43
|
+
Do not edit generated files by hand. Regenerate them when the specification,
|
|
44
|
+
generator, backend, table format, or Unicode snapshot changes.
|
|
45
|
+
|
|
46
|
+
## Table formats
|
|
47
|
+
|
|
48
|
+
`--table-compression none|rows|full` controls row packing. `--table-format
|
|
49
|
+
literal|packed` controls whether packed arrays are emitted as Ruby literals or
|
|
50
|
+
Base64. These affect artifact size and loading cost, not matching semantics.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Public API and stability
|
|
2
|
+
|
|
3
|
+
Compatibility guarantees apply only to the stable surface below. Everything
|
|
4
|
+
else may change between releases without a deprecation cycle.
|
|
5
|
+
|
|
6
|
+
## Public and stable
|
|
7
|
+
|
|
8
|
+
- `Flexr::Lexer` and the DSL methods in [dsl.md](dsl.md).
|
|
9
|
+
- Runtime consumption: `initialize`, `next_token`, `tokens`, `each_token`, and
|
|
10
|
+
`racc_next_token`.
|
|
11
|
+
- Action context methods in [actions.md](actions.md).
|
|
12
|
+
- `Flexr::Runtime::Token`, `Flexr::Runtime::Location`, `Flexr::LexError`,
|
|
13
|
+
`Flexr::CompileError`, `Flexr::UnsupportedRegexpError`, and
|
|
14
|
+
`Flexr::Runtime::TokenTooLargeError`.
|
|
15
|
+
- `Flexr::Diagnostic`, `Flexr::DiagnosticSet`, and `Flexr::Diagnostics`.
|
|
16
|
+
- `Flexr::Generator` for source generation, `Flexr::RakeTask` for Rake builds,
|
|
17
|
+
and `Flexr::CLI.run` for programmatic CLI invocation.
|
|
18
|
+
- `Flexr.compile_pattern` and `Flexr.parse_pattern` for regexp tooling.
|
|
19
|
+
|
|
20
|
+
Method arguments, return shapes, and diagnostics for this surface are described
|
|
21
|
+
in the reference pages and are covered by the test suite.
|
|
22
|
+
|
|
23
|
+
## Experimental
|
|
24
|
+
|
|
25
|
+
- `backend :firstmatch` and `--backend firstmatch`.
|
|
26
|
+
- `option :experimental` as the opt-in for experimental behavior.
|
|
27
|
+
- Any future backend or option explicitly marked experimental in its reference.
|
|
28
|
+
|
|
29
|
+
Experimental behavior may change semantics or output between minor releases.
|
|
30
|
+
`firstmatch` requires the opt-in and generation performs a deterministic
|
|
31
|
+
differential check against the table matcher.
|
|
32
|
+
|
|
33
|
+
## Internal; compatibility is not guaranteed
|
|
34
|
+
|
|
35
|
+
- `Flexr::IR`, `Flexr::Automaton`, `Flexr::Codegen`, and their members.
|
|
36
|
+
- `Flexr::Runtime::Buffer` and `Flexr::Runtime::Interpreter` internals.
|
|
37
|
+
- `Flexr::Generated` installation helpers.
|
|
38
|
+
- `__flexr_*` methods and instance variables.
|
|
39
|
+
- `Flexr::Lexer.compile!` and `.dfa` as implementation inspection hooks.
|
|
40
|
+
|
|
41
|
+
Use stable token and diagnostic APIs instead of depending on internal compiled
|
|
42
|
+
tables or generated installation methods.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Regexp compatibility
|
|
2
|
+
|
|
3
|
+
flexr accepts the regular-expression subset that can be represented by its
|
|
4
|
+
byte-oriented automaton. All matches start at the current lexer position.
|
|
5
|
+
|
|
6
|
+
| Ruby construct | Status | Notes and alternative |
|
|
7
|
+
|---|---|---|
|
|
8
|
+
| Literals, concatenation, alternation | Supported | |
|
|
9
|
+
| Groups and `(?:...)` | Supported | Captures are treated as non-capturing and warn |
|
|
10
|
+
| Character classes and ranges | Supported | POSIX classes are supported; see encoding notes |
|
|
11
|
+
| Unicode `\p{...}` / `\P{...}` | Supported | Uses the vendored UCD snapshot |
|
|
12
|
+
| `*`, `+`, `?` | Supported | Greedy forms only |
|
|
13
|
+
| `{n}`, `{n,m}` | Supported | The upper bound is 1000 |
|
|
14
|
+
| `{n,}` | Unsupported | Split the rule or use states |
|
|
15
|
+
| `^` / `$` | Restricted | Only at outermost pattern boundaries; otherwise `FLEXR-E009` |
|
|
16
|
+
| `/i`, `/m`, `/x`, `/n` | Supported | Inline forms are supported where Ruby permits them |
|
|
17
|
+
| `\d`, `\w`, `\s` | Supported | Unicode-aware with `option :unicode` in UTF-8 |
|
|
18
|
+
| Lookahead / lookbehind | Unsupported | Use `followed_by:` or a state; `FLEXR-E014` |
|
|
19
|
+
| Backreferences | Unsupported | Split the language into states or actions |
|
|
20
|
+
| `\b`, `\B`, `\A`, `\z`, `\G`, `\K` | Unsupported | Use rule boundaries or state/action logic |
|
|
21
|
+
| Lazy or possessive quantifiers | Unsupported | Use a negated character class or separate rule |
|
|
22
|
+
| Conditional, atomic, and capture-dependent groups | Unsupported | Rewrite as DFA-compatible rules |
|
|
23
|
+
|
|
24
|
+
Malformed syntax produces `FLEXR-E001`. Unsupported constructs produce
|
|
25
|
+
`FLEXR-E014`; the diagnostic help normally names the supported replacement.
|
|
26
|
+
Empty matches are rejected with `FLEXR-E005` unless `option :allow_empty_match`
|
|
27
|
+
is explicitly enabled, in which case flexr advances to guarantee progress.
|
|
28
|
+
|
|
29
|
+
## Bounded repetition
|
|
30
|
+
|
|
31
|
+
`{n,m}` expands into a finite automaton. `m` must be at least `n` and no greater
|
|
32
|
+
than 1000. Open repetition is rejected because it is easy to express with `+`
|
|
33
|
+
or a separate rule and otherwise gives a misleading compatibility promise.
|
|
34
|
+
|
|
35
|
+
## Captures
|
|
36
|
+
|
|
37
|
+
Capturing parentheses do not expose match groups to actions. They are treated
|
|
38
|
+
as grouping syntax and produce `FLEXR-W013`; use `(?:...)` and inspect `text`
|
|
39
|
+
in the action instead.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Runtime reference
|
|
2
|
+
|
|
3
|
+
`Flexr::Lexer` includes the runtime methods below. Runtime mode and generated
|
|
4
|
+
mode expose the same consumer-facing methods.
|
|
5
|
+
|
|
6
|
+
## Initialization
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
Lexer.new(input,
|
|
10
|
+
filename: nil,
|
|
11
|
+
error_mode: :raise,
|
|
12
|
+
max_token_size: 16 * 1024 * 1024,
|
|
13
|
+
max_state_stack: 1024,
|
|
14
|
+
chunk_size: 64 * 1024)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`input` must be a `String` or an object responding to `read`. `filename` is
|
|
18
|
+
copied into locations and lexical errors. `error_mode` is `:raise`, `:token`,
|
|
19
|
+
or `:panic`; unknown values fall back to raising when an unmatched byte is
|
|
20
|
+
handled. Size limits are non-negative, and `chunk_size` must be positive.
|
|
21
|
+
|
|
22
|
+
## Consumption
|
|
23
|
+
|
|
24
|
+
| Method | Result |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `next_token` | One token, or `nil` at EOF |
|
|
27
|
+
| `tokens` | Array containing every token until EOF |
|
|
28
|
+
| `each_token` | Enumerator without a block; otherwise yields tokens and returns `self` |
|
|
29
|
+
| `racc_next_token` | `[type, value]`, or `[false, "$end"]` at EOF |
|
|
30
|
+
| `input` | The buffered input string |
|
|
31
|
+
| `buffer` | Internal buffered input object; use only for integrations that need it |
|
|
32
|
+
| `filename` | The configured filename |
|
|
33
|
+
| `error_mode` | The configured default input error mode |
|
|
34
|
+
| `max_token_size` | The configured token-size limit |
|
|
35
|
+
|
|
36
|
+
`each_token` yields two arguments instead of one when `token_kind :yield` is
|
|
37
|
+
selected. `tokens` always returns an array of token values.
|
|
38
|
+
|
|
39
|
+
## State and action context
|
|
40
|
+
|
|
41
|
+
The public action methods are collected in the [actions reference](actions.md).
|
|
42
|
+
State transitions are instance-local; changing one lexer does not change the
|
|
43
|
+
class or another lexer instance.
|
|
44
|
+
|
|
45
|
+
## Streaming input
|
|
46
|
+
|
|
47
|
+
IO input is read lazily in chunks. UTF-8 input is checked at codepoint
|
|
48
|
+
boundaries, while binary input is consumed byte by byte. Invalid UTF-8 becomes
|
|
49
|
+
an unmatched-byte error rather than being silently normalized.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Tokens and locations
|
|
2
|
+
|
|
3
|
+
## Token kinds
|
|
4
|
+
|
|
5
|
+
| `token_kind` | `next_token` / `tokens` | `each_token` |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `:array` (default) | `[type, value]` | Yields one array |
|
|
8
|
+
| `:struct` | `Flexr::Runtime::Token` | Yields one `Token` |
|
|
9
|
+
| `:yield` | `[type, value]` | Yields `type, value` as separate arguments |
|
|
10
|
+
|
|
11
|
+
For `:struct`, `Token` has `type`, `value`, and `location` members. `emit(nil,
|
|
12
|
+
value)` is valid and produces a token whose type is `nil`. `skip` produces no
|
|
13
|
+
token.
|
|
14
|
+
|
|
15
|
+
## Locations
|
|
16
|
+
|
|
17
|
+
`Flexr::Runtime::Location` has these public members:
|
|
18
|
+
|
|
19
|
+
| Member | Meaning |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `filename` | The `filename:` value passed to the lexer |
|
|
22
|
+
| `byte_begin` | Inclusive byte offset of the token |
|
|
23
|
+
| `byte_end` | Exclusive byte offset of the token |
|
|
24
|
+
| `line_begin` / `line_end` | One-based line range |
|
|
25
|
+
| `column_begin` / `column_end` | One-based character columns for UTF-8, byte columns for binary input |
|
|
26
|
+
|
|
27
|
+
Columns are computed lazily by default. `option :eager_columns` fills them while
|
|
28
|
+
the location is created. The end line and column identify the position after
|
|
29
|
+
the token's last byte, so a token ending at the start of the next line may have
|
|
30
|
+
an end column of 1.
|
|
31
|
+
|
|
32
|
+
`more` extends the location from the first match to the final match. Trailing
|
|
33
|
+
context is not part of the location because it is not consumed.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Build a calculator lexer
|
|
2
|
+
|
|
3
|
+
This tutorial takes one specification through runtime execution, diagnostics,
|
|
4
|
+
generation, and generated execution. The complete executable source is
|
|
5
|
+
[`examples/calculator/lexer.flexr.rb`](../../examples/calculator/lexer.flexr.rb).
|
|
6
|
+
|
|
7
|
+
## 1. Install flexr
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
gem install flexr
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For a checkout, use the bundle and add `lib` to Ruby's load path:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
bundle install
|
|
17
|
+
bundle exec ruby -Ilib examples/calculator/lexer.flexr.rb
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 2. Read the specification
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
class Lexer < Flexr::Lexer
|
|
24
|
+
emits :EQ, :ASSIGN, :IF, :IDENT, :INTEGER, :PLUS
|
|
25
|
+
|
|
26
|
+
rule(/[ \t\r\n]+/, skip: true)
|
|
27
|
+
rule(/==/) { emit :EQ }
|
|
28
|
+
rule(/=/) { emit :ASSIGN }
|
|
29
|
+
rule(/if/) { emit :IF }
|
|
30
|
+
rule(/[a-z_][a-z0-9_]*/) { emit :IDENT }
|
|
31
|
+
rule(/[0-9]+/) { emit :INTEGER, text.to_i }
|
|
32
|
+
rule(/\+/) { emit :PLUS }
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`emits` is a declaration used by diagnostics and parser integration. It does
|
|
37
|
+
not create constants and is not required for `emit` to work.
|
|
38
|
+
|
|
39
|
+
The `==` rule is longer than `=`, so it wins even though both rules are active.
|
|
40
|
+
The input `if` matches both the keyword and identifier rules with equal length;
|
|
41
|
+
the keyword rule appears first, so it wins. The input `ifx` is longer under the
|
|
42
|
+
identifier rule and therefore becomes one identifier token.
|
|
43
|
+
|
|
44
|
+
## 3. Run runtime mode
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
ruby -Ilib -e 'require "json"; load "examples/calculator/lexer.flexr.rb"; puts JSON.generate(CalculatorExample::Lexer.new("if ifx == = 12 + 3").tokens)'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Expected output:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
[["IF","if"],["IDENT","ifx"],["EQ","=="],["ASSIGN","="],["INTEGER",12],["PLUS","+"],["INTEGER",3]]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Runtime mode evaluates the Ruby specification normally. The DFA is compiled
|
|
57
|
+
when the lexer class is first used, and actions run in the lexer instance.
|
|
58
|
+
|
|
59
|
+
## 4. Validate the specification
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
flexr check examples/calculator/lexer.flexr.rb --format json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
An empty JSON array means that the specification produced no errors or selected
|
|
66
|
+
warnings. Use `--warn all` to include the full warning catalog.
|
|
67
|
+
|
|
68
|
+
## 5. Generate Ruby
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
flexr examples/calculator/lexer.flexr.rb -o /tmp/calculator_lexer.rb
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The default generator statically resolves patterns and constants with Prism.
|
|
75
|
+
The generated file keeps ordinary Ruby surrounding the DSL, embeds the
|
|
76
|
+
compiled tables, and keeps actions as Ruby code.
|
|
77
|
+
|
|
78
|
+
## 6. Run the generated lexer
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
ruby -Ilib -e 'require "json"; load ARGV.fetch(0); puts JSON.generate(CalculatorExample::Lexer.new("if ifx == = 12 + 3").tokens)' /tmp/calculator_lexer.rb
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The output must match runtime mode. `rake modes:equivalence` checks this
|
|
85
|
+
relationship for every committed example.
|
|
86
|
+
|
|
87
|
+
## 7. Inspect the contract
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
flexr tokens examples/calculator/lexer.flexr.rb
|
|
91
|
+
flexr stats examples/calculator/lexer.flexr.rb
|
|
92
|
+
flexr explain examples/calculator/lexer.flexr.rb --rule 1
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use the [DSL reference](../reference/dsl.md) for method arguments and the
|
|
96
|
+
[CLI reference](../reference/cli.md) for output and exit-status behavior.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Calculator lexer example
|
|
2
|
+
|
|
3
|
+
This is the executable source for the first-time user tutorial. It keeps the
|
|
4
|
+
rules deliberately small while showing two important matching contracts:
|
|
5
|
+
|
|
6
|
+
- `==` wins over `=` because the longest match wins.
|
|
7
|
+
- `if` wins over the identifier rule for the input `if` because equal-length
|
|
8
|
+
matches use source order.
|
|
9
|
+
|
|
10
|
+
Run it directly:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
ruby -Ilib -e 'require "json"; load "examples/calculator/lexer.flexr.rb"; puts JSON.generate(CalculatorExample::Lexer.new("if ifx == = 12 + 3").tokens)'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Expected output:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
[["IF","if"],["IDENT","ifx"],["EQ","=="],["ASSIGN","="],["INTEGER",12],["PLUS","+"],["INTEGER",3]]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Validate and generate it with:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
flexr check examples/calculator/lexer.flexr.rb --format json
|
|
26
|
+
flexr examples/calculator/lexer.flexr.rb -o /tmp/calculator_lexer.rb
|
|
27
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "flexr"
|
|
4
|
+
|
|
5
|
+
module CalculatorExample
|
|
6
|
+
class Lexer < Flexr::Lexer
|
|
7
|
+
emits :EQ, :ASSIGN, :IF, :IDENT, :INTEGER, :PLUS
|
|
8
|
+
|
|
9
|
+
rule(/[ \t\r\n]+/, skip: true)
|
|
10
|
+
rule(/==/) { emit :EQ }
|
|
11
|
+
rule(/=/) { emit :ASSIGN }
|
|
12
|
+
rule(/if/) { emit :IF }
|
|
13
|
+
rule(/[a-z_][a-z0-9_]*/) { emit :IDENT }
|
|
14
|
+
rule(/[0-9]+/) { emit :INTEGER, text.to_i }
|
|
15
|
+
rule(/\+/) { emit :PLUS }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# JSON lexer example
|
|
2
|
+
|
|
3
|
+
## What this example demonstrates
|
|
4
|
+
|
|
5
|
+
- a complete JSON token set with value conversion;
|
|
6
|
+
- the stable `direct` backend; and
|
|
7
|
+
- runtime/generated equivalence for a small practical lexer.
|
|
8
|
+
|
|
9
|
+
## Run in runtime mode
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
ruby -Ilib -e 'require "json"; load "examples/json/lexer.flexr.rb"; puts JSON.generate(JsonExample::Lexer.new(%q({"answer":42})).tokens)'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Expected output:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
[["LBRACE","{"],["STRING","answer"],["COLON",":"],["NUMBER",42.0],["RBRACE","}"]]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Validate and generate
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
flexr check examples/json/lexer.flexr.rb --format json
|
|
25
|
+
flexr examples/json/lexer.flexr.rb -o /tmp/json_lexer.rb
|
|
26
|
+
ruby -Ilib -e 'require "json"; load ARGV.fetch(0); puts JSON.generate(JsonExample::Lexer.new(%q({"answer":42})).tokens)' /tmp/json_lexer.rb
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The generated output should produce the same JSON token array. Use
|
|
30
|
+
`--standalone` when the runtime artifact must not require the flexr gem.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "flexr"
|
|
4
|
+
|
|
5
|
+
module JsonExample
|
|
6
|
+
class Lexer < Flexr::Lexer
|
|
7
|
+
backend :direct
|
|
8
|
+
emits :LBRACE, :RBRACE, :LBRACKET, :RBRACKET, :COLON, :COMMA,
|
|
9
|
+
:STRING, :NUMBER, :TRUE, :FALSE, :NULL
|
|
10
|
+
|
|
11
|
+
rule(/[ \t\r\n]+/, skip: true)
|
|
12
|
+
rule(/\{/) { emit :LBRACE }
|
|
13
|
+
rule(/\}/) { emit :RBRACE }
|
|
14
|
+
rule(/\[/) { emit :LBRACKET }
|
|
15
|
+
rule(/\]/) { emit :RBRACKET }
|
|
16
|
+
rule(/:/) { emit :COLON }
|
|
17
|
+
rule(/,/) { emit :COMMA }
|
|
18
|
+
rule(/true/) { emit :TRUE, true }
|
|
19
|
+
rule(/false/) { emit :FALSE, false }
|
|
20
|
+
rule(/null/) { emit :NULL, nil }
|
|
21
|
+
rule(/-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/) { emit :NUMBER, text.to_f }
|
|
22
|
+
rule(/"(?:\\.|[^"\\])*"/) { emit :STRING, text.byteslice(1...-1) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Ruby subset lexer
|
|
2
|
+
|
|
3
|
+
## What this example demonstrates
|
|
4
|
+
|
|
5
|
+
The specification contains constants, a `%w` array, a heredoc inside an action,
|
|
6
|
+
and ordinary Ruby interpolation. The generator removes only DSL calls and
|
|
7
|
+
preserves the surrounding Ruby.
|
|
8
|
+
|
|
9
|
+
## Run and validate
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
ruby -Ilib -e 'require "json"; load "examples/ruby_subset/lexer.flexr.rb"; puts JSON.generate(RubySubset::Lexer.new(%q(class Foo "ok" end)).tokens)'
|
|
13
|
+
flexr check examples/ruby_subset/lexer.flexr.rb --format json
|
|
14
|
+
flexr examples/ruby_subset/lexer.flexr.rb -o /tmp/ruby_subset_lexer.rb
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Inspect the generated file to see that the constants and action body remain.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "flexr"
|
|
4
|
+
|
|
5
|
+
module RubySubset
|
|
6
|
+
class Lexer < Flexr::Lexer
|
|
7
|
+
KEYWORDS = %w[class def end].freeze
|
|
8
|
+
IDENT = /[A-Za-z_][A-Za-z_0-9]*/
|
|
9
|
+
|
|
10
|
+
emits :KEYWORD, :IDENT, :STRING
|
|
11
|
+
|
|
12
|
+
rule(/[ \t\r\n]+/, skip: true)
|
|
13
|
+
rule(KEYWORDS) { emit :KEYWORD, text.to_sym }
|
|
14
|
+
rule(IDENT) { emit :IDENT, "#{text}:identifier" }
|
|
15
|
+
rule(/"(?:\\.|[^"\\])*"/) do
|
|
16
|
+
value = <<~TOKEN
|
|
17
|
+
#{text[1...-1]}
|
|
18
|
+
TOKEN
|
|
19
|
+
emit :STRING, value.chomp
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Toy language lexer
|
|
2
|
+
|
|
3
|
+
## What this example demonstrates
|
|
4
|
+
|
|
5
|
+
This lexer keeps `DIGIT` and `IDENT` as ordinary Ruby constants and interpolates
|
|
6
|
+
them into regexp literals. It therefore exercises static constant resolution
|
|
7
|
+
without introducing a second lexer language.
|
|
8
|
+
|
|
9
|
+
## Run and validate
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
ruby -Ilib -e 'require "json"; load "examples/toy_lang/lexer.flexr.rb"; puts JSON.generate(ToyLang::Lexer.new("answer + 12").tokens)'
|
|
13
|
+
flexr check examples/toy_lang/lexer.flexr.rb --format json
|
|
14
|
+
flexr examples/toy_lang/lexer.flexr.rb -o /tmp/toy_lang_lexer.rb
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The generated file preserves the constants and produces the same token stream.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "flexr"
|
|
4
|
+
|
|
5
|
+
module ToyLang
|
|
6
|
+
class Lexer < Flexr::Lexer
|
|
7
|
+
DIGIT = /[0-9]/
|
|
8
|
+
IDENT = /[A-Za-z_]/
|
|
9
|
+
|
|
10
|
+
emits :INT, :IDENT, :PLUS, :STRING
|
|
11
|
+
|
|
12
|
+
rule(/[ \t\r\n]+/, skip: true)
|
|
13
|
+
rule(/#{DIGIT}+/) { emit :INT, text.to_i }
|
|
14
|
+
rule(/#{IDENT}[A-Za-z_0-9]*/) { emit :IDENT, text }
|
|
15
|
+
rule(/\+/) { emit :PLUS }
|
|
16
|
+
rule(/"(?:\\.|[^"\\])*"/) { emit :STRING, text[1...-1] }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# flexr with Lrama
|
|
2
|
+
|
|
3
|
+
## What this example demonstrates
|
|
4
|
+
|
|
5
|
+
Lrama consumes the same `racc_next_token` protocol as Racc. The lexer declares
|
|
6
|
+
grammar-facing names with `emits`, and the dependency on Lrama remains optional.
|
|
7
|
+
|
|
8
|
+
## Run and validate
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
ruby -Ilib -e 'load "examples/with_lrama/lexer.flexr.rb"; lexer = WithLrama::LramaLexer.new("12 - 3"); p lexer.tokens'
|
|
12
|
+
flexr tokens examples/with_lrama/lexer.flexr.rb
|
|
13
|
+
flexr check examples/with_lrama/lexer.flexr.rb --format json
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
See the [Lrama integration guide](../../docs/how-to/integrate-with-lrama.md)
|
|
17
|
+
for the EOF protocol and build flow.
|