parseman 0.45.0 → 0.47.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.
- package/README.md +8 -3
- package/dist/analysis/choice-cost-gate.d.ts +158 -0
- package/dist/analysis/choice-cost-gate.d.ts.map +1 -0
- package/dist/analysis/choice-cost-render.d.ts +96 -0
- package/dist/analysis/choice-cost-render.d.ts.map +1 -0
- package/dist/analysis/choice-cost.d.ts +383 -0
- package/dist/analysis/choice-cost.d.ts.map +1 -0
- package/dist/analysis/commitment.d.ts +131 -0
- package/dist/analysis/commitment.d.ts.map +1 -0
- package/dist/analysis/corpus.d.ts +73 -0
- package/dist/analysis/corpus.d.ts.map +1 -0
- package/dist/analysis/diagnose-render.d.ts +65 -0
- package/dist/analysis/diagnose-render.d.ts.map +1 -0
- package/dist/analysis/diagnose.d.ts +15 -0
- package/dist/analysis/diagnose.d.ts.map +1 -1
- package/dist/analysis/diagnostics.cjs +13736 -0
- package/dist/analysis/diagnostics.cjs.map +6 -0
- package/dist/analysis/diagnostics.d.ts +27 -0
- package/dist/analysis/diagnostics.d.ts.map +1 -0
- package/dist/analysis/diagnostics.js +13713 -0
- package/dist/analysis/diagnostics.js.map +6 -0
- package/dist/analysis/duplication.d.ts.map +1 -1
- package/dist/analysis/fix-render.d.ts +43 -0
- package/dist/analysis/fix-render.d.ts.map +1 -0
- package/dist/analysis/fix.d.ts +172 -0
- package/dist/analysis/fix.d.ts.map +1 -0
- package/dist/analysis/format-number.d.ts +23 -0
- package/dist/analysis/format-number.d.ts.map +1 -0
- package/dist/analysis/gating.d.ts +47 -2
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/rebuild.d.ts +61 -0
- package/dist/analysis/rebuild.d.ts.map +1 -0
- package/dist/analysis/terminal.d.ts +145 -0
- package/dist/analysis/terminal.d.ts.map +1 -0
- package/dist/cli/index.cjs +14837 -0
- package/dist/cli/index.cjs.map +6 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +14814 -0
- package/dist/cli/index.js.map +6 -0
- package/dist/combinators/adjacency.d.ts +100 -0
- package/dist/combinators/adjacency.d.ts.map +1 -0
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/completions.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +8 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/gate.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts +22 -0
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts +8 -0
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/map.d.ts +0 -1
- package/dist/combinators/map.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +13 -3
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +1 -1
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/combinators/ref.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts +1 -16
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/repeat.d.ts +30 -2
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/scanTo.d.ts +71 -1
- package/dist/combinators/scanTo.d.ts.map +1 -1
- package/dist/combinators/sequence.d.ts.map +1 -1
- package/dist/combinators/token.d.ts.map +1 -1
- package/dist/combinators/trivia-boundary.d.ts +26 -0
- package/dist/combinators/trivia-boundary.d.ts.map +1 -0
- package/dist/combinators/trivia-skip.d.ts +40 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/combinators/withCtx.d.ts.map +1 -1
- package/dist/compiler/build-arity.d.ts.map +1 -1
- package/dist/compiler/compile-linkable-table.d.ts +104 -0
- package/dist/compiler/compile-linkable-table.d.ts.map +1 -0
- package/dist/compiler/degradation.d.ts +0 -2
- package/dist/compiler/degradation.d.ts.map +1 -1
- package/dist/compiler/fields.d.ts +13 -0
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts +5 -5
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +79 -47
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +81 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -0
- package/dist/compiler/token-dispatch.d.ts +95 -0
- package/dist/compiler/token-dispatch.d.ts.map +1 -0
- package/dist/compiler/token-scanner.d.ts +53 -0
- package/dist/compiler/token-scanner.d.ts.map +1 -0
- package/dist/compiler/value-usage.d.ts.map +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/capture-buffer.d.ts +35 -0
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/cst/reflection-attach.d.ts +8 -0
- package/dist/cst/reflection-attach.d.ts.map +1 -0
- package/dist/cst/reflection-symbols.d.ts +10 -0
- package/dist/cst/reflection-symbols.d.ts.map +1 -0
- package/dist/cst/reflection.d.ts +2 -4
- package/dist/cst/reflection.d.ts.map +1 -1
- package/dist/cst/root-trivia-scope.d.ts +26 -0
- package/dist/cst/root-trivia-scope.d.ts.map +1 -0
- package/dist/cst/trivia-charscan.d.ts +18 -0
- package/dist/cst/trivia-charscan.d.ts.map +1 -0
- package/dist/cst/trivia-kinds.d.ts +19 -0
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/functional/doc.d.ts.map +1 -1
- package/dist/functional/run-tabled.d.ts +12 -0
- package/dist/functional/run-tabled.d.ts.map +1 -0
- package/dist/functional/run.d.ts +25 -42
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/grammar-metadata.d.ts +3 -0
- package/dist/grammar-metadata.d.ts.map +1 -0
- package/dist/index.cjs +10499 -7478
- package/dist/index.cjs.map +3 -4
- package/dist/index.d.ts +24 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10499 -7478
- package/dist/index.js.map +3 -4
- package/dist/language-service/index.cjs +311 -121
- package/dist/language-service/index.cjs.map +3 -4
- package/dist/language-service/index.js +311 -121
- package/dist/language-service/index.js.map +3 -4
- package/dist/oracle/index.cjs.map +0 -1
- package/dist/oracle/index.js.map +0 -1
- package/dist/parse-context.d.ts +49 -0
- package/dist/parse-context.d.ts.map +1 -0
- package/dist/plugin/evaluator.d.ts +41 -2
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/free-identifiers.d.ts +23 -0
- package/dist/plugin/free-identifiers.d.ts.map +1 -0
- package/dist/plugin/index.cjs +11381 -9442
- package/dist/plugin/index.cjs.map +3 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +11381 -9442
- package/dist/plugin/index.js.map +3 -4
- package/dist/plugin/reducer-resolver.d.ts +17 -1
- package/dist/plugin/reducer-resolver.d.ts.map +1 -1
- package/dist/regex/classes.d.ts +28 -0
- package/dist/regex/classes.d.ts.map +1 -1
- package/dist/regex/first-set.d.ts +14 -0
- package/dist/regex/first-set.d.ts.map +1 -1
- package/dist/run/index.cjs +136 -51
- package/dist/run/index.cjs.map +2 -3
- package/dist/run/index.js +136 -51
- package/dist/run/index.js.map +2 -3
- package/dist/spec/index.cjs +2898 -1622
- package/dist/spec/index.cjs.map +3 -4
- package/dist/spec/index.js +2898 -1622
- package/dist/spec/index.js.map +3 -4
- package/dist/spec/model.d.ts +8 -0
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/table/assemble.d.ts +307 -0
- package/dist/table/assemble.d.ts.map +1 -0
- package/dist/table/cell.d.ts +42 -0
- package/dist/table/cell.d.ts.map +1 -0
- package/dist/table/child-slots.d.ts +30 -0
- package/dist/table/child-slots.d.ts.map +1 -0
- package/dist/table/compile-rule-map.d.ts +136 -0
- package/dist/table/compile-rule-map.d.ts.map +1 -0
- package/dist/table/compile.d.ts +56 -0
- package/dist/table/compile.d.ts.map +1 -0
- package/dist/table/duplication-hook.d.ts +32 -0
- package/dist/table/duplication-hook.d.ts.map +1 -0
- package/dist/table/emit-assembly.d.ts +139 -0
- package/dist/table/emit-assembly.d.ts.map +1 -0
- package/dist/table/emit.d.ts +113 -0
- package/dist/table/emit.d.ts.map +1 -0
- package/dist/table/encode-baseline.d.ts +16 -0
- package/dist/table/encode-baseline.d.ts.map +1 -0
- package/dist/table/encode.d.ts +71 -0
- package/dist/table/encode.d.ts.map +1 -0
- package/dist/table/exec-baseline.d.ts +9 -0
- package/dist/table/exec-baseline.d.ts.map +1 -0
- package/dist/table/exec.d.ts +72 -0
- package/dist/table/exec.d.ts.map +1 -0
- package/dist/table/fold.d.ts +25 -0
- package/dist/table/fold.d.ts.map +1 -0
- package/dist/table/index.cjs +11375 -0
- package/dist/table/index.cjs.map +6 -0
- package/dist/table/index.d.ts +100 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/table/index.js +11352 -0
- package/dist/table/index.js.map +6 -0
- package/dist/table/inspect.d.ts +17 -0
- package/dist/table/inspect.d.ts.map +1 -0
- package/dist/table/ops.d.ts +446 -0
- package/dist/table/ops.d.ts.map +1 -0
- package/dist/table/program.d.ts +493 -0
- package/dist/table/program.d.ts.map +1 -0
- package/dist/table/run-support.d.ts +38 -0
- package/dist/table/run-support.d.ts.map +1 -0
- package/dist/{compiler/scannable-run.d.ts → table/scan-shapes.d.ts} +14 -13
- package/dist/table/scan-shapes.d.ts.map +1 -0
- package/dist/table/site-labels.d.ts +58 -0
- package/dist/table/site-labels.d.ts.map +1 -0
- package/dist/table/stamp.d.ts +70 -0
- package/dist/table/stamp.d.ts.map +1 -0
- package/dist/types.d.ts +80 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +41 -3
- package/src/analysis/choice-cost-gate.ts +359 -0
- package/src/analysis/choice-cost-render.ts +293 -0
- package/src/analysis/choice-cost.ts +1008 -0
- package/src/analysis/commitment.ts +557 -0
- package/src/analysis/corpus.ts +130 -0
- package/src/analysis/diagnose-render.ts +625 -0
- package/src/analysis/diagnose.ts +317 -0
- package/src/analysis/diagnostics.ts +33 -0
- package/src/analysis/duplication.ts +1955 -0
- package/src/analysis/fix-render.ts +217 -0
- package/src/analysis/fix.ts +669 -0
- package/src/analysis/format-number.ts +31 -0
- package/src/analysis/gating.ts +792 -0
- package/src/analysis/grammar.ts +72 -0
- package/src/analysis/rebuild.ts +287 -0
- package/src/analysis/terminal.ts +336 -0
- package/src/cli/index.ts +413 -0
- package/src/combinators/adjacency.ts +171 -0
- package/src/combinators/attempt.ts +41 -0
- package/src/combinators/case-fold.ts +90 -0
- package/src/combinators/choice.ts +466 -0
- package/src/combinators/completions.ts +64 -0
- package/src/combinators/dispatch.ts +358 -0
- package/src/combinators/expect.ts +159 -0
- package/src/combinators/first-set.ts +322 -0
- package/src/combinators/gate.ts +50 -0
- package/src/combinators/grammar.ts +272 -0
- package/src/combinators/keywords.ts +145 -0
- package/src/combinators/literal.ts +102 -0
- package/src/combinators/map.ts +106 -0
- package/src/combinators/node.ts +345 -0
- package/src/combinators/not.ts +55 -0
- package/src/combinators/parser.ts +266 -0
- package/src/combinators/peek.ts +63 -0
- package/src/combinators/probe.ts +25 -0
- package/src/combinators/ref.ts +109 -0
- package/src/combinators/regex.ts +170 -0
- package/src/combinators/repeat.ts +561 -0
- package/src/combinators/scanTo.ts +393 -0
- package/src/combinators/sequence.ts +221 -0
- package/src/combinators/token.ts +120 -0
- package/src/combinators/trivia-boundary.ts +48 -0
- package/src/combinators/trivia-skip.ts +821 -0
- package/src/combinators/withCtx.ts +45 -0
- package/src/compiler/build-arity.ts +330 -0
- package/src/compiler/compile-linkable-table.ts +209 -0
- package/src/compiler/degradation.ts +266 -0
- package/src/compiler/fields.ts +191 -0
- package/src/compiler/grammar-coverage-ids.ts +129 -0
- package/src/compiler/ir-serialize.ts +615 -0
- package/src/compiler/linker.ts +954 -0
- package/src/compiler/token-alphabet.ts +209 -0
- package/src/compiler/token-dispatch.ts +353 -0
- package/src/compiler/token-scanner.ts +318 -0
- package/src/compiler/value-usage.ts +147 -0
- package/src/coverage.ts +383 -0
- package/src/cst/capture-buffer.ts +297 -0
- package/src/cst/host-mode.ts +69 -0
- package/src/cst/reflection-attach.ts +11 -0
- package/src/cst/reflection-symbols.ts +9 -0
- package/src/cst/reflection.ts +149 -0
- package/src/cst/relative-spans.ts +207 -0
- package/src/cst/root-trivia-scope.ts +30 -0
- package/src/cst/trivia-charscan.ts +312 -0
- package/src/cst/trivia-entries.ts +285 -0
- package/src/cst/trivia-index.ts +131 -0
- package/src/cst/trivia-kinds.ts +358 -0
- package/src/cst/types.ts +76 -0
- package/src/cst/walk.ts +101 -0
- package/src/functional/doc.ts +959 -0
- package/src/functional/run-tabled.ts +153 -0
- package/src/functional/run.ts +558 -0
- package/src/grammar-metadata.ts +2 -0
- package/src/index.ts +189 -0
- package/src/language-service/index.ts +221 -0
- package/src/line-index.ts +131 -0
- package/src/oracle/digest.ts +620 -0
- package/src/oracle/index.ts +55 -0
- package/src/parse-context.ts +91 -0
- package/src/plugin/direct-builder-static.ts +102 -0
- package/src/plugin/evaluator.ts +1505 -0
- package/src/plugin/free-identifiers.ts +338 -0
- package/src/plugin/index.ts +2281 -0
- package/src/plugin/reducer-resolver.ts +606 -0
- package/src/recovery/scan.ts +155 -0
- package/src/regex/classes.ts +183 -0
- package/src/regex/first-set.ts +316 -0
- package/src/run/index.ts +31 -0
- package/src/spec/ebnf.ts +128 -0
- package/src/spec/index.ts +54 -0
- package/src/spec/model.ts +636 -0
- package/src/spec/railroad-lib.ts +559 -0
- package/src/spec/railroad.ts +256 -0
- package/src/table/assemble.ts +2994 -0
- package/src/table/cell.ts +42 -0
- package/src/table/child-slots.ts +113 -0
- package/src/table/compile-rule-map.ts +336 -0
- package/src/table/compile.ts +244 -0
- package/src/table/duplication-hook.ts +89 -0
- package/src/table/emit-assembly.ts +1766 -0
- package/src/table/emit.ts +418 -0
- package/src/table/encode-baseline.ts +340 -0
- package/src/table/encode.ts +1569 -0
- package/src/table/exec-baseline.ts +537 -0
- package/src/table/exec.ts +1672 -0
- package/src/table/fold.ts +38 -0
- package/src/table/index.ts +108 -0
- package/src/table/inspect.ts +52 -0
- package/src/table/ops.ts +459 -0
- package/src/table/program.ts +855 -0
- package/src/table/run-support.ts +66 -0
- package/src/table/scan-shapes.ts +1578 -0
- package/src/table/site-labels.ts +228 -0
- package/src/table/stamp.ts +229 -0
- package/src/types.ts +563 -0
- package/src/version.ts +16 -0
- package/dist/compiler/codegen.d.ts +0 -334
- package/dist/compiler/codegen.d.ts.map +0 -1
- package/dist/compiler/inline-build.d.ts +0 -11
- package/dist/compiler/inline-build.d.ts.map +0 -1
- package/dist/compiler/inline-callback.d.ts +0 -21
- package/dist/compiler/inline-callback.d.ts.map +0 -1
- package/dist/compiler/line-index.d.ts +0 -2
- package/dist/compiler/line-index.d.ts.map +0 -1
- package/dist/compiler/scannable-run.d.ts.map +0 -1
- package/dist/compiler/scannable-terminal.d.ts +0 -23
- package/dist/compiler/scannable-terminal.d.ts.map +0 -1
- package/dist/compiler/trivia-fast-path.d.ts +0 -38
- package/dist/compiler/trivia-fast-path.d.ts.map +0 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { Combinator, ParseContext, ParseResult, ParserMeta, FirstSet } from '../types.ts'
|
|
2
|
+
import { fromChar, union, empty } from './first-set.ts'
|
|
3
|
+
import { caseFoldVariants } from './case-fold.ts'
|
|
4
|
+
import { failAt } from './probe.ts'
|
|
5
|
+
import { pushCstLeaf, cstCaptureActive } from '../cst/capture-buffer.ts'
|
|
6
|
+
|
|
7
|
+
export type KeywordsOptions = {
|
|
8
|
+
/** Match case-insensitively. */
|
|
9
|
+
caseInsensitive?: boolean
|
|
10
|
+
/**
|
|
11
|
+
* Character class (regex source, e.g. `'A-Za-z0-9_-'`) that must NOT follow a
|
|
12
|
+
* match — a word boundary. Prevents matching `red` inside `redish`.
|
|
13
|
+
*/
|
|
14
|
+
boundary?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type WordOptions = Omit<KeywordsOptions, 'boundary'>
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Match a single keyword with an automatic word-boundary guard. Prevents
|
|
21
|
+
* matching `true` inside `trueish`. The boundary defaults to `_0-9A-Za-z`,
|
|
22
|
+
* which covers most programming-language identifiers.
|
|
23
|
+
*
|
|
24
|
+
* word('true') // matches "true" but not "trueish"
|
|
25
|
+
* word('color', 'A-Za-z-') // CSS-style identifier boundary
|
|
26
|
+
* word('media', 'A-Za-z0-9_-', { caseInsensitive: true })
|
|
27
|
+
* word('media', { caseInsensitive: true }) // default boundary + options
|
|
28
|
+
*
|
|
29
|
+
* `caseInsensitive` matches `keywords()` and exists because it is REQUIRED, not a
|
|
30
|
+
* nicety: CSS at-keywords, function names and units are ASCII case-insensitive per
|
|
31
|
+
* spec (CSS Syntax §3). Without it the only conforming spellings were
|
|
32
|
+
* `regex(/media/i)` — which `analyzeGating` correctly flags as the `keyword-regex`
|
|
33
|
+
* anti-pattern — or `keywords(['media'], …)` for a single word.
|
|
34
|
+
*/
|
|
35
|
+
export function word(str: string, boundary?: string, opts?: WordOptions): Combinator<string>
|
|
36
|
+
export function word(str: string, opts: WordOptions): Combinator<string>
|
|
37
|
+
export function word(
|
|
38
|
+
str: string,
|
|
39
|
+
boundaryOrOpts?: string | WordOptions,
|
|
40
|
+
opts?: WordOptions,
|
|
41
|
+
): Combinator<string> {
|
|
42
|
+
const boundary = typeof boundaryOrOpts === 'string' ? boundaryOrOpts : '_0-9A-Za-z'
|
|
43
|
+
const rest = typeof boundaryOrOpts === 'object' && boundaryOrOpts !== null ? boundaryOrOpts : opts
|
|
44
|
+
return keywords([str], { ...rest, boundary })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a keyword factory with a fixed word-boundary class. Use when many
|
|
49
|
+
* keywords share the same boundary (e.g. CSS identifiers). For a single keyword,
|
|
50
|
+
* `word(str, boundary?)` is enough.
|
|
51
|
+
*
|
|
52
|
+
* const kw = makeWord() // default boundary
|
|
53
|
+
* const cssKw = makeWord('A-Za-z0-9_-', { caseInsensitive: true })
|
|
54
|
+
*
|
|
55
|
+
* const query = kw('query')
|
|
56
|
+
* const color = cssKw('color') // matches "COLOR" but not "color-scheme"
|
|
57
|
+
*/
|
|
58
|
+
export function makeWord(boundary?: string, opts?: WordOptions): (str: string) => Combinator<string>
|
|
59
|
+
export function makeWord(opts: WordOptions): (str: string) => Combinator<string>
|
|
60
|
+
export function makeWord(
|
|
61
|
+
boundaryOrOpts: string | WordOptions = '_0-9A-Za-z',
|
|
62
|
+
opts?: WordOptions,
|
|
63
|
+
): (str: string) => Combinator<string> {
|
|
64
|
+
const boundary = typeof boundaryOrOpts === 'string' ? boundaryOrOpts : '_0-9A-Za-z'
|
|
65
|
+
const rest = typeof boundaryOrOpts === 'object' && boundaryOrOpts !== null ? boundaryOrOpts : opts
|
|
66
|
+
return (str: string) => keywords([str], { ...rest, boundary })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function escapeRe(s: string): string {
|
|
70
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Match any one of a set of fixed keywords, longest-first (so `border` wins over
|
|
75
|
+
* `bord`), with an optional trailing word-boundary guard. Compiles to a single
|
|
76
|
+
* sticky regex — far less error-prone than a hand-maintained alternation, and
|
|
77
|
+
* the common case (keyword sets: CSS colors, units, at-rule names, HTML tags).
|
|
78
|
+
*
|
|
79
|
+
* keywords(CSS_COLOR_NAMES, { caseInsensitive: true, boundary: 'A-Za-z0-9_-' })
|
|
80
|
+
*
|
|
81
|
+
* On success the matched text is recorded as a CSTLeaf (like literal()/regex()).
|
|
82
|
+
*/
|
|
83
|
+
export function keywords(words: readonly string[], opts: KeywordsOptions = {}): Combinator<string> {
|
|
84
|
+
// Longest-first keeps the alternation greedy-correct (regex alternation is
|
|
85
|
+
// first-match, not longest-match).
|
|
86
|
+
const sorted = [...new Set(words)].sort((a, b) => b.length - a.length)
|
|
87
|
+
const alt = sorted.map(escapeRe).join('|')
|
|
88
|
+
const boundary = opts.boundary ? `(?![${opts.boundary}])` : ''
|
|
89
|
+
// Case-INSENSITIVE drops `u` deliberately, so that MATCHING and the first-set
|
|
90
|
+
// below fold the SAME set of characters — the invariant `regex()` established in
|
|
91
|
+
// 0.32.0 (see the flag-aware widening in `src/combinators/regex.ts`):
|
|
92
|
+
// - `/i` WITHOUT `u` folds only pairs that stay on the SAME side of the ASCII
|
|
93
|
+
// boundary — every Basic-Latin pair (a↔A … z↔Z), and also the non-ASCII ones
|
|
94
|
+
// (ä↔Ä, σ↔Σ↔ς), but never a pair that crosses it. `caseFoldVariants` below
|
|
95
|
+
// enumerates exactly that relation → the first-set is a tight, SOUND superset
|
|
96
|
+
// and dispatch stays exact. (Widening by toUpperCase/toLowerCase alone would
|
|
97
|
+
// NOT be: 67 BMP code points sit in fold classes those two miss.)
|
|
98
|
+
// - `/iu` folds by Unicode *simple case folding*, so `/(?:stroke)/iu` also
|
|
99
|
+
// matches `ſtroke` (U+017F → s). An ASCII-only first-set would then dispatch
|
|
100
|
+
// that input AWAY from this arm — an unsound gate. `regex()` answers that by
|
|
101
|
+
// falling back to `any()` (forfeiting the gate); `keywords()` answers it by
|
|
102
|
+
// not entering Unicode mode at all, which keeps the gate.
|
|
103
|
+
// Every keyword is fully escaped by `escapeRe`, so `u` mode changes nothing about
|
|
104
|
+
// what a case-SENSITIVE set matches; that path keeps `u` unchanged.
|
|
105
|
+
const flags = opts.caseInsensitive ? 'iy' : 'uy'
|
|
106
|
+
const re = new RegExp(`(?:${alt})${boundary}`, flags)
|
|
107
|
+
|
|
108
|
+
// First-set: the set of first code points across all keywords (and their
|
|
109
|
+
// case-folded variants when case-insensitive), for choice() dispatch.
|
|
110
|
+
//
|
|
111
|
+
// The fold must cover the SAME pairs the `iy` matcher above accepts, or the gate
|
|
112
|
+
// dispatches valid input away from this arm. `/i` folds non-ASCII pairs too
|
|
113
|
+
// (`/ärger/i` matches `Ärger`, `/σ/i` matches both `Σ` and `ς`) — it only refuses
|
|
114
|
+
// folds that would CROSS the ASCII boundary. `caseFoldVariants` is that exact
|
|
115
|
+
// relation, so the widening stays a tight, sound superset for every first char,
|
|
116
|
+
// not just the Basic-Latin ones.
|
|
117
|
+
let firstSet: FirstSet = empty()
|
|
118
|
+
for (const w of sorted) {
|
|
119
|
+
if (w.length === 0) continue
|
|
120
|
+
const cp = w.codePointAt(0)!
|
|
121
|
+
firstSet = union(firstSet, fromChar(cp))
|
|
122
|
+
if (opts.caseInsensitive) {
|
|
123
|
+
for (const v of caseFoldVariants(cp)) firstSet = union(firstSet, fromChar(v))
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const meta: ParserMeta = { firstSet, canMatchNewline: false, isTrivia: false }
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
_tag: 'keywords',
|
|
131
|
+
_meta: meta,
|
|
132
|
+
_def: { tag: 'keywords', words: sorted, caseInsensitive: opts.caseInsensitive ?? false, boundary: opts.boundary },
|
|
133
|
+
parse(input: string, pos: number, ctx: ParseContext): ParseResult<string> {
|
|
134
|
+
re.lastIndex = pos
|
|
135
|
+
const m = re.exec(input)
|
|
136
|
+
if (m === null || m.index !== pos) {
|
|
137
|
+
return failAt(ctx, ['keyword'], pos)
|
|
138
|
+
}
|
|
139
|
+
const value = m[0]!
|
|
140
|
+
const span = { start: pos, end: pos + value.length }
|
|
141
|
+
if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: 'leaf', value, span })
|
|
142
|
+
return { ok: true, value, span }
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { Combinator, ParseContext, ParseResult, ParserMeta } from '../types.ts'
|
|
2
|
+
import { fromChar, empty } from './first-set.ts'
|
|
3
|
+
import { failAt } from './probe.ts'
|
|
4
|
+
import { pushCstLeaf, cstCaptureActive } from '../cst/capture-buffer.ts'
|
|
5
|
+
import { recordLineRangeFromContext } from '../line-index.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ASCII case-fold equality — the interpreter twin of the compiler's `foldEq`
|
|
9
|
+
* (and `/i`-flag regex lowering). Folds ASCII letters via bit 0x20, exact-matches
|
|
10
|
+
* everything else. Deliberately NOT `Intl.Collator` (measured ~9× slower, and its
|
|
11
|
+
* Unicode accent-folding is the wrong semantic for a parser). Keeps interpreter /
|
|
12
|
+
* compiled / macro output identical for case-insensitive `literal()`.
|
|
13
|
+
*/
|
|
14
|
+
export function asciiFoldEq(a: string, b: string): boolean {
|
|
15
|
+
if (a.length !== b.length) return false
|
|
16
|
+
for (let i = 0; i < a.length; i++) {
|
|
17
|
+
let ca = a.charCodeAt(i)
|
|
18
|
+
let cb = b.charCodeAt(i)
|
|
19
|
+
if (ca >= 65 && ca <= 90) ca += 32
|
|
20
|
+
if (cb >= 65 && cb <= 90) cb += 32
|
|
21
|
+
if (ca !== cb) return false
|
|
22
|
+
}
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type LiteralOptions = {
|
|
27
|
+
caseInsensitive?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function literal(value: string, opts: LiteralOptions = {}): Combinator<string> {
|
|
31
|
+
const caseInsensitive = opts.caseInsensitive ?? false
|
|
32
|
+
|
|
33
|
+
const firstSet = value.length > 0
|
|
34
|
+
? fromChar(value.codePointAt(0)!)
|
|
35
|
+
: empty()
|
|
36
|
+
|
|
37
|
+
const meta: ParserMeta = {
|
|
38
|
+
firstSet,
|
|
39
|
+
canMatchNewline: value.includes('\n'),
|
|
40
|
+
isTrivia: false,
|
|
41
|
+
}
|
|
42
|
+
const canMatchNewline = meta.canMatchNewline
|
|
43
|
+
|
|
44
|
+
if (caseInsensitive) {
|
|
45
|
+
const upper = value.toUpperCase()
|
|
46
|
+
const lower = value.toLowerCase()
|
|
47
|
+
const firstUpper = upper.codePointAt(0)
|
|
48
|
+
const firstLower = lower.codePointAt(0)
|
|
49
|
+
// Two discrete points, not one spanning range — `asciiFoldEq` accepts only
|
|
50
|
+
// the char and its fold twin, so `[min, max]` would over-approximate (e.g.
|
|
51
|
+
// 'a'/'A' → the whole 65..97 span, pulling B–Z/`[\]^_\`` into dispatch).
|
|
52
|
+
meta.firstSet = firstLower !== undefined && firstUpper !== undefined
|
|
53
|
+
? firstLower === firstUpper
|
|
54
|
+
? { kind: 'ranges', ranges: [{ lo: firstLower, hi: firstLower }] }
|
|
55
|
+
: { kind: 'ranges', ranges: [
|
|
56
|
+
{ lo: firstLower, hi: firstLower },
|
|
57
|
+
{ lo: firstUpper, hi: firstUpper },
|
|
58
|
+
]}
|
|
59
|
+
: firstSet
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const expected = [JSON.stringify(value)]
|
|
63
|
+
|
|
64
|
+
// Single-char case-sensitive literals are the bulk of punctuation-heavy
|
|
65
|
+
// grammars (GraphQL `{ } ( ) : $ @ [ ] ! =`, JSON, CSS). A bare `charCodeAt`
|
|
66
|
+
// compare beats the generic `startsWith` builtin call, and an out-of-bounds
|
|
67
|
+
// `charCodeAt` returns NaN — so the length check folds into the compare.
|
|
68
|
+
const parse = !caseInsensitive && value.length === 1
|
|
69
|
+
? (() => {
|
|
70
|
+
const code = value.charCodeAt(0)
|
|
71
|
+
return function parse(input: string, pos: number, ctx: ParseContext): ParseResult<string> {
|
|
72
|
+
if (input.charCodeAt(pos) === code) {
|
|
73
|
+
const span = { start: pos, end: pos + 1 }
|
|
74
|
+
if (canMatchNewline && ctx.trackLines) recordLineRangeFromContext(ctx, input, pos, span.end)
|
|
75
|
+
if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: 'leaf', value, span })
|
|
76
|
+
return { ok: true, value, span }
|
|
77
|
+
}
|
|
78
|
+
return failAt(ctx, expected, pos)
|
|
79
|
+
}
|
|
80
|
+
})()
|
|
81
|
+
: function parse(input: string, pos: number, ctx: ParseContext): ParseResult<string> {
|
|
82
|
+
const end = pos + value.length
|
|
83
|
+
if (end > input.length) {
|
|
84
|
+
return failAt(ctx, expected, pos)
|
|
85
|
+
}
|
|
86
|
+
const matchedValue = caseInsensitive ? input.slice(pos, end) : value
|
|
87
|
+
if (caseInsensitive ? asciiFoldEq(matchedValue, value) : input.startsWith(value, pos)) {
|
|
88
|
+
const span = { start: pos, end }
|
|
89
|
+
if (canMatchNewline && ctx.trackLines) recordLineRangeFromContext(ctx, input, pos, end)
|
|
90
|
+
if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: 'leaf', value: matchedValue, span })
|
|
91
|
+
return { ok: true, value: matchedValue, span }
|
|
92
|
+
}
|
|
93
|
+
return failAt(ctx, expected, pos)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
_tag: 'literal',
|
|
98
|
+
_meta: meta,
|
|
99
|
+
_def: { tag: 'literal', value, caseInsensitive },
|
|
100
|
+
parse,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Combinator, ParseContext, ParseResult } from '../types.ts'
|
|
2
|
+
import { analyzeLabeledTrivia } from '../cst/trivia-kinds.ts'
|
|
3
|
+
import { choice } from './choice.ts'
|
|
4
|
+
import { matchesEmpty } from './first-set.ts'
|
|
5
|
+
import { oneOrMore } from './repeat.ts'
|
|
6
|
+
|
|
7
|
+
export function transform<T, U>(
|
|
8
|
+
combinator: Combinator<T>,
|
|
9
|
+
fn: (value: T, span: { start: number; end: number }) => U
|
|
10
|
+
): Combinator<U> {
|
|
11
|
+
return {
|
|
12
|
+
_tag: 'transform',
|
|
13
|
+
_meta: combinator._meta,
|
|
14
|
+
_def: { tag: 'transform', parser: combinator as Combinator<unknown>, fn: fn as (v: unknown, span: { start: number; end: number }) => unknown },
|
|
15
|
+
parse(input: string, pos: number, ctx: ParseContext): ParseResult<U> {
|
|
16
|
+
const result = combinator.parse(input, pos, ctx)
|
|
17
|
+
if (!result.ok) return result
|
|
18
|
+
return { ok: true, value: fn(result.value, result.span), span: result.span }
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function trivia<T>(combinator: Combinator<T>): Combinator<T> {
|
|
24
|
+
const kindLabels = analyzeLabeledTrivia(combinator as Combinator<unknown>)?.labels
|
|
25
|
+
return {
|
|
26
|
+
_tag: combinator._tag,
|
|
27
|
+
_meta: {
|
|
28
|
+
...combinator._meta,
|
|
29
|
+
isTrivia: true,
|
|
30
|
+
...(kindLabels ? { triviaKindLabels: kindLabels } : {}),
|
|
31
|
+
},
|
|
32
|
+
_def: { tag: 'trivia', parser: combinator as Combinator<unknown> },
|
|
33
|
+
parse: combinator.parse.bind(combinator),
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build trivia whose category labels are part of its recognition structure.
|
|
39
|
+
*
|
|
40
|
+
* Use this for a grammar that exposes selected root trivia. In contrast to
|
|
41
|
+
* `trivia(label('categoryA', broadRegex))`, every category here owns one arm,
|
|
42
|
+
* so one selected category cannot be silently consumed under another category's
|
|
43
|
+
* broad matcher.
|
|
44
|
+
* `rootTrivia: { select }` rejects ordinary scoped trivia
|
|
45
|
+
* unless that scope explicitly declares itself opaque.
|
|
46
|
+
*/
|
|
47
|
+
export function classifiedTrivia(
|
|
48
|
+
arms: Readonly<Record<string, Combinator<unknown>>>,
|
|
49
|
+
): Combinator<unknown> {
|
|
50
|
+
const entries = Object.entries(arms)
|
|
51
|
+
if (entries.length === 0) {
|
|
52
|
+
throw new TypeError('classifiedTrivia() requires at least one named trivia arm.')
|
|
53
|
+
}
|
|
54
|
+
for (let i = 0; i < entries.length; i++) {
|
|
55
|
+
const [name, arm] = entries[i]!
|
|
56
|
+
const first = arm._meta.firstSet
|
|
57
|
+
if (matchesEmpty(arm) || first.kind !== 'ranges' || first.ranges.length === 0) {
|
|
58
|
+
throw new TypeError(
|
|
59
|
+
`classifiedTrivia(): ${JSON.stringify(name)} must be non-nullable with a concrete finite first set.`,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const labeledArms = entries.map(([name, arm]) => label(name, arm)) as [
|
|
64
|
+
Combinator<unknown>,
|
|
65
|
+
...Combinator<unknown>[],
|
|
66
|
+
]
|
|
67
|
+
const result = trivia(oneOrMore(choice(...labeledArms)))
|
|
68
|
+
result._meta.rootTriviaClassified = true
|
|
69
|
+
return result
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Attach a string label to a parser arm (e.g. trivia `choice` branches).
|
|
74
|
+
* Parse behavior is unchanged; the label is metadata for tooling and future
|
|
75
|
+
* trivia-kind capture (`'whitespace'`, `'blockComment'`, …).
|
|
76
|
+
*/
|
|
77
|
+
export function label<T>(name: string, combinator: Combinator<T>): Combinator<T> {
|
|
78
|
+
return {
|
|
79
|
+
_tag: combinator._tag,
|
|
80
|
+
_meta: combinator._meta,
|
|
81
|
+
_def: { tag: 'label', label: name, parser: combinator as Combinator<unknown> },
|
|
82
|
+
parse(input: string, pos: number, ctx: ParseContext): ParseResult<T> {
|
|
83
|
+
const result = combinator.parse(input, pos, ctx)
|
|
84
|
+
if (!result.ok) return { ok: false, expected: [name], span: result.span }
|
|
85
|
+
return result
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Capture a named parse result for the nearest enclosing node() builder.
|
|
92
|
+
* Parse behavior and the normal returned value are unchanged.
|
|
93
|
+
*/
|
|
94
|
+
export function field<T>(name: string, combinator: Combinator<T>): Combinator<T> {
|
|
95
|
+
return {
|
|
96
|
+
_tag: combinator._tag,
|
|
97
|
+
_meta: combinator._meta,
|
|
98
|
+
_def: { tag: 'field', name, parser: combinator as Combinator<unknown> },
|
|
99
|
+
parse(input: string, pos: number, ctx: ParseContext): ParseResult<T> {
|
|
100
|
+
const result = combinator.parse(input, pos, ctx)
|
|
101
|
+
if (!result.ok) return result
|
|
102
|
+
ctx._fields?.push({ name, value: result.value, span: result.span })
|
|
103
|
+
return result
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|