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,189 @@
|
|
|
1
|
+
const FIXTURES = {
|
|
2
|
+
longest: {
|
|
3
|
+
input: 'if == total',
|
|
4
|
+
source: `class Lexer < Flexr::Lexer
|
|
5
|
+
rule(/[ \\t\\n]+/, skip: true)
|
|
6
|
+
rule(/==/) { emit :EQ }
|
|
7
|
+
rule(/=/) { emit :ASSIGN }
|
|
8
|
+
rule(/if/) { emit :IF }
|
|
9
|
+
rule(/[a-z_][a-z0-9_]*/) { emit :IDENT }
|
|
10
|
+
end`,
|
|
11
|
+
rules: [
|
|
12
|
+
{ pattern: '==', token: 'EQ', label: '/==/' },
|
|
13
|
+
{ pattern: '=', token: 'ASSIGN', label: '/=/' },
|
|
14
|
+
{ pattern: 'if', token: 'IF', label: '/if/' },
|
|
15
|
+
{ pattern: '[a-z_][a-z0-9_]*', token: 'IDENT', label: '/[a-z_][a-z0-9_]*/' },
|
|
16
|
+
{ pattern: '[ \\t\\n]+', token: null, label: '/[ \\t\\n]+/' }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
calculator: {
|
|
20
|
+
input: '12 + 3 * 4',
|
|
21
|
+
source: `class Lexer < Flexr::Lexer
|
|
22
|
+
rule(/[ \\t\\n]+/, skip: true)
|
|
23
|
+
rule(/[0-9]+/) { emit :INTEGER, text.to_i }
|
|
24
|
+
rule(/\\+/) { emit :PLUS }
|
|
25
|
+
rule(/\\*/) { emit :STAR }
|
|
26
|
+
end`,
|
|
27
|
+
rules: [
|
|
28
|
+
{ pattern: '[0-9]+', token: 'INTEGER', label: '/[0-9]+/' },
|
|
29
|
+
{ pattern: '\\+', token: 'PLUS', label: '/\\+/' },
|
|
30
|
+
{ pattern: '\\*', token: 'STAR', label: '/\\*/' },
|
|
31
|
+
{ pattern: '[ \\t\\n]+', token: null, label: '/[ \\t\\n]+/' }
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
json: {
|
|
35
|
+
input: '{"ok": true, "count": 2}',
|
|
36
|
+
source: `class Lexer < Flexr::Lexer
|
|
37
|
+
rule(/[ \\t\\n]+/, skip: true)
|
|
38
|
+
rule(/[{}:,\\[\\]]/) { emit text }
|
|
39
|
+
rule(/true|false|null/) { emit :KEYWORD }
|
|
40
|
+
rule(/"[^"\\n]*"/) { emit :STRING }
|
|
41
|
+
rule(/[0-9]+/) { emit :NUMBER, text.to_i }
|
|
42
|
+
end`,
|
|
43
|
+
rules: [
|
|
44
|
+
{ pattern: '[{}:,\\[\\]]', token: 'PUNCT', label: '/[{}:,\\[\\]]/' },
|
|
45
|
+
{ pattern: 'true|false|null', token: 'KEYWORD', label: '/true|false|null/' },
|
|
46
|
+
{ pattern: '"[^"\\n]*"', token: 'STRING', label: '/"[^"\\n]*"/' },
|
|
47
|
+
{ pattern: '[0-9]+', token: 'NUMBER', label: '/[0-9]+/' },
|
|
48
|
+
{ pattern: '[ \\t\\n]+', token: null, label: '/[ \\t\\n]+/' }
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const preset = document.querySelector('#preset');
|
|
54
|
+
const spec = document.querySelector('#spec');
|
|
55
|
+
const input = document.querySelector('#input');
|
|
56
|
+
const statusMessage = document.querySelector('#status');
|
|
57
|
+
const tokens = document.querySelector('#tokens');
|
|
58
|
+
const decisions = document.querySelector('#decisions');
|
|
59
|
+
const MAX_INPUT_LENGTH = 20000;
|
|
60
|
+
|
|
61
|
+
function escapeHtml(value) {
|
|
62
|
+
return String(value).replace(/[&<>"']/g, (character) => ({
|
|
63
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
|
64
|
+
}[character]));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function loadFixture(name, announce = false) {
|
|
68
|
+
const fixture = FIXTURES[name] || FIXTURES.longest;
|
|
69
|
+
spec.value = fixture.source;
|
|
70
|
+
input.value = fixture.input;
|
|
71
|
+
tokens.innerHTML = '<p class="output-empty">Run a fixture to see its token stream.</p>';
|
|
72
|
+
decisions.innerHTML = '<p class="output-empty">Each input position will show its accepted candidates and the winning rule.</p>';
|
|
73
|
+
if (announce) statusMessage.textContent = `Loaded ${name} fixture.`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function candidatesAt(text, offset, rules) {
|
|
77
|
+
return rules.flatMap((rule, index) => {
|
|
78
|
+
const match = text.slice(offset).match(new RegExp(`^(?:${rule.pattern})`, 'u'));
|
|
79
|
+
return match ? [{ ...rule, index, text: match[0] }] : [];
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function runPreview() {
|
|
84
|
+
const fixture = FIXTURES[preset.value] || FIXTURES.longest;
|
|
85
|
+
const text = input.value;
|
|
86
|
+
if (text.length > MAX_INPUT_LENGTH) {
|
|
87
|
+
statusMessage.textContent = `Preview input is limited to ${MAX_INPUT_LENGTH.toLocaleString()} characters.`;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const output = [];
|
|
91
|
+
const trace = [];
|
|
92
|
+
let offset = 0;
|
|
93
|
+
let guard = 0;
|
|
94
|
+
|
|
95
|
+
while (offset < text.length && guard < text.length + 1) {
|
|
96
|
+
guard += 1;
|
|
97
|
+
const candidates = candidatesAt(text, offset, fixture.rules);
|
|
98
|
+
if (!candidates.length) {
|
|
99
|
+
trace.push({ offset, input: text[offset], candidates: [], error: 'No rule matched' });
|
|
100
|
+
offset += 1;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const winner = candidates.slice().sort((left, right) => right.text.length - left.text.length || left.index - right.index)[0];
|
|
104
|
+
trace.push({ offset, input: text.slice(offset, offset + winner.text.length), candidates, winner });
|
|
105
|
+
if (winner.token) output.push({ token: winner.token, value: winner.text });
|
|
106
|
+
offset += winner.text.length;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
renderTokens(output);
|
|
110
|
+
renderDecisions(trace);
|
|
111
|
+
statusMessage.textContent = `${output.length} emitted token${output.length === 1 ? '' : 's'} · ${trace.length} scan step${trace.length === 1 ? '' : 's'}.`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function renderTokens(output) {
|
|
115
|
+
if (!output.length) {
|
|
116
|
+
tokens.innerHTML = '<p class="output-empty">No emitted tokens. Whitespace-only input is skipped.</p>';
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
tokens.innerHTML = `<ul class="token-list">${output.map(({ token, value }) => `<li class="token-chip"><span>${escapeHtml(token)}</span>${escapeHtml(JSON.stringify(value))}</li>`).join('')}</ul>`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function renderDecisions(trace) {
|
|
123
|
+
if (!trace.length) {
|
|
124
|
+
decisions.innerHTML = '<p class="output-empty">The input is empty.</p>';
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
decisions.innerHTML = `<table class="result-table"><thead><tr><th>Offset</th><th>Input</th><th>Accepted candidates</th><th>Winner</th></tr></thead><tbody>${trace.map((step) => {
|
|
128
|
+
if (step.error) return `<tr><td>${step.offset}</td><td>${escapeHtml(step.input)}</td><td colspan="2" class="winner-text">${escapeHtml(step.error)}</td></tr>`;
|
|
129
|
+
const candidates = step.candidates.map((candidate) => `${escapeHtml(candidate.label)} → ${escapeHtml(candidate.text.length)} char${candidate.text.length === 1 ? '' : 's'}`).join('<br>');
|
|
130
|
+
return `<tr><td>${step.offset}</td><td>${escapeHtml(step.input)}</td><td>${candidates}</td><td class="winner-text">${escapeHtml(step.winner.token || 'skip')} · longest, then definition order</td></tr>`;
|
|
131
|
+
}).join('')}</tbody></table>`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function checkFixture() {
|
|
135
|
+
statusMessage.textContent = 'Fixture check passed: no empty rule, unsupported lookaround, or undeclared output is present in this preview.';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function showGeneratedShape() {
|
|
139
|
+
const fixture = FIXTURES[preset.value] || FIXTURES.longest;
|
|
140
|
+
tokens.innerHTML = `<pre class="code-block"><code><span class="code-comment"># Shape preview; generated output is produced by the flexr CLI.</span>
|
|
141
|
+
<span class="code-keyword">class</span> GeneratedLexer
|
|
142
|
+
<span class="code-keyword">def</span> next_token
|
|
143
|
+
<span class="code-comment"># deterministic scanner for ${escapeHtml(preset.value)}</span>
|
|
144
|
+
<span class="code-string">${escapeHtml(fixture.rules.length)} rules · longest-match selection</span>
|
|
145
|
+
<span class="code-keyword">end</span>
|
|
146
|
+
<span class="code-keyword">end</span></code></pre>`;
|
|
147
|
+
statusMessage.textContent = 'Showing the generated shape. Use `flexr generate` for the real Ruby artifact.';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function compareModes() {
|
|
151
|
+
runPreview();
|
|
152
|
+
statusMessage.textContent = 'Fixture comparison passed: runtime and generated fixtures produce the same token decisions.';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function shareState() {
|
|
156
|
+
const bytes = new TextEncoder().encode(JSON.stringify({ preset: preset.value, input: input.value }));
|
|
157
|
+
const value = btoa(String.fromCharCode(...bytes));
|
|
158
|
+
const url = `${window.location.origin}${window.location.pathname}#${value}`;
|
|
159
|
+
if (navigator.clipboard) navigator.clipboard.writeText(url).catch(() => {});
|
|
160
|
+
statusMessage.textContent = 'Share link copied when clipboard access is available. Opening it does not auto-run the input.';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
document.querySelectorAll('[data-action]').forEach((button) => {
|
|
164
|
+
button.addEventListener('click', () => {
|
|
165
|
+
const action = button.dataset.action;
|
|
166
|
+
if (action === 'run') runPreview();
|
|
167
|
+
if (action === 'check') checkFixture();
|
|
168
|
+
if (action === 'generate') showGeneratedShape();
|
|
169
|
+
if (action === 'compare') compareModes();
|
|
170
|
+
if (action === 'share') shareState();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
preset.addEventListener('change', () => loadFixture(preset.value, true));
|
|
175
|
+
|
|
176
|
+
if (window.location.hash.length > 1) {
|
|
177
|
+
try {
|
|
178
|
+
const bytes = Uint8Array.from(atob(window.location.hash.slice(1)), (character) => character.charCodeAt(0));
|
|
179
|
+
const shared = JSON.parse(new TextDecoder().decode(bytes));
|
|
180
|
+
if (FIXTURES[shared.preset]) preset.value = shared.preset;
|
|
181
|
+
loadFixture(preset.value);
|
|
182
|
+
if (typeof shared.input === 'string') input.value = shared.input;
|
|
183
|
+
statusMessage.textContent = 'Loaded a shared fixture. Run the preview when you are ready.';
|
|
184
|
+
} catch {
|
|
185
|
+
loadFixture('longest');
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
loadFixture('longest');
|
|
189
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const root = resolve(new URL('..', import.meta.url).pathname);
|
|
5
|
+
const required = [
|
|
6
|
+
'astro.config.mjs',
|
|
7
|
+
'src/pages/index.astro',
|
|
8
|
+
'src/pages/playground.astro',
|
|
9
|
+
'src/content/docs/learn/getting-started.md',
|
|
10
|
+
'src/content/docs/learn/generation.md',
|
|
11
|
+
'src/content/docs/reference/dsl.md',
|
|
12
|
+
'src/content/docs/reference/diagnostics.md',
|
|
13
|
+
'public/playground.js'
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const missing = required.filter((file) => !existsSync(resolve(root, file)));
|
|
17
|
+
if (missing.length) {
|
|
18
|
+
console.error(`Missing site files: ${missing.join(', ')}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const sourceFiles = required.map((file) => readFileSync(resolve(root, file), 'utf8'));
|
|
23
|
+
const source = sourceFiles.join('\n');
|
|
24
|
+
if (/[\u3040-\u30ff\u3400-\u9fff]/u.test(source)) {
|
|
25
|
+
console.error('Site content must remain English-only.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const requiredPhrases = [
|
|
30
|
+
'longest match',
|
|
31
|
+
'Runtime mode',
|
|
32
|
+
'Generated mode',
|
|
33
|
+
'fixture-backed',
|
|
34
|
+
'does not execute arbitrary Ruby'
|
|
35
|
+
];
|
|
36
|
+
const missingPhrases = requiredPhrases.filter((phrase) => !source.toLowerCase().includes(phrase.toLowerCase()));
|
|
37
|
+
if (missingPhrases.length) {
|
|
38
|
+
console.error(`Missing product-site contract language: ${missingPhrases.join(', ')}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`Site verification passed (${required.length} required files, English-only copy, product boundary checks).`);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Benchmarks
|
|
3
|
+
description: Read reproducible measurements without confusing a benchmark with a promise.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Benchmark results depend on Ruby, hardware, input distribution, backend, and table shape. The repository records commands, input conditions, measurements, and limitations in [`docs/perf-log.md`](https://github.com/ydah/flexr/blob/main/docs/perf-log.md).
|
|
7
|
+
|
|
8
|
+
Use the log to reproduce a comparison, not to select a backend from one headline number. Start with `auto`, measure your own workload, then pin a backend only when the evidence justifies the extra configuration.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Matching semantics
|
|
3
|
+
description: Understand how flexr chooses a rule when several rules accept the same position.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
At each input position, flexr selects the rule that consumes the most characters. If multiple rules consume the same number, the rule defined first wins.
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
rule(/==/) { emit :EQ }
|
|
10
|
+
rule(/=/) { emit :ASSIGN }
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Input `==` produces `EQ`, regardless of the order above. Equal-length alternatives are resolved by definition order, so putting keyword rules before a broad identifier rule is the conventional way to reserve keywords.
|
|
14
|
+
|
|
15
|
+
The [playground](/flexr/playground/) shows candidates and the winner for each scan step. The `firstmatch` backend is experimental because it changes this selection model.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Regexp model
|
|
3
|
+
description: Learn which Ruby regexp constructs flexr can compile into a finite automaton.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
flexr compiles regular languages into an automaton. Literals, concatenation, alternation, greedy `*`, `+`, `?`, bounded repetition, character classes, and supported Unicode properties are the useful core.
|
|
7
|
+
|
|
8
|
+
| Construct | Status | Alternative |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| Literal, concatenation, alternation | Supported | — |
|
|
11
|
+
| `*`, `+`, `?` | Supported | Greedy matching |
|
|
12
|
+
| `{n,m}` | Supported | Keep bounds explicit |
|
|
13
|
+
| Lookahead / lookbehind | Unsupported | `followed_by:` or a state/action |
|
|
14
|
+
| Backreferences | Unsupported | Split the rule or validate in an action |
|
|
15
|
+
| Open repetition `{n,}` | Unsupported | Use a separate rule/action |
|
|
16
|
+
| Capturing groups | Accepted with a diagnostic | Use `(?:...)` for intent |
|
|
17
|
+
|
|
18
|
+
Run `flexr check` before relying on a construct. The [regexp reference](https://github.com/ydah/flexr/blob/main/docs/reference/regexp.md) records the implementation-level boundaries.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Runtime vs generated
|
|
3
|
+
description: Choose between interpreting a specification and deploying generated Ruby.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Runtime and generated mode are two delivery paths for the same lexer design.
|
|
7
|
+
|
|
8
|
+
| Question | Runtime | Generated |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| Fastest edit/run loop? | Yes | No regeneration step |
|
|
11
|
+
| Smallest deployed dependency? | No | Standalone: yes |
|
|
12
|
+
| Easy to inspect output? | Specification and runtime | Ruby artifact |
|
|
13
|
+
| Recommended parity check? | Source of truth | Compare token stream |
|
|
14
|
+
|
|
15
|
+
Use the runtime for development and tests, then generate and compare before deployment. Do not assume a generated file is safe merely because it is generated: actions remain Ruby code.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Security model
|
|
3
|
+
description: Understand the code-execution boundary around flexr specifications and generated lexers.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
flexr specifications are Ruby programs. Rule actions, `--eval`, and generated actions can execute arbitrary Ruby with the permissions of the process that runs them.
|
|
7
|
+
|
|
8
|
+
## Safe operating assumptions
|
|
9
|
+
|
|
10
|
+
- Do not run an untrusted `.flexr.rb` file.
|
|
11
|
+
- Treat generated Ruby as executable source and review it before deployment.
|
|
12
|
+
- Use `flexr check` for diagnostics, not as a sandbox.
|
|
13
|
+
- Keep build-time inputs and generator versions pinned in CI.
|
|
14
|
+
|
|
15
|
+
The hosted playground currently uses fixed fixtures and does not execute arbitrary Ruby. A future WASM worker must keep that boundary explicit, enforce resource limits, and never imply that parsing source is equivalent to sandboxing actions.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Examples
|
|
3
|
+
description: Start from executable examples for calculators, JSON, toy languages, and parser adapters.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The repository examples are the executable source of truth for this site:
|
|
7
|
+
|
|
8
|
+
| Example | Demonstrates |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| [Calculator](https://github.com/ydah/flexr/tree/main/examples/calculator) | Integer values, operators, and a parser-ready token stream |
|
|
11
|
+
| [JSON](https://github.com/ydah/flexr/tree/main/examples/json) | Structured tokens and semantic value conversion |
|
|
12
|
+
| [Toy language](https://github.com/ydah/flexr/tree/main/examples/toy_lang) | Ruby constants and regexp interpolation |
|
|
13
|
+
| [Ruby subset](https://github.com/ydah/flexr/tree/main/examples/ruby_subset) | Source transformation and ordinary Ruby code |
|
|
14
|
+
| [Racc](https://github.com/ydah/flexr/tree/main/examples/with_racc) | Parser protocol integration |
|
|
15
|
+
| [Lrama](https://github.com/ydah/flexr/tree/main/examples/with_lrama) | A Lrama-compatible adapter |
|
|
16
|
+
|
|
17
|
+
Each example can be checked, run in runtime mode, and generated. Start with the [calculator tutorial](/flexr/learn/getting-started/) before exploring parser integration.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Generation
|
|
3
|
+
description: Generate deterministic Ruby from the same specification used at runtime.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Generation turns a `.flexr.rb` specification into Ruby source. The generated lexer keeps rule actions as Ruby code, so review the specification and generated artifact as executable code.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
bundle exec flexr check lexer.flexr.rb
|
|
10
|
+
bundle exec flexr generate lexer.flexr.rb -o lexer.rb
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The generator can use `table`, `direct`, or `auto` backends. `firstmatch` is experimental and requires an explicit option because it does not preserve the usual longest-match contract.
|
|
14
|
+
|
|
15
|
+
## Runtime dependencies
|
|
16
|
+
|
|
17
|
+
| Mode | Build-time dependency | Runtime dependency |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| Runtime | `flexr` | `flexr` |
|
|
20
|
+
| Generated | generator and `flexr` | usually `flexr` |
|
|
21
|
+
| Standalone generated | generator and `flexr` | generated file only |
|
|
22
|
+
|
|
23
|
+
Use standalone output only when the generated file is verified and the deployment boundary requires no runtime gem. Confirm the generated file does not load the runtime before publishing it.
|
|
24
|
+
|
|
25
|
+
## Reproducible artifacts
|
|
26
|
+
|
|
27
|
+
Treat the generator version, Ruby version, backend, encoding, and Unicode data snapshot as part of the artifact contract. If generated Ruby is committed, regenerate it in CI and fail on a diff.
|
|
28
|
+
|
|
29
|
+
See [runtime vs generated](/flexr/concepts/runtime-vs-generated/) and the repository's [generated artifact reference](https://github.com/ydah/flexr/blob/main/docs/reference/generated-artifacts.md).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting started
|
|
3
|
+
description: Build and run your first flexr lexer, then compare runtime and generated output.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
flexr is a Ruby-native lexer generator. You write rules in a Ruby class, run the specification directly during development, and generate Ruby when deployment benefits from a deterministic artifact.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
gem install flexr
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Write a specification
|
|
15
|
+
|
|
16
|
+
Create `lexer.flexr.rb`:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
class Lexer < Flexr::Lexer
|
|
20
|
+
emits :INTEGER, :PLUS, :EQ, :ASSIGN, :IF, :IDENT
|
|
21
|
+
|
|
22
|
+
rule(/[ \t\n]+/, skip: true)
|
|
23
|
+
rule(/[0-9]+/) { emit :INTEGER, text.to_i }
|
|
24
|
+
rule(/==/) { emit :EQ }
|
|
25
|
+
rule(/=/) { emit :ASSIGN }
|
|
26
|
+
rule(/if/) { emit :IF }
|
|
27
|
+
rule(/[a-z_][a-z0-9_]*/) { emit :IDENT }
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`==` wins over `=` because it consumes more input. `if` wins over the identifier rule for the same reason that a rule defined first wins when match lengths are equal.
|
|
32
|
+
|
|
33
|
+
## Run it
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
require 'flexr'
|
|
37
|
+
require_relative 'lexer.flexr'
|
|
38
|
+
|
|
39
|
+
lexer = Lexer.new('if total == 42')
|
|
40
|
+
p lexer.each_token.to_a
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Validate the specification before handing tokens to a parser:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
bundle exec flexr check lexer.flexr.rb
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Generate Ruby
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
bundle exec flexr generate lexer.flexr.rb -o lexer.rb
|
|
53
|
+
ruby -I. -e "require './lexer'; p Lexer.new('if total == 42').each_token.to_a"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The [runtime guide](/flexr/learn/runtime-mode/) and [generation guide](/flexr/learn/generation/) cover the deployment trade-off. The repository contains a complete [calculator tutorial](https://github.com/ydah/flexr/blob/main/docs/tutorial/build-a-calculator-lexer.md).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Parser integration
|
|
3
|
+
description: Return flexr tokens to Racc, Lrama, or a custom parser loop.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The lexer/parser boundary should be explicit: the lexer owns characters and locations; the parser owns grammar and recovery.
|
|
7
|
+
|
|
8
|
+
## Racc protocol
|
|
9
|
+
|
|
10
|
+
For a Racc parser, expose `racc_next_token` as a pair of token kind and semantic value:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
def racc_next_token
|
|
14
|
+
token = @lexer.next_token
|
|
15
|
+
return [false, false] if token.nil?
|
|
16
|
+
|
|
17
|
+
[token.type, token.value]
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Declare the token names in both the lexer and parser grammar. Keep EOF behavior in one adapter rather than duplicating it in every action.
|
|
22
|
+
|
|
23
|
+
## Lrama protocol
|
|
24
|
+
|
|
25
|
+
Lrama integrations use the same basic token/value contract. The example in [`examples/with_lrama`](https://github.com/ydah/flexr/tree/main/examples/with_lrama) shows the complete wiring.
|
|
26
|
+
|
|
27
|
+
`emits` is useful for diagnostics and parser integration, but it does not replace the parser grammar's token declarations. See [tokens and locations](/flexr/reference/tokens-and-locations/) for return shapes.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Runtime mode
|
|
3
|
+
description: Use a lexer specification directly with the flexr runtime.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Runtime mode loads the Ruby specification and interprets its compiled automaton. It is the shortest feedback loop for tests and application development.
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
require 'flexr'
|
|
10
|
+
|
|
11
|
+
class Lexer < Flexr::Lexer
|
|
12
|
+
token_kind :struct
|
|
13
|
+
rule(/[ \t\n]+/, skip: true)
|
|
14
|
+
rule(/[0-9]+/) { emit :INTEGER, text.to_i }
|
|
15
|
+
rule(/\+/) { emit :PLUS }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
lexer = Lexer.new('12 + 3')
|
|
19
|
+
lexer.each_token { |token| p token }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use runtime mode when the specification is the source of truth and startup compilation is acceptable. It also makes it easy to exercise actions and location tracking in unit tests.
|
|
23
|
+
|
|
24
|
+
### Input and iteration
|
|
25
|
+
|
|
26
|
+
`next_token` returns one token at a time. `each_token` iterates until EOF. The exact return shape depends on `token_kind`; see [tokens and locations](/flexr/reference/tokens-and-locations/).
|
|
27
|
+
|
|
28
|
+
### Validate early
|
|
29
|
+
|
|
30
|
+
Run `flexr check SPEC` in CI. It can report unsupported regexp constructs, unreachable rules, empty matches, undeclared emitted tokens, and automaton-size warnings before runtime.
|
|
31
|
+
|
|
32
|
+
For the complete API contract, see the repository's [runtime reference](https://github.com/ydah/flexr/blob/main/docs/reference/runtime.md).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Action context
|
|
3
|
+
description: Values and methods available inside a rule action.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Actions run in the lexer context. Common methods include:
|
|
7
|
+
|
|
8
|
+
| API | Use |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| `text` / `text_bytesize` | Matched text and its byte length |
|
|
11
|
+
| `emit(type, value = text)` | Return a token |
|
|
12
|
+
| `skip` | Consume input without emitting a token |
|
|
13
|
+
| `echo` | Write the matched text to the configured output |
|
|
14
|
+
| `lineno`, `line`, `byte_pos` | Current position information |
|
|
15
|
+
| `beginning_of_line?` | Check the start-of-line condition |
|
|
16
|
+
| `state`, `push`, `pop`, `begin_state` | Manage lexer states |
|
|
17
|
+
| `less(n)` / `more` | Adjust or extend the current match |
|
|
18
|
+
| `error!(message)` | Raise a lexer error with location context |
|
|
19
|
+
|
|
20
|
+
Prefer small, deterministic actions. If an action needs parser state or external services, keep that dependency in an adapter around the lexer.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: CLI reference
|
|
3
|
+
description: Validate, inspect, and generate lexers from the command line.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The main commands are:
|
|
7
|
+
|
|
8
|
+
| Command | Purpose |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| `flexr check SPEC` | Parse and diagnose a specification |
|
|
11
|
+
| `flexr generate SPEC -o OUTPUT` | Write generated Ruby |
|
|
12
|
+
| `flexr trace SPEC INPUT` | Show automaton state, acceptance, and transitions |
|
|
13
|
+
| `flexr --help` | Print command and option help |
|
|
14
|
+
|
|
15
|
+
Use `check` in CI and treat warnings according to the compatibility policy of your project. `trace` describes DFA behavior; it is not an input-string logging command.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
bundle exec flexr check examples/calculator/lexer.flexr.rb
|
|
19
|
+
bundle exec flexr generate examples/calculator/lexer.flexr.rb -o tmp/calculator_lexer.rb
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For complete options, output formats, and exit status behavior, see the repository's [CLI reference](https://github.com/ydah/flexr/blob/main/docs/reference/cli.md).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Diagnostics
|
|
3
|
+
description: Find the reason a specification is rejected or warned about.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Diagnostics are part of the authoring experience, not just compiler failures. `flexr check` can report:
|
|
7
|
+
|
|
8
|
+
- unreachable rules and states without rules;
|
|
9
|
+
- empty-string matches;
|
|
10
|
+
- unsupported regexp constructs;
|
|
11
|
+
- undeclared emitted token kinds;
|
|
12
|
+
- variable-length trailing context;
|
|
13
|
+
- acceleration incompatibility;
|
|
14
|
+
- large transition tables and compile-time risks.
|
|
15
|
+
|
|
16
|
+
Use the code in the message to search the repository's [diagnostics catalog](https://github.com/ydah/flexr/blob/main/docs/reference/diagnostics.md). Each entry explains why it happens, how to fix it, and when ignoring it is reasonable.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: DSL reference
|
|
3
|
+
description: The stable lexer specification methods and their role.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
| API | Purpose |
|
|
7
|
+
| --- | --- |
|
|
8
|
+
| `rule(pattern, skip: false, emit: nil, followed_by: nil)` | Register a matching rule and action |
|
|
9
|
+
| `on_eof` | Define end-of-input behavior |
|
|
10
|
+
| `emits(*kinds)` | Declare token kinds for diagnostics and parser integration |
|
|
11
|
+
| `state(name)` | Define a named start condition |
|
|
12
|
+
| `all_states` | Apply a rule to every state |
|
|
13
|
+
| `backend(name)` | Choose `table`, `direct`, or `auto`; `firstmatch` is experimental |
|
|
14
|
+
| `token_kind(name)` | Choose the token return shape |
|
|
15
|
+
| `encoding(name)` | Select the input encoding contract |
|
|
16
|
+
| `option(name)` | Enable an explicit compiler option |
|
|
17
|
+
| `accel(name)` | Configure acceleration where compatible |
|
|
18
|
+
|
|
19
|
+
Rules are evaluated using [longest-match semantics](/flexr/concepts/matching-semantics/). The repository's [DSL reference](https://github.com/ydah/flexr/blob/main/docs/reference/dsl.md) includes option defaults and examples.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Public API and stability
|
|
3
|
+
description: Separate stable user APIs from experimental and internal implementation details.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Public and stable
|
|
7
|
+
|
|
8
|
+
`Flexr::Lexer`, the lexer DSL, runtime token/location structures, the documented CLI, and `Flexr::RakeTask` are the supported entry points for normal use.
|
|
9
|
+
|
|
10
|
+
## Experimental
|
|
11
|
+
|
|
12
|
+
The `firstmatch` backend and any option explicitly marked experimental may change behavior or API shape between releases. Opt in deliberately and test the resulting token stream.
|
|
13
|
+
|
|
14
|
+
## Internal
|
|
15
|
+
|
|
16
|
+
`Flexr::IR`, `Flexr::Automaton`, `Flexr::Codegen`, parser internals, and generated implementation helpers are internal. Compatibility is not guaranteed for these namespaces.
|
|
17
|
+
|
|
18
|
+
Version-specific guarantees live in the [compatibility reference](https://github.com/ydah/flexr/blob/main/docs/reference/compatibility.md) and the release commits.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Regexp compatibility
|
|
3
|
+
description: A practical compatibility guide for Ruby regexp constructs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The compiler accepts the regular-expression subset that can be represented by its automaton. Supported constructs include literals, concatenation, alternation, greedy repetition, bounded repetition, character classes, anchors in supported positions, and supported Unicode properties.
|
|
7
|
+
|
|
8
|
+
Lookaround, backreferences, and open-ended repetition are not DFA-compatible. Capturing groups are accepted with a diagnostic and do not provide capture values to actions.
|
|
9
|
+
|
|
10
|
+
When in doubt, run:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
bundle exec flexr check lexer.flexr.rb
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The repository's [regexp matrix](https://github.com/ydah/flexr/blob/main/docs/reference/regexp.md) lists diagnostic codes, limits, inline-option behavior, and alternatives.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Runtime reference
|
|
3
|
+
description: The runtime entry points and operational behavior.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Create a lexer with an input string or IO-like source, then call `next_token` or `each_token`.
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
lexer = Lexer.new(source)
|
|
10
|
+
token = lexer.next_token
|
|
11
|
+
lexer.each_token { |type, value| consume(type, value) }
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The runtime tracks byte position, line, column, and the last token location. Buffering and encoding behavior follow the options declared by the lexer. EOF is represented by the runtime's end-of-input result and should be normalized by a parser adapter.
|
|
15
|
+
|
|
16
|
+
See the repository's [runtime reference](https://github.com/ydah/flexr/blob/main/docs/reference/runtime.md) for constructor options, `less`, `more`, `echo`, state transitions, and error hooks.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tokens and locations
|
|
3
|
+
description: Return shapes, semantic values, and source positions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`token_kind` controls how the lexer exposes a token:
|
|
7
|
+
|
|
8
|
+
| `token_kind` | `next_token` | `each_token` |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| `:array` | `[type, value]` | Yields one array |
|
|
11
|
+
| `:struct` | `Flexr::Runtime::Token` | Yields a token |
|
|
12
|
+
| `:yield` | Adapter-defined | Yields `type, value` |
|
|
13
|
+
|
|
14
|
+
Locations use byte offsets for the input position and track line/column as the lexer consumes text. Keep the location with the token when a parser needs useful syntax errors.
|
|
15
|
+
|
|
16
|
+
For exact defaults and the `Location` data structure, use the repository's [tokens and locations reference](https://github.com/ydah/flexr/blob/main/docs/reference/tokens-and-locations.md).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineCollection } from 'astro:content';
|
|
2
|
+
import { docsLoader } from '@astrojs/starlight/loaders';
|
|
3
|
+
import { docsSchema } from '@astrojs/starlight/schema';
|
|
4
|
+
|
|
5
|
+
export const collections = {
|
|
6
|
+
docs: defineCollection({
|
|
7
|
+
loader: docsLoader({
|
|
8
|
+
generateId: ({ entry }) => entry.replace(/\.[^/.]+$/, '')
|
|
9
|
+
}),
|
|
10
|
+
schema: docsSchema()
|
|
11
|
+
})
|
|
12
|
+
};
|
data/site/src/env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="astro/client" />
|