@algosail/lang 0.2.11 → 0.5.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/bin/sail.mjs +4 -0
- package/cli/run-cli.js +176 -0
- package/index.js +11 -2
- package/lib/codegen/README.md +230 -0
- package/lib/codegen/codegen-diagnostics.js +164 -0
- package/lib/codegen/compile-graph.js +107 -0
- package/lib/codegen/emit-adt.js +177 -0
- package/lib/codegen/emit-body.js +1265 -0
- package/lib/codegen/emit-builtin.js +371 -0
- package/lib/codegen/emit-jsdoc-sail.js +383 -0
- package/lib/codegen/emit-module.js +498 -0
- package/lib/codegen/esm-imports.js +26 -0
- package/lib/codegen/index.js +69 -0
- package/lib/codegen/out-layout.js +102 -0
- package/lib/ffi/extract-jsdoc-sail.js +34 -0
- package/lib/io-node/index.js +4 -0
- package/lib/io-node/package-root.js +18 -0
- package/lib/io-node/read-file.js +12 -0
- package/lib/io-node/resolve-package.js +24 -0
- package/lib/io-node/resolve-sail-names-from-disk.js +21 -0
- package/lib/ir/assert-json-serializable.js +30 -0
- package/lib/ir/attach-call-effects.js +108 -0
- package/lib/ir/bind-values.js +594 -0
- package/lib/ir/build-module-ir.js +290 -0
- package/lib/ir/index.js +31 -0
- package/lib/ir/lower-body-steps.js +170 -0
- package/lib/ir/module-metadata.js +65 -0
- package/lib/ir/schema-version.js +15 -0
- package/lib/ir/serialize.js +202 -0
- package/lib/ir/stitch-types.js +92 -0
- package/lib/names/adt-autogen.js +22 -0
- package/lib/names/import-path.js +28 -0
- package/lib/names/index.js +1 -0
- package/lib/names/local-declarations.js +127 -0
- package/lib/names/lower-first.js +6 -0
- package/lib/names/module-scope.js +120 -0
- package/lib/names/resolve-sail.js +365 -0
- package/lib/names/walk-ast-refs.js +91 -0
- package/lib/parse/ast-build.js +51 -0
- package/lib/parse/ast-spec.js +212 -0
- package/lib/parse/builtins-set.js +12 -0
- package/lib/parse/diagnostics.js +180 -0
- package/lib/parse/index.js +46 -0
- package/lib/parse/lexer.js +390 -0
- package/lib/parse/parse-source.js +912 -0
- package/lib/typecheck/adt-autogen-sigs.js +345 -0
- package/lib/typecheck/build-type-env.js +148 -0
- package/lib/typecheck/builtin-signatures.js +183 -0
- package/lib/typecheck/check-word-body.js +1021 -0
- package/lib/typecheck/effect-decl.js +124 -0
- package/lib/typecheck/index.js +55 -0
- package/lib/typecheck/normalize-sig.js +369 -0
- package/lib/typecheck/stack-step-snapshots.js +56 -0
- package/lib/typecheck/unify-type.js +665 -0
- package/lib/typecheck/validate-adt.js +201 -0
- package/package.json +4 -9
- package/scripts/regen-demo-full-syntax-ast.mjs +22 -0
- package/test/cli/sail-cli.test.js +64 -0
- package/test/codegen/compile-bracket-ffi-e2e.test.js +64 -0
- package/test/codegen/compile-stage0.test.js +128 -0
- package/test/codegen/compile-stage4-layout.test.js +124 -0
- package/test/codegen/e2e-prelude-ffi-adt/app/extra.sail +6 -0
- package/test/codegen/e2e-prelude-ffi-adt/app/lib.sail +33 -0
- package/test/codegen/e2e-prelude-ffi-adt/app/main.sail +28 -0
- package/test/codegen/e2e-prelude-ffi-adt/artifacts/.gitignore +2 -0
- package/test/codegen/e2e-prelude-ffi-adt/ffi/helpers.js +27 -0
- package/test/codegen/e2e-prelude-ffi-adt.test.js +100 -0
- package/test/codegen/emit-adt-stage6.test.js +168 -0
- package/test/codegen/emit-async-stage5.test.js +164 -0
- package/test/codegen/emit-body-stage2.test.js +139 -0
- package/test/codegen/emit-body.test.js +163 -0
- package/test/codegen/emit-builtins-stage7.test.js +258 -0
- package/test/codegen/emit-diagnostics-stage9.test.js +90 -0
- package/test/codegen/emit-jsdoc-stage8.test.js +113 -0
- package/test/codegen/emit-module-stage3.test.js +78 -0
- package/test/conformance/conformance-ir-l4.test.js +38 -0
- package/test/conformance/conformance-l5-codegen.test.js +111 -0
- package/test/conformance/conformance-runner.js +91 -0
- package/test/conformance/conformance-suite-l3.test.js +32 -0
- package/test/ffi/prelude-jsdoc.test.js +49 -0
- package/test/fixtures/demo-full-syntax.ast.json +1471 -0
- package/test/fixtures/demo-full-syntax.sail +35 -0
- package/test/fixtures/io-node-ffi-adt/ffi.js +7 -0
- package/test/fixtures/io-node-ffi-adt/use.sail +4 -0
- package/test/fixtures/io-node-mini/dep.sail +2 -0
- package/test/fixtures/io-node-mini/entry.sail +4 -0
- package/test/fixtures/io-node-prelude/entry.sail +4 -0
- package/test/fixtures/io-node-reexport-chain/a.sail +4 -0
- package/test/fixtures/io-node-reexport-chain/b.sail +2 -0
- package/test/fixtures/io-node-reexport-chain/c.sail +2 -0
- package/test/io-node/resolve-disk.test.js +59 -0
- package/test/ir/bind-values.test.js +84 -0
- package/test/ir/build-module-ir.test.js +100 -0
- package/test/ir/call-effects.test.js +97 -0
- package/test/ir/ffi-bracket-ir.test.js +59 -0
- package/test/ir/full-ir-document.test.js +51 -0
- package/test/ir/ir-document-assert.js +67 -0
- package/test/ir/lower-body-steps.test.js +90 -0
- package/test/ir/module-metadata.test.js +42 -0
- package/test/ir/serialization-model.test.js +172 -0
- package/test/ir/stitch-types.test.js +74 -0
- package/test/names/l2-resolve-adt-autogen.test.js +155 -0
- package/test/names/l2-resolve-bracket-ffi.test.js +108 -0
- package/test/names/l2-resolve-declaration-and-bracket-errors.test.js +276 -0
- package/test/names/l2-resolve-graph.test.js +105 -0
- package/test/names/l2-resolve-single-file.test.js +79 -0
- package/test/parse/ast-spec.test.js +56 -0
- package/test/parse/ast.test.js +476 -0
- package/test/parse/contract.test.js +37 -0
- package/test/parse/fixtures-full-syntax.test.js +24 -0
- package/test/parse/helpers.js +27 -0
- package/test/parse/l0-lex-diagnostics-matrix.test.js +59 -0
- package/test/parse/l0-lex.test.js +40 -0
- package/test/parse/l1-diagnostics.test.js +77 -0
- package/test/parse/l1-import.test.js +28 -0
- package/test/parse/l1-parse-diagnostics-matrix.test.js +32 -0
- package/test/parse/l1-top-level.test.js +47 -0
- package/test/parse/l1-types.test.js +31 -0
- package/test/parse/l1-words.test.js +49 -0
- package/test/parse/l2-diagnostics-contract.test.js +67 -0
- package/test/parse/l3-diagnostics-contract.test.js +66 -0
- package/test/typecheck/adt-decl-stage2.test.js +83 -0
- package/test/typecheck/container-contract-e1309.test.js +258 -0
- package/test/typecheck/ffi-bracket-l3.test.js +61 -0
- package/test/typecheck/l3-diagnostics-matrix.test.js +248 -0
- package/test/typecheck/l3-partial-pipeline.test.js +74 -0
- package/test/typecheck/opaque-ffi-type.test.js +78 -0
- package/test/typecheck/sig-type-stage3.test.js +190 -0
- package/test/typecheck/stack-check-stage4.test.js +149 -0
- package/test/typecheck/stack-check-stage5.test.js +74 -0
- package/test/typecheck/stack-check-stage6.test.js +56 -0
- package/test/typecheck/stack-check-stage7.test.js +160 -0
- package/test/typecheck/stack-check-stage8.test.js +146 -0
- package/test/typecheck/stack-check-stage9.test.js +105 -0
- package/test/typecheck/typecheck-env.test.js +53 -0
- package/test/typecheck/typecheck-pipeline.test.js +37 -0
- package/README.md +0 -37
- package/cli/sail.js +0 -151
- package/cli/typecheck.js +0 -39
- package/docs/ARCHITECTURE.md +0 -50
- package/docs/CHANGELOG.md +0 -18
- package/docs/FFI-GUIDE.md +0 -65
- package/docs/RELEASE.md +0 -36
- package/docs/TESTING.md +0 -86
- package/test/integration.test.js +0 -61
|
@@ -0,0 +1,1265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L5 этап 1–2: эмиссия фрагмента тела слова по `irSteps` + `valueBindings`
|
|
3
|
+
* (RFC-compile-0.1 §6.2–6.3, RFC-IR-0.1 §4, RFC-0.1 §10.1).
|
|
4
|
+
*
|
|
5
|
+
* Без массива как модели стека: только `const` и выражения.
|
|
6
|
+
* Quotation: отложенная эмиссия; `call` — инлайн тела; значение Quote — `const qN = (…) => { … }`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
COMBINATOR_BUILTIN_NAMES,
|
|
11
|
+
tryEmitCombinatorBuiltin
|
|
12
|
+
} from './emit-builtin.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {import('../typecheck/normalize-sig.js').NormalizedSignature} NormalizedSignature
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {{
|
|
20
|
+
* localPropagatesAsync: (name: string) => boolean
|
|
21
|
+
* qualifiedPropagatesAsync: (moduleAlias: string, wordName: string) => boolean
|
|
22
|
+
* }} CalleeAsyncResolver
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {{
|
|
27
|
+
* innerSteps: Record<string, unknown>[]
|
|
28
|
+
* innerEntryStackIds: string[]
|
|
29
|
+
* }} QuoteEmitMeta
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Выражение shuffle — уже имя `const`/параметра из idToJs (без побочных эффектов);
|
|
34
|
+
* тогда новый `const` не нужен: это перестановка/dup на уровне имён (RFC-0.1, lowering §6.2).
|
|
35
|
+
*
|
|
36
|
+
* @param {string} expr
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
function isShuffleReuseExpr (expr) {
|
|
40
|
+
return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(expr)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @type {Record<string, (argExprs: string[]) => string[]>} */
|
|
44
|
+
const BUILTIN_SHUFFLE_EXPR = {
|
|
45
|
+
dup: (a) => [a[0], a[0]],
|
|
46
|
+
drop: () => [],
|
|
47
|
+
swap: (a) => [a[1], a[0]],
|
|
48
|
+
over: (a) => [a[0], a[1], a[0]],
|
|
49
|
+
dup2: (a) => [a[0], a[1], a[0], a[1]],
|
|
50
|
+
drop2: () => [],
|
|
51
|
+
rot: (a) => [a[1], a[2], a[0]],
|
|
52
|
+
reverse: (a) => [a[2], a[0], a[1]],
|
|
53
|
+
nip: (a) => [a[1]],
|
|
54
|
+
tuck: (a) => [a[1], a[0], a[1]],
|
|
55
|
+
swap2: (a) => [...a.slice(2, 4), ...a.slice(0, 2)]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {number} nIn
|
|
60
|
+
* @param {number} nOut
|
|
61
|
+
* @returns {NormalizedSignature}
|
|
62
|
+
*/
|
|
63
|
+
function sigWireFromStackLens (nIn, nOut) {
|
|
64
|
+
const slot = { kind: 'prim', name: '_' }
|
|
65
|
+
return {
|
|
66
|
+
left: Array.from({ length: nIn }, () => slot),
|
|
67
|
+
right: Array.from({ length: nOut }, () => slot),
|
|
68
|
+
effectsAdd: [],
|
|
69
|
+
effectsRemove: []
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {Record<string, unknown>[]} innerSteps
|
|
75
|
+
* @returns {number}
|
|
76
|
+
*/
|
|
77
|
+
function inferInnerOutLen (innerSteps) {
|
|
78
|
+
if (!Array.isArray(innerSteps)) return 0
|
|
79
|
+
for (let i = innerSteps.length - 1; i >= 0; i--) {
|
|
80
|
+
const s = innerSteps[i]
|
|
81
|
+
if (s != null && typeof s === 'object' && Array.isArray(s.postTypes)) {
|
|
82
|
+
return s.postTypes.length
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return 0
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {string[]} lines
|
|
90
|
+
* @param {string[]} currentStackIds
|
|
91
|
+
* @param {Map<string, string>} idToJs
|
|
92
|
+
* @param {NormalizedSignature} sig
|
|
93
|
+
* @param {string} wordLabel
|
|
94
|
+
*/
|
|
95
|
+
function appendReturnForLines (lines, currentStackIds, idToJs, sig, wordLabel) {
|
|
96
|
+
const right = Array.isArray(sig.right) ? sig.right : []
|
|
97
|
+
if (right.length !== currentStackIds.length) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`emit-body[${wordLabel}]: длина финального стека (${currentStackIds.length}) ` +
|
|
100
|
+
`не совпадает с sig.right (${right.length})`
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
if (right.length === 0) {
|
|
104
|
+
lines.push('return;')
|
|
105
|
+
} else if (right.length === 1) {
|
|
106
|
+
const id = currentStackIds[0]
|
|
107
|
+
const j = idToJs.get(id)
|
|
108
|
+
if (j == null) {
|
|
109
|
+
throw new Error(`emit-body[${wordLabel}]: нет привязки для финального id ${id}`)
|
|
110
|
+
}
|
|
111
|
+
lines.push(`return ${j};`)
|
|
112
|
+
} else {
|
|
113
|
+
const parts = currentStackIds.map((id) => {
|
|
114
|
+
const j = idToJs.get(id)
|
|
115
|
+
if (j == null) {
|
|
116
|
+
throw new Error(`emit-body[${wordLabel}]: нет привязки для финального id ${id}`)
|
|
117
|
+
}
|
|
118
|
+
return j
|
|
119
|
+
})
|
|
120
|
+
lines.push(`return [${parts.join(', ')}];`)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {Record<string, unknown>} node
|
|
126
|
+
* @returns {string}
|
|
127
|
+
*/
|
|
128
|
+
function literalToJsExpr (node) {
|
|
129
|
+
const litKind = /** @type {string | undefined} */ (node.litKind)
|
|
130
|
+
const raw = node.raw != null ? String(node.raw) : ''
|
|
131
|
+
switch (litKind) {
|
|
132
|
+
case 'number': {
|
|
133
|
+
if (raw === '' || Number.isNaN(Number(raw))) {
|
|
134
|
+
throw new Error(`emit-body: некорректный number literal ${JSON.stringify(raw)}`)
|
|
135
|
+
}
|
|
136
|
+
return raw
|
|
137
|
+
}
|
|
138
|
+
case 'bigint':
|
|
139
|
+
return `BigInt(${JSON.stringify(raw)})`
|
|
140
|
+
case 'bool':
|
|
141
|
+
return raw === 'true' ? 'true' : 'false'
|
|
142
|
+
case 'nil':
|
|
143
|
+
return 'null'
|
|
144
|
+
case 'string':
|
|
145
|
+
return JSON.stringify(raw)
|
|
146
|
+
case 'regexp':
|
|
147
|
+
throw new Error('emit-body: regexp literal пока не поддержан')
|
|
148
|
+
default:
|
|
149
|
+
throw new Error(`emit-body: неизвестный litKind ${String(litKind)}`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @param {string} name
|
|
155
|
+
*/
|
|
156
|
+
function assertValidJsCallee (name) {
|
|
157
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(name)) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`emit-body: имя слова не является допустимым идентификатором JS: ${JSON.stringify(name)}`
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Имя JS для входного слота слова: `named_quote.prefix` или очередное `pK`.
|
|
166
|
+
*
|
|
167
|
+
* @param {unknown} slot
|
|
168
|
+
* @param {() => string} allocP
|
|
169
|
+
* @returns {string}
|
|
170
|
+
*/
|
|
171
|
+
function jsParamNameForEntrySlot (slot, allocP) {
|
|
172
|
+
if (
|
|
173
|
+
slot != null &&
|
|
174
|
+
typeof slot === 'object' &&
|
|
175
|
+
/** @type {{ kind?: string, prefix?: string }} */ (slot).kind === 'named_quote' &&
|
|
176
|
+
typeof /** @type {{ prefix?: string }} */ (slot).prefix === 'string'
|
|
177
|
+
) {
|
|
178
|
+
const px = /** @type {{ prefix: string }} */ (slot).prefix
|
|
179
|
+
try {
|
|
180
|
+
assertValidJsCallee(px)
|
|
181
|
+
return px
|
|
182
|
+
} catch {
|
|
183
|
+
/* fall through */
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return allocP()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Список формальных параметров JS для обёртки `function` (wire `normalizedSig` + `entryStackIds` из bind-values).
|
|
191
|
+
*
|
|
192
|
+
* @param {NormalizedSignature | null | undefined} normalizedSig
|
|
193
|
+
* @param {string[] | null | undefined} entryStackIds
|
|
194
|
+
* @returns {string}
|
|
195
|
+
*/
|
|
196
|
+
export function formatFormalParametersForWireSig (normalizedSig, entryStackIds) {
|
|
197
|
+
if (normalizedSig == null || typeof normalizedSig !== 'object') return ''
|
|
198
|
+
const left = Array.isArray(normalizedSig.left) ? normalizedSig.left : []
|
|
199
|
+
if (left.length === 0) return ''
|
|
200
|
+
if (Array.isArray(entryStackIds) && entryStackIds.length === left.length) {
|
|
201
|
+
let pAcc = 0
|
|
202
|
+
const allocP = () => `p${pAcc++}`
|
|
203
|
+
return left.map((slot) => jsParamNameForEntrySlot(slot, allocP)).join(', ')
|
|
204
|
+
}
|
|
205
|
+
return left.map((_, i) => `p${i}`).join(', ')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Рекурсивно: есть ли в шагах узел с полем `calleeAsync === true` (маркер L3/IR).
|
|
210
|
+
*
|
|
211
|
+
* @param {unknown} steps
|
|
212
|
+
* @returns {boolean}
|
|
213
|
+
*/
|
|
214
|
+
export function irStepsContainsCalleeAsync (steps) {
|
|
215
|
+
if (!Array.isArray(steps)) return false
|
|
216
|
+
for (const raw of steps) {
|
|
217
|
+
if (raw == null || typeof raw !== 'object') continue
|
|
218
|
+
const n = /** @type {Record<string, unknown>} */ (raw)
|
|
219
|
+
if (n.calleeAsync === true) return true
|
|
220
|
+
const kind = String(n.kind ?? '')
|
|
221
|
+
if (kind === 'Builtin' && n.name === 'call') {
|
|
222
|
+
const meta = n.callInlineMeta
|
|
223
|
+
if (meta != null && typeof meta === 'object') {
|
|
224
|
+
const m = /** @type {{ innerSteps?: unknown }} */ (meta)
|
|
225
|
+
if (irStepsContainsCalleeAsync(m.innerSteps)) return true
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (
|
|
229
|
+
kind === 'Builtin' &&
|
|
230
|
+
COMBINATOR_BUILTIN_NAMES.has(String(n.name ?? ''))
|
|
231
|
+
) {
|
|
232
|
+
const cmeta = n.combinatorInlineMeta
|
|
233
|
+
if (cmeta != null && typeof cmeta === 'object') {
|
|
234
|
+
const parts = /** @type {{ parts?: unknown }} */ (cmeta).parts
|
|
235
|
+
if (Array.isArray(parts)) {
|
|
236
|
+
for (const p of parts) {
|
|
237
|
+
if (
|
|
238
|
+
p != null &&
|
|
239
|
+
typeof p === 'object' &&
|
|
240
|
+
/** @type {{ kind?: string }} */ (p).kind === 'inline' &&
|
|
241
|
+
irStepsContainsCalleeAsync(
|
|
242
|
+
/** @type {{ innerSteps?: unknown }} */ (p).innerSteps
|
|
243
|
+
)
|
|
244
|
+
) {
|
|
245
|
+
return true
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (kind === 'Quotation') {
|
|
252
|
+
const qm = n.quoteEmitMeta
|
|
253
|
+
if (qm != null && typeof qm === 'object') {
|
|
254
|
+
const inner = /** @type {{ innerSteps?: unknown }} */ (qm).innerSteps
|
|
255
|
+
if (irStepsContainsCalleeAsync(inner)) return true
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return false
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Рекурсивно: есть ли вызов слова, у которого по сигнатуре всплывает `+Async` (RFC-compile §10, вариант B).
|
|
264
|
+
* Не опирается на `node.calleeAsync` в IR.
|
|
265
|
+
*
|
|
266
|
+
* @param {unknown} steps
|
|
267
|
+
* @param {CalleeAsyncResolver | null | undefined} resolver
|
|
268
|
+
* @returns {boolean}
|
|
269
|
+
*/
|
|
270
|
+
export function irStepsCallsPropagatingAsyncCallee (steps, resolver) {
|
|
271
|
+
if (resolver == null || !Array.isArray(steps)) return false
|
|
272
|
+
for (const raw of steps) {
|
|
273
|
+
if (raw == null || typeof raw !== 'object') continue
|
|
274
|
+
const n = /** @type {Record<string, unknown>} */ (raw)
|
|
275
|
+
const kind = String(n.kind ?? '')
|
|
276
|
+
if (kind === 'Word') {
|
|
277
|
+
const ref = n.ref
|
|
278
|
+
if (ref === 'local') {
|
|
279
|
+
if (resolver.localPropagatesAsync(String(n.name ?? ''))) return true
|
|
280
|
+
} else if (ref === 'qualified') {
|
|
281
|
+
if (
|
|
282
|
+
resolver.qualifiedPropagatesAsync(
|
|
283
|
+
String(n.module ?? ''),
|
|
284
|
+
String(n.word ?? n.name ?? '')
|
|
285
|
+
)
|
|
286
|
+
) {
|
|
287
|
+
return true
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (kind === 'Builtin' && n.name === 'call') {
|
|
292
|
+
const meta = n.callInlineMeta
|
|
293
|
+
if (meta != null && typeof meta === 'object') {
|
|
294
|
+
const m = /** @type {{ innerSteps?: unknown }} */ (meta)
|
|
295
|
+
if (irStepsCallsPropagatingAsyncCallee(m.innerSteps, resolver)) return true
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (
|
|
299
|
+
kind === 'Builtin' &&
|
|
300
|
+
COMBINATOR_BUILTIN_NAMES.has(String(n.name ?? ''))
|
|
301
|
+
) {
|
|
302
|
+
const cmeta = n.combinatorInlineMeta
|
|
303
|
+
if (cmeta != null && typeof cmeta === 'object') {
|
|
304
|
+
const parts = /** @type {{ parts?: unknown }} */ (cmeta).parts
|
|
305
|
+
if (Array.isArray(parts)) {
|
|
306
|
+
for (const p of parts) {
|
|
307
|
+
if (
|
|
308
|
+
p != null &&
|
|
309
|
+
typeof p === 'object' &&
|
|
310
|
+
/** @type {{ kind?: string }} */ (p).kind === 'inline' &&
|
|
311
|
+
irStepsCallsPropagatingAsyncCallee(
|
|
312
|
+
/** @type {{ innerSteps?: unknown }} */ (p).innerSteps,
|
|
313
|
+
resolver
|
|
314
|
+
)
|
|
315
|
+
) {
|
|
316
|
+
return true
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (kind === 'Quotation') {
|
|
323
|
+
const qm = n.quoteEmitMeta
|
|
324
|
+
if (qm != null && typeof qm === 'object') {
|
|
325
|
+
const inner = /** @type {{ innerSteps?: unknown }} */ (qm).innerSteps
|
|
326
|
+
if (irStepsCallsPropagatingAsyncCallee(inner, resolver)) return true
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return false
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @param {Record<string, unknown>} node
|
|
335
|
+
* @param {CalleeAsyncResolver | null | undefined} resolver
|
|
336
|
+
* @returns {boolean}
|
|
337
|
+
*/
|
|
338
|
+
function calleeStepNeedsAwaitFromSig (node, resolver) {
|
|
339
|
+
if (node.calleeAsync === true) return true
|
|
340
|
+
if (resolver == null) return false
|
|
341
|
+
const kind = String(node.kind ?? '')
|
|
342
|
+
if (kind !== 'Word') return false
|
|
343
|
+
const ref = node.ref
|
|
344
|
+
if (ref === 'local') {
|
|
345
|
+
return resolver.localPropagatesAsync(String(node.name ?? ''))
|
|
346
|
+
}
|
|
347
|
+
if (ref === 'qualified') {
|
|
348
|
+
return resolver.qualifiedPropagatesAsync(
|
|
349
|
+
String(node.module ?? ''),
|
|
350
|
+
String(node.word ?? node.name ?? '')
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
return false
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* @param {Record<string, unknown>} node
|
|
358
|
+
* @param {{ argIds: string[], resultIds: string[] }} vb
|
|
359
|
+
* @param {Map<string, string>} idToJs
|
|
360
|
+
* @param {() => string} genName
|
|
361
|
+
* @param {string[]} lines
|
|
362
|
+
*/
|
|
363
|
+
/**
|
|
364
|
+
* @param {Record<string, unknown>} node
|
|
365
|
+
* @param {{ argIds: string[], resultIds: string[] }} vb
|
|
366
|
+
* @param {number} L
|
|
367
|
+
* @param {Map<string, string>} idToJs
|
|
368
|
+
* @param {() => string} genName
|
|
369
|
+
* @param {string[]} lines
|
|
370
|
+
*/
|
|
371
|
+
function emitListLiteralStep (node, vb, L, idToJs, genName, lines) {
|
|
372
|
+
const nIn = vb.argIds.length
|
|
373
|
+
const baseLen = L - nIn
|
|
374
|
+
const topOutIds = vb.resultIds.slice(baseLen)
|
|
375
|
+
if (topOutIds.length !== 1) {
|
|
376
|
+
throw new Error('emit-body: ListLiteral ожидает ровно один новый id на вершине')
|
|
377
|
+
}
|
|
378
|
+
const elements = /** @type {Record<string, unknown>[] | undefined} */ (node.elements)
|
|
379
|
+
if (!Array.isArray(elements)) {
|
|
380
|
+
throw new Error('emit-body: ListLiteral без elements')
|
|
381
|
+
}
|
|
382
|
+
const exprs = elements.map((el) => literalToJsExpr(el))
|
|
383
|
+
const litId = topOutIds[0]
|
|
384
|
+
const nm = genName()
|
|
385
|
+
lines.push(`const ${nm} = [${exprs.join(', ')}];`)
|
|
386
|
+
idToJs.set(litId, nm)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* @param {'read'|'write'} direction
|
|
391
|
+
* @param {string} slotName
|
|
392
|
+
* @param {{ argIds: string[], resultIds: string[] }} vb
|
|
393
|
+
* @param {number} L
|
|
394
|
+
* @param {Map<string, string>} idToJs
|
|
395
|
+
* @param {Map<string, string>} slotExprByName
|
|
396
|
+
* @param {() => string} genName
|
|
397
|
+
* @param {string[]} lines
|
|
398
|
+
* @param {boolean} strict
|
|
399
|
+
* @param {string} wordLabel
|
|
400
|
+
* @param {number} stepIndex
|
|
401
|
+
*/
|
|
402
|
+
function emitSlotStep (
|
|
403
|
+
direction,
|
|
404
|
+
slotName,
|
|
405
|
+
vb,
|
|
406
|
+
L,
|
|
407
|
+
idToJs,
|
|
408
|
+
slotExprByName,
|
|
409
|
+
genName,
|
|
410
|
+
lines,
|
|
411
|
+
strict,
|
|
412
|
+
wordLabel,
|
|
413
|
+
stepIndex
|
|
414
|
+
) {
|
|
415
|
+
if (direction === 'write') {
|
|
416
|
+
const argIds = vb.argIds
|
|
417
|
+
if (argIds.length !== 1) {
|
|
418
|
+
throw new Error(`emit-body[${wordLabel}]: slot_write ожидает 1 argId`)
|
|
419
|
+
}
|
|
420
|
+
const j = idToJs.get(argIds[0])
|
|
421
|
+
if (j == null) {
|
|
422
|
+
throw new Error(`emit-body[${wordLabel}]: slot_write нет JS для arg`)
|
|
423
|
+
}
|
|
424
|
+
slotExprByName.set(slotName, j)
|
|
425
|
+
return
|
|
426
|
+
}
|
|
427
|
+
if (direction === 'read') {
|
|
428
|
+
const src = slotExprByName.get(slotName)
|
|
429
|
+
if (src == null) {
|
|
430
|
+
if (strict) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`emit-body[${wordLabel}]: шаг ${stepIndex} slot_read :${slotName} без предшествующей записи`
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
const nIn = vb.argIds.length
|
|
438
|
+
const baseLen = L - nIn
|
|
439
|
+
const topOutIds = vb.resultIds.slice(baseLen)
|
|
440
|
+
if (topOutIds.length !== 1) {
|
|
441
|
+
throw new Error(`emit-body[${wordLabel}]: slot_read один выход на вершине`)
|
|
442
|
+
}
|
|
443
|
+
const readId = topOutIds[0]
|
|
444
|
+
const nm = genName()
|
|
445
|
+
lines.push(`const ${nm} = ${src};`)
|
|
446
|
+
idToJs.set(readId, nm)
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
throw new Error(`emit-body[${wordLabel}]: Slot без direction`)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function emitLiteralStep (node, vb, idToJs, genName, lines) {
|
|
453
|
+
const pre = node.preTypes
|
|
454
|
+
const L = Array.isArray(pre) ? pre.length : 0
|
|
455
|
+
const nIn = vb.argIds.length
|
|
456
|
+
const baseLen = L - nIn
|
|
457
|
+
const topOutIds = vb.resultIds.slice(baseLen)
|
|
458
|
+
if (topOutIds.length !== 1) {
|
|
459
|
+
throw new Error('emit-body: Literal ожидает ровно один новый id на вершине')
|
|
460
|
+
}
|
|
461
|
+
const litId = topOutIds[0]
|
|
462
|
+
const expr = literalToJsExpr(node)
|
|
463
|
+
const nm = genName()
|
|
464
|
+
lines.push(`const ${nm} = ${expr};`)
|
|
465
|
+
idToJs.set(litId, nm)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* @param {string} name
|
|
470
|
+
* @param {{ argIds: string[], resultIds: string[] }} vb
|
|
471
|
+
* @param {number} L
|
|
472
|
+
* @param {Map<string, string>} idToJs
|
|
473
|
+
* @param {() => string} genName
|
|
474
|
+
* @param {string[]} lines
|
|
475
|
+
*/
|
|
476
|
+
function emitBuiltinShuffleStep (
|
|
477
|
+
name,
|
|
478
|
+
vb,
|
|
479
|
+
L,
|
|
480
|
+
idToJs,
|
|
481
|
+
genName,
|
|
482
|
+
lines
|
|
483
|
+
) {
|
|
484
|
+
const shuffler = BUILTIN_SHUFFLE_EXPR[name]
|
|
485
|
+
if (!shuffler) return false
|
|
486
|
+
const nIn = vb.argIds.length
|
|
487
|
+
const baseLen = L - nIn
|
|
488
|
+
const topOutIds = vb.resultIds.slice(baseLen)
|
|
489
|
+
const argExprs = vb.argIds.map((id) => {
|
|
490
|
+
const j = idToJs.get(id)
|
|
491
|
+
if (j == null) {
|
|
492
|
+
throw new Error(`emit-body: нет JS-имени для argId ${id} (builtin ${name})`)
|
|
493
|
+
}
|
|
494
|
+
return j
|
|
495
|
+
})
|
|
496
|
+
const outExprs = shuffler(argExprs)
|
|
497
|
+
if (outExprs.length !== topOutIds.length) {
|
|
498
|
+
throw new Error(`emit-body: внутренняя ошибка длины shuffle для ${name}`)
|
|
499
|
+
}
|
|
500
|
+
const seen = new Set()
|
|
501
|
+
for (let i = 0; i < topOutIds.length; i++) {
|
|
502
|
+
const rid = topOutIds[i]
|
|
503
|
+
const expr = outExprs[i]
|
|
504
|
+
if (seen.has(rid)) continue
|
|
505
|
+
seen.add(rid)
|
|
506
|
+
if (isShuffleReuseExpr(expr)) {
|
|
507
|
+
idToJs.set(rid, expr)
|
|
508
|
+
} else {
|
|
509
|
+
const nm = genName()
|
|
510
|
+
lines.push(`const ${nm} = ${expr};`)
|
|
511
|
+
idToJs.set(rid, nm)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return true
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* @param {QuoteEmitMeta} meta
|
|
519
|
+
* @param {{
|
|
520
|
+
* idToJs: Map<string, string>
|
|
521
|
+
* lines: string[]
|
|
522
|
+
* genName: () => string
|
|
523
|
+
* genQuoteName: () => string
|
|
524
|
+
* quoteMetaByValueId: Map<string, QuoteEmitMeta>
|
|
525
|
+
* slotExprByName: Map<string, string>
|
|
526
|
+
* strict: boolean
|
|
527
|
+
* wordLabel: string
|
|
528
|
+
* entryParamCounter: { n: number }
|
|
529
|
+
* callerAsync: boolean
|
|
530
|
+
* calleeAsyncResolver?: CalleeAsyncResolver | null
|
|
531
|
+
* sumEliminatorNames?: Set<string> | null
|
|
532
|
+
* }} env
|
|
533
|
+
* @returns {string} имя `qN`
|
|
534
|
+
*/
|
|
535
|
+
function emitQuoteAsFirstClass (meta, env) {
|
|
536
|
+
const n = meta.innerEntryStackIds.length
|
|
537
|
+
const nOut = inferInnerOutLen(meta.innerSteps)
|
|
538
|
+
const params =
|
|
539
|
+
n === 0 ? '' : Array.from({ length: n }, (_, i) => `p${i}`).join(', ')
|
|
540
|
+
const qn = env.genQuoteName()
|
|
541
|
+
const merged = new Map()
|
|
542
|
+
for (let i = 0; i < n; i++) {
|
|
543
|
+
merged.set(meta.innerEntryStackIds[i], `p${i}`)
|
|
544
|
+
}
|
|
545
|
+
const innerLines = []
|
|
546
|
+
const innerAsync = env.calleeAsyncResolver
|
|
547
|
+
? irStepsCallsPropagatingAsyncCallee(
|
|
548
|
+
meta.innerSteps,
|
|
549
|
+
env.calleeAsyncResolver
|
|
550
|
+
)
|
|
551
|
+
: irStepsContainsCalleeAsync(meta.innerSteps)
|
|
552
|
+
const innerEnv = {
|
|
553
|
+
idToJs: merged,
|
|
554
|
+
lines: innerLines,
|
|
555
|
+
genName: env.genName,
|
|
556
|
+
genQuoteName: env.genQuoteName,
|
|
557
|
+
quoteMetaByValueId: env.quoteMetaByValueId,
|
|
558
|
+
slotExprByName: env.slotExprByName,
|
|
559
|
+
strict: env.strict,
|
|
560
|
+
wordLabel: env.wordLabel,
|
|
561
|
+
entryParamCounter: env.entryParamCounter,
|
|
562
|
+
resolveQualifiedCallee: env.resolveQualifiedCallee,
|
|
563
|
+
callerAsync: innerAsync,
|
|
564
|
+
calleeAsyncResolver: env.calleeAsyncResolver,
|
|
565
|
+
sumEliminatorNames: env.sumEliminatorNames,
|
|
566
|
+
initialStackIds:
|
|
567
|
+
meta.innerEntryStackIds.length > 0 ? meta.innerEntryStackIds : undefined
|
|
568
|
+
}
|
|
569
|
+
const finalIds = processIrSteps(meta.innerSteps, innerEnv)
|
|
570
|
+
const nsig = sigWireFromStackLens(n, nOut)
|
|
571
|
+
appendReturnForLines(innerLines, finalIds, merged, nsig, env.wordLabel)
|
|
572
|
+
const body = innerLines.join('\n')
|
|
573
|
+
const innerBlock = body ? ` ${body.split('\n').join('\n ')}` : ''
|
|
574
|
+
const asyncKw = innerAsync ? 'async ' : ''
|
|
575
|
+
env.lines.push(`const ${qn} = ${asyncKw}(${params}) => {\n${innerBlock}\n}`)
|
|
576
|
+
return qn
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* @param {Record<string, unknown>} node
|
|
581
|
+
* @param {{ argIds: string[], resultIds: string[] }} vb
|
|
582
|
+
* @param {number} L
|
|
583
|
+
* @param {Map<string, string>} idToJs
|
|
584
|
+
* @param {() => string} genName
|
|
585
|
+
* @param {string[]} lines
|
|
586
|
+
* @param {Map<string, QuoteEmitMeta>} quoteMetaByValueId
|
|
587
|
+
* @param {() => string} genQuoteName
|
|
588
|
+
* @param {{ n: number }} entryParamCounter
|
|
589
|
+
* @param {boolean} strict
|
|
590
|
+
* @param {string} wordLabel
|
|
591
|
+
* @param {((moduleAlias: string, wordName: string) => string) | undefined} resolveQualifiedCallee
|
|
592
|
+
* @param {boolean} callerAsync
|
|
593
|
+
* @param {CalleeAsyncResolver | null | undefined} calleeAsyncResolver
|
|
594
|
+
* @param {string[]} stackIds стек id до шага (дно→вершина)
|
|
595
|
+
* @param {Set<string> | null | undefined} sumEliminatorNames
|
|
596
|
+
* @param {Map<string, string>} slotExprByName
|
|
597
|
+
*/
|
|
598
|
+
function emitWordLocalStep (
|
|
599
|
+
node,
|
|
600
|
+
vb,
|
|
601
|
+
L,
|
|
602
|
+
idToJs,
|
|
603
|
+
genName,
|
|
604
|
+
lines,
|
|
605
|
+
quoteMetaByValueId,
|
|
606
|
+
genQuoteName,
|
|
607
|
+
entryParamCounter,
|
|
608
|
+
strict,
|
|
609
|
+
wordLabel,
|
|
610
|
+
resolveQualifiedCallee,
|
|
611
|
+
callerAsync,
|
|
612
|
+
calleeAsyncResolver,
|
|
613
|
+
stackIds,
|
|
614
|
+
sumEliminatorNames,
|
|
615
|
+
slotExprByName
|
|
616
|
+
) {
|
|
617
|
+
const name = /** @type {string} */ (node.name)
|
|
618
|
+
assertValidJsCallee(name)
|
|
619
|
+
const nIn = vb.argIds.length
|
|
620
|
+
const baseLen = L - nIn
|
|
621
|
+
const topOutIds = vb.resultIds.slice(baseLen)
|
|
622
|
+
const env = {
|
|
623
|
+
idToJs,
|
|
624
|
+
lines,
|
|
625
|
+
genName,
|
|
626
|
+
genQuoteName,
|
|
627
|
+
quoteMetaByValueId,
|
|
628
|
+
slotExprByName,
|
|
629
|
+
strict,
|
|
630
|
+
wordLabel,
|
|
631
|
+
entryParamCounter,
|
|
632
|
+
resolveQualifiedCallee,
|
|
633
|
+
callerAsync,
|
|
634
|
+
calleeAsyncResolver,
|
|
635
|
+
sumEliminatorNames,
|
|
636
|
+
initialStackIds: undefined
|
|
637
|
+
}
|
|
638
|
+
/** @type {string[]} */
|
|
639
|
+
let argExprs = vb.argIds.map((id) => {
|
|
640
|
+
const qm = quoteMetaByValueId.get(id)
|
|
641
|
+
if (qm) {
|
|
642
|
+
const existing = idToJs.get(id)
|
|
643
|
+
if (existing != null) return existing
|
|
644
|
+
return emitQuoteAsFirstClass(qm, env)
|
|
645
|
+
}
|
|
646
|
+
const j = idToJs.get(id)
|
|
647
|
+
if (j == null) {
|
|
648
|
+
throw new Error(`emit-body: нет JS-имени для argId ${id} (Word ${name})`)
|
|
649
|
+
}
|
|
650
|
+
return j
|
|
651
|
+
})
|
|
652
|
+
if (sumEliminatorNames?.has(name) === true) {
|
|
653
|
+
const prefixLen = L - nIn
|
|
654
|
+
if (stackIds.length !== L) {
|
|
655
|
+
throw new Error(
|
|
656
|
+
`emit-body[${wordLabel}]: неверная длина стека для eliminator (${stackIds.length} !== ${L})`
|
|
657
|
+
)
|
|
658
|
+
}
|
|
659
|
+
const prefixIds = stackIds.slice(0, prefixLen)
|
|
660
|
+
const prefixExprs = prefixIds.map((id) => {
|
|
661
|
+
const qm = quoteMetaByValueId.get(id)
|
|
662
|
+
if (qm) {
|
|
663
|
+
const existing = idToJs.get(id)
|
|
664
|
+
if (existing != null) return existing
|
|
665
|
+
return emitQuoteAsFirstClass(qm, env)
|
|
666
|
+
}
|
|
667
|
+
const j = idToJs.get(id)
|
|
668
|
+
if (j == null) {
|
|
669
|
+
throw new Error(`emit-body: нет JS-имени для prefix id ${id} (Word ${name})`)
|
|
670
|
+
}
|
|
671
|
+
return j
|
|
672
|
+
})
|
|
673
|
+
argExprs = [...prefixExprs, ...argExprs]
|
|
674
|
+
}
|
|
675
|
+
const innerCall = `${name}(${argExprs.join(', ')})`
|
|
676
|
+
const useAwait =
|
|
677
|
+
callerAsync && calleeStepNeedsAwaitFromSig(node, calleeAsyncResolver)
|
|
678
|
+
const callExpr = useAwait ? `await ${innerCall}` : innerCall
|
|
679
|
+
if (topOutIds.length === 0) {
|
|
680
|
+
lines.push(`${callExpr};`)
|
|
681
|
+
return
|
|
682
|
+
}
|
|
683
|
+
if (topOutIds.length === 1) {
|
|
684
|
+
const nm = genName()
|
|
685
|
+
lines.push(`const ${nm} = ${callExpr};`)
|
|
686
|
+
idToJs.set(topOutIds[0], nm)
|
|
687
|
+
return
|
|
688
|
+
}
|
|
689
|
+
const names = topOutIds.map(() => genName())
|
|
690
|
+
lines.push(`const [${names.join(', ')}] = ${callExpr};`)
|
|
691
|
+
for (let i = 0; i < topOutIds.length; i++) {
|
|
692
|
+
idToJs.set(topOutIds[i], names[i])
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* @param {Record<string, unknown>} node
|
|
698
|
+
* @param {{ argIds: string[], resultIds: string[] }} vb
|
|
699
|
+
* @param {number} L
|
|
700
|
+
* @param {Map<string, string>} idToJs
|
|
701
|
+
* @param {() => string} genName
|
|
702
|
+
* @param {string[]} lines
|
|
703
|
+
* @param {Map<string, QuoteEmitMeta>} quoteMetaByValueId
|
|
704
|
+
* @param {() => string} genQuoteName
|
|
705
|
+
* @param {{ n: number }} entryParamCounter
|
|
706
|
+
* @param {boolean} strict
|
|
707
|
+
* @param {string} wordLabel
|
|
708
|
+
* @param {(moduleAlias: string, wordName: string) => string} resolveQualifiedCallee
|
|
709
|
+
* @param {boolean} callerAsync
|
|
710
|
+
* @param {CalleeAsyncResolver | null | undefined} calleeAsyncResolver
|
|
711
|
+
* @param {Map<string, string>} slotExprByName
|
|
712
|
+
*/
|
|
713
|
+
function emitWordQualifiedStep (
|
|
714
|
+
node,
|
|
715
|
+
vb,
|
|
716
|
+
L,
|
|
717
|
+
idToJs,
|
|
718
|
+
genName,
|
|
719
|
+
lines,
|
|
720
|
+
quoteMetaByValueId,
|
|
721
|
+
genQuoteName,
|
|
722
|
+
entryParamCounter,
|
|
723
|
+
strict,
|
|
724
|
+
wordLabel,
|
|
725
|
+
resolveQualifiedCallee,
|
|
726
|
+
callerAsync,
|
|
727
|
+
calleeAsyncResolver,
|
|
728
|
+
slotExprByName
|
|
729
|
+
) {
|
|
730
|
+
const mod = /** @type {string} */ (node.module)
|
|
731
|
+
const name = /** @type {string} */ (node.word ?? node.name)
|
|
732
|
+
const calleeExpr = resolveQualifiedCallee(mod, name)
|
|
733
|
+
const nIn = vb.argIds.length
|
|
734
|
+
const baseLen = L - nIn
|
|
735
|
+
const topOutIds = vb.resultIds.slice(baseLen)
|
|
736
|
+
const env = {
|
|
737
|
+
idToJs,
|
|
738
|
+
lines,
|
|
739
|
+
genName,
|
|
740
|
+
genQuoteName,
|
|
741
|
+
quoteMetaByValueId,
|
|
742
|
+
slotExprByName,
|
|
743
|
+
strict,
|
|
744
|
+
wordLabel,
|
|
745
|
+
entryParamCounter,
|
|
746
|
+
resolveQualifiedCallee,
|
|
747
|
+
callerAsync,
|
|
748
|
+
calleeAsyncResolver,
|
|
749
|
+
sumEliminatorNames: null,
|
|
750
|
+
initialStackIds: undefined
|
|
751
|
+
}
|
|
752
|
+
const argExprs = vb.argIds.map((id) => {
|
|
753
|
+
const qm = quoteMetaByValueId.get(id)
|
|
754
|
+
if (qm) {
|
|
755
|
+
const existing = idToJs.get(id)
|
|
756
|
+
if (existing != null) return existing
|
|
757
|
+
return emitQuoteAsFirstClass(qm, env)
|
|
758
|
+
}
|
|
759
|
+
const j = idToJs.get(id)
|
|
760
|
+
if (j == null) {
|
|
761
|
+
throw new Error(`emit-body: нет JS-имени для argId ${id} (qualified ${mod}/${name})`)
|
|
762
|
+
}
|
|
763
|
+
return j
|
|
764
|
+
})
|
|
765
|
+
const innerCall = `${calleeExpr}(${argExprs.join(', ')})`
|
|
766
|
+
const useAwait =
|
|
767
|
+
callerAsync && calleeStepNeedsAwaitFromSig(node, calleeAsyncResolver)
|
|
768
|
+
const callExpr = useAwait ? `await ${innerCall}` : innerCall
|
|
769
|
+
if (topOutIds.length === 0) {
|
|
770
|
+
lines.push(`${callExpr};`)
|
|
771
|
+
return
|
|
772
|
+
}
|
|
773
|
+
if (topOutIds.length === 1) {
|
|
774
|
+
const nm = genName()
|
|
775
|
+
lines.push(`const ${nm} = ${callExpr};`)
|
|
776
|
+
idToJs.set(topOutIds[0], nm)
|
|
777
|
+
return
|
|
778
|
+
}
|
|
779
|
+
const names = topOutIds.map(() => genName())
|
|
780
|
+
lines.push(`const [${names.join(', ')}] = ${callExpr};`)
|
|
781
|
+
for (let i = 0; i < topOutIds.length; i++) {
|
|
782
|
+
idToJs.set(topOutIds[i], names[i])
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* @param {Record<string, unknown>} node
|
|
788
|
+
* @param {{ argIds: string[], resultIds: string[] }} vb
|
|
789
|
+
* @param {number} L
|
|
790
|
+
* @param {{
|
|
791
|
+
* idToJs: Map<string, string>
|
|
792
|
+
* lines: string[]
|
|
793
|
+
* genName: () => string
|
|
794
|
+
* genQuoteName: () => string
|
|
795
|
+
* quoteMetaByValueId: Map<string, QuoteEmitMeta>
|
|
796
|
+
* strict: boolean
|
|
797
|
+
* wordLabel: string
|
|
798
|
+
* entryParamCounter: { n: number }
|
|
799
|
+
* resolveQualifiedCallee?: (moduleAlias: string, wordName: string) => string
|
|
800
|
+
* callerAsync: boolean
|
|
801
|
+
* calleeAsyncResolver?: CalleeAsyncResolver | null
|
|
802
|
+
* }} env
|
|
803
|
+
*/
|
|
804
|
+
function emitCallInlineStep (node, vb, L, env) {
|
|
805
|
+
const meta = /** @type {{
|
|
806
|
+
kind?: 'inline' | 'invoke'
|
|
807
|
+
innerSteps?: Record<string, unknown>[]
|
|
808
|
+
innerEntryStackIds?: string[]
|
|
809
|
+
innerInLen?: number
|
|
810
|
+
innerOutLen?: number
|
|
811
|
+
} | undefined} */ (node.callInlineMeta)
|
|
812
|
+
if (!meta || typeof meta !== 'object') {
|
|
813
|
+
throw new Error(`emit-body[${env.wordLabel}]: call без callInlineMeta`)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const baseLen = L - vb.argIds.length
|
|
817
|
+
const topOutIds = vb.resultIds.slice(baseLen)
|
|
818
|
+
const m = topOutIds.length
|
|
819
|
+
|
|
820
|
+
if (meta.kind === 'invoke') {
|
|
821
|
+
const n = meta.innerInLen
|
|
822
|
+
const mMeta = meta.innerOutLen
|
|
823
|
+
if (typeof n !== 'number' || typeof mMeta !== 'number') {
|
|
824
|
+
throw new Error(`emit-body[${env.wordLabel}]: call invoke — нет innerInLen/innerOutLen`)
|
|
825
|
+
}
|
|
826
|
+
if (m !== mMeta) {
|
|
827
|
+
throw new Error(
|
|
828
|
+
`emit-body[${env.wordLabel}]: call invoke — m пост-стека (${m}) ≠ innerOutLen (${mMeta})`
|
|
829
|
+
)
|
|
830
|
+
}
|
|
831
|
+
if (vb.argIds.length !== n + 1) {
|
|
832
|
+
throw new Error(
|
|
833
|
+
`emit-body[${env.wordLabel}]: call invoke — ожидалось ${n + 1} argIds, есть ${vb.argIds.length}`
|
|
834
|
+
)
|
|
835
|
+
}
|
|
836
|
+
const quoteId = vb.argIds[vb.argIds.length - 1]
|
|
837
|
+
const quoteJs = env.idToJs.get(quoteId)
|
|
838
|
+
if (quoteJs == null) {
|
|
839
|
+
throw new Error(`emit-body[${env.wordLabel}]: call invoke — нет JS для quote id ${quoteId}`)
|
|
840
|
+
}
|
|
841
|
+
const callArgExprs = vb.argIds.slice(0, -1).map((id) => {
|
|
842
|
+
const j = env.idToJs.get(id)
|
|
843
|
+
if (j == null) {
|
|
844
|
+
throw new Error(`emit-body[${env.wordLabel}]: call invoke — нет JS для arg ${id}`)
|
|
845
|
+
}
|
|
846
|
+
return j
|
|
847
|
+
})
|
|
848
|
+
const paren = callArgExprs.length ? `(${callArgExprs.join(', ')})` : '()'
|
|
849
|
+
let callExpr = `${quoteJs}${paren}`
|
|
850
|
+
const qm = env.quoteMetaByValueId.get(quoteId)
|
|
851
|
+
const invokeInnerAsync =
|
|
852
|
+
qm != null &&
|
|
853
|
+
env.calleeAsyncResolver != null &&
|
|
854
|
+
irStepsCallsPropagatingAsyncCallee(
|
|
855
|
+
qm.innerSteps,
|
|
856
|
+
env.calleeAsyncResolver
|
|
857
|
+
)
|
|
858
|
+
const useAwaitInvoke =
|
|
859
|
+
env.callerAsync &&
|
|
860
|
+
(node.calleeAsync === true || invokeInnerAsync)
|
|
861
|
+
if (useAwaitInvoke) {
|
|
862
|
+
callExpr = `await ${callExpr}`
|
|
863
|
+
}
|
|
864
|
+
if (m === 0) {
|
|
865
|
+
env.lines.push(`${callExpr};`)
|
|
866
|
+
} else if (m === 1) {
|
|
867
|
+
const nm = env.genName()
|
|
868
|
+
env.lines.push(`const ${nm} = ${callExpr};`)
|
|
869
|
+
env.idToJs.set(topOutIds[0], nm)
|
|
870
|
+
} else {
|
|
871
|
+
const names = topOutIds.map(() => env.genName())
|
|
872
|
+
env.lines.push(`const [${names.join(', ')}] = ${callExpr};`)
|
|
873
|
+
for (let i = 0; i < m; i++) {
|
|
874
|
+
env.idToJs.set(topOutIds[i], names[i])
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
const inlineKind = meta.kind == null || meta.kind === 'inline'
|
|
881
|
+
if (
|
|
882
|
+
!inlineKind ||
|
|
883
|
+
!Array.isArray(meta.innerSteps) ||
|
|
884
|
+
!Array.isArray(meta.innerEntryStackIds)
|
|
885
|
+
) {
|
|
886
|
+
throw new Error(`emit-body[${env.wordLabel}]: call без inline callInlineMeta`)
|
|
887
|
+
}
|
|
888
|
+
const innerArgs = vb.argIds.slice(0, -1)
|
|
889
|
+
if (innerArgs.length !== meta.innerEntryStackIds.length) {
|
|
890
|
+
throw new Error(`emit-body[${env.wordLabel}]: call — несовпадение числа аргументов quotation`)
|
|
891
|
+
}
|
|
892
|
+
const merged = new Map(env.idToJs)
|
|
893
|
+
for (let i = 0; i < meta.innerEntryStackIds.length; i++) {
|
|
894
|
+
const aid = innerArgs[i]
|
|
895
|
+
const j = env.idToJs.get(aid)
|
|
896
|
+
if (j == null) {
|
|
897
|
+
throw new Error(`emit-body[${env.wordLabel}]: call — нет JS для arg ${aid}`)
|
|
898
|
+
}
|
|
899
|
+
merged.set(meta.innerEntryStackIds[i], j)
|
|
900
|
+
}
|
|
901
|
+
const innerEnv = {
|
|
902
|
+
idToJs: merged,
|
|
903
|
+
lines: env.lines,
|
|
904
|
+
genName: env.genName,
|
|
905
|
+
genQuoteName: env.genQuoteName,
|
|
906
|
+
quoteMetaByValueId: env.quoteMetaByValueId,
|
|
907
|
+
slotExprByName: env.slotExprByName,
|
|
908
|
+
strict: env.strict,
|
|
909
|
+
wordLabel: env.wordLabel,
|
|
910
|
+
entryParamCounter: env.entryParamCounter,
|
|
911
|
+
resolveQualifiedCallee: env.resolveQualifiedCallee,
|
|
912
|
+
callerAsync: env.callerAsync,
|
|
913
|
+
calleeAsyncResolver: env.calleeAsyncResolver,
|
|
914
|
+
sumEliminatorNames: env.sumEliminatorNames,
|
|
915
|
+
initialStackIds:
|
|
916
|
+
Array.isArray(meta.innerEntryStackIds) && meta.innerEntryStackIds.length > 0
|
|
917
|
+
? meta.innerEntryStackIds
|
|
918
|
+
: undefined
|
|
919
|
+
}
|
|
920
|
+
const innerFinalIds = processIrSteps(meta.innerSteps, innerEnv)
|
|
921
|
+
if (innerFinalIds.length !== m) {
|
|
922
|
+
throw new Error(
|
|
923
|
+
`emit-body[${env.wordLabel}]: call — ожидалось ${m} выходов, внутри ${innerFinalIds.length}`
|
|
924
|
+
)
|
|
925
|
+
}
|
|
926
|
+
for (let i = 0; i < m; i++) {
|
|
927
|
+
const rid = topOutIds[i]
|
|
928
|
+
const innerId = innerFinalIds[i]
|
|
929
|
+
const j = merged.get(innerId)
|
|
930
|
+
if (j == null) {
|
|
931
|
+
throw new Error(`emit-body[${env.wordLabel}]: call — нет JS для внутреннего id ${innerId}`)
|
|
932
|
+
}
|
|
933
|
+
env.idToJs.set(rid, j)
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* @param {Record<string, unknown>[]} irSteps
|
|
939
|
+
* @param {{
|
|
940
|
+
* idToJs: Map<string, string>
|
|
941
|
+
* lines: string[]
|
|
942
|
+
* genName: () => string
|
|
943
|
+
* genQuoteName: () => string
|
|
944
|
+
* quoteMetaByValueId: Map<string, QuoteEmitMeta>
|
|
945
|
+
* slotExprByName: Map<string, string>
|
|
946
|
+
* strict: boolean
|
|
947
|
+
* wordLabel: string
|
|
948
|
+
* entryParamCounter: { n: number }
|
|
949
|
+
* resolveQualifiedCallee?: (moduleAlias: string, wordName: string) => string
|
|
950
|
+
* callerAsync: boolean
|
|
951
|
+
* calleeAsyncResolver?: CalleeAsyncResolver | null
|
|
952
|
+
* sumEliminatorNames?: Set<string> | null
|
|
953
|
+
* initialStackIds?: string[] | undefined
|
|
954
|
+
* }} env
|
|
955
|
+
* @returns {string[]}
|
|
956
|
+
*/
|
|
957
|
+
function processIrSteps (irSteps, env) {
|
|
958
|
+
/** @type {string[]} */
|
|
959
|
+
let currentStackIds = Array.isArray(env.initialStackIds)
|
|
960
|
+
? [...env.initialStackIds]
|
|
961
|
+
: []
|
|
962
|
+
const {
|
|
963
|
+
idToJs,
|
|
964
|
+
lines,
|
|
965
|
+
genName,
|
|
966
|
+
genQuoteName,
|
|
967
|
+
quoteMetaByValueId,
|
|
968
|
+
slotExprByName,
|
|
969
|
+
strict,
|
|
970
|
+
wordLabel,
|
|
971
|
+
entryParamCounter,
|
|
972
|
+
resolveQualifiedCallee,
|
|
973
|
+
sumEliminatorNames
|
|
974
|
+
} = env
|
|
975
|
+
|
|
976
|
+
if (!Array.isArray(irSteps)) {
|
|
977
|
+
throw new Error('emit-body: irSteps должен быть массивом')
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
for (let si = 0; si < irSteps.length; si++) {
|
|
981
|
+
const raw = irSteps[si]
|
|
982
|
+
if (raw == null || typeof raw !== 'object') continue
|
|
983
|
+
const node = /** @type {Record<string, unknown>} */ (raw)
|
|
984
|
+
const kind = String(node.kind ?? '')
|
|
985
|
+
const vb = /** @type {{ argIds?: string[], resultIds?: string[] } | undefined} */ (
|
|
986
|
+
node.valueBindings
|
|
987
|
+
)
|
|
988
|
+
const pre = node.preTypes
|
|
989
|
+
const L = Array.isArray(pre) ? pre.length : -1
|
|
990
|
+
|
|
991
|
+
if (!vb || !Array.isArray(vb.argIds) || !Array.isArray(vb.resultIds)) {
|
|
992
|
+
if (strict) {
|
|
993
|
+
throw new Error(
|
|
994
|
+
`emit-body[${wordLabel}]: шаг ${si} (${kind}) без valueBindings`
|
|
995
|
+
)
|
|
996
|
+
}
|
|
997
|
+
continue
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
if (L < 0) {
|
|
1001
|
+
if (strict) {
|
|
1002
|
+
throw new Error(`emit-body[${wordLabel}]: шаг ${si} (${kind}) без preTypes`)
|
|
1003
|
+
}
|
|
1004
|
+
continue
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
for (const aid of vb.argIds) {
|
|
1008
|
+
if (quoteMetaByValueId.has(aid)) continue
|
|
1009
|
+
if (!idToJs.has(aid)) {
|
|
1010
|
+
const pn = `p${entryParamCounter.n++}`
|
|
1011
|
+
idToJs.set(aid, pn)
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
const stackIds = currentStackIds
|
|
1016
|
+
|
|
1017
|
+
switch (kind) {
|
|
1018
|
+
case 'Literal':
|
|
1019
|
+
emitLiteralStep(node, vb, idToJs, genName, lines)
|
|
1020
|
+
break
|
|
1021
|
+
case 'ListLiteral':
|
|
1022
|
+
emitListLiteralStep(node, vb, L, idToJs, genName, lines)
|
|
1023
|
+
break
|
|
1024
|
+
case 'Builtin': {
|
|
1025
|
+
const bname = /** @type {string} */ (node.name)
|
|
1026
|
+
if (bname === 'call') {
|
|
1027
|
+
emitCallInlineStep(node, vb, L, env)
|
|
1028
|
+
break
|
|
1029
|
+
}
|
|
1030
|
+
if (tryEmitCombinatorBuiltin(bname, node, vb, L, env, processIrSteps)) {
|
|
1031
|
+
break
|
|
1032
|
+
}
|
|
1033
|
+
if (!emitBuiltinShuffleStep(bname, vb, L, idToJs, genName, lines)) {
|
|
1034
|
+
throw new Error(
|
|
1035
|
+
`emit-body[${wordLabel}]: неподдержанный builtin ${JSON.stringify(bname)}`
|
|
1036
|
+
)
|
|
1037
|
+
}
|
|
1038
|
+
break
|
|
1039
|
+
}
|
|
1040
|
+
case 'Slot': {
|
|
1041
|
+
const dir = /** @type {'read'|'write' | undefined} */ (node.direction)
|
|
1042
|
+
const slotName = String(node.slotName ?? '')
|
|
1043
|
+
if (dir !== 'read' && dir !== 'write') {
|
|
1044
|
+
throw new Error(`emit-body[${wordLabel}]: Slot без direction`)
|
|
1045
|
+
}
|
|
1046
|
+
emitSlotStep(
|
|
1047
|
+
dir,
|
|
1048
|
+
slotName,
|
|
1049
|
+
vb,
|
|
1050
|
+
L,
|
|
1051
|
+
idToJs,
|
|
1052
|
+
slotExprByName,
|
|
1053
|
+
genName,
|
|
1054
|
+
lines,
|
|
1055
|
+
strict,
|
|
1056
|
+
wordLabel,
|
|
1057
|
+
si
|
|
1058
|
+
)
|
|
1059
|
+
break
|
|
1060
|
+
}
|
|
1061
|
+
case 'Unknown': {
|
|
1062
|
+
const ak = String(node.astKind ?? 'unknown')
|
|
1063
|
+
throw new Error(
|
|
1064
|
+
`emit-body[${wordLabel}]: шаг ${si} недопустим для codegen (Unknown AST ${ak})`
|
|
1065
|
+
)
|
|
1066
|
+
}
|
|
1067
|
+
case 'Word': {
|
|
1068
|
+
const ref = node.ref
|
|
1069
|
+
if (ref === 'local') {
|
|
1070
|
+
emitWordLocalStep(
|
|
1071
|
+
node,
|
|
1072
|
+
vb,
|
|
1073
|
+
L,
|
|
1074
|
+
idToJs,
|
|
1075
|
+
genName,
|
|
1076
|
+
lines,
|
|
1077
|
+
quoteMetaByValueId,
|
|
1078
|
+
genQuoteName,
|
|
1079
|
+
entryParamCounter,
|
|
1080
|
+
strict,
|
|
1081
|
+
wordLabel,
|
|
1082
|
+
resolveQualifiedCallee,
|
|
1083
|
+
env.callerAsync,
|
|
1084
|
+
env.calleeAsyncResolver,
|
|
1085
|
+
stackIds,
|
|
1086
|
+
sumEliminatorNames ?? null,
|
|
1087
|
+
slotExprByName
|
|
1088
|
+
)
|
|
1089
|
+
} else if (ref === 'qualified') {
|
|
1090
|
+
if (typeof resolveQualifiedCallee !== 'function') {
|
|
1091
|
+
throw new Error(
|
|
1092
|
+
`emit-body[${wordLabel}]: qualified Word без resolveQualifiedCallee`
|
|
1093
|
+
)
|
|
1094
|
+
}
|
|
1095
|
+
emitWordQualifiedStep(
|
|
1096
|
+
node,
|
|
1097
|
+
vb,
|
|
1098
|
+
L,
|
|
1099
|
+
idToJs,
|
|
1100
|
+
genName,
|
|
1101
|
+
lines,
|
|
1102
|
+
quoteMetaByValueId,
|
|
1103
|
+
genQuoteName,
|
|
1104
|
+
entryParamCounter,
|
|
1105
|
+
strict,
|
|
1106
|
+
wordLabel,
|
|
1107
|
+
resolveQualifiedCallee,
|
|
1108
|
+
env.callerAsync,
|
|
1109
|
+
env.calleeAsyncResolver,
|
|
1110
|
+
slotExprByName
|
|
1111
|
+
)
|
|
1112
|
+
} else {
|
|
1113
|
+
throw new Error(
|
|
1114
|
+
`emit-body[${wordLabel}]: Word ref=${String(ref)} пока не поддержан`
|
|
1115
|
+
)
|
|
1116
|
+
}
|
|
1117
|
+
break
|
|
1118
|
+
}
|
|
1119
|
+
case 'Quotation': {
|
|
1120
|
+
const meta = /** @type {QuoteEmitMeta | undefined} */ (node.quoteEmitMeta)
|
|
1121
|
+
if (!meta) {
|
|
1122
|
+
throw new Error(`emit-body[${wordLabel}]: Quotation без quoteEmitMeta`)
|
|
1123
|
+
}
|
|
1124
|
+
const delta = vb.resultIds.length - L
|
|
1125
|
+
const newIds = vb.resultIds.slice(L)
|
|
1126
|
+
if (newIds.length !== delta) {
|
|
1127
|
+
throw new Error('emit-body: Quotation delta ids')
|
|
1128
|
+
}
|
|
1129
|
+
for (const qid of newIds) {
|
|
1130
|
+
quoteMetaByValueId.set(qid, meta)
|
|
1131
|
+
}
|
|
1132
|
+
break
|
|
1133
|
+
}
|
|
1134
|
+
default:
|
|
1135
|
+
throw new Error(`emit-body[${wordLabel}]: неподдержанный kind ${kind}`)
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
currentStackIds = [...vb.resultIds]
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
return currentStackIds
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Генерирует JS-фрагмент тела слова по `irSteps` с заполненными `valueBindings`.
|
|
1146
|
+
*
|
|
1147
|
+
* Контракт `return`: если передан `normalizedSig`, в конец добавляется `return`:
|
|
1148
|
+
* один выход — `return <expr>;`, несколько — `return [<expr>, ...];` (дно → вершина),
|
|
1149
|
+
* ноль выходов — `return;`. Без `normalizedSig` оператор `return` не добавляется.
|
|
1150
|
+
*
|
|
1151
|
+
* @param {Record<string, unknown>[]} irSteps
|
|
1152
|
+
* @param {{
|
|
1153
|
+
* strict?: boolean
|
|
1154
|
+
* normalizedSig?: NormalizedSignature | null
|
|
1155
|
+
* wordName?: string
|
|
1156
|
+
* entryStackIds?: string[] | null
|
|
1157
|
+
* resolveQualifiedCallee?: (moduleAlias: string, wordName: string) => string
|
|
1158
|
+
* callerAsync?: boolean
|
|
1159
|
+
* calleeAsyncResolver?: CalleeAsyncResolver | null
|
|
1160
|
+
* sumEliminatorNames?: Set<string> | null
|
|
1161
|
+
* }} [options]
|
|
1162
|
+
* @returns {{ source: string }}
|
|
1163
|
+
*
|
|
1164
|
+
* Входной стек слова: если `bind-values` подставил «resync»-id до первого реального шага,
|
|
1165
|
+
* в теле используются свободные имена `p0`, `p1`, … (дно → вершина, вершина — больший индекс),
|
|
1166
|
+
* как формальные параметры будущей обёртки-функции.
|
|
1167
|
+
*/
|
|
1168
|
+
export function emitWordBodyIr (irSteps, options = {}) {
|
|
1169
|
+
const strict = options.strict === true
|
|
1170
|
+
const wordLabel = options.wordName != null ? String(options.wordName) : 'word'
|
|
1171
|
+
/** @type {Map<string, string>} */
|
|
1172
|
+
const idToJs = new Map()
|
|
1173
|
+
let counter = 0
|
|
1174
|
+
let quoteCounter = 0
|
|
1175
|
+
const genName = () => {
|
|
1176
|
+
const n = counter++
|
|
1177
|
+
return `v${n}`
|
|
1178
|
+
}
|
|
1179
|
+
const genQuoteName = () => {
|
|
1180
|
+
const n = quoteCounter++
|
|
1181
|
+
return `q${n}`
|
|
1182
|
+
}
|
|
1183
|
+
/** @type {Map<string, QuoteEmitMeta>} */
|
|
1184
|
+
const quoteMetaByValueId = new Map()
|
|
1185
|
+
/** @type {Map<string, string>} */
|
|
1186
|
+
const slotExprByName = new Map()
|
|
1187
|
+
/** @type {string[]} */
|
|
1188
|
+
const lines = []
|
|
1189
|
+
const entryParamCounter = { n: 0 }
|
|
1190
|
+
|
|
1191
|
+
const env = {
|
|
1192
|
+
idToJs,
|
|
1193
|
+
lines,
|
|
1194
|
+
genName,
|
|
1195
|
+
genQuoteName,
|
|
1196
|
+
quoteMetaByValueId,
|
|
1197
|
+
slotExprByName,
|
|
1198
|
+
strict,
|
|
1199
|
+
wordLabel,
|
|
1200
|
+
entryParamCounter,
|
|
1201
|
+
resolveQualifiedCallee: options.resolveQualifiedCallee,
|
|
1202
|
+
callerAsync: options.callerAsync === true,
|
|
1203
|
+
calleeAsyncResolver: options.calleeAsyncResolver ?? null,
|
|
1204
|
+
sumEliminatorNames: options.sumEliminatorNames ?? null,
|
|
1205
|
+
initialStackIds:
|
|
1206
|
+
Array.isArray(options.entryStackIds) && options.entryStackIds.length > 0
|
|
1207
|
+
? options.entryStackIds
|
|
1208
|
+
: undefined
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const nsig = options.normalizedSig
|
|
1212
|
+
const entryStackIds = options.entryStackIds
|
|
1213
|
+
if (
|
|
1214
|
+
nsig != null &&
|
|
1215
|
+
typeof nsig === 'object' &&
|
|
1216
|
+
Array.isArray(nsig.left) &&
|
|
1217
|
+
Array.isArray(entryStackIds) &&
|
|
1218
|
+
entryStackIds.length === nsig.left.length
|
|
1219
|
+
) {
|
|
1220
|
+
let pAcc = 0
|
|
1221
|
+
const allocP = () => `p${pAcc++}`
|
|
1222
|
+
for (let i = 0; i < entryStackIds.length; i++) {
|
|
1223
|
+
idToJs.set(entryStackIds[i], jsParamNameForEntrySlot(nsig.left[i], allocP))
|
|
1224
|
+
}
|
|
1225
|
+
entryParamCounter.n = pAcc
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
const currentStackIds = processIrSteps(irSteps, env)
|
|
1229
|
+
|
|
1230
|
+
if (options.normalizedSig != null) {
|
|
1231
|
+
for (const id of currentStackIds) {
|
|
1232
|
+
if (!idToJs.has(id) && quoteMetaByValueId.has(id)) {
|
|
1233
|
+
const meta = quoteMetaByValueId.get(id)
|
|
1234
|
+
if (meta) {
|
|
1235
|
+
const qn = emitQuoteAsFirstClass(meta, {
|
|
1236
|
+
idToJs,
|
|
1237
|
+
lines,
|
|
1238
|
+
genName,
|
|
1239
|
+
genQuoteName,
|
|
1240
|
+
quoteMetaByValueId,
|
|
1241
|
+
slotExprByName,
|
|
1242
|
+
strict,
|
|
1243
|
+
wordLabel,
|
|
1244
|
+
entryParamCounter,
|
|
1245
|
+
resolveQualifiedCallee: options.resolveQualifiedCallee,
|
|
1246
|
+
callerAsync: options.callerAsync === true,
|
|
1247
|
+
calleeAsyncResolver: options.calleeAsyncResolver ?? null,
|
|
1248
|
+
sumEliminatorNames: options.sumEliminatorNames ?? null,
|
|
1249
|
+
initialStackIds: undefined
|
|
1250
|
+
})
|
|
1251
|
+
idToJs.set(id, qn)
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
appendReturnForLines(
|
|
1256
|
+
lines,
|
|
1257
|
+
currentStackIds,
|
|
1258
|
+
idToJs,
|
|
1259
|
+
options.normalizedSig,
|
|
1260
|
+
wordLabel
|
|
1261
|
+
)
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
return { source: lines.join('\n') }
|
|
1265
|
+
}
|