functionalscript 0.45.0 → 0.46.1
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/rtti/validate/module.f.d.mts +81 -19
- package/fjs/types/rtti/validate/module.f.mjs +107 -61
- package/fjs/types/rtti/validate/proof.f.d.mts +13 -10
- package/fjs/types/rtti/validate/proof.f.mjs +186 -197
- 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/types.d.ts +0 -6
package/fjs/bnf/ll1/proof.f.mjs
CHANGED
|
@@ -1,64 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
3
|
-
*
|
|
2
|
+
* @import { CodePoint } from '../../text/utf16/types.ts'
|
|
4
3
|
* @import { RuleSet } from '../data/types.ts'
|
|
4
|
+
* @import { CodePointMeta } from '../descent/types.ts'
|
|
5
|
+
* @import { Rule as FRule } from '../types.ts'
|
|
5
6
|
* @import { MatchResult } from './types.ts'
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { stringToCodePointList } from '../../text/utf16/module.f.mjs'
|
|
9
|
-
import { toArray } from '../../types/list/module.f.mjs'
|
|
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 { toData } from '../data/module.f.mjs'
|
|
13
|
+
import { descentParser } from '../descent/module.f.mjs'
|
|
12
14
|
import { dispatchMap, parser, parserRuleSet } from './module.f.mjs'
|
|
13
|
-
import { assertEq } from '../../asserts/module.f.mjs'
|
|
14
|
-
import { deterministic } from '../testlib.f.mjs'
|
|
15
|
+
import { assertEq, assertNotNullish } from '../../asserts/module.f.mjs'
|
|
16
|
+
import { deterministic, showAst } from '../testlib.f.mjs'
|
|
17
|
+
|
|
18
|
+
/** @type {(cp: CodePoint) => CodePointMeta<unknown>} */
|
|
19
|
+
const mapCodePoint = cp => [cp, undefined]
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* One grammar matched by both backends, which must build the same AST for it:
|
|
23
|
+
* the shape is pinned once and asserted against each backend, so neither can
|
|
24
|
+
* drift from the shared contract without this failing.
|
|
25
|
+
*
|
|
26
|
+
* @type {(rule: FRule, s: string, expected: string) => () => void}
|
|
27
|
+
*/
|
|
28
|
+
const bothBackends = (rule, s, expected) => () => {
|
|
29
|
+
const [, entry] = toData(rule)
|
|
30
|
+
const cp = toArray(stringToCodePointList(s))
|
|
31
|
+
const dm = descentParser(rule)(entry, toArray(map(mapCodePoint)(cp)))
|
|
32
|
+
assertEq(showAst(dm.ast), expected, s)
|
|
33
|
+
assertEq(showAst(parser(rule)(entry, cp)[0]), expected, s)
|
|
34
|
+
}
|
|
15
35
|
|
|
16
36
|
export const proof = {
|
|
17
37
|
dispatch: [
|
|
18
38
|
() => {
|
|
19
39
|
const terminalRangeRule = range('AF')
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
const dm = dispatchMap(toData(terminalRangeRule)[0])
|
|
41
|
+
assertEq(
|
|
42
|
+
JSON.stringify(dm),
|
|
43
|
+
'{"":{"rangeMap":[[null,64],[{"name":""},70]]}}')
|
|
24
44
|
},
|
|
25
45
|
() => {
|
|
46
|
+
// The sequence's own entry holds its first set only — the first
|
|
47
|
+
// item's, because that item cannot match empty. Nothing is
|
|
48
|
+
// consumed by dispatch, so no chain of follow-up rules is stored.
|
|
26
49
|
const stringRule = 'AB'
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
const dm = dispatchMap(toData(stringRule)[0])
|
|
51
|
+
assertEq(
|
|
52
|
+
JSON.stringify(dm),
|
|
53
|
+
'{"0":{"rangeMap":[[null,64],[{"name":"0"},65]]},"1":{"rangeMap":[[null,65],[{"name":"1"},66]]},"":{"rangeMap":[[null,64],[{"name":"0"},65]]}}')
|
|
31
54
|
},
|
|
32
55
|
() => {
|
|
33
56
|
const a = range('AA')
|
|
34
57
|
const b = range('BB')
|
|
35
58
|
const ab = [a, b]
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
const dm = dispatchMap(toData(ab)[0])
|
|
60
|
+
assertEq(
|
|
61
|
+
JSON.stringify(dm),
|
|
62
|
+
'{"0":{"rangeMap":[[null,64],[{"name":"0"},65]]},"1":{"rangeMap":[[null,65],[{"name":"1"},66]]},"":{"rangeMap":[[null,64],[{"name":"0"},65]]}}')
|
|
40
63
|
},
|
|
41
64
|
() => {
|
|
42
65
|
const emptyRule = ''
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
const result = JSON.stringify(dm)
|
|
46
|
-
if (result !== '{"":{"emptyTag":true,"rangeMap":[]}}') { throw result }
|
|
66
|
+
const dm = dispatchMap(toData(emptyRule)[0])
|
|
67
|
+
assertEq(JSON.stringify(dm), '{"":{"rangeMap":[]}}')
|
|
47
68
|
},
|
|
48
69
|
() => {
|
|
70
|
+
// A variant's entry is the one whose values are read: the branch
|
|
71
|
+
// each lookahead selects, entered before the symbol is consumed.
|
|
49
72
|
const variantRule = { 'a': range('AA'), 'b': range('BB')}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
73
|
+
const dm = dispatchMap(toData(variantRule)[0])
|
|
74
|
+
assertEq(
|
|
75
|
+
JSON.stringify(dm),
|
|
76
|
+
'{"0":{"rangeMap":[[null,64],[{"name":"0"},65]]},"1":{"rangeMap":[[null,65],[{"name":"1"},66]]},"":{"rangeMap":[[null,64],[{"tag":"a","name":"0"},65],[{"tag":"b","name":"1"},66]]}}')
|
|
54
77
|
},
|
|
55
78
|
() => {
|
|
79
|
+
// `empty` is the nullable branch a dispatch miss selects.
|
|
56
80
|
const emptyRule = ''
|
|
57
81
|
const variantRule = { 'e': emptyRule, 'a': range('AA')}
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
82
|
+
const dm = dispatchMap(toData(variantRule)[0])
|
|
83
|
+
assertEq(
|
|
84
|
+
JSON.stringify(dm),
|
|
85
|
+
'{"0":{"rangeMap":[]},"1":{"rangeMap":[[null,64],[{"name":"1"},65]]},"":{"empty":{"tag":"e","name":"0"},"rangeMap":[[null,64],[{"tag":"a","name":"1"},65]]}}')
|
|
62
86
|
},
|
|
63
87
|
() => {
|
|
64
88
|
const emptyRule = ''
|
|
@@ -66,12 +90,14 @@ export const proof = {
|
|
|
66
90
|
const optionalMinusRule = { 'none': emptyRule, 'minus': minursRule}
|
|
67
91
|
const digitRule = range('09')
|
|
68
92
|
const numberRule = [optionalMinusRule, digitRule]
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
93
|
+
const dm = dispatchMap(toData(numberRule)[0])
|
|
94
|
+
assertEq(
|
|
95
|
+
JSON.stringify(dm),
|
|
96
|
+
'{"0":{"empty":{"tag":"none","name":"1"},"rangeMap":[[null,44],[{"tag":"minus","name":"2"},45]]},"1":{"rangeMap":[]},"2":{"rangeMap":[[null,44],[{"name":"2"},45]]},"3":{"rangeMap":[[null,47],[{"name":"3"},57]]},"":{"rangeMap":[[null,44],[{"tag":"minus","name":"2"},45],[null,47],[{"name":"3"},57]]}}')
|
|
73
97
|
},
|
|
74
98
|
() => {
|
|
99
|
+
// A nullable prefix merges each item's first set into the
|
|
100
|
+
// sequence's, up to and including the first non-nullable item.
|
|
75
101
|
const emptyRule = ''
|
|
76
102
|
const spaceRule = range(' ')
|
|
77
103
|
const optionalSpaceRule = { 'none': emptyRule, 'space': spaceRule}
|
|
@@ -79,113 +105,126 @@ export const proof = {
|
|
|
79
105
|
const optionalMinusRule = { 'none': emptyRule, 'minus': minusRule}
|
|
80
106
|
const digitRule = range('09')
|
|
81
107
|
const numberRule = [optionalSpaceRule, optionalMinusRule, digitRule]
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
108
|
+
const dm = dispatchMap(toData(numberRule)[0])
|
|
109
|
+
assertEq(
|
|
110
|
+
JSON.stringify(dm),
|
|
111
|
+
'{"0":{"empty":{"tag":"none","name":"1"},"rangeMap":[[null,31],[{"tag":"space","name":"2"},32]]},"1":{"rangeMap":[]},"2":{"rangeMap":[[null,31],[{"name":"2"},32]]},"3":{"empty":{"tag":"none","name":"1"},"rangeMap":[[null,44],[{"tag":"minus","name":"4"},45]]},"4":{"rangeMap":[[null,44],[{"name":"4"},45]]},"5":{"rangeMap":[[null,47],[{"name":"5"},57]]},"":{"rangeMap":[[null,31],[{"tag":"space","name":"2"},32],[null,44],[{"tag":"minus","name":"4"},45],[null,47],[{"name":"5"},57]]}}')
|
|
112
|
+
},
|
|
113
|
+
() => {
|
|
114
|
+
// A sequence of nullable items only: the first-set loop runs off
|
|
115
|
+
// the end instead of stopping at a non-nullable item.
|
|
116
|
+
/** @type {RuleSet} */
|
|
117
|
+
const ruleSet = {
|
|
118
|
+
s: ['o1', 'o2'],
|
|
119
|
+
o1: { some: 'm', none: 'e' },
|
|
120
|
+
o2: { some: 'a', none: 'e' },
|
|
121
|
+
m: range('--'),
|
|
122
|
+
a: range('AA'),
|
|
123
|
+
e: [],
|
|
124
|
+
}
|
|
125
|
+
assertEq(
|
|
126
|
+
JSON.stringify(dispatchMap(ruleSet)),
|
|
127
|
+
'{"m":{"rangeMap":[[null,44],[{"name":"m"},45]]},"e":{"rangeMap":[]},"o1":{"empty":{"tag":"none","name":"e"},"rangeMap":[[null,44],[{"tag":"some","name":"m"},45]]},"a":{"rangeMap":[[null,64],[{"name":"a"},65]]},"o2":{"empty":{"tag":"none","name":"e"},"rangeMap":[[null,64],[{"tag":"some","name":"a"},65]]},"s":{"rangeMap":[[null,44],[{"tag":"some","name":"m"},45],[null,64],[{"tag":"some","name":"a"},65]]}}')
|
|
128
|
+
assertEq(
|
|
129
|
+
JSON.stringify(parserRuleSet(ruleSet)('s', [45, 65])),
|
|
130
|
+
'[{"sequence":[{"tag":"some","sequence":[45]},{"tag":"some","sequence":[65]}]},true,[]]')
|
|
86
131
|
}
|
|
87
132
|
],
|
|
88
133
|
parser: [
|
|
89
134
|
() => {
|
|
90
135
|
const emptyRule = ''
|
|
91
136
|
const m = parser(emptyRule)
|
|
92
|
-
|
|
93
|
-
const result = JSON.stringify(mr)
|
|
94
|
-
if (result !== '[{"tag":true,"sequence":[]},true,[]]') { throw result }
|
|
137
|
+
assertEq(JSON.stringify(m("", [])), '[{"sequence":[]},true,[]]')
|
|
95
138
|
},
|
|
96
139
|
() => {
|
|
97
140
|
const emptyRule = ''
|
|
98
141
|
const m = parser(emptyRule)
|
|
99
|
-
|
|
100
|
-
const result = JSON.stringify(mr)
|
|
101
|
-
if (result !== '[{"tag":true,"sequence":[]},true,[65,70]]') { throw result }
|
|
142
|
+
assertEq(JSON.stringify(m("", [65, 70])), '[{"sequence":[]},true,[65,70]]')
|
|
102
143
|
},
|
|
103
144
|
() => {
|
|
104
145
|
const terminalRangeRule = range('AF')
|
|
105
146
|
const m = parser(terminalRangeRule)
|
|
106
|
-
|
|
107
|
-
const result = JSON.stringify(mr)
|
|
108
|
-
if (result !== '[{"sequence":[65]},true,[]]') { throw result }
|
|
147
|
+
assertEq(JSON.stringify(m("", [65])), '[{"sequence":[65]},true,[]]')
|
|
109
148
|
},
|
|
110
149
|
() => {
|
|
111
150
|
const terminalRangeRule = 0x000079_000087
|
|
112
151
|
const m = parser(terminalRangeRule)
|
|
113
|
-
|
|
114
|
-
const result = JSON.stringify(mr)
|
|
115
|
-
if (result !== '[{"sequence":[]},false,[64]]') { throw result }
|
|
152
|
+
assertEq(JSON.stringify(m("", [64])), '[{"sequence":[]},false,[64]]')
|
|
116
153
|
},
|
|
117
154
|
() => {
|
|
118
155
|
const terminalRangeRule = 0x000080_000087 //broken range
|
|
119
156
|
const m = parser(terminalRangeRule)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
157
|
+
assertEq(JSON.stringify(m("", [64])), '[{"sequence":[]},false,[64]]')
|
|
158
|
+
},
|
|
159
|
+
() => {
|
|
160
|
+
const variantRule = { 'a': range('AA'), 'b': range('BB')}
|
|
161
|
+
const m = parser(variantRule)
|
|
162
|
+
assertEq(JSON.stringify(m("", [65])), '[{"tag":"a","sequence":[65]},true,[]]')
|
|
123
163
|
},
|
|
124
164
|
() => {
|
|
125
165
|
const variantRule = { 'a': range('AA'), 'b': range('BB')}
|
|
126
166
|
const m = parser(variantRule)
|
|
127
|
-
|
|
128
|
-
const result = JSON.stringify(mr)
|
|
129
|
-
if (result !== '[{"tag":"a","sequence":[65]},true,[]]') { throw result }
|
|
167
|
+
assertEq(JSON.stringify(m("", [64])), '[{"sequence":[]},false,[64]]')
|
|
130
168
|
},
|
|
131
169
|
() => {
|
|
170
|
+
// No branch dispatches on the end of input and none is nullable:
|
|
171
|
+
// the match ran out of input, the `null` remainder.
|
|
132
172
|
const variantRule = { 'a': range('AA'), 'b': range('BB')}
|
|
133
173
|
const m = parser(variantRule)
|
|
134
|
-
|
|
135
|
-
const result = JSON.stringify(mr)
|
|
136
|
-
if (result !== '[{"sequence":[]},false,[64]]') { throw result }
|
|
174
|
+
assertEq(JSON.stringify(m("", [])), '[{"sequence":[]},true,null]')
|
|
137
175
|
},
|
|
138
176
|
() => {
|
|
139
177
|
const emptyRule = ''
|
|
140
178
|
const variantRule = { 'e': emptyRule, 'a': range('AA')}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const result = JSON.stringify(mr)
|
|
144
|
-
if (result !== '[{"tag":"e","sequence":[]},true,[]]') { throw result }
|
|
179
|
+
const m = parser(variantRule)
|
|
180
|
+
assertEq(JSON.stringify(m("", [])), '[{"tag":"e","sequence":[]},true,[]]')
|
|
145
181
|
},
|
|
146
182
|
() => {
|
|
147
183
|
const emptyRule = ''
|
|
148
184
|
const variantRule = { 'e': emptyRule, 'a': range('AA')}
|
|
149
185
|
const m = parser(variantRule)
|
|
150
|
-
|
|
151
|
-
const result = JSON.stringify(mr)
|
|
152
|
-
if (result !== '[{"tag":"e","sequence":[]},true,[64]]') { throw result }
|
|
186
|
+
assertEq(JSON.stringify(m("", [64])), '[{"tag":"e","sequence":[]},true,[64]]')
|
|
153
187
|
},
|
|
154
188
|
() => {
|
|
189
|
+
// Every item of a sequence owns a node — the leading one included.
|
|
155
190
|
const stringRule = 'AB'
|
|
156
191
|
const m = parser(stringRule)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
192
|
+
assertEq(
|
|
193
|
+
JSON.stringify(m("", [65,66])),
|
|
194
|
+
'[{"sequence":[{"sequence":[65]},{"sequence":[66]}]},true,[]]')
|
|
160
195
|
},
|
|
161
196
|
() => {
|
|
197
|
+
// LL(1) never backtracks, so a failure keeps the position where
|
|
198
|
+
// matching stopped: on the rejected `C`.
|
|
162
199
|
const stringRule = 'AB'
|
|
163
200
|
const m = parser(stringRule)
|
|
164
|
-
|
|
165
|
-
const result = JSON.stringify(mr)
|
|
166
|
-
if (result !== '[{"sequence":[]},false,[67]]') { throw result }
|
|
201
|
+
assertEq(JSON.stringify(m("", [65,67])), '[{"sequence":[]},false,[67]]')
|
|
167
202
|
},
|
|
168
203
|
() => {
|
|
204
|
+
// An option that matched empty still owns a node, so the AST says
|
|
205
|
+
// the option was considered.
|
|
169
206
|
const emptyRule = ''
|
|
170
207
|
const minursRule = range('--')
|
|
171
208
|
const optionalMinusRule = { 'none': emptyRule, 'minus': minursRule}
|
|
172
209
|
const digitRule = range('09')
|
|
173
210
|
const numberRule = [optionalMinusRule, digitRule]
|
|
174
211
|
const m = parser(numberRule)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
212
|
+
assertEq(
|
|
213
|
+
JSON.stringify(m("", [50])),
|
|
214
|
+
'[{"sequence":[{"tag":"none","sequence":[]},{"sequence":[50]}]},true,[]]')
|
|
178
215
|
},
|
|
179
216
|
() => {
|
|
217
|
+
// Taken, the option's tag lands on its own node, not the
|
|
218
|
+
// enclosing sequence's.
|
|
180
219
|
const emptyRule = ''
|
|
181
220
|
const minusRule = range('--')
|
|
182
221
|
const optionalMinusRule = { 'none': emptyRule, 'minus': minusRule}
|
|
183
222
|
const digitRule = range('09')
|
|
184
223
|
const numberRule = [optionalMinusRule, digitRule]
|
|
185
224
|
const m = parser(numberRule)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
225
|
+
assertEq(
|
|
226
|
+
JSON.stringify(m("", [45,50])),
|
|
227
|
+
'[{"sequence":[{"tag":"minus","sequence":[45]},{"sequence":[50]}]},true,[]]')
|
|
189
228
|
},
|
|
190
229
|
() => {
|
|
191
230
|
const emptyRule = ''
|
|
@@ -194,9 +233,11 @@ export const proof = {
|
|
|
194
233
|
const digitRule = range('09')
|
|
195
234
|
const numberRule = [optionalMinusRule, digitRule]
|
|
196
235
|
const m = parser(numberRule)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
236
|
+
// The `null` remainder means the match ran out of input; the AST
|
|
237
|
+
// keeps what had matched by then.
|
|
238
|
+
assertEq(
|
|
239
|
+
JSON.stringify(m("", [])),
|
|
240
|
+
'[{"sequence":[{"tag":"none","sequence":[]},{"sequence":[]}]},true,null]')
|
|
200
241
|
},
|
|
201
242
|
() => {
|
|
202
243
|
const m = parser(option('a'))
|
|
@@ -242,51 +283,78 @@ export const proof = {
|
|
|
242
283
|
expect('{a}', true)
|
|
243
284
|
},
|
|
244
285
|
() => {
|
|
245
|
-
const
|
|
286
|
+
const rule = deterministic()
|
|
287
|
+
const [, entry] = toData(rule)
|
|
288
|
+
const md = descentParser(rule)
|
|
289
|
+
const ml = parser(rule)
|
|
246
290
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
291
|
+
// The same inputs as `bnf/descent`'s copy of this group, which
|
|
292
|
+
// pins the AST each one builds. Both backends produce the same
|
|
293
|
+
// AST for the same grammar, so this side asserts equality with
|
|
294
|
+
// `bnf/descent` instead of restating the expectations.
|
|
295
|
+
/** @type {(s: string) => void} */
|
|
296
|
+
const expectSameAst = s => {
|
|
297
|
+
const cp = toArray(stringToCodePointList(s))
|
|
298
|
+
const dm = md(entry, toArray(map(mapCodePoint)(cp)))
|
|
299
|
+
const mr = ml(entry, cp)
|
|
300
|
+
assertEq(mr[1], true, s)
|
|
301
|
+
assertEq(mr[2]?.length, 0, s)
|
|
302
|
+
assertEq(showAst(mr[0]), showAst(dm.ast), s)
|
|
253
303
|
}
|
|
254
304
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
305
|
+
/** @type {(s: string) => void} */
|
|
306
|
+
const expectNoMatch = s => {
|
|
307
|
+
const mr = ml(entry, toArray(stringToCodePointList(s)))
|
|
308
|
+
assertEq(mr[1] && mr[2]?.length === 0, false, s)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
expectSameAst(' true ')
|
|
312
|
+
expectSameAst(' "Hello" ')
|
|
313
|
+
expectSameAst(' "Hello\\n\\r\\"" ')
|
|
314
|
+
expectSameAst(' -56.7e+5 ')
|
|
315
|
+
expectSameAst(' [] ')
|
|
316
|
+
expectSameAst(' {} ')
|
|
317
|
+
expectSameAst(' [[[]]] ')
|
|
318
|
+
expectSameAst(' [1] ')
|
|
319
|
+
expectSameAst(' [ 12, false, "a"] ')
|
|
320
|
+
expectSameAst(' { "q": [ 12, false, [{"b" : "c"}], "a"] } ')
|
|
321
|
+
expectSameAst(' { "q": [ 12, false, [{}], "a"] } ')
|
|
322
|
+
expectSameAst(' [{ "q": [ 12, false, [{}], "a"] }] ')
|
|
323
|
+
|
|
324
|
+
expectNoMatch(' tr2ue ')
|
|
325
|
+
expectNoMatch(' true" ')
|
|
326
|
+
expectNoMatch(' "Hello ')
|
|
327
|
+
expectNoMatch(' h-56.7e+5 ')
|
|
328
|
+
expectNoMatch(' -56.7e+5 3')
|
|
329
|
+
expectNoMatch(' [ 12, false2, "a"] ')
|
|
330
|
+
expectNoMatch(' { "q": [ 12, false, [}], "a"] } ')
|
|
331
|
+
|
|
332
|
+
// The invalid input in detail. This backend never backtracks, so
|
|
333
|
+
// the remainder is where matching stopped: on the `}` that closes
|
|
334
|
+
// nothing, the same position the descent backend reports as its
|
|
335
|
+
// furthest failure.
|
|
336
|
+
const bad = ' [{ "q": [ 12, false, [}], "a"] }] '
|
|
337
|
+
const badMr = ml(entry, toArray(stringToCodePointList(bad)))
|
|
338
|
+
assertEq(badMr[1], false, bad)
|
|
339
|
+
const remainder = assertNotNullish(badMr[2])
|
|
340
|
+
assertEq(bad.length - remainder.length, 25, bad)
|
|
341
|
+
assertEq(bad[25], '}', bad)
|
|
275
342
|
}
|
|
276
343
|
],
|
|
277
344
|
longInput: [
|
|
278
345
|
() => {
|
|
279
|
-
// Long
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
const m = parser(
|
|
283
|
-
const [, success, remainder] = m(
|
|
346
|
+
// Long repetition across the whole input: matched iteratively, so
|
|
347
|
+
// neither the JS call stack nor the frame stack grows with it.
|
|
348
|
+
const rule = repeat0Plus(set(' \n\r\t'))
|
|
349
|
+
const m = parser(rule)
|
|
350
|
+
const [, success, remainder] = m(toData(rule)[1], toArray(stringToCodePointList(' '.repeat(10000))))
|
|
284
351
|
assertEq(success, true)
|
|
285
352
|
assertEq(remainder?.length, 0)
|
|
286
353
|
},
|
|
287
354
|
() => {
|
|
288
|
-
// Deep non-repetition nesting: 5000 bracket levels in the
|
|
289
|
-
// test grammar
|
|
355
|
+
// Deep non-repetition nesting: 5000 bracket levels in the
|
|
356
|
+
// JSON-like test grammar. The frame stack grows on the heap, so
|
|
357
|
+
// depth is bounded by memory rather than the JS call stack.
|
|
290
358
|
const m = parser(deterministic())
|
|
291
359
|
const n = 5000
|
|
292
360
|
const cp = toArray(stringToCodePointList('['.repeat(n) + ']'.repeat(n)))
|
|
@@ -300,7 +368,7 @@ export const proof = {
|
|
|
300
368
|
// EOF dispatches below every ordinary symbol, so its cut point is
|
|
301
369
|
// `-2` — the stored endpoint codes never reach the dispatch map.
|
|
302
370
|
const dm = dispatchMap(toData(eof)[0])
|
|
303
|
-
assertEq(JSON.stringify(dm), '{"":{"rangeMap":[[null,-2],[{"
|
|
371
|
+
assertEq(JSON.stringify(dm), '{"":{"rangeMap":[[null,-2],[{"name":""},-1]]}}')
|
|
304
372
|
},
|
|
305
373
|
() => {
|
|
306
374
|
// The matcher synthesizes one EOF after the physical input, so an
|
|
@@ -319,14 +387,20 @@ export const proof = {
|
|
|
319
387
|
// Non-empty input: the terminal consumes the synthesized EOF after
|
|
320
388
|
// the last code point.
|
|
321
389
|
const m = parser([range('AA'), eof])
|
|
322
|
-
assertEq(JSON.stringify(m('', [65])), '[{"sequence":[65,{"sequence":[]}]},true,[]]')
|
|
390
|
+
assertEq(JSON.stringify(m('', [65])), '[{"sequence":[{"sequence":[65]},{"sequence":[]}]},true,[]]')
|
|
323
391
|
},
|
|
324
392
|
() => {
|
|
325
393
|
// Exactly one EOF is synthesized: the second `eof` terminal has
|
|
326
394
|
// nothing to consume, so the match runs out of input — the `null`
|
|
327
395
|
// remainder this backend reports for that.
|
|
328
396
|
const m = parser([eof, eof])
|
|
329
|
-
assertEq(JSON.stringify(m('', [])), '[{"sequence":[{"sequence":[]}]},true,null]')
|
|
397
|
+
assertEq(JSON.stringify(m('', [])), '[{"sequence":[{"sequence":[]},{"sequence":[]}]},true,null]')
|
|
398
|
+
},
|
|
399
|
+
() => {
|
|
400
|
+
// A variant past the consumed EOF has no symbol to dispatch on:
|
|
401
|
+
// the match runs out of input there too.
|
|
402
|
+
const m = parser([eof, { a: range('AA') }])
|
|
403
|
+
assertEq(JSON.stringify(m('', [])), '[{"sequence":[{"sequence":[]},{"sequence":[]}]},true,null]')
|
|
330
404
|
},
|
|
331
405
|
() => {
|
|
332
406
|
// EOF as one alternative among ordinary terminals.
|
|
@@ -334,56 +408,156 @@ export const proof = {
|
|
|
334
408
|
assertEq(JSON.stringify(m('', [])), '[{"tag":"e","sequence":[]},true,[]]')
|
|
335
409
|
assertEq(JSON.stringify(m('', [65])), '[{"tag":"a","sequence":[65]},true,[]]')
|
|
336
410
|
},
|
|
337
|
-
],
|
|
338
|
-
repeat: [
|
|
339
|
-
() => {
|
|
340
|
-
/** @type {readonly [RuleSet, string]} */
|
|
341
|
-
const repeatData = [{"":["ws","repa"],"ws":[],"repa":["a",""],"a":1090519105},""]
|
|
342
|
-
const dm = dispatchMap(repeatData[0])
|
|
343
|
-
const result = JSON.stringify(dm)
|
|
344
|
-
if (result !== '{"ws":{"emptyTag":true,"rangeMap":[]},"a":{"rangeMap":[[null,64],[{"rules":[]},65]]},"repa":{"rangeMap":[[null,64],[{"rules":[""]},65]]},"":{"rangeMap":[[null,64],[{"rules":[""]},65]]}}') { throw result }
|
|
345
|
-
}
|
|
346
|
-
],
|
|
347
|
-
repeatParser: [
|
|
348
411
|
() => {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
|
|
412
|
+
// Repetition terminates on EOF: consuming it moves the cursor, so
|
|
413
|
+
// the repeat makes exactly one round and then stops — its second
|
|
414
|
+
// round has no symbol left to dispatch on.
|
|
415
|
+
const rule = repeat0Plus(eof)
|
|
416
|
+
const m = parser(rule)
|
|
417
|
+
assertEq(JSON.stringify(m(toData(rule)[1], [])), '[{"sequence":[{"sequence":[]}]},true,[]]')
|
|
355
418
|
},
|
|
356
419
|
() => {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
420
|
+
// Running out of input inside a repetition round ends the whole
|
|
421
|
+
// match, keeping the rounds collected so far.
|
|
422
|
+
const rule = repeat0Plus([eof, eof])
|
|
423
|
+
const m = parser(rule)
|
|
424
|
+
assertEq(
|
|
425
|
+
JSON.stringify(m(toData(rule)[1], [])),
|
|
426
|
+
'[{"sequence":[{"sequence":[{"sequence":[]},{"sequence":[]}]}]},true,null]')
|
|
363
427
|
},
|
|
428
|
+
],
|
|
429
|
+
repeat: [
|
|
364
430
|
() => {
|
|
431
|
+
// A `repeat` rule's entry is its item's first set: the matcher
|
|
432
|
+
// starts one more round exactly while the lookahead is in it, and
|
|
433
|
+
// matches the repetition iteratively into one flat node.
|
|
434
|
+
const rule = repeat0Plus(range('AF'))
|
|
435
|
+
const [ruleSet, entry] = toData(rule)
|
|
436
|
+
assertEq(JSON.stringify(ruleSet[entry]), '"0"')
|
|
437
|
+
assertEq(
|
|
438
|
+
JSON.stringify(dispatchMap(ruleSet)[entry]),
|
|
439
|
+
'{"rangeMap":[[null,64],[{"name":"0"},70]]}')
|
|
440
|
+
},
|
|
441
|
+
() => {
|
|
442
|
+
// A repetition of itself has no first set to dispatch on, and
|
|
443
|
+
// asking for one would not terminate. `toData` never folds such a
|
|
444
|
+
// rule; a hand-written rule set can still hold one, and it can
|
|
445
|
+
// only match zero rounds.
|
|
446
|
+
/** @type {RuleSet} */
|
|
447
|
+
const ruleSet = { repeated: 'repeated' }
|
|
448
|
+
assertEq(
|
|
449
|
+
JSON.stringify(dispatchMap(ruleSet)),
|
|
450
|
+
'{"repeated":{"rangeMap":[]}}')
|
|
451
|
+
const m = parserRuleSet(ruleSet)
|
|
452
|
+
assertEq(JSON.stringify(m('repeated', [])), '[{"sequence":[]},true,[]]')
|
|
453
|
+
assertEq(JSON.stringify(m('repeated', [65])), '[{"sequence":[]},true,[65]]')
|
|
454
|
+
},
|
|
455
|
+
() => {
|
|
456
|
+
// The right-recursive spelling `toData` folds away can still be
|
|
457
|
+
// hand-written; it then dispatches — and nests — as the grammar
|
|
458
|
+
// says, one recursion level per item.
|
|
365
459
|
/** @type {readonly [RuleSet, string]} */
|
|
366
460
|
const repeatData = [{"":["ws","repa"],"ws":[],"repa":["a",""],"a":1090519105},""]
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (result !== '[{"sequence":[65,{"sequence":[65,{"sequence":[65,{"sequence":[]}]}]}]},true,null]') { throw result }
|
|
461
|
+
assertEq(
|
|
462
|
+
JSON.stringify(dispatchMap(repeatData[0])),
|
|
463
|
+
'{"ws":{"rangeMap":[]},"a":{"rangeMap":[[null,64],[{"name":"a"},65]]},"repa":{"rangeMap":[[null,64],[{"name":"a"},65]]},"":{"rangeMap":[[null,64],[{"name":"a"},65]]}}')
|
|
371
464
|
},
|
|
465
|
+
],
|
|
466
|
+
repeatParser: [
|
|
372
467
|
() => {
|
|
468
|
+
// The hand-written right-recursive chain: `""` requires `repa`,
|
|
469
|
+
// which requires `a` and `""` again, so every input eventually
|
|
470
|
+
// runs out — the `null` remainder — and the AST nests one level
|
|
471
|
+
// per matched item because the *grammar* is right-recursive.
|
|
373
472
|
/** @type {readonly [RuleSet, string]} */
|
|
374
473
|
const repeatData = [{"":["ws","repa"],"ws":[],"repa":["a",""],"a":1090519105},""]
|
|
375
474
|
const m = parserRuleSet(repeatData[0])
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
475
|
+
assertEq(
|
|
476
|
+
JSON.stringify(m("", [])),
|
|
477
|
+
'[{"sequence":[{"sequence":[]},{"sequence":[{"sequence":[]}]}]},true,null]')
|
|
478
|
+
const mr1 = m("", [65])
|
|
479
|
+
assertEq(showAst(mr1[0]), '(() (("A") (() (()))))')
|
|
480
|
+
assertEq(mr1[1], true)
|
|
481
|
+
assertEq(mr1[2], null)
|
|
482
|
+
const mr3 = m("", [65,65,65])
|
|
483
|
+
assertEq(showAst(mr3[0]), '(() (("A") (() (("A") (() (("A") (() (()))))))))')
|
|
484
|
+
assertEq(mr3[1], true)
|
|
485
|
+
assertEq(mr3[2], null)
|
|
486
|
+
assertEq(JSON.stringify(m("", [66])), '[{"sequence":[]},false,[66]]')
|
|
487
|
+
},
|
|
380
488
|
],
|
|
489
|
+
// The two backends consume the same `RuleSet` and must agree about the AST
|
|
490
|
+
// it implies: `bnf/descent` builds a node per rule *invocation*, and so
|
|
491
|
+
// does this backend — a rule is entered before its first symbol is
|
|
492
|
+
// consumed, so the dispatch only selects and never absorbs a child's leaf,
|
|
493
|
+
// children or tag into the enclosing node.
|
|
494
|
+
//
|
|
495
|
+
// Each case below is one grammar matched by both backends against one
|
|
496
|
+
// pinned AST, so neither can drift from the shared contract unremarked.
|
|
497
|
+
// These are the cases that used to diverge (see the git history of
|
|
498
|
+
// `../ll1/README.md` for the old shapes); only `variant` always agreed.
|
|
499
|
+
descentEquivalence: {
|
|
500
|
+
// Every item of a sequence owns a node, the leading one included.
|
|
501
|
+
leadingItem: bothBackends(
|
|
502
|
+
[range('AA'), range('BB'), range('CC')], 'ABC',
|
|
503
|
+
'(("A") ("B") ("C"))'),
|
|
504
|
+
// A taken variant tags the branch's own node.
|
|
505
|
+
variant: bothBackends(
|
|
506
|
+
{ a: range('AA'), b: range('BB') }, 'A',
|
|
507
|
+
'"a"("A")'),
|
|
508
|
+
// A nullable item that matched empty still owns a node, so the AST
|
|
509
|
+
// says the option was considered.
|
|
510
|
+
skippedOption: bothBackends(
|
|
511
|
+
[option(range('--')), range('09')], '5',
|
|
512
|
+
'("none"() ("5"))'),
|
|
513
|
+
// Taken, the option's tag lands on its own node, not the enclosing one.
|
|
514
|
+
takenOption: bothBackends(
|
|
515
|
+
[option(range('--')), range('09')], '-5',
|
|
516
|
+
'("some"("-") ("5"))'),
|
|
517
|
+
// A repetition is one flat node of items in both backends.
|
|
518
|
+
repetition: bothBackends(
|
|
519
|
+
repeat0Plus(range('AA')), 'AAA',
|
|
520
|
+
'(("A") ("A") ("A"))'),
|
|
521
|
+
// Grouping survives: `[[A,B],C]` and `[A,B,C]` — the `leadingItem`
|
|
522
|
+
// case above — are distinguishable ASTs.
|
|
523
|
+
nesting: bothBackends(
|
|
524
|
+
[[range('AA'), range('BB')], range('CC')], 'ABC',
|
|
525
|
+
'((("A") ("B")) ("C"))'),
|
|
526
|
+
// A repetition leading a sequence: its first set is inlined into the
|
|
527
|
+
// sequence's dispatch entry at build time, yet it still matches as an
|
|
528
|
+
// iterative rule invocation with a flat node of its own.
|
|
529
|
+
repetitionInNullablePrefix: bothBackends(
|
|
530
|
+
[repeat0Plus(range('AA')), range('09')], 'AA5',
|
|
531
|
+
'((("A") ("A")) ("5"))'),
|
|
532
|
+
// The same repetition matching empty leaves an empty node, not nothing.
|
|
533
|
+
emptyRepetitionInNullablePrefix: bothBackends(
|
|
534
|
+
[repeat0Plus(range('AA')), range('09')], '5',
|
|
535
|
+
'(() ("5"))'),
|
|
536
|
+
},
|
|
381
537
|
throw: {
|
|
382
538
|
ambiguousVariantDispatch: () => {
|
|
383
539
|
// Two alternatives covering the same code point — dispatch merge throws.
|
|
384
540
|
const conflictRule = { 'a': range('AA'), 'b': range('AA') }
|
|
385
541
|
dispatchMap(toData(conflictRule)[0])
|
|
386
|
-
}
|
|
542
|
+
},
|
|
543
|
+
// Left recursion is not LL(1): no lookahead can decide it, and a match
|
|
544
|
+
// would loop at the same position forever, so building throws instead.
|
|
545
|
+
leftRecursiveSequence: () => {
|
|
546
|
+
// `s` reaches itself through its nullable first item.
|
|
547
|
+
/** @type {RuleSet} */
|
|
548
|
+
const ruleSet = {
|
|
549
|
+
s: ['o', 's'],
|
|
550
|
+
o: { some: 'm', none: 'e' },
|
|
551
|
+
m: range('--'),
|
|
552
|
+
e: [],
|
|
553
|
+
}
|
|
554
|
+
dispatchMap(ruleSet)
|
|
555
|
+
},
|
|
556
|
+
leftRecursiveVariant: () => {
|
|
557
|
+
/** @type {RuleSet} */
|
|
558
|
+
const ruleSet = { v: { x: 'v', a: 'a' }, a: range('AA') }
|
|
559
|
+
dispatchMap(ruleSet)
|
|
560
|
+
},
|
|
387
561
|
},
|
|
388
562
|
// Regression: `emptyTagMap` must stay correct for cyclic rule references.
|
|
389
563
|
// A naive single-pass, memoized derivation that marks a rule nullable as
|