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,686 @@
1
+ /**
2
+ * Runtime behavior of the edag `exp` schema — one section per node kind, plus
3
+ * a value nested through several kinds to exercise the mutual recursion.
4
+ * Exception: `comma` has no section of its own — its shape (`[',', exps]`) is
5
+ * a known-incomplete placeholder pending a redesign that can express "at
6
+ * least two operands, last is the result, each pre-result operand a true
7
+ * root" (a single-operand `,` is the identity, a reachable operand a
8
+ * redundant anchor — both non-canonical), not a settled node to pin. The
9
+ * `exps` section does validate `,`-tagged values, but only to reach `exps`,
10
+ * which `comma` is now the sole route to; it pins the operand array's
11
+ * element schema, and claims nothing about what a `,` means.
12
+ *
13
+ * @import { ValidationError } from '../types/rtti/common/types.ts'
14
+ * @import { Unknown } from '../types/rtti/ts/types.ts'
15
+ * @import { StringMap } from '../types/object/types.ts'
16
+ */
17
+
18
+ import { validate } from '../types/rtti/validate/module.f.mjs'
19
+ import { assert, assertEq, assertStructurallySame, todo } from '../asserts/module.f.mjs'
20
+ import {
21
+ exp, op0Id, op1Id, op2Id,
22
+ optionLambda, optionPropertyLambda, propertyLambda,
23
+ } from './module.f.mjs'
24
+
25
+ /** @type {(r: readonly [string, unknown]) => void} */
26
+ const assertOk = ([k]) => { assertEq(k, 'ok', 'expected ok') }
27
+
28
+ /**
29
+ * `exp` is a top-level `or` trying every node kind in turn, so when a value
30
+ * matches none of them the reported failure is always the root (`path: []`,
31
+ * `message: 'no match'`) — there is no single branch whose deeper path is
32
+ * "the" failure. Same rule as `../types/rtti/validate/proof.f.mjs`'s `orRoot`.
33
+ * The three lambda schemas are `or`s too, so their failures report the same
34
+ * way.
35
+ * @type {(r: readonly [string, unknown]) => void}
36
+ */
37
+ const assertNoMatch = r => {
38
+ assert(r[0] === 'error', 'expected error')
39
+ const e = /** @type {ValidationError} */ (r[1])
40
+ assertStructurallySame(e.path, [], 'unexpected error path')
41
+ assertEq(e.message, 'no match')
42
+ }
43
+
44
+ /** @type {(value: Unknown) => readonly [string, unknown]} */
45
+ const v = value => validate(exp)(value)
46
+
47
+ /** @type {(value: Unknown) => readonly [string, unknown]} */
48
+ const vOp0Id = value => validate(op0Id)(value)
49
+
50
+ /** @type {(value: Unknown) => readonly [string, unknown]} */
51
+ const vOp1Id = value => validate(op1Id)(value)
52
+
53
+ /** @type {(value: Unknown) => readonly [string, unknown]} */
54
+ const vOp2Id = value => validate(op2Id)(value)
55
+
56
+ /**
57
+ * The three chain continuations, validated directly rather than only through
58
+ * the node that owns one. Each is a state of the two hidden-control-flow bits
59
+ * — a live receiver, an open short-circuit region — so which productions each
60
+ * admits *is* the grammar, and reaching them only through `dot`/`?.`/`?.()`
61
+ * would leave that untested where the states differ.
62
+ * @type {(value: Unknown) => readonly [string, unknown]}
63
+ */
64
+ const vPropertyLambda = value => validate(propertyLambda)(value)
65
+
66
+ /** @type {(value: Unknown) => readonly [string, unknown]} */
67
+ const vOptionLambda = value => validate(optionLambda)(value)
68
+
69
+ /** @type {(value: Unknown) => readonly [string, unknown]} */
70
+ const vOptionPropertyLambda = value => validate(optionPropertyLambda)(value)
71
+
72
+ /** Every id `op0` currently accepts — kept as a literal list, not derived
73
+ * from `op0Id`, so deleting one from the schema reddens exactly its own
74
+ * assertion below rather than silently shrinking this list too. */
75
+ const op0Ids = /** @type {const} */ (['undefined', 'args', 'frame'])
76
+
77
+ /** Same purpose as `op0Ids`, for `op1`. */
78
+ const op1Ids = /** @type {const} */ (['String', 'Number', 'neg', '!', '~'])
79
+
80
+ /** Same purpose as `op0Ids`, for `op2`. */
81
+ const op2Ids = /** @type {const} */ ([
82
+ '=>', 'own',
83
+ '===', '!==', '>', '>=', '<', '<=',
84
+ '+', '-', '*', '/', '%', '**',
85
+ '&', '|', '^', '<<', '>>', '>>>',
86
+ '&&', '||', '??',
87
+ ])
88
+
89
+ /**
90
+ * The naive desugaring of `a?.at` — the shape `?.` looks like it could lower
91
+ * to. It guards both nullish values, as `?.` does: `null?.at` is `undefined`,
92
+ * not a throw, so a guard on `undefined` alone would not be the desugaring of
93
+ * anything. Every branch is taken by `chainsJs.desugaredOptional`, which is
94
+ * also where the assertions live: an assertion inside a `throw` case would be
95
+ * masked, since a failing one throws and so reads as the expected failure.
96
+ *
97
+ * @type {(o: any) => any}
98
+ */
99
+ const desugarOptionalAt = o => o !== null && o !== undefined ? o.at : undefined
100
+
101
+ export const proof = {
102
+ primitive: {
103
+ ok: () => {
104
+ assertOk(v(null))
105
+ assertOk(v(true))
106
+ assertOk(v(false))
107
+ assertOk(v(42))
108
+ assertOk(v('hello'))
109
+ assertOk(v(7n))
110
+ },
111
+ error: () => {
112
+ // `{}` is a legal `Unknown` value (an object with no properties)
113
+ // but matches none of `exp`'s alternatives — not a primitive,
114
+ // and not a tagged tuple with an index 0.
115
+ assertNoMatch(v({}))
116
+ // Bare `undefined` is not a primitive here — see `op0`.
117
+ // A bare `undefined` would be indistinguishable from a missing
118
+ // tuple position, so `['undefined']` is its own node instead.
119
+ assertNoMatch(v(undefined))
120
+ },
121
+ },
122
+ array: {
123
+ ok: () => {
124
+ assertOk(v(['[]', []]))
125
+ assertOk(v(['[]', [1, 'a', true]]))
126
+ assertOk(v(['[]', [1, ['[]', [2, 3]]]])) // an exp nested inside the elements
127
+ // `spread` (`['...', exp]`) is not a top-level `exp` alternative —
128
+ // it's only reachable as an `items` alternative here, or as a
129
+ // `properties` alternative below in `object`.
130
+ assertOk(v(['[]', [1, ['...', 2]]])) // a spread element among plain elements
131
+ },
132
+ error: () => {
133
+ assertNoMatch(v(['[]', 'not-an-array']))
134
+ assertNoMatch(v(['[]', [1, {}]]))
135
+ assertNoMatch(v(['[]', [['...']]])) // spread missing its operand
136
+ assertNoMatch(v(['[]', [['..', 2]]])) // wrong tag, not `...`
137
+ },
138
+ },
139
+ object: {
140
+ ok: () => {
141
+ assertOk(v(['{}', []]))
142
+ assertOk(v(['{}', [[':', 'a', 1], [':', 'b', 'x']]]))
143
+ assertOk(v(['{}', [[':', 'a', 1], ['...', 2]]])) // a spread entry among plain properties
144
+ },
145
+ error: () => {
146
+ assertNoMatch(v(['{}', [[':', 'a', {}]]])) // bad value
147
+ assertNoMatch(v(['{}', [['a', 1]]])) // missing the `:` tag
148
+ assertNoMatch(v(['{}', [[':', 'a']]])) // missing the value
149
+ assertNoMatch(v(['{}', [['...']]])) // spread missing its operand
150
+ },
151
+ },
152
+ // `comma` is `exps`'s only route now that `array` holds `rttiArray(items)`,
153
+ // so these pin `exps` — an array of `exp`, not a single one — through it.
154
+ // The `,` node's own contract is still unsettled (see the module note
155
+ // above); nothing here depends on it beyond the tag and the operand slot.
156
+ exps: {
157
+ ok: () => {
158
+ assertOk(v([',', []]))
159
+ assertOk(v([',', [1, 'a', true]]))
160
+ assertOk(v([',', [['[]', []]]])) // an exp nested inside the operands
161
+ },
162
+ // The operand is the array, not one `exp` in its place — the
163
+ // single-vs-array slip that `array` carried until this branch. A
164
+ // bare string is a valid `exp`, so were `exps` a single `exp` this
165
+ // would validate.
166
+ singleExpIsError: () => assertNoMatch(v([',', 'not-an-array'])),
167
+ error: () => assertNoMatch(v([',', [{}]])),
168
+ },
169
+ // Every tuple in the schema is closed — none says `open` — so a trailing
170
+ // element past what a node declares is rejected rather than ignored. That
171
+ // is what makes "exactly one spelling" literal instead of "one spelling up
172
+ // to trailing junk": an `open` tuple would let `['args', 'extra']` be a
173
+ // second graph for the same function, and a fourth element on a `.` node a
174
+ // third.
175
+ closed: {
176
+ extraTailIsError: () => {
177
+ assertNoMatch(v(['args', 'extra']))
178
+ assertNoMatch(v(['neg', 1, 'extra']))
179
+ assertNoMatch(v(['+', 1, 2, 3]))
180
+ assertNoMatch(v(['[]', [], 'extra']))
181
+ assertNoMatch(v(['{}', [], 'extra']))
182
+ assertNoMatch(v(['[]', [['...', 1, 'extra']]]))
183
+ assertNoMatch(v(['{}', [[':', 'a', 1, 'extra']]]))
184
+ assertNoMatch(v([',', [], 'extra']))
185
+ assertNoMatch(v(['()', 'f', 1, 'extra']))
186
+ assertNoMatch(v(['.', 'a', 'b', null, 'extra']))
187
+ assertNoMatch(v(['?.', 'a', 'b', null, 'extra']))
188
+ assertNoMatch(v(['?.()', 'f', 1, null, 'extra']))
189
+ assertNoMatch(v(['.', 'a', 'b', ['|()', 1, null, 'extra']]))
190
+ assertNoMatch(v(['.', 'a', 'b', ['|?.()', 1, null, 'extra']]))
191
+ assertNoMatch(v(['?.', 'a', 'b', ['|.', 'c', null, 'extra']]))
192
+ assertNoMatch(v(['?.', 'a', 'b', ['|!()', 1, null, 'extra']]))
193
+ assertNoMatch(v(['.', 'a', ['Number', 1, 'extra'], null]))
194
+ },
195
+ },
196
+ dot: {
197
+ ok: () => {
198
+ assertOk(v(['.', 'a', 'b', null]))
199
+ assertOk(v(['.', ['[]', [1, 2]], 0, null]))
200
+ // `index`'s three accepted shapes, pinned explicitly: string,
201
+ // number (above), and a `numberCast` (below) — not `boolean`
202
+ // (see `error`).
203
+ assertOk(v(['.', 'a', ['Number', 1], null]))
204
+ },
205
+ // `index` — string, number, or `numberCast` — never admitted
206
+ // `undefined`, so a missing index has always been a real error.
207
+ missingIndexIsError: () => assertNoMatch(v(['.', 'a', null, null])),
208
+ // The continuation is not optional. `null` says "the receiver is
209
+ // dropped here"; a missing position says nothing, and reads as
210
+ // `undefined`, which no lambda admits.
211
+ missingTailIsError: () => assertNoMatch(v(['.', 'a', 'b'])),
212
+ error: () => {
213
+ assertNoMatch(v(['x', 'a', 'b', null]))
214
+ assertNoMatch(v(['.', {}, 'b', null]))
215
+ // `index` excludes `boolean` on purpose — not narrowed to just
216
+ // `string`/`number` by accident.
217
+ assertNoMatch(v(['.', 'a', true, null]))
218
+ },
219
+ },
220
+ // The chain continuations. There are three because a chain carries two
221
+ // bits — a live receiver (P) and an open short-circuit region (O) — and
222
+ // `00` is the definition of a node boundary, so the fourth cell is an
223
+ // `Exp`. A lambda is never an `exp` (`notAnExp`), and each state admits
224
+ // exactly the steps whose nesting would be observable there.
225
+ lambdas: {
226
+ // `propertyLambda` — P live, no region. Only a call can be here,
227
+ // because only a call spends a receiver; `|()` is terminal and
228
+ // `|?.()` opens a region that owns the rest of the chain.
229
+ propertyLambda: () => {
230
+ assertOk(vPropertyLambda(null))
231
+ assertOk(vPropertyLambda(['|()', 1, null]))
232
+ assertOk(vPropertyLambda(['|?.()', 1, null]))
233
+ assertOk(vPropertyLambda(['|?.()', 1, ['|.', 'c', null]]))
234
+ // No `|.`: a property step here would waste the receiver with no
235
+ // region to keep it in, so `a.b.c` nests `.` nodes instead. That
236
+ // absence is what gives a plain property path one spelling.
237
+ assertNoMatch(vPropertyLambda(['|.', 'c', null]))
238
+ // No `|!()`: there is no open region for it to close.
239
+ assertNoMatch(vPropertyLambda(['|!()', 1, null]))
240
+ },
241
+ // `optionLambda` — a plain value inside a region. A call stays in the
242
+ // region (it must, or the region would not cover it) and a property
243
+ // step hands a receiver on within it.
244
+ optionLambda: () => {
245
+ assertOk(vOptionLambda(null))
246
+ assertOk(vOptionLambda(['|()', 1, null]))
247
+ assertOk(vOptionLambda(['|.', 'c', null]))
248
+ assertOk(vOptionLambda(['|.', 'c', ['|!()', 1, null]]))
249
+ // Neither `|?.()` nor `|!()` is here: with the receiver already
250
+ // spent, guarding or closing at this point protects nothing a
251
+ // nested node would not protect equally.
252
+ assertNoMatch(vOptionLambda(['|?.()', 1, null]))
253
+ assertNoMatch(vOptionLambda(['|!()', 1, null]))
254
+ },
255
+ // `optionPropertyLambda` — both bits live, so every production is
256
+ // here: the three ways a call can relate to its region, plus the
257
+ // property step the region will not let leave.
258
+ optionPropertyLambda: () => {
259
+ assertOk(vOptionPropertyLambda(null))
260
+ assertOk(vOptionPropertyLambda(['|()', 1, null]))
261
+ assertOk(vOptionPropertyLambda(['|.', 'c', null]))
262
+ assertOk(vOptionPropertyLambda(['|?.()', 1, null]))
263
+ assertOk(vOptionPropertyLambda(['|!()', 1, null]))
264
+ // `|.` hands the region back to this same state, so every
265
+ // production above is reachable one property step further in —
266
+ // `a?.b.c?.(...d)` is the guarded call through a `|.`.
267
+ assertOk(vOptionPropertyLambda(['|.', 'c', ['|?.()', 1, null]]))
268
+ assertOk(vOptionPropertyLambda(['|.', 'c', ['|.', 'd', null]]))
269
+ },
270
+ // `|.` takes an `index` and the call steps take an `exp`, the same
271
+ // operand schemas the nodes use — so a general `exp` in a naming
272
+ // position is rejected where it is accepted in an argument one.
273
+ operandSchemas: () => {
274
+ assertOk(vOptionLambda(['|.', 0, null]))
275
+ assertOk(vOptionLambda(['|.', ['Number', 1], null]))
276
+ assertNoMatch(vOptionLambda(['|.', ['[]', []], null]))
277
+ assertOk(vOptionLambda(['|()', ['[]', [1, 2]], null]))
278
+ },
279
+ // A lambda only means anything as the continuation of a chain node:
280
+ // it takes its input implicitly, so on its own it is not an `exp` and
281
+ // cannot be lifted out as a shared node. The `|` prefix is what makes
282
+ // that statable — see `tagsAreDisjoint`.
283
+ notAnExp: () => {
284
+ assertNoMatch(v(['|.', 'b', null]))
285
+ assertNoMatch(v(['|()', 1, null]))
286
+ assertNoMatch(v(['|?.()', 1, null]))
287
+ assertNoMatch(v(['|!()', 1, null]))
288
+ },
289
+ // The prefix is a correctness requirement, not a readability one.
290
+ // Unprefixed, `['()', f, null]` would be both a `call` — call `f`
291
+ // with `null` as its arguments — and an `optionLambda` — call the
292
+ // chain's value with `f` as its arguments, and stop. Equal length, so
293
+ // closedness could not have separated them — it bounds a tuple's
294
+ // length and says nothing about its tag; only disjoint vocabularies
295
+ // can, and these two assertions are that disjointness.
296
+ tagsAreDisjoint: () => {
297
+ assertOk(v(['()', 'f', null]))
298
+ assertNoMatch(vOptionLambda(['()', 'f', null]))
299
+ assertNoMatch(v(['|()', 'f', null]))
300
+ },
301
+ // Uniform arity is the other half. `propertyLambda`'s `|()` is
302
+ // terminal, and it says so with an explicit `null` rather than by
303
+ // being one element shorter: a two-element terminal handed a real
304
+ // continuation would validate with the rest silently dropped.
305
+ terminalsAreExplicit: () => {
306
+ assertNoMatch(vPropertyLambda(['|()', 1]))
307
+ assertNoMatch(vPropertyLambda(['|()', 1, ['|.', 'c', null]]))
308
+ assertNoMatch(vOptionPropertyLambda(['|!()', 1]))
309
+ assertNoMatch(vOptionPropertyLambda(['|!()', 1, ['|.', 'c', null]]))
310
+ },
311
+ missingTailIsError: () => {
312
+ assertNoMatch(vOptionPropertyLambda(['|.', 'c']))
313
+ assertNoMatch(vOptionPropertyLambda(['|()', 1]))
314
+ assertNoMatch(vOptionPropertyLambda(['|?.()', 1]))
315
+ },
316
+ // Each tag is a real constraint, not a stand-in for `string`: a node
317
+ // tag in a step position is rejected, and so is an unknown one.
318
+ unknownOpIsRejected: () => {
319
+ assertNoMatch(vOptionPropertyLambda(['.', 'b', null]))
320
+ assertNoMatch(vOptionPropertyLambda(['|.z', 'b', null]))
321
+ assertNoMatch(vOptionPropertyLambda('xyz'))
322
+ },
323
+ },
324
+ call: {
325
+ ok: () => {
326
+ assertOk(v(['()', 'f', ['[]', []]]))
327
+ assertOk(v(['()', ['.', 'o', 'k', null], 1])) // (0, o.k)(...args)
328
+ assertOk(v(['()', 'f', ['[]', [1, 2]]]))
329
+ },
330
+ // A missing argument operand reads as `undefined` — an error, same
331
+ // as `op1`/`op2`'s `missingTailIsError`.
332
+ missingTailIsError: () => assertNoMatch(v(['()', 'f'])),
333
+ // `()` no longer carries a chain operand: a call with a receiver is a
334
+ // `.` node owning its call, so the three-element shape is the whole
335
+ // node and a `lambdas`-era graph is simply not one.
336
+ lambdasShapeIsError: () => assertNoMatch(v(['()', 'f', [], 1])),
337
+ },
338
+ optionDot: {
339
+ ok: () => {
340
+ assertOk(v(['?.', 'a', 'b', null]))
341
+ assertOk(v(['?.', 'a', ['Number', 1], null]))
342
+ assertOk(v(['?.', 'a', 'b', ['|.', 'c', null]]))
343
+ },
344
+ // Same three `index` shapes as `.`, `boolean` excluded the same way.
345
+ error: () => {
346
+ assertNoMatch(v(['?.', 'a', true, null]))
347
+ assertNoMatch(v(['?.z', 'a', 'b', null]))
348
+ },
349
+ // The continuation is required — `null` says "the optional region
350
+ // ends here", a missing position says nothing.
351
+ missingTailIsError: () => assertNoMatch(v(['?.', 'a', 'b'])),
352
+ },
353
+ optionCall: {
354
+ ok: () => {
355
+ assertOk(v(['?.()', 'f', 1, null]))
356
+ assertOk(v(['?.()', 'f', ['[]', [1]], ['|()', 2, null]]))
357
+ },
358
+ // One continuation, not two: the callee is an ordinary expression, so
359
+ // there is no pre-call chain for this node to own.
360
+ missingTailIsError: () => {
361
+ assertNoMatch(v(['?.()', 'f', 1]))
362
+ assertNoMatch(v(['?.()', 'f']))
363
+ },
364
+ error: () => assertNoMatch(v(['?.()', 'f', [], 1, []])),
365
+ },
366
+ // One entry per JS spelling whose grouping or hidden control flow the
367
+ // grammar exists to distinguish — the shape only, since what each
368
+ // denotes is the JSDoc on the nodes in `./module.f.mjs` and the executor
369
+ // proofs in `./amnesia/proof.f.mjs`, and lowering these spellings is
370
+ // `../djs/todo/compile-modules-to-edag.md`. Read as pairs: the members
371
+ // of a pair differ in JS, so they must differ here too.
372
+ chains: {
373
+ // A receiver is born in a `.` (or `?.`) node and spent by the call
374
+ // that node owns. Reaching the call through a complete node instead
375
+ // is the detached spelling, and a different graph.
376
+ receiver: () => {
377
+ assertOk(v(['.', 'a', 'b', ['|()', 'args', null]])) // a.b(...args)
378
+ assertOk(v(['()', ['.', 'a', 'b', null], 'args'])) // (0, a.b)(...args)
379
+ assertOk(v(['.', 'a', 'b', ['|?.()', 'args', null]])) // a.b?.(...args)
380
+ assertOk(v(['?.()', 'a', 'args', null])) // a?.(...args)
381
+ // (a?.(...args))(...args2)
382
+ assertOk(v(['()', ['?.()', 'a', 'args', null], 'args2']))
383
+ },
384
+ // A plain property path nests, because `propertyLambda` has no `|.`
385
+ // production — so `a.b.c` has exactly one spelling and the dead-prefix
386
+ // rule needs no lowering pass to hold.
387
+ propertyPath: () => {
388
+ assertOk(v(['.', ['.', 'a', 'b', null], 'c', null])) // a.b.c
389
+ // a.b(...args).c — the inner call is the inner node's business.
390
+ assertOk(v(['.', ['.', 'a', 'b', ['|()', 'args', null]], 'c', null]))
391
+ },
392
+ // An optional region is one continuation chain, however long, and
393
+ // grouping is what ends it: `a?.b.c` skips `.c` on a nullish `a`,
394
+ // `(a?.b).c` throws there — one node against two.
395
+ optionalRegion: () => {
396
+ assertOk(v(['?.', 'a', 'b', null])) // a?.b
397
+ assertOk(v(['?.', 'a', 'b', ['|.', 'c', null]])) // a?.b.c
398
+ assertOk(v(['.', ['?.', 'a', 'b', null], 'c', null])) // (a?.b).c
399
+ // a?.b.c(...args) — the call is inside the region.
400
+ assertOk(v(['?.', 'a', 'b', ['|.', 'c', ['|()', 'args', null]]]))
401
+ // (a?.b).c(...args) — the parens ended it, so a `.` node owns the
402
+ // call and `a?.b` is a complete node under it.
403
+ assertOk(v(['.', ['?.', 'a', 'b', null], 'c', ['|()', 'args', null]]))
404
+ // a?.b(...args).c(...args2) — one region across two calls.
405
+ assertOk(v(['?.', 'a', 'b',
406
+ ['|()', 'args', ['|.', 'c', ['|()', 'args2', null]]]]))
407
+ // a?.(...args).c and a?.(...args)(...args2)
408
+ assertOk(v(['?.()', 'a', 'args', ['|.', 'c', null]]))
409
+ assertOk(v(['?.()', 'a', 'args', ['|()', 'args2', null]]))
410
+ },
411
+ // The three ways a call can relate to the region around it — the
412
+ // complete taxonomy, and the reason `|!()` is a tag of its own.
413
+ callsAgainstTheRegion: () => {
414
+ assertOk(v(['?.', 'a', 'b', ['|()', 'args', null]])) // a?.b(...args)
415
+ assertOk(v(['?.', 'a', 'b', ['|?.()', 'args', null]])) // a?.b?.(...args)
416
+ assertOk(v(['?.', 'a', 'b', ['|!()', 'args', null]])) // (a?.b)(...args)
417
+ // (a?.b.c)(...args) — the region closes after a property step,
418
+ // which is the same `|!()` one step further in.
419
+ assertOk(v(['?.', 'a', 'b', ['|.', 'c', ['|!()', 'args', null]]]))
420
+ // a?.b.c?.(...args) — and so is the guarded call.
421
+ assertOk(v(['?.', 'a', 'b', ['|.', 'c', ['|?.()', 'args', null]]]))
422
+ },
423
+ // The operands an optional node skips on its nullish branch have to
424
+ // be operands *of* that node, which is what makes `k`/`a`
425
+ // observably unevaluated: `a?.[k]` and `f?.(...a)`.
426
+ skippedOperands: () => {
427
+ assertOk(v(['?.', 'a', ['Number', 'k'], null])) // a?.[k]
428
+ assertOk(v(['?.()', 'f', 'a', null])) // f?.(...a)
429
+ },
430
+ },
431
+ // The four duplicate families a flat step array admitted are not
432
+ // forbidden here — they cannot be written. Each is one production the
433
+ // grammar does not have, and each `assertNoMatch` is the family it kills.
434
+ unspellable: {
435
+ // `a?.b?.c` — no lambda has a `?.` production at all; `?.` is only
436
+ // ever a node tag, so a guarded property access always starts a node.
437
+ optionalPropertyStep: () => {
438
+ assertNoMatch(v(['?.', 'a', 'b', ['|?.', 'c', null]]))
439
+ assertOk(v(['?.', ['?.', 'a', 'b', null], 'c', null])) // the spelling
440
+ },
441
+ // `a.b(...c)?.d` — `propertyLambda`'s `|()` is terminal, so the chain
442
+ // exits and what follows is an ordinary node over an ordinary value.
443
+ callTerminatesPropertyLambda: () => {
444
+ assertNoMatch(v(['.', 'a', 'b', ['|()', 'c', ['|.', 'd', null]]]))
445
+ assertOk(v(['?.', ['.', 'a', 'b', ['|()', 'c', null]], 'd', null]))
446
+ },
447
+ // `(a?.(...b))(...c)` — `optionLambda` has no `|!()`, since with the
448
+ // receiver already spent there is nothing for the close to keep. The
449
+ // outer call is a plain `()` over a complete `?.()` node.
450
+ closeWithoutReceiver: () => {
451
+ assertNoMatch(v(['?.()', 'a', 'b', ['|!()', 'c', null]]))
452
+ assertOk(v(['()', ['?.()', 'a', 'b', null], 'c']))
453
+ },
454
+ // `a?.b(...c)?.d` — `optionLambda` has no guarded step either, so the
455
+ // guarded access after the call starts its own node.
456
+ guardedStepAfterCall: () => {
457
+ assertNoMatch(v(['?.', 'a', 'b', ['|()', 'c', ['|?.()', 'd', null]]]))
458
+ assertOk(v(['?.()', ['?.', 'a', 'b', ['|()', 'c', null]], 'd', null]))
459
+ },
460
+ },
461
+ op0: {
462
+ ok: () => {
463
+ // Every id `op0` accepts, pinned individually: deleting any one
464
+ // of these three from `op0Id` reddens exactly this loop, not
465
+ // some other assertion that happens to still pass.
466
+ for (const id of op0Ids) {
467
+ assertOk(v([id]))
468
+ }
469
+ },
470
+ error: () => {
471
+ assertNoMatch(v([]))
472
+ assertNoMatch(v(['argz']))
473
+ },
474
+ // `op0Id` is a real constraint, not a stand-in for `string`: an id
475
+ // outside its three members is rejected, both directly and as part
476
+ // of a full `exp` value.
477
+ unknownIdIsRejected: () => {
478
+ assertNoMatch(vOp0Id('xyz'))
479
+ assertNoMatch(v(['xyz']))
480
+ },
481
+ },
482
+ op1: {
483
+ ok: () => {
484
+ // Every id `op1` accepts, pinned individually: deleting any one
485
+ // of these five from `op1Id` reddens exactly this loop, not
486
+ // some other assertion that happens to still pass.
487
+ for (const id of op1Ids) {
488
+ assertOk(v([id, 1]))
489
+ }
490
+ assertOk(v(['neg', ['neg', 1]])) // an exp nested inside the operand
491
+ // Composes through `exp`'s recursion like any other node.
492
+ assertOk(v(['[]', [['Number', 1]]]))
493
+ assertOk(v(['()', ['Number', 1], 2]))
494
+ },
495
+ // A missing operand reads as `undefined`, no longer a valid bare
496
+ // `exp` — see `op0`.
497
+ missingTailIsError: () => assertNoMatch(v(['neg'])),
498
+ error: () => assertNoMatch(v(['negz', 1])),
499
+ // `op1Id` is a real constraint, not a stand-in for `string`: an id
500
+ // outside its five members is rejected, both directly and as part
501
+ // of a full `exp` value.
502
+ unknownIdIsRejected: () => {
503
+ assertNoMatch(vOp1Id('xyz'))
504
+ assertNoMatch(v(['xyz', 1]))
505
+ },
506
+ // `own`'s point is bypassing the prototype chain — including the
507
+ // `__proto__` special case a computed key already avoids in JS.
508
+ // Demonstrates the pattern `['own', ...]` (an `op2` id) denotes;
509
+ // not a schema check.
510
+ ownJs: () => {
511
+ /** @type {<T>(a: StringMap<T>, k: string) => T|undefined } */
512
+ const own = (a, k) => Object.getOwnPropertyDescriptor(a, k)?.value
513
+ const a = { ['__proto__']: 42 }
514
+ assertEq(own(a, '__proto__'), 42)
515
+ assertEq(own(a, 'x'), undefined)
516
+ },
517
+ },
518
+ op2: {
519
+ ok: () => {
520
+ // Every id `op2` accepts, pinned individually: deleting any one
521
+ // of the twenty-four from `op2Id` reddens exactly this loop, not
522
+ // some other assertion that happens to still pass.
523
+ for (const id of op2Ids) {
524
+ assertOk(v([id, 1, 2]))
525
+ }
526
+ assertOk(v(['+', ['+', 1, 2], 3])) // an exp nested inside an operand
527
+ },
528
+ // A missing operand reads as `undefined`, no longer a valid bare
529
+ // `exp` — see `op0`. True whether one or both are missing.
530
+ missingTailIsError: () => {
531
+ assertNoMatch(v(['+', 1]))
532
+ assertNoMatch(v(['+']))
533
+ },
534
+ error: () => assertNoMatch(v(['+z', 1, 2])),
535
+ // Same point as `op1`'s: `op2Id` constrains membership.
536
+ unknownIdIsRejected: () => {
537
+ assertNoMatch(vOp2Id('xyz'))
538
+ assertNoMatch(v(['xyz', 1, 2]))
539
+ },
540
+ },
541
+ // The JS these nodes have to agree with, run on the host engine — the
542
+ // `ownJs` pattern one level up: what the vocabulary denotes, not what the
543
+ // schema accepts. Every spelling here is one `chains` pins the shape of,
544
+ // and each is why that shape is what it is. `todo()` always throws, so it
545
+ // doubles as an evaluation probe: reaching it is observable without any
546
+ // mutation, and a case that returns instead proves the operand was
547
+ // skipped.
548
+ chainsJs: {
549
+ // Receiver: a property reference carries its base into the call as
550
+ // `this`, and parentheses around the reference do not break that —
551
+ // only detaching the value does (`throw.detachedReceiver`). It holds
552
+ // across an optional link too, which is why `(a?.b)(d)` is a `?.`
553
+ // node with a `|!()` continuation, `['?.', a, 'b', ['|!()', d, null]]`,
554
+ // rather than a `()` over a complete `['?.', a, 'b', null]`: the
555
+ // latter would produce an ordinary value and lose the receiver.
556
+ receiver: () => {
557
+ const a = [42]
558
+ assertEq(a.at(0), 42)
559
+ assertEq((a.at)(0), 42)
560
+ assertEq((a?.at)(0), 42) // ['?.', a, 'at', ['|!()', …, null]]
561
+ assertEq((a?.at)?.(0), 42) // ['?.', a, 'at', ['|?.()', …, null]]
562
+ assertEq(a.at?.(0), 42) // ['.', a, 'at', ['|?.()', …, null]]
563
+ assertEq(a?.at?.(0), 42) // ['?.', a, 'at', ['|?.()', …, null]]
564
+ },
565
+ // Short-circuit: a nullish link skips the rest of its chain, and
566
+ // grouping is what ends that chain — `u?.at.name` is `undefined`
567
+ // where `(u?.at).name` throws (`throw.groupedOptional`), one
568
+ // continuation against two nodes.
569
+ shortCircuit: () => {
570
+ /** @type {any} */
571
+ const u = undefined
572
+ assertEq(u?.at, undefined)
573
+ assertEq(u?.at.name, undefined) // ['?.', u, 'at', ['|.', 'name', null]]
574
+ assertEq(u?.at?.(0), undefined)
575
+ // The operands on the skipped branch are never evaluated: an
576
+ // optional property's index, and an optional call's arguments.
577
+ assertEq(u?.[todo()], undefined)
578
+ assertEq(u?.(todo()), undefined)
579
+ },
580
+ // The quiet half of `throw.groupedOptional`: a group ends the chain,
581
+ // but when what follows is itself guarded the ending is unobservable.
582
+ // That is the parenthesis law's own escape clause, and the reason
583
+ // `null` is the right spelling of a bare `(a?.b)`.
584
+ grouping: () => {
585
+ /** @type {any} */
586
+ const u = undefined
587
+ assertEq((u?.at)?.name, undefined)
588
+ assertEq((u?.at)?.(0), undefined)
589
+ },
590
+ // What the naive desugaring of `?.` gets right, every branch of it:
591
+ // `undefined` on either nullish input, and on anything else the *same
592
+ // function* `a.at` denotes. Nothing is wrong with any of those values
593
+ // — what it loses is the receiver, which `throw.desugaredOptional`
594
+ // pins by calling the last one. That is why `?.` cannot lower to a
595
+ // conditional, and why `(a?.b)(d)` keeps its receiver through a
596
+ // `|!()` step instead of completing a `['?.', …]` node that would
597
+ // hand on an ordinary value.
598
+ desugaredOptional: () => {
599
+ assertEq(desugarOptionalAt(null), undefined)
600
+ assertEq(desugarOptionalAt(undefined), undefined)
601
+ // Identity, not just equality: a receiver-bound function (e.g.
602
+ // `o.at.bind(o)`) is `!==` to `[42].at` even though it behaves
603
+ // the same on `(0)`. So this line and `throw.desugaredOptional`
604
+ // both redden under that mutation — the identity check here,
605
+ // the call there.
606
+ assertEq(desugarOptionalAt([42]), [42].at)
607
+ },
608
+ // The two spellings that differ *only* in whether the arguments ran —
609
+ // `a.b(...c)` throws at the access with `c` untouched, `(a?.b)(...c)`
610
+ // short-circuits, evaluates `c`, and throws at the call — cannot be
611
+ // pinned here, nor by the node either: both readings throw, and a
612
+ // `throw` case is pass/fail rather than payload-inspecting. What
613
+ // carries that order is the shape of `callProperty` in
614
+ // `./amnesia/module.f.mjs`; "Where the host engines disagree" in
615
+ // `./README.md` states the gap. Nor can `(u?.at)(0)` be pinned: both are
616
+ // `|!()` terms, JavaScriptCore (so `bun test`) carries the
617
+ // short-circuit through the parentheses and answers `undefined` where
618
+ // V8 throws, so asserting either answer would redden a runner. The
619
+ // node is unaffected — `['?.', u, 'at', ['|!()', …, null]]` means the
620
+ // throwing reading — and `optionRegion.throw.closeStepOnUndefined` in
621
+ // `./amnesia/proof.f.mjs` pins it by evaluating the node, which is
622
+ // the only oracle that works on every runner. See "Chains" in
623
+ // `./README.md`. `throw.groupedOptional` below pins the same boundary
624
+ // through a property access, where every engine agrees.
625
+ throw: {
626
+ // `const at = a.at; at(0)` — the value without its receiver,
627
+ // the case that makes receiver state part of what a graph means.
628
+ detachedReceiver: () => {
629
+ const at = [42].at
630
+ return at(0)
631
+ },
632
+ // The desugaring above, called: `42` through `(a?.at)(0)`
633
+ // (`receiver`), a throw once a conditional has made it a value.
634
+ desugaredOptional: () => desugarOptionalAt([42])(0),
635
+ // `(u?.at).name` — the parens ended the optional chain, so
636
+ // `.name` runs on `undefined` instead of being skipped.
637
+ groupedOptional: () => {
638
+ /** @type {any} */
639
+ const u = undefined
640
+ return (u?.at).name
641
+ },
642
+ // The mirror of `shortCircuit`'s last two: on a non-nullish
643
+ // input those same operands *are* evaluated.
644
+ evaluatedIndex: () => [42]?.[todo()],
645
+ evaluatedArgument: () => [42].at?.(todo()),
646
+ // `(u?.(0))(1)` — the inner call already cleared the receiver, so
647
+ // the group's boundary carries nothing and every runner agrees.
648
+ // That is also why the grammar has no `|!()` in `optionLambda`:
649
+ // this is a plain `()` over a complete `?.()` node.
650
+ groupedOptionalCallOfCall: () => {
651
+ /** @type {any} */
652
+ const u = undefined
653
+ return (u?.(0))(1)
654
+ },
655
+ // Two shapes bun answers differently, so neither can be
656
+ // asserted. Both throw everywhere else; see "Chains" in
657
+ // `./README.md` for which is the engine and which is the
658
+ // transpiler, and
659
+ // `../../todo/blocked/bun-optional-chain-parentheses.md` for what
660
+ // unblocks them. The second must stay commented rather than merely
661
+ // fail: bun rejects it at parse, taking the file down.
662
+ //
663
+ // groupedOptionalCall: () => {
664
+ // /** @type {any} */
665
+ // const u = undefined
666
+ // return (u?.at)(0)
667
+ // },
668
+ //
669
+ // groupedOptionalTag: () => {
670
+ // /** @type {any} */
671
+ // const u = undefined
672
+ // return (u?.at)`tag`
673
+ // },
674
+ },
675
+ },
676
+ // `f(...args)[k](obj.a)` in AST form — exercises the mutual recursion
677
+ // through `exp` rather than any one node kind in isolation.
678
+ nested: () => {
679
+ const value = /** @type {const} */ (['.',
680
+ ['()', 'f', ['args']],
681
+ 'k',
682
+ ['|()', ['[]', [['.', 'obj', 'a', null]]], null],
683
+ ])
684
+ assertOk(v(value))
685
+ },
686
+ }