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.
Files changed (178) hide show
  1. package/fjs/basen/cbase32/module.f.mjs +18 -10
  2. package/fjs/basen/cbase32/proof.f.d.mts +3 -0
  3. package/fjs/basen/cbase32/proof.f.mjs +24 -2
  4. package/fjs/basen/proof.f.mjs +8 -16
  5. package/fjs/bnf/ll1/proof.f.d.mts +2 -0
  6. package/fjs/bnf/ll1/proof.f.mjs +33 -18
  7. package/fjs/bnf/module.f.d.mts +2 -2
  8. package/fjs/bnf/module.f.mjs +2 -2
  9. package/fjs/cas/proof.f.mjs +4 -26
  10. package/fjs/ci/common/module.f.mjs +6 -0
  11. package/fjs/ci/config/module.f.d.mts +7 -7
  12. package/fjs/ci/config/module.f.mjs +7 -7
  13. package/fjs/cli/module.f.mjs +14 -10
  14. package/fjs/crypto/vdf/module.f.d.mts +0 -1
  15. package/fjs/crypto/vdf/module.f.mjs +3 -14
  16. package/fjs/dev/module.f.mjs +10 -20
  17. package/fjs/djs/parser/module.f.d.mts +101 -53
  18. package/fjs/djs/parser/module.f.mjs +732 -525
  19. package/fjs/djs/parser/proof.f.d.mts +6 -0
  20. package/fjs/djs/parser/proof.f.mjs +315 -11
  21. package/fjs/djs/parser/types.d.ts +29 -2
  22. package/fjs/djs/tokenizer/module.f.mjs +122 -105
  23. package/fjs/djs/tokenizer/proof.f.mjs +17 -0
  24. package/fjs/djs/transpiler/module.f.mjs +4 -5
  25. package/fjs/djs/transpiler/proof.f.d.mts +2 -0
  26. package/fjs/djs/transpiler/proof.f.mjs +25 -0
  27. package/fjs/edag/amnesia/module.f.d.mts +20 -0
  28. package/fjs/edag/amnesia/module.f.mjs +303 -0
  29. package/fjs/edag/amnesia/proof.f.d.mts +79 -0
  30. package/fjs/edag/amnesia/proof.f.mjs +573 -0
  31. package/fjs/edag/amnesia/types.d.ts +15 -0
  32. package/fjs/edag/module.f.d.mts +410 -0
  33. package/fjs/edag/module.f.mjs +559 -0
  34. package/fjs/edag/proof.f.d.mts +116 -0
  35. package/fjs/edag/proof.f.mjs +686 -0
  36. package/fjs/edag/types.d.ts +55 -0
  37. package/fjs/effects/node/memory/module.d.mts +37 -3
  38. package/fjs/effects/node/memory/module.mjs +38 -4
  39. package/fjs/effects/node/memory/proof.d.mts +5 -2
  40. package/fjs/effects/node/memory/proof.mjs +36 -6
  41. package/fjs/effects/node/module.d.mts +9 -1
  42. package/fjs/effects/node/module.f.d.mts +39 -0
  43. package/fjs/effects/node/module.f.mjs +46 -0
  44. package/fjs/effects/node/module.mjs +234 -32
  45. package/fjs/effects/node/types.d.ts +28 -2
  46. package/fjs/effects/node/virtual/module.f.d.mts +19 -10
  47. package/fjs/effects/node/virtual/module.f.mjs +246 -32
  48. package/fjs/effects/node/virtual/proof.f.d.mts +17 -2
  49. package/fjs/effects/node/virtual/proof.f.mjs +232 -8
  50. package/fjs/effects/node/virtual/types.d.ts +57 -1
  51. package/fjs/emergent_testing/browser/proof.d.mts +55 -0
  52. package/fjs/emergent_testing/browser/proof.mjs +352 -0
  53. package/fjs/emergent_testing/browser/species.proof.d.mts +4 -0
  54. package/fjs/emergent_testing/browser/species.proof.mjs +45 -0
  55. package/fjs/emergent_testing/browser.d.mts +79 -0
  56. package/fjs/emergent_testing/browser.mjs +432 -0
  57. package/fjs/emergent_testing/proof.f.d.mts +1 -1
  58. package/fjs/emergent_testing/proof.f.mjs +2 -2
  59. package/fjs/js/string_escape/module.f.d.mts +47 -0
  60. package/fjs/js/string_escape/module.f.mjs +95 -0
  61. package/fjs/js/string_escape/proof.f.d.mts +8 -0
  62. package/fjs/js/string_escape/proof.f.mjs +68 -0
  63. package/fjs/js/tokenizer/module.f.mjs +14 -12
  64. package/fjs/mcp/cas/module.f.d.mts +1 -7
  65. package/fjs/mcp/cas/module.f.mjs +24 -15
  66. package/fjs/mcp/evo/module.f.mjs +9 -24
  67. package/fjs/mcp/module.f.d.mts +3 -1
  68. package/fjs/mcp/module.f.mjs +4 -2
  69. package/fjs/media/json/rtti/module.f.d.mts +2 -2
  70. package/fjs/media/json/rtti/module.f.mjs +1 -1
  71. package/fjs/media/json/schema/module.f.d.mts +15 -5
  72. package/fjs/media/json/schema/module.f.mjs +42 -15
  73. package/fjs/media/json/schema/proof.f.d.mts +10 -1
  74. package/fjs/media/json/schema/proof.f.mjs +88 -16
  75. package/fjs/media/json/serializer/module.f.mjs +14 -26
  76. package/fjs/media/json/types.d.ts +1 -1
  77. package/fjs/media/lock/module.f.d.mts +8 -3
  78. package/fjs/media/lock/module.f.mjs +9 -3
  79. package/fjs/media/lock/proof.f.d.mts +1 -1
  80. package/fjs/media/lock/proof.f.mjs +2 -2
  81. package/fjs/media/lock/types.d.ts +1 -3
  82. package/fjs/media/module.f.d.mts +20 -13
  83. package/fjs/media/module.f.mjs +29 -16
  84. package/fjs/media/note/module.f.d.mts +14 -7
  85. package/fjs/media/note/module.f.mjs +14 -7
  86. package/fjs/media/note/proof.f.mjs +1 -1
  87. package/fjs/media/proof.f.mjs +5 -5
  88. package/fjs/media/revision/module.f.d.mts +8 -2
  89. package/fjs/media/revision/module.f.mjs +9 -3
  90. package/fjs/media/revision/proof.f.mjs +1 -1
  91. package/fjs/media/revision/types.d.ts +1 -1
  92. package/fjs/media/type/module.f.d.mts +20 -0
  93. package/fjs/media/type/module.f.mjs +74 -0
  94. package/fjs/media/type/proof.f.d.mts +5 -0
  95. package/fjs/media/type/proof.f.mjs +24 -1
  96. package/fjs/module.f.mjs +6 -0
  97. package/fjs/path/module.f.d.mts +60 -3
  98. package/fjs/path/module.f.mjs +138 -14
  99. package/fjs/path/proof.f.d.mts +3 -0
  100. package/fjs/path/proof.f.mjs +298 -2
  101. package/fjs/protocol/json_rpc/module.f.d.mts +65 -28
  102. package/fjs/protocol/json_rpc/module.f.mjs +49 -13
  103. package/fjs/protocol/json_rpc/proof.f.d.mts +4 -0
  104. package/fjs/protocol/json_rpc/proof.f.mjs +29 -0
  105. package/fjs/protocol/json_rpc/types.d.ts +12 -2
  106. package/fjs/protocol/mcp/module.f.d.mts +89 -54
  107. package/fjs/protocol/mcp/module.f.mjs +110 -56
  108. package/fjs/protocol/mcp/proof.f.d.mts +13 -0
  109. package/fjs/protocol/mcp/proof.f.mjs +107 -7
  110. package/fjs/protocol/mcp/stdio/module.f.mjs +3 -4
  111. package/fjs/protocol/mcp/stdio/proof.f.mjs +2 -2
  112. package/fjs/protocol/mcp/types.d.ts +14 -2
  113. package/fjs/text/utf8/module.f.mjs +27 -1
  114. package/fjs/text/utf8/proof.f.mjs +37 -0
  115. package/fjs/types/array/module.f.mjs +18 -11
  116. package/fjs/types/array/proof.f.mjs +27 -1
  117. package/fjs/types/bigfloat/module.f.d.mts +48 -9
  118. package/fjs/types/bigfloat/module.f.mjs +147 -59
  119. package/fjs/types/bigfloat/proof.f.d.mts +2 -0
  120. package/fjs/types/bigfloat/proof.f.mjs +136 -4
  121. package/fjs/types/bigfloat/types.d.ts +17 -0
  122. package/fjs/types/function/module.f.d.mts +11 -2
  123. package/fjs/types/function/module.f.mjs +20 -2
  124. package/fjs/types/function/proof.f.mjs +5 -1
  125. package/fjs/types/phantom/types.d.ts +21 -0
  126. package/fjs/types/result/module.f.d.mts +4 -4
  127. package/fjs/types/result/module.f.mjs +2 -2
  128. package/fjs/types/result/proof.f.d.mts +6 -2
  129. package/fjs/types/result/proof.f.mjs +17 -1
  130. package/fjs/types/rtti/common/module.f.d.mts +78 -5
  131. package/fjs/types/rtti/common/module.f.mjs +168 -3
  132. package/fjs/types/rtti/common/proof.f.d.mts +14 -0
  133. package/fjs/types/rtti/common/proof.f.mjs +61 -2
  134. package/fjs/types/rtti/common/types.d.ts +23 -1
  135. package/fjs/types/rtti/data/module.f.d.mts +57 -1
  136. package/fjs/types/rtti/data/module.f.mjs +241 -47
  137. package/fjs/types/rtti/data/proof.f.d.mts +10 -0
  138. package/fjs/types/rtti/data/proof.f.mjs +272 -39
  139. package/fjs/types/rtti/data/types.d.ts +14 -7
  140. package/fjs/types/rtti/host.proof.d.mts +33 -0
  141. package/fjs/types/rtti/host.proof.mjs +170 -0
  142. package/fjs/types/rtti/module.f.d.mts +51 -6
  143. package/fjs/types/rtti/module.f.mjs +50 -3
  144. package/fjs/types/rtti/parse/module.f.d.mts +31 -24
  145. package/fjs/types/rtti/parse/module.f.mjs +197 -59
  146. package/fjs/types/rtti/parse/proof.f.d.mts +37 -2
  147. package/fjs/types/rtti/parse/proof.f.mjs +171 -47
  148. package/fjs/types/rtti/proof.f.d.mts +6 -0
  149. package/fjs/types/rtti/proof.f.mjs +42 -1
  150. package/fjs/types/rtti/ts/module.f.d.mts +9 -3
  151. package/fjs/types/rtti/ts/module.f.mjs +68 -15
  152. package/fjs/types/rtti/ts/proof.f.d.mts +42 -2
  153. package/fjs/types/rtti/ts/proof.f.mjs +139 -6
  154. package/fjs/types/rtti/ts/types.d.ts +164 -16
  155. package/fjs/types/rtti/types.d.ts +31 -3
  156. package/fjs/types/rtti/validate/module.f.d.mts +42 -25
  157. package/fjs/types/rtti/validate/module.f.mjs +187 -53
  158. package/fjs/types/rtti/validate/proof.f.d.mts +28 -2
  159. package/fjs/types/rtti/validate/proof.f.mjs +430 -75
  160. package/fjs/types/ts/types.d.ts +4 -0
  161. package/fjs/web/module.f.d.mts +115 -0
  162. package/fjs/web/module.f.mjs +581 -0
  163. package/fjs/web/proof.f.d.mts +39 -0
  164. package/fjs/web/proof.f.mjs +413 -0
  165. package/fjs/web/types.d.ts +39 -0
  166. package/fjs/website/browser-prepare.d.mts +5 -0
  167. package/fjs/website/browser-prepare.mjs +115 -0
  168. package/fjs/website/browser-source.d.mts +37 -0
  169. package/fjs/website/browser-source.mjs +202 -0
  170. package/fjs/website/browser-source.proof.d.mts +31 -0
  171. package/fjs/website/browser-source.proof.mjs +162 -0
  172. package/fjs/website/browser.d.mts +5 -0
  173. package/fjs/website/browser.mjs +8 -0
  174. package/fjs/website/module.f.d.mts +3 -3
  175. package/fjs/website/module.f.mjs +62 -9
  176. package/fjs/website/proof.f.d.mts +4 -0
  177. package/fjs/website/proof.f.mjs +18 -3
  178. 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 {};