functionalscript 0.45.0 → 0.46.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 +5 -3
- package/fjs/asn.1/module.f.mjs +8 -1
- package/fjs/asn.1/proof.f.d.mts +10 -0
- package/fjs/asn.1/proof.f.mjs +16 -0
- package/fjs/basen/base128/module.f.mjs +22 -5
- package/fjs/bnf/data/module.f.d.mts +32 -6
- package/fjs/bnf/data/module.f.mjs +112 -7
- package/fjs/bnf/data/proof.f.d.mts +2 -0
- package/fjs/bnf/data/proof.f.mjs +74 -3
- package/fjs/bnf/data/types.d.ts +20 -2
- package/fjs/bnf/descent/module.f.d.mts +28 -14
- package/fjs/bnf/descent/module.f.mjs +122 -77
- package/fjs/bnf/descent/proof.f.d.mts +2 -0
- package/fjs/bnf/descent/proof.f.mjs +117 -31
- package/fjs/bnf/descent/types.d.ts +12 -14
- package/fjs/bnf/ll1/module.f.d.mts +64 -22
- package/fjs/bnf/ll1/module.f.mjs +214 -154
- package/fjs/bnf/ll1/proof.f.d.mts +15 -2
- package/fjs/bnf/ll1/proof.f.mjs +323 -149
- package/fjs/bnf/ll1/types.d.ts +16 -24
- package/fjs/bnf/matcher/module.f.d.mts +66 -0
- package/fjs/bnf/matcher/module.f.mjs +81 -0
- package/fjs/bnf/matcher/proof.f.d.mts +10 -0
- package/fjs/bnf/matcher/proof.f.mjs +79 -0
- package/fjs/bnf/matcher/types.d.ts +54 -0
- package/fjs/bnf/testlib.f.d.mts +31 -0
- package/fjs/bnf/testlib.f.mjs +80 -0
- package/fjs/cas/cli/module.f.d.mts +1 -1
- package/fjs/cas/cli/module.f.mjs +14 -20
- package/fjs/cas/cli/proof.f.d.mts +1 -3
- package/fjs/cas/cli/proof.f.mjs +44 -33
- package/fjs/cas/evo/module.f.d.mts +64 -18
- package/fjs/cas/evo/module.f.mjs +148 -70
- package/fjs/cas/evo/proof.f.d.mts +10 -1
- package/fjs/cas/evo/proof.f.mjs +305 -223
- package/fjs/cas/evo/types.d.ts +45 -9
- package/fjs/cas/module.f.d.mts +18 -32
- package/fjs/cas/module.f.mjs +129 -128
- package/fjs/cas/proof.f.d.mts +11 -8
- package/fjs/cas/proof.f.mjs +259 -147
- package/fjs/cas/types.d.ts +24 -10
- package/fjs/ci/config/module.f.d.mts +2 -2
- package/fjs/ci/config/module.f.mjs +2 -2
- package/fjs/ci/module.f.d.mts +5 -5
- package/fjs/ci/module.f.mjs +8 -7
- package/fjs/ci/nix/module.f.d.mts +7 -5
- package/fjs/ci/nix/module.f.mjs +13 -12
- package/fjs/ci/nix/proof.f.mjs +2 -2
- package/fjs/ci/proof.f.mjs +9 -5
- package/fjs/cli/module.f.d.mts +4 -6
- package/fjs/cli/module.f.mjs +4 -8
- package/fjs/cli/proof.f.mjs +17 -16
- package/fjs/cli/types.d.ts +2 -3
- package/fjs/common/monoid/types.d.ts +1 -1
- package/fjs/crypto/hmac/module.f.mjs +2 -2
- package/fjs/crypto/sha2/module.f.mjs +3 -1
- package/fjs/crypto/sha2/proof.f.d.mts +1 -0
- package/fjs/crypto/sha2/proof.f.mjs +24 -0
- package/fjs/crypto/sha2/types.d.ts +11 -0
- package/fjs/crypto/sign/module.f.mjs +2 -2
- package/fjs/dev/module.f.d.mts +13 -4
- package/fjs/dev/module.f.mjs +56 -27
- package/fjs/dev/update/module.f.d.mts +9 -4
- package/fjs/dev/update/module.f.mjs +14 -10
- package/fjs/dev/update/proof.f.d.mts +1 -3
- package/fjs/dev/update/proof.f.mjs +10 -5
- package/fjs/djs/module.f.d.mts +13 -5
- package/fjs/djs/module.f.mjs +31 -16
- package/fjs/djs/parser/module.f.d.mts +13 -3
- package/fjs/djs/parser/module.f.mjs +117 -16
- package/fjs/djs/parser/proof.f.d.mts +5 -1
- package/fjs/djs/parser/proof.f.mjs +274 -12
- package/fjs/djs/parser/types.d.ts +7 -1
- package/fjs/djs/proof.f.d.mts +18 -2
- package/fjs/djs/proof.f.mjs +187 -12
- package/fjs/djs/serializer/module.f.d.mts +25 -8
- package/fjs/djs/serializer/module.f.mjs +61 -15
- package/fjs/djs/serializer/proof.f.d.mts +5 -0
- package/fjs/djs/serializer/proof.f.mjs +23 -1
- package/fjs/djs/tokenizer/module.f.d.mts +17 -5
- package/fjs/djs/tokenizer/module.f.mjs +99 -60
- package/fjs/djs/tokenizer/proof.f.d.mts +1 -1
- package/fjs/djs/tokenizer/proof.f.mjs +74 -67
- package/fjs/djs/transpiler/module.f.d.mts +12 -7
- package/fjs/djs/transpiler/module.f.mjs +82 -56
- package/fjs/djs/transpiler/types.d.ts +7 -3
- package/fjs/djs/types.d.ts +7 -1
- package/fjs/effects/list/module.f.d.mts +14 -11
- package/fjs/effects/list/module.f.mjs +13 -11
- package/fjs/effects/list/types.d.ts +27 -7
- package/fjs/effects/memory/module.f.d.mts +2 -1
- package/fjs/effects/memory/module.f.mjs +3 -4
- package/fjs/effects/memory/proof.f.mjs +13 -7
- package/fjs/effects/memory/types.d.ts +4 -3
- package/fjs/effects/mock/module.f.d.mts +20 -4
- package/fjs/effects/mock/module.f.mjs +37 -5
- package/fjs/effects/mock/types.d.ts +10 -1
- package/fjs/effects/module.d.mts +4 -2
- package/fjs/effects/module.f.d.mts +466 -280
- package/fjs/effects/module.f.mjs +537 -299
- package/fjs/effects/module.mjs +2 -1
- package/fjs/effects/node/memory/module.d.mts +4 -2
- package/fjs/effects/node/memory/module.mjs +6 -3
- package/fjs/effects/node/memory/proof.mjs +10 -4
- package/fjs/effects/node/module.d.mts +4 -3
- package/fjs/effects/node/module.f.d.mts +168 -33
- package/fjs/effects/node/module.f.mjs +256 -52
- package/fjs/effects/node/module.mjs +57 -34
- package/fjs/effects/node/proof.f.d.mts +28 -2
- package/fjs/effects/node/proof.f.mjs +161 -42
- package/fjs/effects/node/types.d.ts +106 -18
- package/fjs/effects/node/virtual/module.f.d.mts +18 -4
- package/fjs/effects/node/virtual/module.f.mjs +110 -68
- package/fjs/effects/node/virtual/proof.f.d.mts +28 -2
- package/fjs/effects/node/virtual/proof.f.mjs +190 -9
- package/fjs/effects/proof.f.d.mts +69 -37
- package/fjs/effects/proof.f.mjs +410 -130
- package/fjs/effects/types.d.ts +161 -33
- package/fjs/emergent_testing/module.f.d.mts +19 -12
- package/fjs/emergent_testing/module.f.mjs +93 -33
- package/fjs/emergent_testing/proof.f.d.mts +21 -7
- package/fjs/emergent_testing/proof.f.mjs +166 -32
- package/fjs/emergent_testing/types.d.ts +22 -4
- package/fjs/fsm/module.f.d.mts +14 -4
- package/fjs/fsm/module.f.mjs +54 -37
- package/fjs/fsm/proof.f.d.mts +2 -0
- package/fjs/fsm/proof.f.mjs +83 -114
- package/fjs/js/keywords/module.f.d.mts +52 -0
- package/fjs/js/keywords/module.f.mjs +72 -0
- package/fjs/js/keywords/proof.f.d.mts +3 -0
- package/fjs/js/keywords/proof.f.mjs +13 -0
- package/fjs/js/tokenizer/module.f.d.mts +26 -6
- package/fjs/js/tokenizer/module.f.mjs +145 -159
- package/fjs/js/tokenizer/proof.f.d.mts +1 -0
- package/fjs/js/tokenizer/proof.f.mjs +54 -24
- package/fjs/js/tokenizer/types.d.ts +33 -24
- package/fjs/mcp/cas/module.f.d.mts +1 -6
- package/fjs/mcp/cas/module.f.mjs +55 -51
- package/fjs/mcp/cas/proof.f.d.mts +15 -0
- package/fjs/mcp/cas/proof.f.mjs +174 -0
- package/fjs/mcp/evo/module.f.d.mts +19 -10
- package/fjs/mcp/evo/module.f.mjs +48 -27
- package/fjs/mcp/evo/proof.f.d.mts +6 -1
- package/fjs/mcp/evo/proof.f.mjs +115 -31
- package/fjs/mcp/module.f.d.mts +4 -4
- package/fjs/mcp/module.f.mjs +6 -6
- package/fjs/mcp/proof.f.d.mts +5 -3
- package/fjs/mcp/proof.f.mjs +112 -46
- package/fjs/media/html/module.f.mjs +1 -1
- package/fjs/media/json/extended/module.f.d.mts +82 -0
- package/fjs/media/json/extended/module.f.mjs +153 -0
- package/fjs/media/json/extended/proof.f.d.mts +42 -0
- package/fjs/media/json/extended/proof.f.mjs +127 -0
- package/fjs/media/json/extended/types.d.ts +23 -0
- package/fjs/media/json/module.f.d.mts +8 -2
- package/fjs/media/json/module.f.mjs +43 -41
- package/fjs/media/json/number/module.f.d.mts +59 -0
- package/fjs/media/json/number/module.f.mjs +136 -0
- package/fjs/media/json/number/proof.f.d.mts +24 -0
- package/fjs/media/json/number/proof.f.mjs +86 -0
- package/fjs/media/json/number/types.d.ts +28 -0
- package/fjs/media/json/parser/module.f.d.mts +25 -13
- package/fjs/media/json/parser/module.f.mjs +114 -70
- package/fjs/media/json/parser/proof.f.d.mts +5 -0
- package/fjs/media/json/parser/proof.f.mjs +31 -1
- package/fjs/media/json/parser/types.d.ts +33 -14
- package/fjs/media/json/rtti/module.f.d.mts +1 -1
- package/fjs/media/json/rtti/module.f.mjs +1 -1
- package/fjs/media/json/rtti/proof.f.mjs +9 -9
- package/fjs/media/json/schema/module.f.mjs +3 -13
- package/fjs/media/json/schema/proof.f.d.mts +0 -1
- package/fjs/media/json/schema/proof.f.mjs +1 -2
- package/fjs/media/json/serializer/module.f.d.mts +32 -1
- package/fjs/media/json/serializer/module.f.mjs +64 -2
- package/fjs/media/json/tokenizer/module.f.mjs +7 -3
- package/fjs/media/json/tokenizer/proof.f.d.mts +6 -0
- package/fjs/media/json/tokenizer/proof.f.mjs +62 -21
- package/fjs/media/json/types.d.ts +36 -10
- package/fjs/media/lock/module.f.d.mts +100 -0
- package/fjs/media/lock/module.f.mjs +125 -0
- package/fjs/media/lock/proof.f.d.mts +33 -0
- package/fjs/media/lock/proof.f.mjs +196 -0
- package/fjs/media/lock/types.d.ts +15 -0
- package/fjs/media/module.f.d.mts +6 -5
- package/fjs/media/module.f.mjs +8 -7
- package/fjs/media/note/module.f.d.mts +121 -0
- package/fjs/media/note/module.f.mjs +131 -0
- package/fjs/media/note/proof.f.d.mts +29 -0
- package/fjs/media/note/proof.f.mjs +150 -0
- package/fjs/media/note/types.d.ts +10 -0
- package/fjs/media/proof.f.d.mts +4 -1
- package/fjs/media/proof.f.mjs +40 -21
- package/fjs/media/revision/module.f.d.mts +78 -7
- package/fjs/media/revision/module.f.mjs +119 -12
- package/fjs/media/revision/proof.f.d.mts +10 -0
- package/fjs/media/revision/proof.f.mjs +88 -0
- package/fjs/media/revision/types.d.ts +34 -5
- package/fjs/media/type/module.f.d.mts +33 -15
- package/fjs/media/type/module.f.mjs +35 -29
- package/fjs/media/type/proof.f.d.mts +2 -1
- package/fjs/media/type/proof.f.mjs +30 -10
- package/fjs/module.f.mjs +29 -8
- package/fjs/nanvm/proof.f.mjs +3 -3
- package/fjs/nanvm/rust/module.f.mjs +1 -1
- package/fjs/nanvm/update/module.f.d.mts +4 -4
- package/fjs/nanvm/update/module.f.mjs +8 -9
- package/fjs/nanvm/update/proof.f.mjs +4 -3
- package/fjs/proof.f.d.mts +3 -3
- package/fjs/proof.f.mjs +36 -10
- package/fjs/protocol/json_rpc/module.f.d.mts +2 -2
- package/fjs/protocol/json_rpc/module.f.mjs +3 -3
- package/fjs/protocol/json_rpc/proof.f.mjs +4 -4
- package/fjs/protocol/mcp/module.f.d.mts +10 -13
- package/fjs/protocol/mcp/module.f.mjs +74 -58
- package/fjs/protocol/mcp/proof.f.d.mts +13 -4
- package/fjs/protocol/mcp/proof.f.mjs +193 -61
- package/fjs/protocol/mcp/stdio/module.f.d.mts +15 -7
- package/fjs/protocol/mcp/stdio/module.f.mjs +38 -25
- package/fjs/protocol/mcp/stdio/proof.f.d.mts +4 -2
- package/fjs/protocol/mcp/stdio/proof.f.mjs +45 -12
- package/fjs/protocol/mcp/stdio/types.d.ts +1 -1
- package/fjs/protocol/mcp/types.d.ts +17 -6
- package/fjs/sul/id/module.f.d.mts +0 -1
- package/fjs/sul/id/module.f.mjs +2 -3
- package/fjs/sul/level/hash/module.f.mjs +2 -1
- package/fjs/sul/level/hash/proof.f.mjs +2 -2
- package/fjs/sul/module.f.mjs +18 -13
- package/fjs/text/code_point/module.f.d.mts +8 -0
- package/fjs/text/code_point/module.f.mjs +8 -1
- package/fjs/text/code_point/proof.f.d.mts +1 -0
- package/fjs/text/code_point/proof.f.mjs +11 -0
- package/fjs/text/sgr/module.f.d.mts +4 -6
- package/fjs/text/sgr/module.f.mjs +6 -7
- package/fjs/text/utf16/module.f.mjs +7 -2
- package/fjs/text/utf16/proof.f.mjs +3 -3
- package/fjs/text/utf8/module.f.mjs +4 -2
- package/fjs/types/array/module.f.mjs +14 -2
- package/fjs/types/bit_vec/module.f.d.mts +0 -2
- package/fjs/types/bit_vec/module.f.mjs +46 -32
- package/fjs/types/bit_vec/proof.f.mjs +2 -2
- package/fjs/types/btree/remove/module.f.mjs +1 -1
- package/fjs/types/btree/set/module.f.mjs +9 -11
- package/fjs/types/btree/set/proof.f.mjs +12 -0
- package/fjs/types/byte_set/module.f.d.mts +11 -4
- package/fjs/types/byte_set/module.f.mjs +14 -6
- package/fjs/types/byte_set/proof.f.mjs +7 -7
- package/fjs/types/function/compare/module.f.mjs +10 -3
- package/fjs/types/list/module.f.d.mts +1 -1
- package/fjs/types/list/module.f.mjs +1 -1
- package/fjs/types/nullable/module.f.d.mts +18 -4
- package/fjs/types/nullable/module.f.mjs +21 -4
- package/fjs/types/nullable/proof.f.d.mts +4 -0
- package/fjs/types/nullable/proof.f.mjs +15 -0
- package/fjs/types/object/module.f.d.mts +12 -2
- package/fjs/types/object/module.f.mjs +11 -1
- package/fjs/types/patricia_trie/module.f.mjs +26 -11
- package/fjs/types/result/module.f.d.mts +3 -3
- package/fjs/types/result/module.f.mjs +3 -3
- package/fjs/types/rtti/common/module.f.d.mts +25 -28
- package/fjs/types/rtti/common/module.f.mjs +36 -32
- package/fjs/types/rtti/common/proof.f.mjs +5 -5
- package/fjs/types/rtti/data/module.f.d.mts +14 -0
- package/fjs/types/rtti/data/module.f.mjs +25 -2
- package/fjs/types/rtti/data/proof.f.d.mts +1 -0
- package/fjs/types/rtti/data/proof.f.mjs +25 -2
- package/fjs/types/rtti/parse/module.f.d.mts +24 -14
- package/fjs/types/rtti/parse/module.f.mjs +37 -28
- package/fjs/types/rtti/parse/proof.f.d.mts +3 -2
- package/fjs/types/rtti/parse/proof.f.mjs +33 -14
- package/fjs/types/rtti/proof.f.mjs +3 -1
- package/fjs/types/rtti/ts/module.f.mjs +13 -17
- package/fjs/types/rtti/ts/types.d.ts +14 -1
- package/fjs/types/sorted_set/module.f.d.mts +18 -0
- package/fjs/types/sorted_set/module.f.mjs +22 -0
- package/fjs/types/sorted_set/proof.f.d.mts +1 -0
- package/fjs/types/sorted_set/proof.f.mjs +16 -1
- package/fjs/types/uint8array/module.f.d.mts +1 -1
- package/fjs/types/uint8array/module.f.mjs +1 -1
- package/fjs/website/module.f.d.mts +3 -3
- package/fjs/website/module.f.mjs +4 -7
- package/fjs/website/proof.f.mjs +2 -1
- package/package.json +2 -2
- package/fjs/dev/package_json/module.f.d.mts +0 -39
- package/fjs/dev/package_json/module.f.mjs +0 -40
- package/fjs/dev/package_json/proof.f.d.mts +0 -6
- package/fjs/dev/package_json/proof.f.mjs +0 -32
- package/fjs/effects/eff/module.f.d.mts +0 -20
- package/fjs/effects/eff/module.f.mjs +0 -70
- package/fjs/effects/eff/proof.f.d.mts +0 -15
- package/fjs/effects/eff/proof.f.mjs +0 -69
- package/fjs/effects/eff/types.d.ts +0 -71
- package/fjs/types/rtti/validate/module.f.d.mts +0 -41
- package/fjs/types/rtti/validate/module.f.mjs +0 -171
- package/fjs/types/rtti/validate/proof.f.d.mts +0 -125
- package/fjs/types/rtti/validate/proof.f.mjs +0 -437
- package/fjs/types/rtti/validate/types.d.ts +0 -6
|
@@ -4,52 +4,49 @@
|
|
|
4
4
|
* Built from the serializable IR in `fjs/bnf/data`, this is a sibling of the
|
|
5
5
|
* LL(1) dispatch builder (`fjs/bnf/ll1`). It walks the grammar by recursive
|
|
6
6
|
* descent and preserves per-code-point metadata, producing a metadata-aware
|
|
7
|
-
* AST
|
|
8
|
-
*
|
|
7
|
+
* AST over leaves that carry it — `Ast<CodePointMeta<T>>`, built with the
|
|
8
|
+
* shared layer in `fjs/bnf/matcher`. Nullability (which rule can match empty
|
|
9
|
+
* input) is computed once by {@link emptyTagMap} in `fjs/bnf/data`.
|
|
9
10
|
*
|
|
10
11
|
* The caller passes physical symbols only; the matcher synthesizes the one
|
|
11
|
-
* logical EOF
|
|
12
|
-
*
|
|
12
|
+
* logical EOF after them, so a grammar can require the end of input with the
|
|
13
|
+
* `eof` terminal.
|
|
13
14
|
*
|
|
14
15
|
* A failed result also carries a {@link DescentFailure}: the furthest position a
|
|
15
16
|
* terminal was rejected at, which — unlike the result's own index — never
|
|
16
17
|
* rewinds and is what diagnostics should be built from.
|
|
17
18
|
*
|
|
19
|
+
* A `Repeat` rule is matched iteratively rather than by descending into itself
|
|
20
|
+
* once per item, so a repetition produces one AST node holding a flat sequence
|
|
21
|
+
* of the items it matched instead of the right-recursive chain its functional
|
|
22
|
+
* spelling would otherwise build.
|
|
23
|
+
*
|
|
18
24
|
* See `./types.ts` for the type-level API.
|
|
19
25
|
*
|
|
20
26
|
* @module
|
|
21
27
|
*
|
|
22
28
|
* @import { TerminalRange } from '../types.ts'
|
|
23
|
-
* @import { Rule as DataRule, Sequence } from '../data/types.ts'
|
|
29
|
+
* @import { Rule as DataRule, RuleSet, Sequence } from '../data/types.ts'
|
|
24
30
|
* @import { Rule as FRule } from '../types.ts'
|
|
25
|
-
* @import {
|
|
31
|
+
* @import { List } from '../../types/list/types.ts'
|
|
32
|
+
* @import { Ast, AstResult, AstSequence, AstTag, Cursor } from '../matcher/types.ts'
|
|
33
|
+
* @import { CodePointMeta, DescentFailure, DescentMatch, DescentMatchResult, DescentMatchRule } from './types.ts'
|
|
26
34
|
*/
|
|
27
35
|
|
|
28
|
-
import {
|
|
36
|
+
import { rangeDecode } from '../module.f.mjs'
|
|
29
37
|
import { contains as rangeContains } from '../../types/range/module.f.mjs'
|
|
38
|
+
import { concat, toArray } from '../../types/list/module.f.mjs'
|
|
30
39
|
import { definedEntries } from '../../types/object/module.f.mjs'
|
|
31
|
-
import { emptyTagMap, toData } from '../data/module.f.mjs'
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* A match position that includes EOF consumption: `0 .. cp.length` are the
|
|
35
|
-
* physical positions, and `cp.length + 1` is where the one synthesized EOF has
|
|
36
|
-
* been consumed.
|
|
37
|
-
*
|
|
38
|
-
* This is the complete cursor the design calls `(idx, eofConsumed)`, written as
|
|
39
|
-
* one number: `eofConsumed` can only be true at the physical end, so the pair
|
|
40
|
-
* and the extended position hold the same information, and comparing cursors
|
|
41
|
-
* compares progress — consuming EOF *is* progress even though the public index
|
|
42
|
-
* does not move.
|
|
43
|
-
*
|
|
44
|
-
* @typedef {number} _Cursor
|
|
45
|
-
*/
|
|
40
|
+
import { emptyTagMap, isRepeat, toData } from '../data/module.f.mjs'
|
|
41
|
+
import { leafAt, mrFail, mrSuccess, physicalIdx, symbolAt } from '../matcher/module.f.mjs'
|
|
46
42
|
|
|
47
43
|
/**
|
|
48
44
|
* The furthest-failure record while matching, positioned by the complete
|
|
49
|
-
*
|
|
45
|
+
* {@link Cursor}. {@link DescentFailure} is its public, physically-positioned
|
|
46
|
+
* form.
|
|
50
47
|
*
|
|
51
48
|
* @typedef {{
|
|
52
|
-
* readonly pos:
|
|
49
|
+
* readonly pos: Cursor
|
|
53
50
|
* readonly expected: readonly TerminalRange[]
|
|
54
51
|
* }} _Failure
|
|
55
52
|
*/
|
|
@@ -57,41 +54,22 @@ import { emptyTagMap, toData } from '../data/module.f.mjs'
|
|
|
57
54
|
/**
|
|
58
55
|
* The machine's own result: a {@link DescentMatchResult} positioned by the
|
|
59
56
|
* complete cursor, and with no failure record — that one is tracked per match
|
|
60
|
-
* rather than per frame.
|
|
57
|
+
* rather than per frame. This backend always has a position, so it needs no
|
|
58
|
+
* `null` case.
|
|
61
59
|
*
|
|
62
60
|
* @template T
|
|
63
|
-
* @typedef {
|
|
64
|
-
* readonly ast: AstRuleMeta<T>
|
|
65
|
-
* readonly success: boolean
|
|
66
|
-
* readonly pos: _Cursor
|
|
67
|
-
* }} _Result
|
|
68
|
-
*/
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* The public, physical index of a cursor. Consuming EOF moves the cursor past
|
|
72
|
-
* the physical end, and both cursors report `input.length`.
|
|
73
|
-
*
|
|
74
|
-
* @type {(length: number) => (pos: _Cursor) => number}
|
|
61
|
+
* @typedef {AstResult<CodePointMeta<T>, Cursor>} _Result
|
|
75
62
|
*/
|
|
76
|
-
const physicalIdx = length => pos => Math.min(pos, length)
|
|
77
63
|
|
|
78
64
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* where the cursor still has a symbol, `pos <= cp.length`.
|
|
65
|
+
* A leaf here is a code point with its metadata, so its symbol is the first
|
|
66
|
+
* half. This is the only thing {@link symbolAt} needs to know about a leaf.
|
|
82
67
|
*
|
|
83
|
-
* @type {<T>(
|
|
68
|
+
* @type {<T>(leaf: CodePointMeta<T>) => number}
|
|
84
69
|
*/
|
|
85
|
-
const
|
|
70
|
+
const symbolOf = ([symbol]) => symbol
|
|
86
71
|
|
|
87
|
-
|
|
88
|
-
* What consuming the symbol at a cursor contributes to the AST: the code point
|
|
89
|
-
* with its metadata, and nothing for the synthesized EOF — it has no physical
|
|
90
|
-
* source element.
|
|
91
|
-
*
|
|
92
|
-
* @type {<T>(cp: readonly CodePointMeta<T>[], pos: _Cursor) => AstSequenceMeta<T>}
|
|
93
|
-
*/
|
|
94
|
-
const leafAt = (cp, pos) => pos < cp.length ? [cp[pos]] : []
|
|
72
|
+
const symbolAtCp = symbolAt(symbolOf)
|
|
95
73
|
|
|
96
74
|
/**
|
|
97
75
|
* Folds one rejected terminal into the furthest-failure record: further along
|
|
@@ -99,7 +77,7 @@ const leafAt = (cp, pos) => pos < cp.length ? [cp[pos]] : []
|
|
|
99
77
|
* discarded. The comparison is on the complete cursor, so a failure after EOF
|
|
100
78
|
* was consumed is further than one at the same physical index before it.
|
|
101
79
|
*
|
|
102
|
-
* @type {(failure: _Failure, pos:
|
|
80
|
+
* @type {(failure: _Failure, pos: Cursor, terminal: TerminalRange) => _Failure}
|
|
103
81
|
*/
|
|
104
82
|
const recordFailure = (failure, pos, terminal) => {
|
|
105
83
|
if (pos > failure.pos) { return { pos, expected: [terminal] } }
|
|
@@ -108,21 +86,15 @@ const recordFailure = (failure, pos, terminal) => {
|
|
|
108
86
|
}
|
|
109
87
|
|
|
110
88
|
/**
|
|
111
|
-
* Creates a recursive descent parser
|
|
112
|
-
* code point.
|
|
89
|
+
* Creates a recursive descent parser from an already materialized
|
|
90
|
+
* {@link RuleSet}, preserving metadata for each consumed code point.
|
|
113
91
|
*
|
|
114
92
|
* @template T
|
|
115
|
-
* @param {
|
|
93
|
+
* @param {RuleSet} ruleSet
|
|
116
94
|
* @returns {DescentMatch<T>}
|
|
117
95
|
*/
|
|
118
|
-
export const
|
|
119
|
-
const
|
|
120
|
-
const emptyTags = emptyTagMap(data[0])
|
|
121
|
-
|
|
122
|
-
/** @type {(tag: AstTag, sequence: AstSequenceMeta<T>, pos: _Cursor) => _Result<T>} */
|
|
123
|
-
const mrSuccess = (tag, sequence, pos) => ({ ast: {tag, sequence}, success: true, pos })
|
|
124
|
-
/** @type {(tag: AstTag, sequence: AstSequenceMeta<T>, pos: _Cursor) => _Result<T>} */
|
|
125
|
-
const mrFail = (tag, sequence, pos) => ({ ast: {tag, sequence}, success: false, pos })
|
|
96
|
+
export const descentParserRuleSet = ruleSet => {
|
|
97
|
+
const emptyTags = emptyTagMap(ruleSet)
|
|
126
98
|
|
|
127
99
|
// A suspended sequence match: items[itemIndex] is being matched by the current
|
|
128
100
|
// task; `seq` holds the ASTs of the items already matched.
|
|
@@ -132,8 +104,8 @@ export const descentParser = fr => {
|
|
|
132
104
|
* readonly tag: AstTag
|
|
133
105
|
* readonly items: Sequence
|
|
134
106
|
* readonly itemIndex: number
|
|
135
|
-
* readonly startPos:
|
|
136
|
-
* readonly seq:
|
|
107
|
+
* readonly startPos: Cursor
|
|
108
|
+
* readonly seq: AstSequence<CodePointMeta<T>>
|
|
137
109
|
* }} _SeqFrame
|
|
138
110
|
*/
|
|
139
111
|
|
|
@@ -145,12 +117,30 @@ export const descentParser = fr => {
|
|
|
145
117
|
* readonly kind: 'variant'
|
|
146
118
|
* readonly entries: readonly (readonly [string, string])[]
|
|
147
119
|
* readonly entryIndex: number
|
|
148
|
-
* readonly pos:
|
|
120
|
+
* readonly pos: Cursor
|
|
149
121
|
* readonly emptyResult: _Result<T>
|
|
150
122
|
* }} _VariantFrame
|
|
151
123
|
*/
|
|
152
124
|
|
|
153
|
-
|
|
125
|
+
// A suspended repetition: `item` is being matched by the current task for the
|
|
126
|
+
// round that began at `roundStart`, and `items` holds the ASTs of the rounds
|
|
127
|
+
// that already completed. They accumulate as a list rather than an array
|
|
128
|
+
// because a repetition is as long as its input: appending to an array per
|
|
129
|
+
// round would copy the whole prefix each time and make one repetition
|
|
130
|
+
// quadratic in the number of items it matched.
|
|
131
|
+
/**
|
|
132
|
+
* @typedef {{
|
|
133
|
+
* readonly kind: 'repeat'
|
|
134
|
+
* readonly tag: AstTag
|
|
135
|
+
* readonly item: string
|
|
136
|
+
* readonly items: _Items
|
|
137
|
+
* readonly roundStart: Cursor
|
|
138
|
+
* }} _RepeatFrame
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
/** @typedef {List<Ast<CodePointMeta<T>>>} _Items */
|
|
142
|
+
|
|
143
|
+
/** @typedef {_SeqFrame | _VariantFrame | _RepeatFrame} _Frame */
|
|
154
144
|
|
|
155
145
|
// Immutable cons-cell stack: O(1) push/pop, no array copying per step.
|
|
156
146
|
/**
|
|
@@ -164,12 +154,28 @@ export const descentParser = fr => {
|
|
|
164
154
|
// tuple), or null when a result is ready to resume the innermost frame.
|
|
165
155
|
/**
|
|
166
156
|
* @typedef {{
|
|
157
|
+
* readonly kind: 'rule'
|
|
167
158
|
* readonly name: string
|
|
168
159
|
* readonly tag: AstTag
|
|
169
|
-
* readonly pos:
|
|
170
|
-
* }}
|
|
160
|
+
* readonly pos: Cursor
|
|
161
|
+
* }} _RuleTask
|
|
171
162
|
*/
|
|
172
163
|
|
|
164
|
+
// The next round of a repetition, about to be started. Both the rule that
|
|
165
|
+
// introduces a repetition and the frame that finishes one of its rounds go
|
|
166
|
+
// through this, so a round is set up in exactly one place.
|
|
167
|
+
/**
|
|
168
|
+
* @typedef {{
|
|
169
|
+
* readonly kind: 'repeat'
|
|
170
|
+
* readonly tag: AstTag
|
|
171
|
+
* readonly item: string
|
|
172
|
+
* readonly items: _Items
|
|
173
|
+
* readonly pos: Cursor
|
|
174
|
+
* }} _RepeatTask
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
/** @typedef {_RuleTask | _RepeatTask} _Task */
|
|
178
|
+
|
|
173
179
|
// The recursive-descent matcher as an explicit-stack machine: each iteration either
|
|
174
180
|
// starts the current task (pushing a frame for a sequence/variant and descending into
|
|
175
181
|
// its first child) or feeds the pending result into the innermost frame. Semantics are
|
|
@@ -183,7 +189,7 @@ export const descentParser = fr => {
|
|
|
183
189
|
/** @type {_Stack} */
|
|
184
190
|
let stack = null
|
|
185
191
|
/** @type {_Task | null} */
|
|
186
|
-
let task = { name, tag, pos: startPos }
|
|
192
|
+
let task = { kind: 'rule', name, tag, pos: startPos }
|
|
187
193
|
/** @type {_Result<T>} */
|
|
188
194
|
let result = mrFail(undefined, [], startPos)
|
|
189
195
|
// High-water mark across the whole match, so it survives the rewinds a
|
|
@@ -196,16 +202,25 @@ export const descentParser = fr => {
|
|
|
196
202
|
// The explicit annotation cuts a control-flow inference cycle (TS7022):
|
|
197
203
|
// `name`'s narrowed type feeds `rule`, whose type would otherwise feed the
|
|
198
204
|
// later `task` assignments that `name`'s narrowing depends on.
|
|
199
|
-
const
|
|
205
|
+
const current = /** @type {_Task} */ (task)
|
|
200
206
|
task = null
|
|
207
|
+
if (current.kind === 'repeat') {
|
|
208
|
+
// One round of a repetition is one match of its item, so the
|
|
209
|
+
// frame below carries the rounds already collected and the
|
|
210
|
+
// position this round may have to rewind to.
|
|
211
|
+
stack = { top: { kind: 'repeat', tag: current.tag, item: current.item, items: current.items, roundStart: current.pos }, rest: stack }
|
|
212
|
+
task = { kind: 'rule', name: current.item, tag: undefined, pos: current.pos }
|
|
213
|
+
continue
|
|
214
|
+
}
|
|
215
|
+
const { name, tag, pos } = current
|
|
201
216
|
/** @type {DataRule} */
|
|
202
|
-
const rule =
|
|
217
|
+
const rule = ruleSet[name]
|
|
203
218
|
if (typeof rule === 'number') {
|
|
204
219
|
// No nullable case: `emptyTagOf` in `bnf/data` returns `undefined`
|
|
205
220
|
// for every terminal, so `emptyTags[name]` here is always
|
|
206
221
|
// `undefined` and a terminal either consumes one symbol or fails.
|
|
207
222
|
// Past the synthesized EOF there is no symbol left to consume.
|
|
208
|
-
if (pos <= cp.length && rangeContains(...rangeDecode(rule))(
|
|
223
|
+
if (pos <= cp.length && rangeContains(...rangeDecode(rule))(symbolAtCp(cp, pos))) {
|
|
209
224
|
result = mrSuccess(tag, leafAt(cp, pos), pos + 1)
|
|
210
225
|
} else {
|
|
211
226
|
// The only place a terminal is rejected, so the only place
|
|
@@ -218,8 +233,10 @@ export const descentParser = fr => {
|
|
|
218
233
|
result = mrSuccess(tag, [], pos)
|
|
219
234
|
} else {
|
|
220
235
|
stack = { top: { kind: 'seq', tag, items: rule, itemIndex: 0, startPos: pos, seq: [] }, rest: stack }
|
|
221
|
-
task = { name: rule[0], tag: undefined, pos }
|
|
236
|
+
task = { kind: 'rule', name: rule[0], tag: undefined, pos }
|
|
222
237
|
}
|
|
238
|
+
} else if (isRepeat(rule)) {
|
|
239
|
+
task = { kind: 'repeat', tag, item: rule, items: null, pos }
|
|
223
240
|
} else {
|
|
224
241
|
const entries = definedEntries(rule)
|
|
225
242
|
const emptyTag = emptyTags[name]
|
|
@@ -229,7 +246,7 @@ export const descentParser = fr => {
|
|
|
229
246
|
} else {
|
|
230
247
|
stack = { top: { kind: 'variant', entries, entryIndex: 0, pos, emptyResult }, rest: stack }
|
|
231
248
|
const [entryTag, entryName] = entries[0]
|
|
232
|
-
task = { name: entryName, tag: entryTag, pos }
|
|
249
|
+
task = { kind: 'rule', name: entryName, tag: entryTag, pos }
|
|
233
250
|
}
|
|
234
251
|
}
|
|
235
252
|
continue
|
|
@@ -256,11 +273,29 @@ export const descentParser = fr => {
|
|
|
256
273
|
const itemIndex = frame.itemIndex + 1
|
|
257
274
|
if (itemIndex < frame.items.length) {
|
|
258
275
|
stack = { top: { ...frame, itemIndex, seq }, rest: stack }
|
|
259
|
-
task = { name: frame.items[itemIndex], tag: undefined, pos }
|
|
276
|
+
task = { kind: 'rule', name: frame.items[itemIndex], tag: undefined, pos }
|
|
260
277
|
} else {
|
|
261
278
|
result = mrSuccess(frame.tag, seq, pos)
|
|
262
279
|
}
|
|
263
280
|
}
|
|
281
|
+
} else if (frame.kind === 'repeat') {
|
|
282
|
+
if (result.success === false) {
|
|
283
|
+
// A round that fails ends the repetition instead of failing
|
|
284
|
+
// it: the rounds before it stand, and the whole match rewinds
|
|
285
|
+
// to where the failed round began.
|
|
286
|
+
result = mrSuccess(frame.tag, toArray(frame.items), frame.roundStart)
|
|
287
|
+
} else {
|
|
288
|
+
const items = concat(frame.items)([result.ast])
|
|
289
|
+
if (result.pos === frame.roundStart) {
|
|
290
|
+
// A round that consumed nothing would repeat forever. Only
|
|
291
|
+
// a hand-written `repeat` over a nullable rule reaches
|
|
292
|
+
// this — `toData` never derives one — so keep the one
|
|
293
|
+
// empty round and stop.
|
|
294
|
+
result = mrSuccess(frame.tag, toArray(items), result.pos)
|
|
295
|
+
} else {
|
|
296
|
+
task = { kind: 'repeat', tag: frame.tag, item: frame.item, items, pos: result.pos }
|
|
297
|
+
}
|
|
298
|
+
}
|
|
264
299
|
} else {
|
|
265
300
|
// success that consumed input wins immediately: the frame stays popped and
|
|
266
301
|
// `result` propagates to the frame below, matching the recursive `return m`.
|
|
@@ -271,7 +306,7 @@ export const descentParser = fr => {
|
|
|
271
306
|
if (entryIndex < frame.entries.length) {
|
|
272
307
|
stack = { top: { ...frame, entryIndex, emptyResult }, rest: stack }
|
|
273
308
|
const [entryTag, entryName] = frame.entries[entryIndex]
|
|
274
|
-
task = { name: entryName, tag: entryTag, pos: frame.pos }
|
|
309
|
+
task = { kind: 'rule', name: entryName, tag: entryTag, pos: frame.pos }
|
|
275
310
|
} else {
|
|
276
311
|
result = emptyResult
|
|
277
312
|
}
|
|
@@ -287,3 +322,13 @@ export const descentParser = fr => {
|
|
|
287
322
|
|
|
288
323
|
return match
|
|
289
324
|
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Creates a recursive descent parser that preserves metadata for each consumed
|
|
328
|
+
* code point.
|
|
329
|
+
*
|
|
330
|
+
* @template T
|
|
331
|
+
* @param {FRule} fr
|
|
332
|
+
* @returns {DescentMatch<T>}
|
|
333
|
+
*/
|
|
334
|
+
export const descentParser = fr => descentParserRuleSet(toData(fr)[0])
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @module
|
|
3
3
|
*
|
|
4
4
|
* @import { CodePoint } from '../../text/utf16/types.ts'
|
|
5
|
+
* @import { RuleSet } from '../data/types.ts'
|
|
5
6
|
* @import { DescentMatch, CodePointMeta, DescentMatchResult } from './types.ts'
|
|
6
7
|
*/
|
|
7
8
|
export declare const proof: {
|
|
@@ -10,5 +11,6 @@ export declare const proof: {
|
|
|
10
11
|
longInput: (() => void)[];
|
|
11
12
|
descentParserWithMeta: (() => void)[];
|
|
12
13
|
logicalEof: (() => void)[];
|
|
14
|
+
repeat: (() => void)[];
|
|
13
15
|
furthestFailure: (() => void)[];
|
|
14
16
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @module
|
|
3
3
|
*
|
|
4
4
|
* @import { CodePoint } from '../../text/utf16/types.ts'
|
|
5
|
+
* @import { RuleSet } from '../data/types.ts'
|
|
5
6
|
* @import { DescentMatch, CodePointMeta, DescentMatchResult } from './types.ts'
|
|
6
7
|
*/
|
|
7
8
|
|
|
@@ -9,9 +10,9 @@ import { stringToCodePointList } from '../../text/utf16/module.f.mjs'
|
|
|
9
10
|
import { map, toArray } from '../../types/list/module.f.mjs'
|
|
10
11
|
import { commaJoin0Plus, eof, option, range, repeat0Plus, set } from '../module.f.mjs'
|
|
11
12
|
import { emptyTagMap, toData } from '../data/module.f.mjs'
|
|
12
|
-
import { descentParser } from './module.f.mjs'
|
|
13
|
+
import { descentParser, descentParserRuleSet } from './module.f.mjs'
|
|
13
14
|
import { assertEq, assertNotNullish } from '../../asserts/module.f.mjs'
|
|
14
|
-
import { deterministic } from '../testlib.f.mjs'
|
|
15
|
+
import { deterministic, showAst } from '../testlib.f.mjs'
|
|
15
16
|
|
|
16
17
|
/** @type {(cp: CodePoint) => CodePointMeta<unknown>} */
|
|
17
18
|
const mapCodePoint = cp => [cp, undefined]
|
|
@@ -77,11 +78,14 @@ export const proof = {
|
|
|
77
78
|
if (result !== '{"0":"none","3":true,"":true,"r":"none"}') { throw result }
|
|
78
79
|
},
|
|
79
80
|
() => {
|
|
81
|
+
// The whole repetition is one `repeat` rule, and a repetition always
|
|
82
|
+
// matches empty — with no tag, because it is a list of items rather
|
|
83
|
+
// than a choice between branches.
|
|
80
84
|
const repeatRule = repeat0Plus(set(' \n\r\t'))
|
|
81
85
|
const data = toData(repeatRule)
|
|
82
86
|
const emptyTags = emptyTagMap(data[0])
|
|
83
87
|
const result = JSON.stringify(emptyTags)
|
|
84
|
-
|
|
88
|
+
assertEq(result, '{"r":true}')
|
|
85
89
|
}
|
|
86
90
|
],
|
|
87
91
|
descentParser: [
|
|
@@ -254,33 +258,62 @@ export const proof = {
|
|
|
254
258
|
() => {
|
|
255
259
|
const m = descentParser(deterministic())
|
|
256
260
|
|
|
257
|
-
|
|
261
|
+
// A match is pinned by the AST it built, not just by the fact that it
|
|
262
|
+
// matched: the shape is what a repetition changes, and asserting it
|
|
263
|
+
// subsumes success — only a full match produces one. `showAst` writes
|
|
264
|
+
// a node as `tag(children)` with the code points it consumed as one
|
|
265
|
+
// quoted string, `*` for the tagless empty match.
|
|
266
|
+
//
|
|
267
|
+
// The leading three spaces are the `repeat` to read first: they are
|
|
268
|
+
// three siblings of one node here, and three levels of nesting in the
|
|
269
|
+
// LL(1) backend's copy of these expectations.
|
|
270
|
+
/** @type {(s: string, expected: string) => void} */
|
|
271
|
+
const expectAst = (s, expected) => {
|
|
258
272
|
const cp = toArray(stringToCodePointList(s))
|
|
259
273
|
const mr = descentParserCpOnly(m, '', cp)
|
|
260
|
-
|
|
261
|
-
assertEq(
|
|
274
|
+
assertEq(mr.success, true, s)
|
|
275
|
+
assertEq(mr.idx, cp.length, s)
|
|
276
|
+
assertEq(showAst(mr.ast), expected, s)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** @type {(s: string) => void} */
|
|
280
|
+
const expectNoMatch = s => {
|
|
281
|
+
const cp = toArray(stringToCodePointList(s))
|
|
282
|
+
const mr = descentParserCpOnly(m, '', cp)
|
|
283
|
+
assertEq(mr.success && mr.idx === cp.length, false, s)
|
|
262
284
|
}
|
|
263
285
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
286
|
+
expectAst(' true ', '((" "(" ") " "(" ") " "(" ")) "true"(("t") ("r") ("u") ("e")) (" "(" ") " "(" ") " "(" ")))')
|
|
287
|
+
expectAst(' "Hello" ', '((" "(" ") " "(" ") " "(" ")) "string"((("\\"")) ("0x2300005b"("H") "0x5d10ffff"("e") "0x5d10ffff"("l") "0x5d10ffff"("l") "0x5d10ffff"("o")) (("\\""))) (" "(" ") " "(" ") " "(" ")))')
|
|
288
|
+
expectAst(' "Hello\\n\\r\\"" ', '((" "(" ") " "(" ") " "(" ")) "string"((("\\"")) ("0x2300005b"("H") "0x5d10ffff"("e") "0x5d10ffff"("l") "0x5d10ffff"("l") "0x5d10ffff"("o") "escape"((("\\\\")) "n"("n")) "escape"((("\\\\")) "r"("r")) "escape"((("\\\\")) "\\""("\\""))) (("\\""))) (" "(" ") " "(" ") " "(" ")))')
|
|
289
|
+
expectAst(' -56.7e+5 ', '((" "(" ") " "(" ") " "(" ")) "number"("some"(("-")) "onenine"(("5") (("6"))) "some"(((".")) (("7") ())) "some"("e"("e") "+"("+") (("5") ()))) (" "(" ") " "(" ")))')
|
|
290
|
+
expectAst(' [] ', '((" "(" ") " "(" ") " "(" ")) "array"((("[")) () "none"() (("]"))) (" "(" ")))')
|
|
291
|
+
expectAst(' {} ', '((" "(" ") " "(" ") " "(" ")) "object"((("{")) () "none"() (("}"))) (" "(" ")))')
|
|
292
|
+
expectAst(' [[[]]] ', '((" "(" ") " "(" ") " "(" ")) "array"((("[")) () "some"(("array"((("[")) () "some"(("array"((("[")) () "none"() (("]"))) ()) "none"()) (("]"))) ()) "none"()) (("]"))) (" "(" ")))')
|
|
293
|
+
expectAst(' [1] ', '((" "(" ") " "(" ") " "(" ")) "array"((("[")) () "some"(("number"("none"() "onenine"(("1") ()) "none"() "none"()) ()) "none"()) (("]"))) (" "(" ")))')
|
|
294
|
+
expectAst(' [ 12, false, "a"] ', '((" "(" ") " "(" ") " "(" ")) "array"((("[")) (" "(" ")) "some"(("number"("none"() "onenine"(("1") (("2"))) "none"() "none"()) ()) "some"(((((",")) (" "(" "))) ("false"(("f") ("a") ("l") ("s") ("e")) ())) "some"(((((",")) (" "(" "))) ("string"((("\\"")) ("0x5d10ffff"("a")) (("\\""))) ())) "none"()))) (("]"))) (" "(" ") " "(" ")))')
|
|
295
|
+
expectAst(' { "q": [ 12, false, [{"b" : "c"}], "a"] } ', '((" "(" ") " "(" ") " "(" ")) "object"((("{")) (" "(" ")) "some"(((((("\\"")) ("0x5d10ffff"("q")) (("\\""))) () ((":")) (" "(" ")) "array"((("[")) (" "(" ")) "some"(("number"("none"() "onenine"(("1") (("2"))) "none"() "none"()) ()) "some"(((((",")) (" "(" "))) ("false"(("f") ("a") ("l") ("s") ("e")) ())) "some"(((((",")) (" "(" "))) ("array"((("[")) () "some"(("object"((("{")) () "some"(((((("\\"")) ("0x5d10ffff"("b")) (("\\""))) (" "(" ")) ((":")) (" "(" ")) "string"((("\\"")) ("0x5d10ffff"("c")) (("\\"")))) ()) "none"()) (("}"))) ()) "none"()) (("]"))) ())) "some"(((((",")) (" "(" "))) ("string"((("\\"")) ("0x5d10ffff"("a")) (("\\""))) ())) "none"())))) (("]")))) (" "(" "))) "none"()) (("}"))) (" "(" ") " "(" ")))')
|
|
296
|
+
expectAst(' { "q": [ 12, false, [{}], "a"] } ', '((" "(" ") " "(" ") " "(" ")) "object"((("{")) (" "(" ")) "some"(((((("\\"")) ("0x5d10ffff"("q")) (("\\""))) () ((":")) (" "(" ")) "array"((("[")) (" "(" ")) "some"(("number"("none"() "onenine"(("1") (("2"))) "none"() "none"()) ()) "some"(((((",")) (" "(" "))) ("false"(("f") ("a") ("l") ("s") ("e")) ())) "some"(((((",")) (" "(" "))) ("array"((("[")) () "some"(("object"((("{")) () "none"() (("}"))) ()) "none"()) (("]"))) ())) "some"(((((",")) (" "(" "))) ("string"((("\\"")) ("0x5d10ffff"("a")) (("\\""))) ())) "none"())))) (("]")))) (" "(" "))) "none"()) (("}"))) (" "(" ") " "(" ")))')
|
|
297
|
+
expectAst(' [{ "q": [ 12, false, [{}], "a"] }] ', '((" "(" ") " "(" ") " "(" ")) "array"((("[")) () "some"(("object"((("{")) (" "(" ")) "some"(((((("\\"")) ("0x5d10ffff"("q")) (("\\""))) () ((":")) (" "(" ")) "array"((("[")) (" "(" ")) "some"(("number"("none"() "onenine"(("1") (("2"))) "none"() "none"()) ()) "some"(((((",")) (" "(" "))) ("false"(("f") ("a") ("l") ("s") ("e")) ())) "some"(((((",")) (" "(" "))) ("array"((("[")) () "some"(("object"((("{")) () "none"() (("}"))) ()) "none"()) (("]"))) ())) "some"(((((",")) (" "(" "))) ("string"((("\\"")) ("0x5d10ffff"("a")) (("\\""))) ())) "none"())))) (("]")))) (" "(" "))) "none"()) (("}"))) ()) "none"()) (("]"))) (" "(" ") " "(" ")))')
|
|
298
|
+
|
|
299
|
+
expectNoMatch(' tr2ue ')
|
|
300
|
+
expectNoMatch(' true" ')
|
|
301
|
+
expectNoMatch(' "Hello ')
|
|
302
|
+
expectNoMatch(' h-56.7e+5 ')
|
|
303
|
+
expectNoMatch(' -56.7e+5 3')
|
|
304
|
+
expectNoMatch(' [ 12, false2, "a"] ')
|
|
305
|
+
expectNoMatch(' { "q": [ 12, false, [}], "a"] } ')
|
|
306
|
+
|
|
307
|
+
// The invalid input in detail. A failed match's own index rewound to
|
|
308
|
+
// the start and locates nothing; the furthest failure is the
|
|
309
|
+
// high-water mark, and it lands on the `}` that closes nothing.
|
|
310
|
+
const bad = ' [{ "q": [ 12, false, [}], "a"] }] '
|
|
311
|
+
const badMr = descentParserCpOnly(m, '', toArray(stringToCodePointList(bad)))
|
|
312
|
+
assertEq(badMr.success, false, bad)
|
|
313
|
+
assertEq(badMr.idx, 0, bad)
|
|
314
|
+
const failure = assertNotNullish(badMr.failure)
|
|
315
|
+
assertEq(failure.idx, 25, bad)
|
|
316
|
+
assertEq(bad[failure.idx], '}', bad)
|
|
284
317
|
}
|
|
285
318
|
],
|
|
286
319
|
// Regression for the stack-recursive matcher bug: the matcher used to recurse
|
|
@@ -374,11 +407,12 @@ export const proof = {
|
|
|
374
407
|
},
|
|
375
408
|
() => {
|
|
376
409
|
// Repetition terminates on EOF: consuming it moves the complete
|
|
377
|
-
// cursor, so the repeat makes exactly one round and then
|
|
378
|
-
//
|
|
379
|
-
const
|
|
380
|
-
const
|
|
381
|
-
|
|
410
|
+
// cursor, so the repeat makes exactly one round and then stops —
|
|
411
|
+
// with `idx` alone this would never stop.
|
|
412
|
+
const rule = repeat0Plus(eof)
|
|
413
|
+
const m = descentParser(rule)
|
|
414
|
+
const mr = m(toData(rule)[1], [])
|
|
415
|
+
assertEq(JSON.stringify(mr), '{"ast":{"sequence":[{"sequence":[]}]},"success":true,"idx":0}')
|
|
382
416
|
},
|
|
383
417
|
() => {
|
|
384
418
|
// Backtracking restores the complete cursor: `x` consumes EOF and
|
|
@@ -399,6 +433,58 @@ export const proof = {
|
|
|
399
433
|
assertEq(f.expected[0], range('BB'))
|
|
400
434
|
},
|
|
401
435
|
],
|
|
436
|
+
repeat: [
|
|
437
|
+
() => {
|
|
438
|
+
// The whole repetition is one node holding a flat sequence of the
|
|
439
|
+
// items it matched — not the right-recursive chain of `some`/`none`
|
|
440
|
+
// nodes its functional spelling builds.
|
|
441
|
+
const rule = repeat0Plus(set(' \n\r\t'))
|
|
442
|
+
const m = descentParser(rule)
|
|
443
|
+
const name = toData(rule)[1]
|
|
444
|
+
const mr = descentParserCpOnly(m, name, toArray(stringToCodePointList(' ')))
|
|
445
|
+
assertEq(
|
|
446
|
+
JSON.stringify(mr),
|
|
447
|
+
`{"ast":{"sequence":[{"tag":" ","sequence":[[${cp1(' ')},null]]},{"tag":" ","sequence":[[${cp1(' ')},null]]}]},"success":true,"idx":2}`)
|
|
448
|
+
},
|
|
449
|
+
() => {
|
|
450
|
+
// Zero items is a match, and the node is empty rather than tagged.
|
|
451
|
+
const rule = repeat0Plus(set(' \n\r\t'))
|
|
452
|
+
const m = descentParser(rule)
|
|
453
|
+
const mr = m(toData(rule)[1], [])
|
|
454
|
+
assertEq(JSON.stringify(mr), '{"ast":{"sequence":[]},"success":true,"idx":0}')
|
|
455
|
+
},
|
|
456
|
+
() => {
|
|
457
|
+
// A round that fails ends the repetition rather than failing it: the
|
|
458
|
+
// rounds before it stand and the match stops where the failed one
|
|
459
|
+
// began.
|
|
460
|
+
const rule = repeat0Plus(set(' \n\r\t'))
|
|
461
|
+
const m = descentParser(rule)
|
|
462
|
+
const name = toData(rule)[1]
|
|
463
|
+
const mr = descentParserCpOnly(m, name, toArray(stringToCodePointList(' x')))
|
|
464
|
+
assertEq(
|
|
465
|
+
JSON.stringify(mr),
|
|
466
|
+
`{"ast":{"sequence":[{"tag":" ","sequence":[[${cp1(' ')},null]]}]},"success":true,"idx":1}`)
|
|
467
|
+
},
|
|
468
|
+
() => {
|
|
469
|
+
// `toData` never folds a nullable item into a `repeat`, but a
|
|
470
|
+
// hand-written rule set can hold one. A round that consumes nothing
|
|
471
|
+
// would repeat forever, so it is kept once and ends the repetition.
|
|
472
|
+
/** @type {RuleSet} */
|
|
473
|
+
const ruleSet = {
|
|
474
|
+
repeated: 'optionalA',
|
|
475
|
+
optionalA: { some: 'a', none: 'e' },
|
|
476
|
+
a: range('aa'),
|
|
477
|
+
e: [],
|
|
478
|
+
}
|
|
479
|
+
const m = descentParserRuleSet(ruleSet)
|
|
480
|
+
assertEq(
|
|
481
|
+
JSON.stringify(m('repeated', [])),
|
|
482
|
+
'{"ast":{"sequence":[{"tag":"none","sequence":[]}]},"success":true,"idx":0}')
|
|
483
|
+
assertEq(
|
|
484
|
+
JSON.stringify(m('repeated', [[cp1('a'), null]])),
|
|
485
|
+
`{"ast":{"sequence":[{"tag":"some","sequence":[[${cp1('a')},null]]},{"tag":"none","sequence":[]}]},"success":true,"idx":1}`)
|
|
486
|
+
},
|
|
487
|
+
],
|
|
402
488
|
furthestFailure: [
|
|
403
489
|
() => {
|
|
404
490
|
// A branch rejected *before* the high-water mark must not pull it back:
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for the recursive descent matcher backend.
|
|
3
3
|
*
|
|
4
|
+
* The AST it builds is the shared one, over leaves that carry metadata:
|
|
5
|
+
* `Ast<CodePointMeta<T>>` from [`../matcher`](../matcher). What is declared
|
|
6
|
+
* here is what belongs to *this* backend — the metadata-carrying leaf, the
|
|
7
|
+
* diagnostics a backtracking matcher can report, and its public result.
|
|
8
|
+
*
|
|
4
9
|
* @module
|
|
5
10
|
*/
|
|
6
11
|
import type { CodePoint } from '../../text/utf16/types.ts';
|
|
12
|
+
import type { Ast, AstTag } from '../matcher/types.ts';
|
|
7
13
|
import type { TerminalRange } from '../types.ts';
|
|
8
|
-
export type AstTag = string | true | undefined;
|
|
9
14
|
/**
|
|
10
15
|
* Recursive descent matcher for a single named rule, starting from `startPos` —
|
|
11
16
|
* a position that counts the synthesized end-of-input symbol, unlike the public
|
|
@@ -53,7 +58,7 @@ export type DescentFailure = {
|
|
|
53
58
|
* stopped just before it.
|
|
54
59
|
*/
|
|
55
60
|
export type DescentMatchResult<T> = {
|
|
56
|
-
readonly ast:
|
|
61
|
+
readonly ast: Ast<CodePointMeta<T>>;
|
|
57
62
|
readonly success: boolean;
|
|
58
63
|
readonly idx: number;
|
|
59
64
|
readonly failure?: DescentFailure;
|
|
@@ -63,17 +68,10 @@ export type DescentMatchResult<T> = {
|
|
|
63
68
|
*/
|
|
64
69
|
export type DescentMatch<T> = (name: string, s: readonly CodePointMeta<T>[]) => DescentMatchResult<T>;
|
|
65
70
|
/**
|
|
66
|
-
* Code point value paired with metadata.
|
|
71
|
+
* Code point value paired with metadata: this backend's AST leaf.
|
|
72
|
+
*
|
|
73
|
+
* Preserving metadata per consumed code point is what distinguishes this
|
|
74
|
+
* backend, so the leaf type stays here rather than moving to the shared layer,
|
|
75
|
+
* which is parameterized by it.
|
|
67
76
|
*/
|
|
68
77
|
export type CodePointMeta<T> = readonly [CodePoint, T];
|
|
69
|
-
/**
|
|
70
|
-
* AST sequence for the metadata-aware parser.
|
|
71
|
-
*/
|
|
72
|
-
export type AstSequenceMeta<T> = readonly (AstRuleMeta<T> | CodePointMeta<T>)[];
|
|
73
|
-
/**
|
|
74
|
-
* Metadata-aware AST node.
|
|
75
|
-
*/
|
|
76
|
-
export type AstRuleMeta<T> = {
|
|
77
|
-
readonly tag: AstTag;
|
|
78
|
-
readonly sequence: AstSequenceMeta<T>;
|
|
79
|
-
};
|