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
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution semantics of `vm` — one section per operand shape, since that is
|
|
3
|
+
* how `map`'s handlers are built (`o1`/`o2`/`o2lazy`), plus the nodes that
|
|
4
|
+
* evaluate their operands themselves (`,`, `[]`, `{}`, and the four chain
|
|
5
|
+
* nodes — `()`, `.`, `?.`, and `?.()`). Nothing is `todo` any more.
|
|
6
|
+
* This is the executing counterpart of `../proof.f.mjs`, which pins what the
|
|
7
|
+
* schema *accepts*; nothing here validates.
|
|
8
|
+
*
|
|
9
|
+
* @import { Exp } from '../types.ts'
|
|
10
|
+
* @import { Context } from './types.ts'
|
|
11
|
+
* @import { Assert } from '../../asserts/types.ts'
|
|
12
|
+
* @import { Equal } from '../../types/ts/types.ts'
|
|
13
|
+
* @import { Array as ExpArray, Call, Dot, Index, Op1, Op2 } from '../types.ts'
|
|
14
|
+
* @import { Get } from './types.ts'
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { assert, assertEq, assertStructurallySame } from '../../asserts/module.f.mjs'
|
|
18
|
+
import { vm } from './module.f.mjs'
|
|
19
|
+
|
|
20
|
+
// `TagMap` exists so a dispatcher generic over `K` sees one handler
|
|
21
|
+
// signature; these pin the tag -> node-tuple correlation it is built on,
|
|
22
|
+
// including the tags whose node kinds are not `op1`/`op2`.
|
|
23
|
+
/** @typedef {Assert<Equal<Get<'+'>, Op2>>} _PlusIsOp2 */
|
|
24
|
+
/** @typedef {Assert<Equal<Get<'neg'>, Op1>>} _NegIsOp1 */
|
|
25
|
+
/** @typedef {Assert<Equal<Get<'[]'>, ExpArray>>} _BracketsIsArray */
|
|
26
|
+
/** @typedef {Assert<Equal<Get<'()'>, Call>>} _CallIsCall */
|
|
27
|
+
/** @typedef {Assert<Equal<Get<'.'>, Dot>>} _DotIsDot */
|
|
28
|
+
|
|
29
|
+
/** @type {Context} */
|
|
30
|
+
const context = { frame: { x: 1 }, args: [10, 20] }
|
|
31
|
+
|
|
32
|
+
/** @type {(e: Exp) => unknown} */
|
|
33
|
+
const ev = e => vm(context)(e)
|
|
34
|
+
|
|
35
|
+
/** `ev` composed with `assertEq`, the shape almost every case below has. */
|
|
36
|
+
/** @type {(e: Exp, expected: unknown) => void} */
|
|
37
|
+
const eq = (e, expected) => { assertEq(ev(e), expected) }
|
|
38
|
+
|
|
39
|
+
/** The same for a value built by the node rather than passed through it. */
|
|
40
|
+
/** @type {(e: Exp, expected: unknown) => void} */
|
|
41
|
+
const same = (e, expected) => { assertStructurallySame(ev(e), expected) }
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* An operand that throws when evaluated — `null['x']` — so a case can claim
|
|
45
|
+
* "this operand is *not* evaluated" by the test simply passing. The
|
|
46
|
+
* `throw` section calls its forced counterparts.
|
|
47
|
+
* @type {Exp}
|
|
48
|
+
*/
|
|
49
|
+
const boom = ['.', null, 'x', null]
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The same, in a naming position: an `index` is a `string`, a `number`, or a
|
|
53
|
+
* `Number` cast, so this is how a skipped step's *index* is made observable.
|
|
54
|
+
* @type {Index}
|
|
55
|
+
*/
|
|
56
|
+
const boomIndex = ['Number', boom]
|
|
57
|
+
|
|
58
|
+
/** `['undefined']`, the node — the nullish input every guard is about. @type {Exp} */
|
|
59
|
+
const undef = ['undefined']
|
|
60
|
+
|
|
61
|
+
/** `[]` as an argument list, for a call whose arguments are beside the point. @type {Exp} */
|
|
62
|
+
const noArgs = ['[]', []]
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* `a => a` with an empty frame — the smallest callable, used wherever a
|
|
66
|
+
* case is about the call and not about what the callee computes.
|
|
67
|
+
* @type {Exp}
|
|
68
|
+
*/
|
|
69
|
+
const identity = ['=>', ['[]', []], ['.', ['args'], 0, null]]
|
|
70
|
+
|
|
71
|
+
/** `(...a) => a` — hands back the whole argument array. @type {Exp} */
|
|
72
|
+
const argsNode = ['=>', ['[]', []], ['args']]
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* `() => (a => a)` — one call away from `identity`, so a chain can spend a
|
|
76
|
+
* call step and still have something to call.
|
|
77
|
+
* @type {Exp}
|
|
78
|
+
*/
|
|
79
|
+
const constIdentity = ['=>', ['[]', []], identity]
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* `{ id: a => a, args: (...a) => a, f: () => (a => a), o: { id: a => a } }`
|
|
83
|
+
* — a receiver for the property steps, holding a callee at depth one and at
|
|
84
|
+
* depth two. Its methods are `=>` closures, so none of them can observe the
|
|
85
|
+
* `this` a step hands over; `chain.receiver` uses a host method for that.
|
|
86
|
+
* @type {Exp}
|
|
87
|
+
*/
|
|
88
|
+
const methods = ['{}', [
|
|
89
|
+
[':', 'id', identity],
|
|
90
|
+
[':', 'args', argsNode],
|
|
91
|
+
[':', 'f', constIdentity],
|
|
92
|
+
[':', 'o', ['{}', [[':', 'id', identity]]]],
|
|
93
|
+
]]
|
|
94
|
+
|
|
95
|
+
/** `() => methods` — a chain starting with a call step needs one. @type {Exp} */
|
|
96
|
+
const constMethods = ['=>', ['[]', []], methods]
|
|
97
|
+
|
|
98
|
+
export const proof = {
|
|
99
|
+
// The non-`Array` side of `vm`'s only branch: a primitive is its own
|
|
100
|
+
// value, returned without ever reaching `map`.
|
|
101
|
+
primitive: () => {
|
|
102
|
+
eq(1, 1)
|
|
103
|
+
eq('a', 'a')
|
|
104
|
+
eq(null, null)
|
|
105
|
+
eq(true, true)
|
|
106
|
+
eq(false, false)
|
|
107
|
+
eq(1n, 1n)
|
|
108
|
+
},
|
|
109
|
+
// `op0` — the three handlers that read `context` instead of operands.
|
|
110
|
+
// `undefined` is a node, not the bare value (see `Primitive`).
|
|
111
|
+
op0: () => {
|
|
112
|
+
eq(undef, undefined)
|
|
113
|
+
eq(['frame'], context.frame)
|
|
114
|
+
eq(['args'], context.args)
|
|
115
|
+
// ... and `context` is threaded, not defaulted: another one is seen.
|
|
116
|
+
/** @type {Context} */
|
|
117
|
+
const other = { frame: 'f', args: [] }
|
|
118
|
+
assertEq(vm(other)(['frame']), 'f')
|
|
119
|
+
assertStructurallySame(vm(other)(['args']), [])
|
|
120
|
+
},
|
|
121
|
+
// `o1` — one evaluated operand.
|
|
122
|
+
op1: () => {
|
|
123
|
+
eq(['!', 0], true)
|
|
124
|
+
eq(['!', 1], false)
|
|
125
|
+
eq(['~', 0], -1)
|
|
126
|
+
eq(['neg', 5], -5)
|
|
127
|
+
eq(['Number', '42'], 42)
|
|
128
|
+
eq(['String', 42], '42')
|
|
129
|
+
},
|
|
130
|
+
// `o2` — both operands evaluated. Each case asserts a *value*, which is
|
|
131
|
+
// what pins the whole group against `o2` wrapping another `(c, e) =>`
|
|
132
|
+
// around `o2lazy`: that returns the handler uncalled, so every binary
|
|
133
|
+
// operator would evaluate to a function rather than to a result.
|
|
134
|
+
op2: () => {
|
|
135
|
+
eq(['+', 2, 3], 5)
|
|
136
|
+
eq(['-', 2, 3], -1)
|
|
137
|
+
eq(['*', 2, 3], 6)
|
|
138
|
+
eq(['/', 6, 3], 2)
|
|
139
|
+
eq(['%', 7, 3], 1)
|
|
140
|
+
eq(['**', 2, 3], 8)
|
|
141
|
+
eq(['===', 2, 2], true)
|
|
142
|
+
eq(['!==', 2, 3], true)
|
|
143
|
+
eq(['<', 2, 3], true)
|
|
144
|
+
eq(['<=', 3, 3], true)
|
|
145
|
+
eq(['>', 2, 3], false)
|
|
146
|
+
eq(['>=', 3, 3], true)
|
|
147
|
+
eq(['&', 6, 3], 2)
|
|
148
|
+
eq(['|', 6, 3], 7)
|
|
149
|
+
eq(['^', 6, 3], 5)
|
|
150
|
+
eq(['<<', 1, 3], 8)
|
|
151
|
+
eq(['>>', -8, 1], -4)
|
|
152
|
+
eq(['>>>', -1, 31], 1)
|
|
153
|
+
},
|
|
154
|
+
// `.` with a `null` continuation is the plain read — the receiver it
|
|
155
|
+
// produced is dropped, exactly as reading `a.b` for its value drops it.
|
|
156
|
+
// `own` is the `o2` next to it, and they differ on the prototype chain.
|
|
157
|
+
property: () => {
|
|
158
|
+
eq(['.', ['[]', [1, 2, 3]], 1, null], 2)
|
|
159
|
+
eq(['.', ['{}', [[':', 'a', 7]]], 'a', null], 7)
|
|
160
|
+
// An `index` is a string, a number, or `['Number', exp]`, and all
|
|
161
|
+
// three name a property the same way.
|
|
162
|
+
eq(['.', ['[]', [1, 2, 3]], ['Number', '1'], null], 2)
|
|
163
|
+
eq(['own', ['{}', [[':', 'a', 7]]], 'a'], 7)
|
|
164
|
+
// Absent: no descriptor, so `?.value` is `undefined` rather than a
|
|
165
|
+
// read of `undefined.value`.
|
|
166
|
+
eq(['own', ['{}', []], 'a'], undefined)
|
|
167
|
+
// Inherited: `.` walks the prototype chain and `own` does not —
|
|
168
|
+
// the whole reason `own` is a separate node.
|
|
169
|
+
eq(['own', ['{}', []], 'toString'], undefined)
|
|
170
|
+
// A key that is a string only after JS coercion is not a string key:
|
|
171
|
+
// `own`'s key operand must *evaluate* to one, so `1` is rejected
|
|
172
|
+
// rather than silently reading `'1'` — see `throw.ownNonStringKey`.
|
|
173
|
+
eq(['own', ['{}', [[':', '1', 42]]], '1'], 42)
|
|
174
|
+
assert(typeof ev(['.', ['{}', []], 'toString', null]) === 'function')
|
|
175
|
+
},
|
|
176
|
+
// `o2lazy` — the right operand is a thunk, so these three short-circuit.
|
|
177
|
+
// Each case that claims "not evaluated" uses `boom`, which throws if it
|
|
178
|
+
// is; `throw.forced` calls the same nodes with the other left operand.
|
|
179
|
+
lazy: () => {
|
|
180
|
+
eq(['&&', false, boom], false)
|
|
181
|
+
eq(['&&', true, 7], 7)
|
|
182
|
+
eq(['||', true, boom], true)
|
|
183
|
+
eq(['||', false, 7], 7)
|
|
184
|
+
eq(['??', 0, boom], 0)
|
|
185
|
+
eq(['??', null, 7], 7)
|
|
186
|
+
},
|
|
187
|
+
// `,` — every operand evaluated in order, the last one is the value.
|
|
188
|
+
comma: () => {
|
|
189
|
+
eq([',', [1, 2, 3]], 3)
|
|
190
|
+
// No operands: the `reduce` seed is the result.
|
|
191
|
+
eq([',', []], undefined)
|
|
192
|
+
eq([',', [['+', 1, 1]]], 2)
|
|
193
|
+
},
|
|
194
|
+
// `[]` — an item is spread only when it is an array tagged `'...'`, so
|
|
195
|
+
// all three shapes an item can have appear here: a primitive (not an
|
|
196
|
+
// array), a tagged node that is not a spread, and a spread.
|
|
197
|
+
array: () => {
|
|
198
|
+
same(['[]', []], [])
|
|
199
|
+
same(['[]', [1, ['+', 1, 1], ['...', ['[]', [3, 4]]]]], [1, 2, 3, 4])
|
|
200
|
+
// A spread of an empty array contributes nothing.
|
|
201
|
+
same(['[]', [['...', ['[]', []]], 1]], [1])
|
|
202
|
+
// The operand is *iterated*, not spliced in as one element, so a
|
|
203
|
+
// string contributes its characters — `[...'ab']` is `['a', 'b']`.
|
|
204
|
+
same(['[]', [['...', 'ab']]], ['a', 'b'])
|
|
205
|
+
},
|
|
206
|
+
// `{}` — `:` builds one entry from two evaluated operands, `'...'`
|
|
207
|
+
// takes the own enumerable entries of an evaluated object.
|
|
208
|
+
object: () => {
|
|
209
|
+
same(['{}', []], {})
|
|
210
|
+
same(['{}', [[':', 'a', 1], [':', ['String', 'b'], ['+', 1, 1]]]], { a: 1, b: 2 })
|
|
211
|
+
same(
|
|
212
|
+
['{}', [[':', 'a', 1], ['...', ['{}', [[':', 'b', 2]]]]]],
|
|
213
|
+
{ a: 1, b: 2 },
|
|
214
|
+
)
|
|
215
|
+
// Later entries win, as in JavaScript's own object literal.
|
|
216
|
+
same(['{}', [[':', 'a', 1], ['...', ['{}', [[':', 'a', 2]]]]]], { a: 2 })
|
|
217
|
+
// Object spread takes whatever own enumerable properties the operand
|
|
218
|
+
// has, and most values have none — a nullish one contributes nothing
|
|
219
|
+
// rather than throwing, which is where it parts from array spread.
|
|
220
|
+
same(['{}', [['...', null]]], {})
|
|
221
|
+
same(['{}', [['...', undef]]], {})
|
|
222
|
+
same(['{}', [['...', 1]]], {})
|
|
223
|
+
same(['{}', [['...', true]]], {})
|
|
224
|
+
// ... and a string contributes its indices.
|
|
225
|
+
same(['{}', [['...', 'ab']]], { 0: 'a', 1: 'b' })
|
|
226
|
+
},
|
|
227
|
+
// Operands are evaluated through `vm(context)`, so a node composes with
|
|
228
|
+
// every other node kind and sees the same context at any depth.
|
|
229
|
+
nested: () => {
|
|
230
|
+
eq(['+', ['+', 1, 2], 3], 6)
|
|
231
|
+
eq(['.', ['args'], 1, null], 20)
|
|
232
|
+
eq(['.', ['frame'], 'x', null], 1)
|
|
233
|
+
same(
|
|
234
|
+
['{}', [[':', 'a', ['[]', [['.', ['args'], 0, null], ['neg', 1]]]]]],
|
|
235
|
+
{ a: [10, -1] },
|
|
236
|
+
)
|
|
237
|
+
},
|
|
238
|
+
// `=>` evaluates its *frame* operand and not its body: the value is the
|
|
239
|
+
// captured frame paired with the body graph, which is why a closure can
|
|
240
|
+
// outlive the scope that built it. Here that pair is a host function, so
|
|
241
|
+
// these also pin that representation choice — a `typeof`-`'function'`
|
|
242
|
+
// value the host can call directly, not an inert record.
|
|
243
|
+
lambda: () => {
|
|
244
|
+
const f = ev(identity)
|
|
245
|
+
assertEq(typeof f, 'function')
|
|
246
|
+
assertEq(/**@type {(a: unknown) => unknown}*/(f)(7), 7)
|
|
247
|
+
// Every evaluation builds a fresh closure, as `x => x` does in JS —
|
|
248
|
+
// the `=>` node is shared, the values it produces are not.
|
|
249
|
+
assert(ev(identity) !== ev(identity))
|
|
250
|
+
},
|
|
251
|
+
// `()` — the call with no receiver and no region. A call rebuilds the
|
|
252
|
+
// callee's scope from two places: `frame` comes from the closure, `args`
|
|
253
|
+
// from the call site.
|
|
254
|
+
call: () => {
|
|
255
|
+
eq(['()', identity, ['[]', [7]]], 7)
|
|
256
|
+
// The args operand is one node evaluating to the complete argument
|
|
257
|
+
// array, so `['args']` in the callee *is* that array — not the array
|
|
258
|
+
// wrapped in another one, and not just its first element.
|
|
259
|
+
same(['()', argsNode, ['[]', [5, 6]]], [5, 6])
|
|
260
|
+
same(['()', argsNode, noArgs], [])
|
|
261
|
+
// ... and any node evaluating to an array serves, which is what
|
|
262
|
+
// makes `f(...xs)` need no `...` node: the whole array passes through.
|
|
263
|
+
same(['()', argsNode, ['[]', [1, ['...', ['[]', [2, 3]]]]]], [1, 2, 3])
|
|
264
|
+
// Operands are evaluated in the *caller's* scope, before the callee's
|
|
265
|
+
// exists: the callee expression as much as the arguments.
|
|
266
|
+
eq(['()', ['.', ['[]', [identity]], 0, null], ['[]', [['+', 3, 4]]]], 7)
|
|
267
|
+
},
|
|
268
|
+
// The continuation of a `.` node — `propertyLambda`, the state with a
|
|
269
|
+
// live receiver and no region around it. A step is a function of the
|
|
270
|
+
// chain's current value with its argument elided (`../README.md`,
|
|
271
|
+
// "Chains"), so it can be neither an `exp` nor shared, and the receiver
|
|
272
|
+
// exists only while the chain is being walked. Only the two call steps
|
|
273
|
+
// are here, because only a call spends a receiver.
|
|
274
|
+
chain: {
|
|
275
|
+
// `['|()', exp, null]` — the terminal call step. The value called is
|
|
276
|
+
// the property, and the object it came from is the receiver
|
|
277
|
+
// (`receiver`, below).
|
|
278
|
+
callStep: () => {
|
|
279
|
+
// a.b(...c)
|
|
280
|
+
eq(['.', methods, 'id', ['|()', ['[]', [7]], null]], 7)
|
|
281
|
+
// (a.b.c)(...d) — a plain property path nests, and a non-optional
|
|
282
|
+
// chain means the same parenthesized or not.
|
|
283
|
+
eq(['.', ['.', methods, 'o', null], 'id', ['|()', ['[]', [7]], null]], 7)
|
|
284
|
+
// The args operand is still one node evaluating to the whole
|
|
285
|
+
// argument array: a chain changes what is called, not how it is
|
|
286
|
+
// called.
|
|
287
|
+
same(['.', methods, 'args', ['|()', ['[]', [5, 6]], null]], [5, 6])
|
|
288
|
+
same(['.', methods, 'args', ['|()', noArgs, null]], [])
|
|
289
|
+
// The three `index` forms, in the naming position of the node
|
|
290
|
+
// that owns the call.
|
|
291
|
+
eq(['.', ['[]', [identity]], 0, ['|()', ['[]', [7]], null]], 7)
|
|
292
|
+
eq(['.', ['[]', [identity]], ['Number', '0'], ['|()', ['[]', [7]], null]], 7)
|
|
293
|
+
},
|
|
294
|
+
// `['|?.()', exp, k]` — the guarded call step: it spends the receiver
|
|
295
|
+
// and *opens* a region, so unlike `|()` it carries a continuation.
|
|
296
|
+
// With a non-nullish value it behaves exactly as `|()` does.
|
|
297
|
+
optionCallStep: () => {
|
|
298
|
+
// a.b?.(...c)
|
|
299
|
+
eq(['.', methods, 'id', ['|?.()', ['[]', [7]], null]], 7)
|
|
300
|
+
// a.b?.(...c).d(...e) — the region it opened owns the rest.
|
|
301
|
+
eq(['.', ['{}', [[':', 'g', constMethods]]], 'g', ['|?.()', noArgs,
|
|
302
|
+
['|.', 'id', ['|()', ['[]', [7]], null]]]], 7)
|
|
303
|
+
},
|
|
304
|
+
// ... and the guard is the whole difference: on a nullish value the
|
|
305
|
+
// region opens and immediately short-circuits, so the node is
|
|
306
|
+
// `undefined` rather than a call on nothing — and neither the
|
|
307
|
+
// arguments nor any later step runs.
|
|
308
|
+
optionCallStepSkips: () => {
|
|
309
|
+
eq(['.', ['{}', []], 'absent', ['|?.()', boom, null]], undefined)
|
|
310
|
+
eq(['.', ['{}', [[':', 'b', null]]], 'b', ['|?.()', boom,
|
|
311
|
+
['|.', boomIndex, ['|()', boom, null]]]], undefined)
|
|
312
|
+
},
|
|
313
|
+
// The receiver is what a property step leaves behind, and it is
|
|
314
|
+
// real rather than bookkeeping: `[42].at(0)` is `42` only because
|
|
315
|
+
// `at` is called *on* the array. A `.` node with a `null`
|
|
316
|
+
// continuation computes the same function value and drops it
|
|
317
|
+
// (`throw.detachedReceiver`) — the pair `chainsJs.receiver` makes in
|
|
318
|
+
// JavaScript, made here by the nodes.
|
|
319
|
+
receiver: () => {
|
|
320
|
+
eq(['.', ['[]', [42]], 'at', ['|()', ['[]', [0]], null]], 42)
|
|
321
|
+
eq(['.', ['[]', [42]], 'at', ['|?.()', ['[]', [0]], null]], 42)
|
|
322
|
+
// A call step consumed the receiver of the step before it, so
|
|
323
|
+
// `'ab'.at(0).toUpperCase()` needs a second `.` node to make its
|
|
324
|
+
// own — which is exactly why `|()` is terminal here.
|
|
325
|
+
eq(['.',
|
|
326
|
+
['.', 'ab', 'at', ['|()', ['[]', [0]], null]],
|
|
327
|
+
'toUpperCase',
|
|
328
|
+
['|()', noArgs, null]], 'A')
|
|
329
|
+
},
|
|
330
|
+
throw: {
|
|
331
|
+
// `const at = a.at; at(0)` — the receiver a `.` node keeps for
|
|
332
|
+
// the call it owns is exactly what a `null` continuation drops,
|
|
333
|
+
// and the host method is strict, so the detached call throws.
|
|
334
|
+
detachedReceiver: () =>
|
|
335
|
+
ev(['()', ['.', ['[]', [42]], 'at', null], ['[]', [0]]]),
|
|
336
|
+
// `((a.at)(0))(0)` — the same detachment reached through a call
|
|
337
|
+
// node, so the callee is a bare value rather than an accessor.
|
|
338
|
+
// A host method is what makes that observable: an `=>` closure
|
|
339
|
+
// ignores whatever `this` it is handed, so only this spelling
|
|
340
|
+
// catches a receiver *invented* for a bare value — which is
|
|
341
|
+
// what a method-call spelling of `callValue` would do, returning
|
|
342
|
+
// `Array.prototype.at` where JavaScript throws. See `callValue`
|
|
343
|
+
// in `./module.f.mjs`.
|
|
344
|
+
detachedReceiverAfterCall: () =>
|
|
345
|
+
ev(['()',
|
|
346
|
+
['()', ['.', ['[]', [42]], 'at', null], ['[]', [0]]],
|
|
347
|
+
['[]', [0]]]),
|
|
348
|
+
// A step is only as good as what it lands on: a call step onto a
|
|
349
|
+
// value that is not callable reaches the same host `TypeError`
|
|
350
|
+
// as `throw.callNonFunction`, one node earlier.
|
|
351
|
+
callStepOnNonFunction: () =>
|
|
352
|
+
ev(['.', ['{}', [[':', 'a', 1]]], 'a', ['|()', noArgs, null]]),
|
|
353
|
+
// A `.` node guards nothing, so a nullish base throws at the
|
|
354
|
+
// access — the operand-evaluation half of the pair
|
|
355
|
+
// `../proof.f.mjs`'s `chainsJs.throw` cannot state in JavaScript:
|
|
356
|
+
// here the arguments are never reached, where
|
|
357
|
+
// `optionRegion.throw.closeStepOnUndefined` evaluates them and
|
|
358
|
+
// then calls `undefined`.
|
|
359
|
+
propertyOnUndefined: () => ev(['.', undef, 'at', ['|()', noArgs, null]]),
|
|
360
|
+
propertyOnNull: () => ev(['.', null, 'at', ['|()', noArgs, null]]),
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
// `?.` — the node that opens an optional *region*: its own `?.[index]`
|
|
364
|
+
// is the region's first step and the continuation is the rest, so a
|
|
365
|
+
// nullish input makes the node `undefined` instead of running into a
|
|
366
|
+
// call. Every case here has a counterpart under `chain` that throws for
|
|
367
|
+
// exactly that reason.
|
|
368
|
+
optionDot: () => {
|
|
369
|
+
// a?.b — the node's own step, which is the whole node when the
|
|
370
|
+
// continuation is `null`. Reading `a` and skipping the step would
|
|
371
|
+
// evaluate to `a` itself, so these pin the index is applied.
|
|
372
|
+
eq(['?.', ['{}', [[':', 'a', 7]]], 'a', null], 7)
|
|
373
|
+
// A closure is a value like any other — compared by `typeof`, since
|
|
374
|
+
// every evaluation of a `=>` builds a fresh one (see `lambda`).
|
|
375
|
+
assert(typeof ev(['?.', methods, 'id', null]) === 'function')
|
|
376
|
+
same(['?.', ['[]', [1, 2, 3]], 1, null], 2)
|
|
377
|
+
eq(['?.', ['[]', [1, 2, 3]], ['Number', '1'], null], 2)
|
|
378
|
+
// An absent property is `undefined`, not an error: `?.` guards its
|
|
379
|
+
// *input*, never its result.
|
|
380
|
+
eq(['?.', ['{}', []], 'absent', null], undefined)
|
|
381
|
+
// ... and on a nullish input the node is `undefined`, both ways of
|
|
382
|
+
// being nullish.
|
|
383
|
+
eq(['?.', undef, 'a', null], undefined)
|
|
384
|
+
eq(['?.', null, 'a', null], undefined)
|
|
385
|
+
// a?.b.c — `|.` continues the region, handing the receiver on within
|
|
386
|
+
// it, and the steps run when nothing short-circuited.
|
|
387
|
+
eq(['?.', ['{}', [[':', 'o', ['{}', [[':', 'a', 7]]]]]], 'o',
|
|
388
|
+
['|.', 'a', null]], 7)
|
|
389
|
+
// a?.b(...c) — `|()` inherits the region's guard and the receiver
|
|
390
|
+
// survives into it, which is why `?.` owns its call rather than
|
|
391
|
+
// evaluating to a value a `()` node would then have to call:
|
|
392
|
+
// `[42]?.at(0)` is `42` only if `at` is called *on* the array.
|
|
393
|
+
eq(['?.', ['[]', [42]], 'at', ['|()', ['[]', [0]], null]], 42)
|
|
394
|
+
// a?.b?.(...c) — `|?.()` adds its own guard on top of the region's.
|
|
395
|
+
eq(['?.', ['[]', [42]], 'at', ['|?.()', ['[]', [0]], null]], 42)
|
|
396
|
+
// (a?.b)(...c) — `|!()` escapes the region, and keeps the receiver:
|
|
397
|
+
// the parentheses end the chain, they do not detach the reference.
|
|
398
|
+
eq(['?.', ['[]', [42]], 'at', ['|!()', ['[]', [0]], null]], 42)
|
|
399
|
+
// (a?.b.c)(...d) — the same close one property step further in.
|
|
400
|
+
eq(['?.', methods, 'o', ['|.', 'id', ['|!()', ['[]', [7]], null]]], 7)
|
|
401
|
+
// a?.b.c?.(...d) — the guarded call reached through a property step,
|
|
402
|
+
// which is the region handing `optionPropertyLambda` back to itself.
|
|
403
|
+
eq(['?.', methods, 'o', ['|.', 'id', ['|?.()', ['[]', [7]], null]]], 7)
|
|
404
|
+
// a?.b(...c).d(...e) — one region across two calls, the second
|
|
405
|
+
// making its own receiver.
|
|
406
|
+
eq(['?.', ['{}', [[':', 'g', constMethods]]], 'g',
|
|
407
|
+
['|()', noArgs, ['|.', 'id', ['|()', ['[]', [7]], null]]]], 7)
|
|
408
|
+
},
|
|
409
|
+
// `?.()` — the other region-opening node. Its callee is an ordinary
|
|
410
|
+
// expression, so it never carries a receiver; what it owns is the rest
|
|
411
|
+
// of the region, run on the call's result.
|
|
412
|
+
optionCall: () => {
|
|
413
|
+
// f?.(...c)
|
|
414
|
+
eq(['?.()', identity, ['[]', [7]], null], 7)
|
|
415
|
+
// ... and the args operand is one node evaluating to the whole
|
|
416
|
+
// argument array, as everywhere else a call takes one.
|
|
417
|
+
same(['?.()', argsNode, ['[]', [5, 6]], null], [5, 6])
|
|
418
|
+
// f?.(...c)(...d) — `|()` stays inside the region.
|
|
419
|
+
eq(['?.()', constIdentity, noArgs, ['|()', ['[]', [7]], null]], 7)
|
|
420
|
+
// f?.(...c).d(...e) — `|.` makes a receiver for the call after it,
|
|
421
|
+
// which is the receiver chain `../README.md` gives as the reason
|
|
422
|
+
// there is no `.()` node.
|
|
423
|
+
eq(['?.()', constMethods, noArgs,
|
|
424
|
+
['|.', 'id', ['|()', ['[]', [7]], null]]], 7)
|
|
425
|
+
},
|
|
426
|
+
// The short-circuit, which is what the two region-opening nodes exist
|
|
427
|
+
// for: they return rather than throw, so — unlike a `.` node, where
|
|
428
|
+
// every nullish case is a `throw` — the skip is directly observable,
|
|
429
|
+
// operands included.
|
|
430
|
+
optionRegion: {
|
|
431
|
+
skips: () => {
|
|
432
|
+
// u?.b.c is `undefined`, where `(u?.b).c` throws: one region
|
|
433
|
+
// against two nodes (`../README.md`, "Chains"). `boomIndex` as
|
|
434
|
+
// the skipped step's index would throw if the step ran.
|
|
435
|
+
eq(['?.', undef, 'a', ['|.', boomIndex, null]], undefined)
|
|
436
|
+
// u?.b(...c) is `undefined`, where `(u?.b)(...c)` throws — the
|
|
437
|
+
// pair `throw.closeStepOnUndefined` completes. The skipped
|
|
438
|
+
// call's arguments are not evaluated either.
|
|
439
|
+
eq(['?.', undef, 'at', ['|()', boom, null]], undefined)
|
|
440
|
+
// The node's own index is skipped too, which is the operand
|
|
441
|
+
// `../proof.f.mjs`'s `chainsJs.shortCircuit` pins in JavaScript
|
|
442
|
+
// as `u?.[todo()]`.
|
|
443
|
+
eq(['?.', undef, boomIndex, null], undefined)
|
|
444
|
+
eq(['?.', null, boomIndex, ['|.', boomIndex, null]], undefined)
|
|
445
|
+
// A guarded step mid-region short-circuits the same way: here
|
|
446
|
+
// `a.b` is `undefined`, so `|?.()` skips itself and everything
|
|
447
|
+
// after it.
|
|
448
|
+
eq(['?.', ['{}', [[':', 'b', undef]]], 'b',
|
|
449
|
+
['|?.()', boom, ['|.', boomIndex, null]]], undefined)
|
|
450
|
+
// The nullish value need not be the node's own input: a property
|
|
451
|
+
// step reading an absent property produces one mid-region, and
|
|
452
|
+
// the guard after it skips the rest.
|
|
453
|
+
eq(['?.', methods, 'absent', ['|?.()', boom, null]], undefined)
|
|
454
|
+
// f?.(...c) with a nullish `f`: `undefined`, and the arguments
|
|
455
|
+
// are not evaluated. Both ways of being nullish.
|
|
456
|
+
eq(['?.()', undef, boom, null], undefined)
|
|
457
|
+
eq(['?.()', null, boom, null], undefined)
|
|
458
|
+
// ... and the continuation is skipped along with the call.
|
|
459
|
+
eq(['?.()', undef, boom, ['|.', boomIndex, ['|()', boom, null]]],
|
|
460
|
+
undefined)
|
|
461
|
+
},
|
|
462
|
+
throw: {
|
|
463
|
+
// `(u?.b)(...c)` — the one step a short-circuit does *not* skip.
|
|
464
|
+
// The parentheses ended the region, so the `undefined` it
|
|
465
|
+
// produced is what gets called, and that is a throw on every
|
|
466
|
+
// host. It cannot be pinned in JavaScript at all:
|
|
467
|
+
// JavaScriptCore (so `bun test`) carries the short-circuit
|
|
468
|
+
// through the parentheses and answers `undefined`, which is why
|
|
469
|
+
// `../proof.f.mjs`'s `chainsJs.throw.groupedOptionalCall` is
|
|
470
|
+
// commented out. The node denotes the throw regardless — see
|
|
471
|
+
// "Chains" in `../README.md`.
|
|
472
|
+
closeStepOnUndefined: () =>
|
|
473
|
+
ev(['?.', undef, 'at', ['|!()', noArgs, null]]),
|
|
474
|
+
closeStepOnNull: () =>
|
|
475
|
+
ev(['?.', null, 'at', ['|!()', noArgs, null]]),
|
|
476
|
+
// `(u?.b.c)(...d)` — the same, reached past a skipped `|.`: the
|
|
477
|
+
// walk that drops steps has to keep looking for the close rather
|
|
478
|
+
// than stop at the first one it skips.
|
|
479
|
+
closeStepPastSkippedProperty: () =>
|
|
480
|
+
ev(['?.', undef, 'at', ['|.', boomIndex, ['|!()', noArgs, null]]]),
|
|
481
|
+
// `(u?.(...a).c)(...d)` — and it reaches one from the other
|
|
482
|
+
// region-opening node too, through the `|.` that leaves
|
|
483
|
+
// `optionLambda` for `optionPropertyLambda`.
|
|
484
|
+
closeStepAfterOptionCall: () =>
|
|
485
|
+
ev(['?.()', undef, boom, ['|.', boomIndex, ['|!()', noArgs, null]]]),
|
|
486
|
+
// `(a.absent?.(...b).m)(...d)` — and from a `.` node, whose
|
|
487
|
+
// `|?.()` opens a region that short-circuits at once. That is the
|
|
488
|
+
// third and last entry to `skip`, so between them the three cases
|
|
489
|
+
// cover every state a region can be abandoned in.
|
|
490
|
+
closeStepAfterPropertyGuard: () =>
|
|
491
|
+
ev(['.', methods, 'absent',
|
|
492
|
+
['|?.()', boom, ['|.', boomIndex, ['|!()', noArgs, null]]]]),
|
|
493
|
+
// `(a.absent?.(...b))(...d)` — the same short-circuit under a
|
|
494
|
+
// *node* boundary instead of a step: the `.` node evaluates to
|
|
495
|
+
// `undefined` and the `()` over it calls that. The step spelling
|
|
496
|
+
// above and this one are the two halves of the parenthesis law
|
|
497
|
+
// at the same place, and they agree.
|
|
498
|
+
callOfSkippedGuard: () =>
|
|
499
|
+
ev(['()', ['.', methods, 'absent', ['|?.()', boom, null]], noArgs]),
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
// The frame is the only channel outward: a body's leaves are constants,
|
|
503
|
+
// `['args']` and `['frame']`, so a captured value has to arrive as data.
|
|
504
|
+
closure: () => {
|
|
505
|
+
// `['=>', ['[]', [100]], …]` captures `100` at closure-creation time.
|
|
506
|
+
eq(['()', ['=>', ['[]', [100]],
|
|
507
|
+
['+', ['.', ['args'], 0, null], ['.', ['frame'], 0, null]]],
|
|
508
|
+
['[]', [5]]], 105)
|
|
509
|
+
// Nested: the outer call's argument is copied into the inner frame,
|
|
510
|
+
// and the inner body reads it as `['frame']` — the same node
|
|
511
|
+
// `['.', ['args'], 0, null]` could not have been shared across the `=>`.
|
|
512
|
+
const outer = /** @type {Exp} */ ([
|
|
513
|
+
'=>', ['[]', []],
|
|
514
|
+
['=>', ['[]', [['.', ['args'], 0, null]]], ['.', ['frame'], 0, null]],
|
|
515
|
+
])
|
|
516
|
+
eq(['()', ['()', outer, ['[]', [7]]], noArgs], 7)
|
|
517
|
+
// The frame operand is evaluated in the enclosing scope, so it sees
|
|
518
|
+
// that scope's `['args']` — the one place a `=>` node reaches out.
|
|
519
|
+
assertEq(vm({ frame: null, args: [11] })(
|
|
520
|
+
['()', ['=>', ['[]', [['.', ['args'], 0, null]]], ['.', ['frame'], 0, null]],
|
|
521
|
+
noArgs]),
|
|
522
|
+
11)
|
|
523
|
+
},
|
|
524
|
+
// Closures are ordinary values: passable as arguments, returnable, and
|
|
525
|
+
// callable from a node that computed them rather than named them.
|
|
526
|
+
higherOrder: () => {
|
|
527
|
+
// `(g, x) => g(x)`
|
|
528
|
+
const apply = /** @type {Exp} */ ([
|
|
529
|
+
'=>', ['[]', []],
|
|
530
|
+
['()', ['.', ['args'], 0, null], ['[]', [['.', ['args'], 1, null]]]],
|
|
531
|
+
])
|
|
532
|
+
eq(['()', apply, ['[]', [identity, 7]]], 7)
|
|
533
|
+
// `x => y => x + y`, applied twice — the classic case the frame
|
|
534
|
+
// exists for.
|
|
535
|
+
const add = /** @type {Exp} */ ([
|
|
536
|
+
'=>', ['[]', []],
|
|
537
|
+
['=>', ['[]', [['.', ['args'], 0, null]]],
|
|
538
|
+
['+', ['.', ['frame'], 0, null], ['.', ['args'], 0, null]]],
|
|
539
|
+
])
|
|
540
|
+
eq(['()', ['()', add, ['[]', [2]]], ['[]', [3]]], 5)
|
|
541
|
+
},
|
|
542
|
+
throw: {
|
|
543
|
+
// The index of a `?.` whose input is *not* nullish is evaluated, the
|
|
544
|
+
// mirror of `optionRegion.skips`'s skipped operands.
|
|
545
|
+
evaluatedIndex: () => ev(['?.', ['{}', []], boomIndex, null]),
|
|
546
|
+
// ... and so are an optional call's arguments once its callee turns
|
|
547
|
+
// out to be there.
|
|
548
|
+
evaluatedArgument: () => ev(['?.()', identity, boom, null]),
|
|
549
|
+
// `?.()` guards against a *nullish* callee, not against a
|
|
550
|
+
// non-callable one: `1?.()` is the host `TypeError`, exactly as
|
|
551
|
+
// `throw.callNonFunction` is for `()`.
|
|
552
|
+
optionCallOnNonFunction: () =>
|
|
553
|
+
ev(['?.()', ['.', ['{}', [[':', 'a', 1]]], 'a', null], noArgs, null]),
|
|
554
|
+
// An array spread iterates its operand, so a non-iterable one throws
|
|
555
|
+
// where the object form would have contributed nothing.
|
|
556
|
+
arraySpreadOfNumber: () => ev(['[]', [['...', 1]]]),
|
|
557
|
+
arraySpreadOfNull: () => ev(['[]', [['...', null]]]),
|
|
558
|
+
// `own`'s key operand must evaluate to a string, and `ToPropertyKey`
|
|
559
|
+
// coercion is exactly what that rules out.
|
|
560
|
+
ownNonStringKey: () => ev(['own', ['{}', [[':', '1', 42]]], 1]),
|
|
561
|
+
// Not a function: `()` calls whatever the callee operand evaluates
|
|
562
|
+
// to, so this is the host `TypeError`, not a check of its own.
|
|
563
|
+
callNonFunction: () => ev(['()', 1, noArgs]),
|
|
564
|
+
// The other side of `lazy`: with the left operand that does not
|
|
565
|
+
// short-circuit, the thunk *is* forced and `boom` throws. Without
|
|
566
|
+
// these, `o2lazy` returning `a` unconditionally would still pass.
|
|
567
|
+
forcedAnd: () => ev(['&&', true, boom]),
|
|
568
|
+
forcedOr: () => ev(['||', false, boom]),
|
|
569
|
+
forcedCoalesce: () => ev(['??', null, boom]),
|
|
570
|
+
// ... and `o2` forces it with no short-circuit to begin with.
|
|
571
|
+
forcedEager: () => ev(['+', 1, boom]),
|
|
572
|
+
},
|
|
573
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Exp } from '../types.ts';
|
|
2
|
+
export type ExpOp = Extract<Exp, readonly unknown[]>;
|
|
3
|
+
export type Context = {
|
|
4
|
+
readonly frame: unknown;
|
|
5
|
+
readonly args: readonly unknown[];
|
|
6
|
+
};
|
|
7
|
+
type Get0<T extends ExpOp, K extends ExpOp[0]> = T extends readonly [infer Op, ...readonly unknown[]] ? K extends Op ? T : never : never;
|
|
8
|
+
export type TagMap = {
|
|
9
|
+
[K in ExpOp[0]]: Get0<ExpOp, K>;
|
|
10
|
+
};
|
|
11
|
+
export type Map = {
|
|
12
|
+
readonly [K in ExpOp[0]]: (c: Context, r: TagMap[K]) => unknown;
|
|
13
|
+
};
|
|
14
|
+
export type Get<K extends ExpOp[0]> = TagMap[K];
|
|
15
|
+
export {};
|