functionalscript 0.46.1 → 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/fjs/basen/cbase32/module.f.mjs +18 -10
- package/fjs/basen/cbase32/proof.f.d.mts +3 -0
- package/fjs/basen/cbase32/proof.f.mjs +24 -2
- package/fjs/basen/proof.f.mjs +8 -16
- package/fjs/bnf/ll1/proof.f.d.mts +2 -0
- package/fjs/bnf/ll1/proof.f.mjs +33 -18
- package/fjs/bnf/module.f.d.mts +2 -2
- package/fjs/bnf/module.f.mjs +2 -2
- package/fjs/cas/proof.f.mjs +4 -26
- package/fjs/ci/common/module.f.mjs +6 -0
- package/fjs/ci/config/module.f.d.mts +7 -7
- package/fjs/ci/config/module.f.mjs +7 -7
- package/fjs/cli/module.f.mjs +14 -10
- package/fjs/crypto/vdf/module.f.d.mts +0 -1
- package/fjs/crypto/vdf/module.f.mjs +3 -14
- package/fjs/dev/module.f.mjs +10 -20
- package/fjs/djs/parser/module.f.d.mts +101 -53
- package/fjs/djs/parser/module.f.mjs +732 -525
- package/fjs/djs/parser/proof.f.d.mts +6 -0
- package/fjs/djs/parser/proof.f.mjs +315 -11
- package/fjs/djs/parser/types.d.ts +29 -2
- package/fjs/djs/tokenizer/module.f.mjs +122 -105
- package/fjs/djs/tokenizer/proof.f.mjs +17 -0
- package/fjs/djs/transpiler/module.f.mjs +4 -5
- package/fjs/djs/transpiler/proof.f.d.mts +2 -0
- package/fjs/djs/transpiler/proof.f.mjs +25 -0
- package/fjs/edag/amnesia/module.f.d.mts +20 -0
- package/fjs/edag/amnesia/module.f.mjs +303 -0
- package/fjs/edag/amnesia/proof.f.d.mts +79 -0
- package/fjs/edag/amnesia/proof.f.mjs +573 -0
- package/fjs/edag/amnesia/types.d.ts +15 -0
- package/fjs/edag/module.f.d.mts +410 -0
- package/fjs/edag/module.f.mjs +559 -0
- package/fjs/edag/proof.f.d.mts +116 -0
- package/fjs/edag/proof.f.mjs +686 -0
- package/fjs/edag/types.d.ts +55 -0
- package/fjs/effects/node/memory/module.d.mts +37 -3
- package/fjs/effects/node/memory/module.mjs +38 -4
- package/fjs/effects/node/memory/proof.d.mts +5 -2
- package/fjs/effects/node/memory/proof.mjs +36 -6
- package/fjs/effects/node/module.d.mts +9 -1
- package/fjs/effects/node/module.f.d.mts +39 -0
- package/fjs/effects/node/module.f.mjs +46 -0
- package/fjs/effects/node/module.mjs +234 -32
- package/fjs/effects/node/types.d.ts +28 -2
- package/fjs/effects/node/virtual/module.f.d.mts +19 -10
- package/fjs/effects/node/virtual/module.f.mjs +246 -32
- package/fjs/effects/node/virtual/proof.f.d.mts +17 -2
- package/fjs/effects/node/virtual/proof.f.mjs +232 -8
- package/fjs/effects/node/virtual/types.d.ts +57 -1
- package/fjs/emergent_testing/browser/proof.d.mts +55 -0
- package/fjs/emergent_testing/browser/proof.mjs +352 -0
- package/fjs/emergent_testing/browser/species.proof.d.mts +4 -0
- package/fjs/emergent_testing/browser/species.proof.mjs +45 -0
- package/fjs/emergent_testing/browser.d.mts +79 -0
- package/fjs/emergent_testing/browser.mjs +432 -0
- package/fjs/emergent_testing/proof.f.d.mts +1 -1
- package/fjs/emergent_testing/proof.f.mjs +2 -2
- package/fjs/js/string_escape/module.f.d.mts +47 -0
- package/fjs/js/string_escape/module.f.mjs +95 -0
- package/fjs/js/string_escape/proof.f.d.mts +8 -0
- package/fjs/js/string_escape/proof.f.mjs +68 -0
- package/fjs/js/tokenizer/module.f.mjs +14 -12
- package/fjs/mcp/cas/module.f.d.mts +1 -7
- package/fjs/mcp/cas/module.f.mjs +24 -15
- package/fjs/mcp/evo/module.f.mjs +9 -24
- package/fjs/mcp/module.f.d.mts +3 -1
- package/fjs/mcp/module.f.mjs +4 -2
- package/fjs/media/json/rtti/module.f.d.mts +2 -2
- package/fjs/media/json/rtti/module.f.mjs +1 -1
- package/fjs/media/json/schema/module.f.d.mts +15 -5
- package/fjs/media/json/schema/module.f.mjs +42 -15
- package/fjs/media/json/schema/proof.f.d.mts +10 -1
- package/fjs/media/json/schema/proof.f.mjs +88 -16
- package/fjs/media/json/serializer/module.f.mjs +14 -26
- package/fjs/media/json/types.d.ts +1 -1
- package/fjs/media/lock/module.f.d.mts +8 -3
- package/fjs/media/lock/module.f.mjs +9 -3
- package/fjs/media/lock/proof.f.d.mts +1 -1
- package/fjs/media/lock/proof.f.mjs +2 -2
- package/fjs/media/lock/types.d.ts +1 -3
- package/fjs/media/module.f.d.mts +20 -13
- package/fjs/media/module.f.mjs +29 -16
- package/fjs/media/note/module.f.d.mts +14 -7
- package/fjs/media/note/module.f.mjs +14 -7
- package/fjs/media/note/proof.f.mjs +1 -1
- package/fjs/media/proof.f.mjs +5 -5
- package/fjs/media/revision/module.f.d.mts +8 -2
- package/fjs/media/revision/module.f.mjs +9 -3
- package/fjs/media/revision/proof.f.mjs +1 -1
- package/fjs/media/revision/types.d.ts +1 -1
- package/fjs/media/type/module.f.d.mts +20 -0
- package/fjs/media/type/module.f.mjs +74 -0
- package/fjs/media/type/proof.f.d.mts +5 -0
- package/fjs/media/type/proof.f.mjs +24 -1
- package/fjs/module.f.mjs +6 -0
- package/fjs/path/module.f.d.mts +60 -3
- package/fjs/path/module.f.mjs +138 -14
- package/fjs/path/proof.f.d.mts +3 -0
- package/fjs/path/proof.f.mjs +298 -2
- package/fjs/protocol/json_rpc/module.f.d.mts +65 -28
- package/fjs/protocol/json_rpc/module.f.mjs +49 -13
- package/fjs/protocol/json_rpc/proof.f.d.mts +4 -0
- package/fjs/protocol/json_rpc/proof.f.mjs +29 -0
- package/fjs/protocol/json_rpc/types.d.ts +12 -2
- package/fjs/protocol/mcp/module.f.d.mts +89 -54
- package/fjs/protocol/mcp/module.f.mjs +110 -56
- package/fjs/protocol/mcp/proof.f.d.mts +13 -0
- package/fjs/protocol/mcp/proof.f.mjs +107 -7
- package/fjs/protocol/mcp/stdio/module.f.mjs +3 -4
- package/fjs/protocol/mcp/stdio/proof.f.mjs +2 -2
- package/fjs/protocol/mcp/types.d.ts +14 -2
- package/fjs/text/utf8/module.f.mjs +27 -1
- package/fjs/text/utf8/proof.f.mjs +37 -0
- package/fjs/types/array/module.f.mjs +18 -11
- package/fjs/types/array/proof.f.mjs +27 -1
- package/fjs/types/bigfloat/module.f.d.mts +48 -9
- package/fjs/types/bigfloat/module.f.mjs +147 -59
- package/fjs/types/bigfloat/proof.f.d.mts +2 -0
- package/fjs/types/bigfloat/proof.f.mjs +136 -4
- package/fjs/types/bigfloat/types.d.ts +17 -0
- package/fjs/types/function/module.f.d.mts +11 -2
- package/fjs/types/function/module.f.mjs +20 -2
- package/fjs/types/function/proof.f.mjs +5 -1
- package/fjs/types/phantom/types.d.ts +21 -0
- package/fjs/types/result/module.f.d.mts +4 -4
- package/fjs/types/result/module.f.mjs +2 -2
- package/fjs/types/result/proof.f.d.mts +6 -2
- package/fjs/types/result/proof.f.mjs +17 -1
- package/fjs/types/rtti/common/module.f.d.mts +78 -5
- package/fjs/types/rtti/common/module.f.mjs +168 -3
- package/fjs/types/rtti/common/proof.f.d.mts +14 -0
- package/fjs/types/rtti/common/proof.f.mjs +61 -2
- package/fjs/types/rtti/common/types.d.ts +23 -1
- package/fjs/types/rtti/data/module.f.d.mts +57 -1
- package/fjs/types/rtti/data/module.f.mjs +241 -47
- package/fjs/types/rtti/data/proof.f.d.mts +10 -0
- package/fjs/types/rtti/data/proof.f.mjs +272 -39
- package/fjs/types/rtti/data/types.d.ts +14 -7
- package/fjs/types/rtti/host.proof.d.mts +33 -0
- package/fjs/types/rtti/host.proof.mjs +170 -0
- package/fjs/types/rtti/module.f.d.mts +51 -6
- package/fjs/types/rtti/module.f.mjs +50 -3
- package/fjs/types/rtti/parse/module.f.d.mts +31 -24
- package/fjs/types/rtti/parse/module.f.mjs +197 -59
- package/fjs/types/rtti/parse/proof.f.d.mts +37 -2
- package/fjs/types/rtti/parse/proof.f.mjs +171 -47
- package/fjs/types/rtti/proof.f.d.mts +6 -0
- package/fjs/types/rtti/proof.f.mjs +42 -1
- package/fjs/types/rtti/ts/module.f.d.mts +9 -3
- package/fjs/types/rtti/ts/module.f.mjs +68 -15
- package/fjs/types/rtti/ts/proof.f.d.mts +42 -2
- package/fjs/types/rtti/ts/proof.f.mjs +139 -6
- package/fjs/types/rtti/ts/types.d.ts +164 -16
- package/fjs/types/rtti/types.d.ts +31 -3
- package/fjs/types/rtti/validate/module.f.d.mts +42 -25
- package/fjs/types/rtti/validate/module.f.mjs +187 -53
- package/fjs/types/rtti/validate/proof.f.d.mts +28 -2
- package/fjs/types/rtti/validate/proof.f.mjs +430 -75
- package/fjs/types/ts/types.d.ts +4 -0
- package/fjs/web/module.f.d.mts +115 -0
- package/fjs/web/module.f.mjs +581 -0
- package/fjs/web/proof.f.d.mts +39 -0
- package/fjs/web/proof.f.mjs +413 -0
- package/fjs/web/types.d.ts +39 -0
- package/fjs/website/browser-prepare.d.mts +5 -0
- package/fjs/website/browser-prepare.mjs +115 -0
- package/fjs/website/browser-source.d.mts +37 -0
- package/fjs/website/browser-source.mjs +202 -0
- package/fjs/website/browser-source.proof.d.mts +31 -0
- package/fjs/website/browser-source.proof.mjs +162 -0
- package/fjs/website/browser.d.mts +5 -0
- package/fjs/website/browser.mjs +8 -0
- package/fjs/website/module.f.d.mts +3 -3
- package/fjs/website/module.f.mjs +62 -9
- package/fjs/website/proof.f.d.mts +4 -0
- package/fjs/website/proof.f.mjs +18 -3
- package/package.json +2 -3
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* @import { DjsTokenWithMetadata } from '../tokenizer/types.ts'
|
|
3
3
|
*/
|
|
4
4
|
export declare const proof: {
|
|
5
|
+
parseCorpus: (() => void)[];
|
|
6
|
+
stackSafety: (() => void)[];
|
|
7
|
+
syntaxBeforeSemantic: (() => void)[];
|
|
8
|
+
eofContract: (() => void)[];
|
|
9
|
+
lexicalErrorStreamShape: (() => void)[];
|
|
10
|
+
framingKeywordsAsIdentifiers: (() => void)[];
|
|
5
11
|
valid: (() => void)[];
|
|
6
12
|
computedKey: (() => void)[];
|
|
7
13
|
invalidComputedKey: (() => void)[];
|
|
@@ -9,14 +9,316 @@ import { sort } from '../../types/object/module.f.mjs'
|
|
|
9
9
|
import { stringToList } from '../../text/utf16/module.f.mjs'
|
|
10
10
|
import { stringifyAsTree } from '../serializer/module.f.mjs'
|
|
11
11
|
import { stringify } from '../../media/json/module.f.mjs'
|
|
12
|
-
import { assert, assertEq } from '../../asserts/module.f.mjs'
|
|
12
|
+
import { assert, assertEq, assertStructurallySame } from '../../asserts/module.f.mjs'
|
|
13
13
|
|
|
14
14
|
/** @type {(s: string) => readonly DjsTokenWithMetadata[]} */
|
|
15
15
|
const tokenizeString = s => toArray(tokenize(stringToList(s))(''))
|
|
16
16
|
|
|
17
17
|
const stringifyDjsModule = stringifyAsTree(sort)
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* `count` copies of `element`, comma-joined.
|
|
21
|
+
*
|
|
22
|
+
* Built by repeating the *string*, which allocates the result directly, rather
|
|
23
|
+
* than allocating an array and then writing over it: `Array(n).fill(x)` mutates
|
|
24
|
+
* what it just made, and proof code is held to the same immutability rule as
|
|
25
|
+
* everything else here.
|
|
26
|
+
*
|
|
27
|
+
* @type {(element: string) => (count: number) => string}
|
|
28
|
+
*/
|
|
29
|
+
const repeated = element => count => `${`${element},`.repeat(count - 1)}${element}`
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `count` distinct `k<i>:<i>` properties, comma-joined — the object form of
|
|
33
|
+
* {@link repeated}, where each entry has to differ.
|
|
34
|
+
*
|
|
35
|
+
* @type {(count: number) => string}
|
|
36
|
+
*/
|
|
37
|
+
const numberedMembers = count =>
|
|
38
|
+
Array.from({ length: count }, (_, i) => `k${i}:${i}`).join(',')
|
|
39
|
+
|
|
40
|
+
/** @type {(kind: 'ws' | 'nl' | 'null' | 'true' | 'false' | 'undefined' | 'eof', line: number) => DjsTokenWithMetadata} */
|
|
41
|
+
const proofKind = (kind, line) => ({ token: { kind }, metadata: { path: 'a.js', line, column: 1 } })
|
|
42
|
+
|
|
43
|
+
/** @type {(value: string, line: number) => DjsTokenWithMetadata} */
|
|
44
|
+
const proofId = (value, line) => ({ token: { kind: 'id', value }, metadata: { path: 'a.js', line, column: 1 } })
|
|
45
|
+
|
|
19
46
|
export const proof = {
|
|
47
|
+
// The corpus that proved parity against the hand-written state machine,
|
|
48
|
+
// kept as fixed expectations now that the state machine is gone.
|
|
49
|
+
//
|
|
50
|
+
// Every value below was **recorded from that parser** before it was deleted,
|
|
51
|
+
// not written by hand and not read off the replacement — so these still say
|
|
52
|
+
// "the parser behaves as it always did", which a test written against the
|
|
53
|
+
// new implementation could not.
|
|
54
|
+
parseCorpus: [
|
|
55
|
+
() => {
|
|
56
|
+
for (const [source, expected] of [
|
|
57
|
+
["export default null", "[[],[null]]"],
|
|
58
|
+
["export default true", "[[],[true]]"],
|
|
59
|
+
["export default false", "[[],[false]]"],
|
|
60
|
+
["export default undefined", "[[],[undefined]]"],
|
|
61
|
+
["export default 0.1", "[[],[0.1]]"],
|
|
62
|
+
["export default 1.1e+2", "[[],[110]]"],
|
|
63
|
+
["export default \"abc\"", "[[],[\"abc\"]]"],
|
|
64
|
+
["export default 1234567890n", "[[],[1234567890n]]"],
|
|
65
|
+
["export default []", "[[],[[\"array\",[]]]]"],
|
|
66
|
+
["export default [1]", "[[],[[\"array\",[1]]]]"],
|
|
67
|
+
["export default [1,]", "[[],[[\"array\",[1]]]]"],
|
|
68
|
+
["export default [[]]", "[[],[[\"array\",[[\"array\",[]]]]]]"],
|
|
69
|
+
["export default [0,[1,[2,[]]],3]", "[[],[[\"array\",[0,[\"array\",[1,[\"array\",[2,[\"array\",[]]]]]],3]]]]"],
|
|
70
|
+
["export default [1234567890n]", "[[],[[\"array\",[1234567890n]]]]"],
|
|
71
|
+
["export default {}", "[[],[{}]]"],
|
|
72
|
+
["export default {\"a\":1}", "[[],[{\"a\":1}]]"],
|
|
73
|
+
["export default {a: 1}", "[[],[{\"a\":1}]]"],
|
|
74
|
+
["export default {\"a\":1,}", "[[],[{\"a\":1}]]"],
|
|
75
|
+
["export default {[\"a\"]:1}", "[[],[{\"a\":1}]]"],
|
|
76
|
+
["export default {a:1,\"b\":2,[\"c\"]:3,}", "[[],[{\"a\":1,\"b\":2,\"c\":3}]]"],
|
|
77
|
+
["export default {\"a\":{\"b\":{\"c\":[\"d\"]}}}", "[[],[{\"a\":{\"b\":{\"c\":[\"array\",[\"d\"]]}}}]]"],
|
|
78
|
+
["export default {\"a\":true,\"b\":false,\"c\":null,\"d\":undefined}", "[[],[{\"a\":true,\"b\":false,\"c\":null,\"d\":undefined}]]"],
|
|
79
|
+
["export default {a:1,a:2}", "[[],[{\"a\":2}]]"],
|
|
80
|
+
["export default {[\"__proto__\"]: 1}", "[[],[{\"__proto__\":1}]]"],
|
|
81
|
+
["const a = 1\nexport default a", "[[],[1,[\"cref\",0]]]"],
|
|
82
|
+
["const a = 1\nconst b = 2\nexport default [a,b]", "[[],[1,2,[\"array\",[[\"cref\",0],[\"cref\",1]]]]]"],
|
|
83
|
+
["const a = a\nexport default a", "[[],[[\"cref\",0],[\"cref\",0]]]"],
|
|
84
|
+
["import x from \"m\"\nexport default x", "[[\"m\"],[[\"aref\",0]]]"],
|
|
85
|
+
["import x from \"m\"\nconst a = 1\nexport default a", "[[\"m\"],[1,[\"cref\",0]]]"],
|
|
86
|
+
["import x from \"m\"\nimport y from \"n\"\nexport default [x,y]", "[[\"m\",\"n\"],[[\"array\",[[\"aref\",0],[\"aref\",1]]]]]"],
|
|
87
|
+
["// c\nexport default 1", "[[],[1]]"],
|
|
88
|
+
["/* c */ export default 1", "[[],[1]]"],
|
|
89
|
+
["\n\n export default 1 \n\n", "[[],[1]]"],
|
|
90
|
+
["const export = 1\nexport default export", "[[],[1,[\"cref\",0]]]"],
|
|
91
|
+
["export default { from: 2, default: 3 }", "[[],[{\"default\":3,\"from\":2}]]"],
|
|
92
|
+
]) {
|
|
93
|
+
const [tag, value] = parseFromTokens(tokenizeString(source))
|
|
94
|
+
assert(tag === 'ok', [source, tag])
|
|
95
|
+
assertEq(stringifyDjsModule(value), expected, source)
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
() => {
|
|
99
|
+
/** @type {readonly(readonly[string, string, readonly[number, number] | null])[]} */
|
|
100
|
+
const cases = [
|
|
101
|
+
["42", "unexpected token", [1, 1]],
|
|
102
|
+
["", "unexpected end", [1, 1]],
|
|
103
|
+
["export default", "unexpected end", [1, 15]],
|
|
104
|
+
["[1,2]", "unexpected token", [1, 1]],
|
|
105
|
+
["{\"a\":1}", "unexpected token", [1, 1]],
|
|
106
|
+
["export default [", "unexpected end", [1, 17]],
|
|
107
|
+
["export default {", "unexpected end", [1, 17]],
|
|
108
|
+
["const a = 1 export default a", "unexpected token", [1, 13]],
|
|
109
|
+
["export default 1 2", "unexpected token", [1, 18]],
|
|
110
|
+
["export default {a}", "unexpected token", [1, 18]],
|
|
111
|
+
["export default {:1}", "unexpected token", [1, 17]],
|
|
112
|
+
["export default [,]", "unexpected token", [1, 17]],
|
|
113
|
+
["import x from y\nexport default x", "unexpected token", [1, 15]],
|
|
114
|
+
["export x from \"m\"\nexport default 1", "unexpected token", [1, 8]],
|
|
115
|
+
["const = 1\nexport default 1", "unexpected token", [1, 7]],
|
|
116
|
+
["export default {[1]:2}", "unexpected token", [1, 18]],
|
|
117
|
+
["const a = 1;\nexport default a", "unexpected token", [1, 12]],
|
|
118
|
+
["export default 1\nconst b = 2", "unexpected token", [2, 1]],
|
|
119
|
+
["import x from \"m\"", "unexpected end", [1, 18]],
|
|
120
|
+
["const a = 1", "unexpected end", [1, 12]],
|
|
121
|
+
["const a = 1\nconst a = 2\nexport default a", "duplicate id", [2, 7]],
|
|
122
|
+
["import x from \"m\"\nimport x from \"n\"\nexport default x", "duplicate id", [2, 8]],
|
|
123
|
+
["import x from \"m\"\nconst x = 1\nexport default x", "duplicate id", [2, 7]],
|
|
124
|
+
["export default zzz", "const not found", [1, 16]],
|
|
125
|
+
["const a = zzz\nexport default a", "const not found", [1, 11]],
|
|
126
|
+
["export default [zzz]", "const not found", [1, 17]],
|
|
127
|
+
["export default {a: zzz}", "const not found", [1, 20]],
|
|
128
|
+
["export default {__proto__: 1}", "__proto__ requires the computed key form", [1, 17]],
|
|
129
|
+
["export default {\"__proto__\": 1}", "__proto__ requires the computed key form", [1, 17]],
|
|
130
|
+
["import x from \"m\"\nimport x from \"n\"\nimport y from \"o\"\nexport default y", "duplicate id", [2, 8]],
|
|
131
|
+
["const a = 1\nconst a = 2\nconst b = 3\nexport default b", "duplicate id", [2, 7]],
|
|
132
|
+
]
|
|
133
|
+
for (const [source, message, position] of cases) {
|
|
134
|
+
const [tag, value] = parseFromTokens(tokenizeString(source))
|
|
135
|
+
assert(tag === 'error', [source, tag])
|
|
136
|
+
assertEq(value.message, message, source)
|
|
137
|
+
const { metadata } = value
|
|
138
|
+
assertStructurallySame(
|
|
139
|
+
metadata === null ? null : [metadata.line, metadata.column],
|
|
140
|
+
position,
|
|
141
|
+
source)
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
// Wide and deep values, which cost the BNF path a stack frame each until
|
|
146
|
+
// both its traversals were made iterative.
|
|
147
|
+
//
|
|
148
|
+
// A repetition is only *flat* in the AST when `toData` recognizes the
|
|
149
|
+
// right-recursive shape, and nested inside `delimited`'s option scaffolding
|
|
150
|
+
// it does not — so 5,000 siblings are 5,000 levels of tree, and a recursive
|
|
151
|
+
// walk over them overflows exactly as deep nesting would. The state machine
|
|
152
|
+
// was fixed for its own version of this; see `containerStackCost`.
|
|
153
|
+
stackSafety: [
|
|
154
|
+
() => {
|
|
155
|
+
const source = `export default [${repeated('null')(5000)}]`
|
|
156
|
+
const tokens = tokenizeString(source)
|
|
157
|
+
assertEq(parseFromTokens(tokens)[0], 'ok')
|
|
158
|
+
},
|
|
159
|
+
() => {
|
|
160
|
+
const source = `export default [${repeated('{}')(5000)}]`
|
|
161
|
+
const tokens = tokenizeString(source)
|
|
162
|
+
assertEq(parseFromTokens(tokens)[0], 'ok')
|
|
163
|
+
},
|
|
164
|
+
() => {
|
|
165
|
+
const source = `export default ${'['.repeat(5000)}${']'.repeat(5000)}`
|
|
166
|
+
const tokens = tokenizeString(source)
|
|
167
|
+
assertEq(parseFromTokens(tokens)[0], 'ok')
|
|
168
|
+
},
|
|
169
|
+
() => {
|
|
170
|
+
// wide objects walk the member list rather than the element list
|
|
171
|
+
const source = `export default {${numberedMembers(5000)}}`
|
|
172
|
+
const tokens = tokenizeString(source)
|
|
173
|
+
assertEq(parseFromTokens(tokens)[0], 'ok')
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
// A syntax error is reported ahead of a semantic one, wherever each sits.
|
|
177
|
+
//
|
|
178
|
+
// The grammar matches the whole module before the fold runs, so a malformed
|
|
179
|
+
// suffix is found before any name is resolved. The parser this replaced
|
|
180
|
+
// streamed, so it met an unresolved name first and said so. Both are true of
|
|
181
|
+
// the input; they answer different questions about it.
|
|
182
|
+
//
|
|
183
|
+
// This is the design's stated failure-parity bar rather than a departure
|
|
184
|
+
// from it — the error must "identify the furthest relevant token or EOF",
|
|
185
|
+
// and the syntax failure is the furthest relevant token. It is pinned here
|
|
186
|
+
// because it is a *change*, and because the reasoning is not recoverable
|
|
187
|
+
// from the positions alone.
|
|
188
|
+
syntaxBeforeSemantic: [
|
|
189
|
+
() => {
|
|
190
|
+
/** @type {readonly(readonly[string, string, readonly[number, number]])[]} */
|
|
191
|
+
const cases = [
|
|
192
|
+
// was: const not found @1:11
|
|
193
|
+
['const a = missing x', 'unexpected token', [1, 19]],
|
|
194
|
+
// was: const not found @1:11
|
|
195
|
+
['const a = missing', 'unexpected end', [1, 18]],
|
|
196
|
+
// was: const not found @1:16
|
|
197
|
+
['export default missing 1', 'unexpected token', [1, 24]],
|
|
198
|
+
// was: duplicate id @2:7
|
|
199
|
+
['const a = 1\nconst a = 2 x', 'unexpected token', [2, 13]],
|
|
200
|
+
]
|
|
201
|
+
for (const [source, message, position] of cases) {
|
|
202
|
+
const [tag, value] = parseFromTokens(tokenizeString(source))
|
|
203
|
+
assert(tag === 'error', [source, tag])
|
|
204
|
+
assertEq(value.message, message, source)
|
|
205
|
+
const { metadata } = value
|
|
206
|
+
assertStructurallySame(
|
|
207
|
+
metadata === null ? null : [metadata.line, metadata.column],
|
|
208
|
+
position,
|
|
209
|
+
source)
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
() => {
|
|
213
|
+
// with nothing malformed after it, the semantic error is still the
|
|
214
|
+
// one reported — the change is which error wins, not whether names
|
|
215
|
+
// are checked
|
|
216
|
+
const [tag, value] = parseFromTokens(tokenizeString('export default missing'))
|
|
217
|
+
assert(tag === 'error', tag)
|
|
218
|
+
assertEq(value.message, 'const not found')
|
|
219
|
+
assertEq(value.metadata?.column, 16)
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
// The tokenizer's EOF contract, checked through the parser rather than
|
|
223
|
+
// through `splitEof` alone.
|
|
224
|
+
//
|
|
225
|
+
// Two of these are a **deliberate behaviour change at cutover**: the state
|
|
226
|
+
// machine accepts a stream with no `eof`, and one with two, because it only
|
|
227
|
+
// ever asks what the next token is. The BNF path requires exactly one, in
|
|
228
|
+
// final position, because a backend synthesizes its own logical end and a
|
|
229
|
+
// second marker would be a symbol the grammar has no rule for. Neither
|
|
230
|
+
// stream can come from the tokenizer, so this tightens a contract only a
|
|
231
|
+
// hand-built list could break — but it does tighten it, and the cutover
|
|
232
|
+
// should not be where that is discovered.
|
|
233
|
+
eofContract: [
|
|
234
|
+
() => {
|
|
235
|
+
const wellFormed = [
|
|
236
|
+
proofId('export', 1), proofKind('ws', 1), proofId('default', 1),
|
|
237
|
+
proofKind('ws', 1), proofKind('null', 1), proofKind('eof', 1),
|
|
238
|
+
]
|
|
239
|
+
assertEq(parseFromTokens(wellFormed)[0], 'ok')
|
|
240
|
+
},
|
|
241
|
+
() => {
|
|
242
|
+
// no `eof`: the state machine read this as a complete module
|
|
243
|
+
const noEof = [
|
|
244
|
+
proofId('export', 1), proofKind('ws', 1), proofId('default', 1),
|
|
245
|
+
proofKind('ws', 1), proofKind('null', 1),
|
|
246
|
+
]
|
|
247
|
+
const [tag, value] = parseFromTokens(noEof)
|
|
248
|
+
assert(tag === 'error', tag)
|
|
249
|
+
assertEq(value.message, 'missing end-of-input token')
|
|
250
|
+
},
|
|
251
|
+
() => {
|
|
252
|
+
// a second `eof`: likewise invisible to the state machine
|
|
253
|
+
const twoEof = [
|
|
254
|
+
proofId('export', 1), proofKind('ws', 1), proofId('default', 1),
|
|
255
|
+
proofKind('ws', 1), proofKind('null', 1), proofKind('eof', 1), proofKind('eof', 2),
|
|
256
|
+
]
|
|
257
|
+
const [tag, value] = parseFromTokens(twoEof)
|
|
258
|
+
assert(tag === 'error', tag)
|
|
259
|
+
assertEq(value.message, 'end-of-input token is not final')
|
|
260
|
+
},
|
|
261
|
+
() => {
|
|
262
|
+
const [tag, value] = parseFromTokens([])
|
|
263
|
+
assert(tag === 'error', tag)
|
|
264
|
+
assertEq(value.message, 'missing end-of-input token')
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
// A lexical failure ends the token stream at an `error` token and emits no
|
|
268
|
+
// `eof`. `splitEof` reads a missing `eof` that way rather than as a broken
|
|
269
|
+
// tokenizer contract, and reports the error where it happened — so that
|
|
270
|
+
// reading is pinned here against the real tokenizer, alongside the position
|
|
271
|
+
// the current parser reports for the same input.
|
|
272
|
+
lexicalErrorStreamShape: [
|
|
273
|
+
() => {
|
|
274
|
+
const tokens = tokenizeString('const a = "abc')
|
|
275
|
+
assertEq(tokens.length, 1)
|
|
276
|
+
assertEq(tokens[0].token.kind, 'error')
|
|
277
|
+
assertEq(tokens[0].metadata.column, 11)
|
|
278
|
+
},
|
|
279
|
+
() => {
|
|
280
|
+
const [tag, value] = parseFromTokens(tokenizeString('const a = "abc'))
|
|
281
|
+
assert(tag === 'error', tag)
|
|
282
|
+
assertEq(value.metadata?.line, 1)
|
|
283
|
+
assertEq(value.metadata?.column, 11)
|
|
284
|
+
},
|
|
285
|
+
() => {
|
|
286
|
+
const [tag, value] = parseFromTokens(tokenizeString('const a = /* x'))
|
|
287
|
+
assert(tag === 'error', tag)
|
|
288
|
+
assertEq(value.metadata?.column, 15)
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
// None of the framing keywords is reserved: outside the positions that frame
|
|
292
|
+
// a module, the parser accepts them as ordinary identifiers. Pinned here
|
|
293
|
+
// because the BNF replacement gives each its own token symbol, and a grammar
|
|
294
|
+
// whose identifier rule accepted only the `id` symbol would stop parsing
|
|
295
|
+
// every case below without any other test noticing.
|
|
296
|
+
framingKeywordsAsIdentifiers: [
|
|
297
|
+
() => {
|
|
298
|
+
const [tag, value] = parseFromTokens(tokenizeString('const export = 1\nexport default export'))
|
|
299
|
+
assert(tag === 'ok', tag)
|
|
300
|
+
// `export` bound as a const and then referenced: the exported value
|
|
301
|
+
// resolves back to that binding, so the word acted as an identifier
|
|
302
|
+
// in both positions.
|
|
303
|
+
assertStructurallySame(value[1], [1, ['cref', 0]])
|
|
304
|
+
},
|
|
305
|
+
() => {
|
|
306
|
+
const [tag] = parseFromTokens(tokenizeString('const from = 1\nexport default from'))
|
|
307
|
+
assert(tag === 'ok', tag)
|
|
308
|
+
},
|
|
309
|
+
() => {
|
|
310
|
+
const [tag] = parseFromTokens(tokenizeString('const import = 1\nexport default import'))
|
|
311
|
+
assert(tag === 'ok', tag)
|
|
312
|
+
},
|
|
313
|
+
() => {
|
|
314
|
+
const [tag] = parseFromTokens(tokenizeString('export default { from: 2, default: 3 }'))
|
|
315
|
+
assert(tag === 'ok', tag)
|
|
316
|
+
},
|
|
317
|
+
() => {
|
|
318
|
+
const [tag] = parseFromTokens(tokenizeString('export default { export: 1 }'))
|
|
319
|
+
assert(tag === 'ok', tag)
|
|
320
|
+
},
|
|
321
|
+
],
|
|
20
322
|
valid: [
|
|
21
323
|
() => {
|
|
22
324
|
const tokenList = tokenizeString('export default null')
|
|
@@ -501,15 +803,14 @@ export const proof = {
|
|
|
501
803
|
assertEq(obj[1].message, 'unexpected end')
|
|
502
804
|
},
|
|
503
805
|
() => {
|
|
504
|
-
// `parseFromTokens` itself, called with no tokens at all
|
|
806
|
+
// `parseFromTokens` itself, called with no tokens at all. The
|
|
505
807
|
// tokenizer never produces this — it always emits at least an
|
|
506
|
-
// `eof`
|
|
507
|
-
//
|
|
508
|
-
//
|
|
509
|
-
const obj = parseFromTokens(
|
|
808
|
+
// `eof` — but the exported function's contract must still answer.
|
|
809
|
+
// Since the BNF cutover it is named for what is missing rather
|
|
810
|
+
// than for running out of input.
|
|
811
|
+
const obj = parseFromTokens([])
|
|
510
812
|
assert(obj[0] === 'error', obj)
|
|
511
|
-
assertEq(obj[1].message, '
|
|
512
|
-
assertEq(obj[1].metadata, null)
|
|
813
|
+
assertEq(obj[1].message, 'missing end-of-input token')
|
|
513
814
|
},
|
|
514
815
|
],
|
|
515
816
|
errorMetadata: [
|
|
@@ -617,7 +918,10 @@ export const proof = {
|
|
|
617
918
|
const tokenList = tokenizeString('const b = 1 \n import a from "a.f.js" \n export default [a,b]')
|
|
618
919
|
const obj = parseFromTokens(tokenList)
|
|
619
920
|
assert(obj[0] === 'error', obj)
|
|
620
|
-
|
|
921
|
+
// reworded at the BNF cutover: statement ordering is shape now, so
|
|
922
|
+
// a late `import` is a token the grammar cannot use rather than a
|
|
923
|
+
// rule about ordering it could name. The position is unchanged.
|
|
924
|
+
assertEq(obj[1].message, 'unexpected token')
|
|
621
925
|
},
|
|
622
926
|
() => {
|
|
623
927
|
// nothing follows `export default` but trivia
|
|
@@ -793,13 +1097,13 @@ export const proof = {
|
|
|
793
1097
|
containerStackCost: [
|
|
794
1098
|
() => {
|
|
795
1099
|
const [tag, value] = parseFromTokens(tokenizeString(
|
|
796
|
-
`export default [${
|
|
1100
|
+
`export default [${repeated('{}')(20000)}]`))
|
|
797
1101
|
assert(tag === 'ok', tag)
|
|
798
1102
|
assertEq(value[1].length, 1)
|
|
799
1103
|
},
|
|
800
1104
|
() => {
|
|
801
1105
|
const [tag] = parseFromTokens(tokenizeString(
|
|
802
|
-
`export default [${
|
|
1106
|
+
`export default [${repeated('[]')(20000)}]`))
|
|
803
1107
|
assert(tag === 'ok', tag)
|
|
804
1108
|
},
|
|
805
1109
|
() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type-level API for `fjs/djs/parser/module.f.mjs`: the `ParseError` shape
|
|
3
|
-
* `parseFromTokens` reports,
|
|
4
|
-
*
|
|
3
|
+
* `parseFromTokens` reports, the `_ValueToken` subset `tokenToValue` accepts,
|
|
4
|
+
* and the parser layer's token alphabet.
|
|
5
5
|
*
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
@@ -11,6 +11,33 @@ export type ParseError = {
|
|
|
11
11
|
readonly message: string;
|
|
12
12
|
readonly metadata: TokenMetadata | null;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* The words that frame a module, which the grammar has to tell apart from an
|
|
16
|
+
* ordinary identifier.
|
|
17
|
+
*
|
|
18
|
+
* The tokenizer emits all five as `{ kind: 'id' }` with the word in `value`, so
|
|
19
|
+
* a parser layer keyed on `kind` alone would give them the same symbol as any
|
|
20
|
+
* other identifier — and a grammar over that alphabet could not distinguish
|
|
21
|
+
* `export default` from two arbitrary names. They therefore get terminals of
|
|
22
|
+
* their own, which is what a registered alphabet allows: a name's symbol comes
|
|
23
|
+
* from its position in the list, so a name has no length limit.
|
|
24
|
+
*
|
|
25
|
+
* This is the same distinction the hand-written parser makes by comparing
|
|
26
|
+
* `token.value`, lifted from control flow into the alphabet.
|
|
27
|
+
*/
|
|
28
|
+
export type _FramingKeyword = 'import' | 'const' | 'export' | 'default' | 'from';
|
|
29
|
+
/**
|
|
30
|
+
* A token name the parser layer's grammar can name as a terminal: every
|
|
31
|
+
* `DjsToken` kind except `eof`, plus the framing keywords.
|
|
32
|
+
*
|
|
33
|
+
* `eof` is excluded because a BNF backend synthesizes its own logical
|
|
34
|
+
* end-of-input; the tokenizer's physical `eof` token is split off the stream
|
|
35
|
+
* before any name is mapped, so it never reaches this alphabet.
|
|
36
|
+
*
|
|
37
|
+
* The kinds are derived from `DjsToken` rather than listed, so a token kind
|
|
38
|
+
* added there cannot silently go unrepresented at the parser layer.
|
|
39
|
+
*/
|
|
40
|
+
export type _OrdinaryTokenName = Exclude<DjsToken['kind'], 'eof'> | _FramingKeyword;
|
|
14
41
|
/** DJS tokens that carry a directly-usable value. */
|
|
15
42
|
export type _ValueToken = Extract<DjsToken, {
|
|
16
43
|
readonly kind: 'null' | 'false' | 'true' | 'undefined' | 'string' | 'number' | 'bigint';
|