@algosail/lang 0.2.12 → 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,594 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L4 фаза 4: привязки значений (RFC-IR section 4).
|
|
3
|
+
* Внутренний массив id (дно→вершина) — только алгоритм обхода; в wire нет поля «стек».
|
|
4
|
+
*
|
|
5
|
+
* На узле шага опционально **`valueBindings`**:
|
|
6
|
+
* - `argIds: string[]` — id, снимаемые для эффекта шага; **последний элемент — вершина** до шага.
|
|
7
|
+
* - `resultIds: string[]` — id после шага, порядок **дно → вершина** (как `postTypes`).
|
|
8
|
+
* - `dup`: один `argId`, два одинаковых id в `resultIds` (та же привязка дважды).
|
|
9
|
+
*
|
|
10
|
+
* Шаги без `preTypes`/`postTypes` или при несовпадении длины стека id с `preTypes` (в режиме по умолчанию):
|
|
11
|
+
* привязки для шага не выставляются, стек id пересобирается из `postTypes` свежими id, чтобы не ломать длину для следующих шагов.
|
|
12
|
+
* `strict: true` — ошибка при несовместимости.
|
|
13
|
+
*
|
|
14
|
+
* Семантика арности встроенных слов — по шаблонам {@link getBuiltinStackSignature} (без повторного typecheck).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { getBuiltinStackSignature } from '../typecheck/builtin-signatures.js'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {{
|
|
21
|
+
* wordName: string
|
|
22
|
+
* modulePath: string
|
|
23
|
+
* strict?: boolean
|
|
24
|
+
* resolveLocalWordSig?: (name: string) => import('../typecheck/normalize-sig.js').NormalizedSignature | null | undefined
|
|
25
|
+
* resolveQualifiedWordSig?: (module: string, word: string) => import('../typecheck/normalize-sig.js').NormalizedSignature | null | undefined
|
|
26
|
+
* }} BindValueBindingsOptions
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** RFC-builtins §4.4–4.5: ветки valueBindings + combinatorInlineMeta для async-обхода. */
|
|
30
|
+
const BUILTIN_COMBINATOR_NAMES = new Set([
|
|
31
|
+
'dip',
|
|
32
|
+
'keep',
|
|
33
|
+
'bi',
|
|
34
|
+
'tri',
|
|
35
|
+
'spread',
|
|
36
|
+
'both'
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
/** @type {Record<string, (ids: string[]) => string[]>} */
|
|
40
|
+
const BUILTIN_SHUFFLE_TOP = {
|
|
41
|
+
dup: (ids) => [ids[0], ids[0]],
|
|
42
|
+
drop: () => [],
|
|
43
|
+
swap: (ids) => [ids[1], ids[0]],
|
|
44
|
+
over: (ids) => [ids[0], ids[1], ids[0]],
|
|
45
|
+
dup2: (ids) => [ids[0], ids[1], ids[0], ids[1]],
|
|
46
|
+
drop2: () => [],
|
|
47
|
+
rot: (ids) => [ids[1], ids[2], ids[0]],
|
|
48
|
+
reverse: (ids) => [ids[2], ids[0], ids[1]],
|
|
49
|
+
nip: (ids) => [ids[1]],
|
|
50
|
+
tuck: (ids) => [ids[1], ids[0], ids[1]],
|
|
51
|
+
swap2: (ids) => [...ids.slice(2, 4), ...ids.slice(0, 2)]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {BindValueBindingsOptions} options
|
|
56
|
+
*/
|
|
57
|
+
function createCtx (options) {
|
|
58
|
+
let counter = 0
|
|
59
|
+
const base = `${options.modulePath}#${options.wordName}#`
|
|
60
|
+
return {
|
|
61
|
+
options,
|
|
62
|
+
strict: options.strict === true,
|
|
63
|
+
stack: /** @type {string[]} */ ([]),
|
|
64
|
+
/** дно→вершина: id входного стека слова после первого resync (только корневой bindRecursive) */
|
|
65
|
+
entryStackIds: /** @type {string[] | null} */ (null),
|
|
66
|
+
/** id значения Quote → тело для codegen (этап 2: `call`, HOF) */
|
|
67
|
+
quoteRegistry:
|
|
68
|
+
/** @type {Map<string, { innerSteps: Record<string, unknown>[], innerEntryStackIds: string[] }>} */ (
|
|
69
|
+
new Map()
|
|
70
|
+
),
|
|
71
|
+
makeId (hint = 'v') {
|
|
72
|
+
counter++
|
|
73
|
+
return `${base}${hint}${counter}`
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {{ stack: string[], makeId: (hint?: string) => string, strict: boolean }} ctx
|
|
80
|
+
* @param {number} R
|
|
81
|
+
*/
|
|
82
|
+
function resyncStack (ctx, R) {
|
|
83
|
+
ctx.stack = Array.from({ length: R }, () => ctx.makeId('resync'))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {Record<string, unknown>} node
|
|
88
|
+
* @param {string[]} argIds
|
|
89
|
+
* @param {string[]} resultIds
|
|
90
|
+
*/
|
|
91
|
+
function attachBindings (node, argIds, resultIds) {
|
|
92
|
+
node.valueBindings = { argIds: [...argIds], resultIds: [...resultIds] }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {Record<string, unknown>[]} irSteps
|
|
97
|
+
* @param {{ stack: string[], makeId: (hint?: string) => string, strict: boolean, options: BindValueBindingsOptions, entryStackIds: string[] | null }} ctx
|
|
98
|
+
* @param {boolean} isRootLevel
|
|
99
|
+
*/
|
|
100
|
+
function bindRecursive (irSteps, ctx, isRootLevel) {
|
|
101
|
+
for (let si = 0; si < irSteps.length; si++) {
|
|
102
|
+
const raw = irSteps[si]
|
|
103
|
+
if (raw == null || typeof raw !== 'object') continue
|
|
104
|
+
const node = /** @type {Record<string, unknown>} */ (raw)
|
|
105
|
+
const pre = node.preTypes
|
|
106
|
+
const post = node.postTypes
|
|
107
|
+
if (!Array.isArray(pre)) {
|
|
108
|
+
if (ctx.strict) {
|
|
109
|
+
throw new Error(`bind-values: шаг без preTypes (${node.kind})`)
|
|
110
|
+
}
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
const L = pre.length
|
|
114
|
+
const R =
|
|
115
|
+
post !== undefined && Array.isArray(post) ? post.length : null
|
|
116
|
+
if (R === null) {
|
|
117
|
+
if (ctx.strict) {
|
|
118
|
+
throw new Error(`bind-values: шаг без postTypes (${node.kind})`)
|
|
119
|
+
}
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (ctx.stack.length !== L) {
|
|
124
|
+
if (ctx.strict) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`bind-values: ожидалась длина стека id ${L}, есть ${ctx.stack.length} (${node.kind})`
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
resyncStack(ctx, L)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (isRootLevel && si === 0) {
|
|
133
|
+
ctx.entryStackIds = [...ctx.stack]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const stackIds = ctx.stack
|
|
137
|
+
|
|
138
|
+
switch (node.kind) {
|
|
139
|
+
case 'Literal':
|
|
140
|
+
case 'ListLiteral': {
|
|
141
|
+
const litId = ctx.makeId('lit')
|
|
142
|
+
const argIds = []
|
|
143
|
+
const resultIds = [...stackIds, litId]
|
|
144
|
+
attachBindings(node, argIds, resultIds)
|
|
145
|
+
ctx.stack = resultIds
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
case 'Builtin': {
|
|
149
|
+
const name = /** @type {string} */ (node.name)
|
|
150
|
+
if (name === 'call') {
|
|
151
|
+
if (L < 1) {
|
|
152
|
+
if (ctx.strict) {
|
|
153
|
+
throw new Error('bind-values: call при пустом стеке id')
|
|
154
|
+
}
|
|
155
|
+
resyncStack(ctx, R)
|
|
156
|
+
break
|
|
157
|
+
}
|
|
158
|
+
const quoteId = stackIds[L - 1]
|
|
159
|
+
const reg = ctx.quoteRegistry.get(quoteId)
|
|
160
|
+
const topT =
|
|
161
|
+
Array.isArray(node.preTypes) && node.preTypes.length >= L
|
|
162
|
+
? node.preTypes[L - 1]
|
|
163
|
+
: null
|
|
164
|
+
const qInner =
|
|
165
|
+
topT != null &&
|
|
166
|
+
typeof topT === 'object' &&
|
|
167
|
+
(topT.kind === 'quote' || topT.kind === 'named_quote') &&
|
|
168
|
+
topT.inner != null &&
|
|
169
|
+
typeof topT.inner === 'object'
|
|
170
|
+
? /** @type {{ left?: unknown[], right?: unknown[] }} */ (topT.inner)
|
|
171
|
+
: null
|
|
172
|
+
|
|
173
|
+
/** @type {number} */
|
|
174
|
+
let n
|
|
175
|
+
/** @type {Record<string, unknown>[]} */
|
|
176
|
+
let innerStepsArr
|
|
177
|
+
/** @type {string[]} */
|
|
178
|
+
let innerEntryArr
|
|
179
|
+
/** @type {'inline' | 'invoke'} */
|
|
180
|
+
let callKind
|
|
181
|
+
|
|
182
|
+
if (reg) {
|
|
183
|
+
n = reg.innerEntryStackIds.length
|
|
184
|
+
innerStepsArr = reg.innerSteps
|
|
185
|
+
innerEntryArr = [...reg.innerEntryStackIds]
|
|
186
|
+
callKind = 'inline'
|
|
187
|
+
} else if (qInner) {
|
|
188
|
+
n = Array.isArray(qInner.left) ? qInner.left.length : 0
|
|
189
|
+
innerStepsArr = []
|
|
190
|
+
innerEntryArr = []
|
|
191
|
+
callKind = 'invoke'
|
|
192
|
+
} else {
|
|
193
|
+
if (ctx.strict) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`bind-values: call — нет quotation в реестре и нет Quote в preTypes для id ${quoteId}`
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
resyncStack(ctx, R)
|
|
199
|
+
break
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (L < 1 + n) {
|
|
203
|
+
if (ctx.strict) {
|
|
204
|
+
throw new Error('bind-values: call — недостаточно слотов под вход quotation')
|
|
205
|
+
}
|
|
206
|
+
resyncStack(ctx, R)
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
const prefLen = L - 1 - n
|
|
210
|
+
const m = R - prefLen
|
|
211
|
+
if (m < 0 || prefLen < 0) {
|
|
212
|
+
if (ctx.strict) {
|
|
213
|
+
throw new Error('bind-values: call — несогласованные длины pre/post')
|
|
214
|
+
}
|
|
215
|
+
resyncStack(ctx, R)
|
|
216
|
+
break
|
|
217
|
+
}
|
|
218
|
+
if (
|
|
219
|
+
callKind === 'invoke' &&
|
|
220
|
+
qInner &&
|
|
221
|
+
Array.isArray(qInner.right) &&
|
|
222
|
+
qInner.right.length !== m
|
|
223
|
+
) {
|
|
224
|
+
if (ctx.strict) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`bind-values: call invoke — несовпадение m (${m}) и inner.right (${qInner.right.length})`
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
resyncStack(ctx, R)
|
|
230
|
+
break
|
|
231
|
+
}
|
|
232
|
+
const argIds = stackIds.slice(prefLen)
|
|
233
|
+
const baseIds = stackIds.slice(0, prefLen)
|
|
234
|
+
const topOut = Array.from({ length: m }, () => ctx.makeId('call'))
|
|
235
|
+
const resultIds = baseIds.concat(topOut)
|
|
236
|
+
if (resultIds.length !== R) {
|
|
237
|
+
if (ctx.strict) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
`bind-values: call: ожидалась длина пост-стека ${R}, получилось ${resultIds.length}`
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
resyncStack(ctx, R)
|
|
243
|
+
break
|
|
244
|
+
}
|
|
245
|
+
/** @type {Record<string, unknown>} */
|
|
246
|
+
const callNode = node
|
|
247
|
+
if (callKind === 'inline') {
|
|
248
|
+
callNode.callInlineMeta = {
|
|
249
|
+
kind: 'inline',
|
|
250
|
+
innerSteps: innerStepsArr,
|
|
251
|
+
innerEntryStackIds: innerEntryArr
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
callNode.callInlineMeta = {
|
|
255
|
+
kind: 'invoke',
|
|
256
|
+
innerInLen: n,
|
|
257
|
+
innerOutLen: m
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
attachBindings(callNode, argIds, resultIds)
|
|
261
|
+
ctx.stack = resultIds
|
|
262
|
+
break
|
|
263
|
+
}
|
|
264
|
+
if (BUILTIN_COMBINATOR_NAMES.has(name)) {
|
|
265
|
+
const combTemplate = getBuiltinStackSignature(name)
|
|
266
|
+
if (!combTemplate) {
|
|
267
|
+
if (ctx.strict) {
|
|
268
|
+
throw new Error(`bind-values: нет сигнатуры для комбинатора ${name}`)
|
|
269
|
+
}
|
|
270
|
+
resyncStack(ctx, R)
|
|
271
|
+
break
|
|
272
|
+
}
|
|
273
|
+
const nCombIn = combTemplate.left.length
|
|
274
|
+
const nCombOut = combTemplate.right.length
|
|
275
|
+
const combArgIds =
|
|
276
|
+
nCombIn === 0 ? [] : stackIds.slice(-nCombIn)
|
|
277
|
+
const combBase =
|
|
278
|
+
nCombIn === 0 ? stackIds : stackIds.slice(0, stackIds.length - nCombIn)
|
|
279
|
+
const mk = () => ctx.makeId(name)
|
|
280
|
+
/** @type {string[]} */
|
|
281
|
+
let combTopOut
|
|
282
|
+
switch (name) {
|
|
283
|
+
case 'dip':
|
|
284
|
+
case 'keep':
|
|
285
|
+
combTopOut = [mk(), combArgIds[1]]
|
|
286
|
+
break
|
|
287
|
+
case 'bi':
|
|
288
|
+
case 'tri': {
|
|
289
|
+
const freshTops = Array.from(
|
|
290
|
+
{ length: nCombOut - 1 },
|
|
291
|
+
() => mk()
|
|
292
|
+
)
|
|
293
|
+
combTopOut = [combArgIds[0], ...freshTops]
|
|
294
|
+
break
|
|
295
|
+
}
|
|
296
|
+
case 'spread':
|
|
297
|
+
case 'both':
|
|
298
|
+
combTopOut = [combArgIds[0], mk(), mk()]
|
|
299
|
+
break
|
|
300
|
+
default:
|
|
301
|
+
if (ctx.strict) {
|
|
302
|
+
throw new Error(`bind-values: неподдержанный комбинатор ${name}`)
|
|
303
|
+
}
|
|
304
|
+
resyncStack(ctx, R)
|
|
305
|
+
combTopOut = []
|
|
306
|
+
}
|
|
307
|
+
if (combTopOut.length !== nCombOut) {
|
|
308
|
+
if (ctx.strict) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`bind-values: комбинатор ${name}: внутренняя ошибка длины topOut`
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
resyncStack(ctx, R)
|
|
314
|
+
break
|
|
315
|
+
}
|
|
316
|
+
const combResultIds = combBase.concat(combTopOut)
|
|
317
|
+
if (combResultIds.length !== R) {
|
|
318
|
+
if (ctx.strict) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
`bind-values: комбинатор ${name}: ожидалась длина пост-стека ${R}, получилось ${combResultIds.length}`
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
resyncStack(ctx, R)
|
|
324
|
+
break
|
|
325
|
+
}
|
|
326
|
+
const pre = node.preTypes
|
|
327
|
+
/** @type {object[]} */
|
|
328
|
+
const parts = []
|
|
329
|
+
/** @type {number[]} */
|
|
330
|
+
let quoteIdxs = []
|
|
331
|
+
if (name === 'dip' || name === 'keep') quoteIdxs = [2]
|
|
332
|
+
else if (name === 'bi') quoteIdxs = [2, 3]
|
|
333
|
+
else if (name === 'tri') quoteIdxs = [2, 3, 4]
|
|
334
|
+
else if (name === 'spread') quoteIdxs = [3, 4]
|
|
335
|
+
else if (name === 'both') quoteIdxs = [3]
|
|
336
|
+
for (const qi of quoteIdxs) {
|
|
337
|
+
const qid = combArgIds[qi]
|
|
338
|
+
const reg = ctx.quoteRegistry.get(qid)
|
|
339
|
+
if (reg) {
|
|
340
|
+
parts.push({
|
|
341
|
+
kind: 'inline',
|
|
342
|
+
innerSteps: reg.innerSteps,
|
|
343
|
+
innerEntryStackIds: [...reg.innerEntryStackIds]
|
|
344
|
+
})
|
|
345
|
+
continue
|
|
346
|
+
}
|
|
347
|
+
const idxTy = L - nCombIn + qi
|
|
348
|
+
const slotTy =
|
|
349
|
+
Array.isArray(pre) && idxTy >= 0 && idxTy < pre.length
|
|
350
|
+
? pre[idxTy]
|
|
351
|
+
: null
|
|
352
|
+
/** @type {{ left?: unknown[], right?: unknown[] } | null} */
|
|
353
|
+
let inner = null
|
|
354
|
+
if (
|
|
355
|
+
slotTy != null &&
|
|
356
|
+
typeof slotTy === 'object' &&
|
|
357
|
+
/** @type {{ kind?: string }} */ (slotTy).kind !== undefined &&
|
|
358
|
+
(/** @type {{ kind: string }} */ (slotTy).kind === 'quote' ||
|
|
359
|
+
/** @type {{ kind: string }} */ (slotTy).kind === 'named_quote')
|
|
360
|
+
) {
|
|
361
|
+
const inn = /** @type {{ inner?: object }} */ (slotTy).inner
|
|
362
|
+
if (inn != null && typeof inn === 'object') {
|
|
363
|
+
const io = /** @type {{ left?: unknown[], right?: unknown[] }} */ (
|
|
364
|
+
inn
|
|
365
|
+
)
|
|
366
|
+
if (Array.isArray(io.left) && Array.isArray(io.right)) {
|
|
367
|
+
inner = io
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (!inner) {
|
|
372
|
+
if (ctx.strict) {
|
|
373
|
+
throw new Error(
|
|
374
|
+
`bind-values: ${name} — нет quotation в реестре и нет Quote в preTypes для id ${qid}`
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
resyncStack(ctx, R)
|
|
378
|
+
break
|
|
379
|
+
}
|
|
380
|
+
parts.push({
|
|
381
|
+
kind: 'invoke',
|
|
382
|
+
innerInLen: inner.left.length,
|
|
383
|
+
innerOutLen: inner.right.length
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
if (parts.length !== quoteIdxs.length) {
|
|
387
|
+
break
|
|
388
|
+
}
|
|
389
|
+
/** @type {Record<string, unknown>} */
|
|
390
|
+
const combNode = node
|
|
391
|
+
combNode.combinatorInlineMeta = { parts }
|
|
392
|
+
attachBindings(combNode, combArgIds, combResultIds)
|
|
393
|
+
ctx.stack = combResultIds
|
|
394
|
+
break
|
|
395
|
+
}
|
|
396
|
+
const template = getBuiltinStackSignature(name)
|
|
397
|
+
if (!template) {
|
|
398
|
+
if (ctx.strict) {
|
|
399
|
+
throw new Error(`bind-values: неизвестный builtin ${name}`)
|
|
400
|
+
}
|
|
401
|
+
resyncStack(ctx, R)
|
|
402
|
+
break
|
|
403
|
+
}
|
|
404
|
+
const nIn = template.left.length
|
|
405
|
+
const nOut = template.right.length
|
|
406
|
+
const argIds = nIn === 0 ? [] : stackIds.slice(-nIn)
|
|
407
|
+
const base = nIn === 0 ? stackIds : stackIds.slice(0, stackIds.length - nIn)
|
|
408
|
+
const shuffle = BUILTIN_SHUFFLE_TOP[name]
|
|
409
|
+
let topOut
|
|
410
|
+
if (shuffle) {
|
|
411
|
+
topOut = shuffle(argIds)
|
|
412
|
+
} else {
|
|
413
|
+
topOut = Array.from({ length: nOut }, () => ctx.makeId(name))
|
|
414
|
+
}
|
|
415
|
+
if (topOut.length !== nOut) {
|
|
416
|
+
if (ctx.strict) {
|
|
417
|
+
throw new Error(`bind-values: внутренняя ошибка shuffle ${name}`)
|
|
418
|
+
}
|
|
419
|
+
resyncStack(ctx, R)
|
|
420
|
+
break
|
|
421
|
+
}
|
|
422
|
+
const resultIds = base.concat(topOut)
|
|
423
|
+
if (resultIds.length !== R) {
|
|
424
|
+
if (ctx.strict) {
|
|
425
|
+
throw new Error(
|
|
426
|
+
`bind-values: builtin ${name}: ожидалась длина пост-стека ${R}, получилось ${resultIds.length}`
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
resyncStack(ctx, R)
|
|
430
|
+
break
|
|
431
|
+
}
|
|
432
|
+
attachBindings(node, argIds, resultIds)
|
|
433
|
+
ctx.stack = resultIds
|
|
434
|
+
break
|
|
435
|
+
}
|
|
436
|
+
case 'Word': {
|
|
437
|
+
const sig =
|
|
438
|
+
node.ref === 'qualified' &&
|
|
439
|
+
typeof node.module === 'string' &&
|
|
440
|
+
typeof node.word === 'string'
|
|
441
|
+
? ctx.options.resolveQualifiedWordSig?.(node.module, node.word) ??
|
|
442
|
+
null
|
|
443
|
+
: typeof node.name === 'string'
|
|
444
|
+
? ctx.options.resolveLocalWordSig?.(node.name) ?? null
|
|
445
|
+
: null
|
|
446
|
+
if (!sig) {
|
|
447
|
+
if (ctx.strict) {
|
|
448
|
+
throw new Error('bind-values: нет сигнатуры для вызова Word')
|
|
449
|
+
}
|
|
450
|
+
resyncStack(ctx, R)
|
|
451
|
+
break
|
|
452
|
+
}
|
|
453
|
+
const nIn = sig.left.length
|
|
454
|
+
const nOut = sig.right.length
|
|
455
|
+
// slice(-0) === slice(0) даёт весь массив — для nIn===0 нужны пустые argIds
|
|
456
|
+
const argIds = nIn === 0 ? [] : stackIds.slice(-nIn)
|
|
457
|
+
const base = nIn === 0 ? stackIds : stackIds.slice(0, stackIds.length - nIn)
|
|
458
|
+
const topOut = Array.from({ length: nOut }, () => ctx.makeId('w'))
|
|
459
|
+
const resultIds = base.concat(topOut)
|
|
460
|
+
if (resultIds.length !== R) {
|
|
461
|
+
if (ctx.strict) {
|
|
462
|
+
throw new Error(
|
|
463
|
+
`bind-values: Word: несовпадение длины пост-стека (ожидалось ${R})`
|
|
464
|
+
)
|
|
465
|
+
}
|
|
466
|
+
resyncStack(ctx, R)
|
|
467
|
+
break
|
|
468
|
+
}
|
|
469
|
+
attachBindings(node, argIds, resultIds)
|
|
470
|
+
ctx.stack = resultIds
|
|
471
|
+
break
|
|
472
|
+
}
|
|
473
|
+
case 'Quotation': {
|
|
474
|
+
const innerSteps = /** @type {Record<string, unknown>[] | undefined} */ (
|
|
475
|
+
node.steps
|
|
476
|
+
)
|
|
477
|
+
/** @type {string[]} */
|
|
478
|
+
let innerEntryStackIds = []
|
|
479
|
+
if (Array.isArray(innerSteps) && innerSteps.length > 0) {
|
|
480
|
+
const inner0 = innerSteps[0]
|
|
481
|
+
const innerPreLen = Array.isArray(inner0?.preTypes)
|
|
482
|
+
? inner0.preTypes.length
|
|
483
|
+
: 0
|
|
484
|
+
const innerStack = Array.from({ length: innerPreLen }, () =>
|
|
485
|
+
ctx.makeId('iq')
|
|
486
|
+
)
|
|
487
|
+
innerEntryStackIds = [...innerStack]
|
|
488
|
+
const innerCtx = {
|
|
489
|
+
...ctx,
|
|
490
|
+
stack: innerStack
|
|
491
|
+
}
|
|
492
|
+
bindRecursive(innerSteps, innerCtx, false)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** @type {Record<string, unknown>} */
|
|
496
|
+
const quotNode = node
|
|
497
|
+
quotNode.quoteEmitMeta = {
|
|
498
|
+
innerSteps: Array.isArray(innerSteps) ? innerSteps : [],
|
|
499
|
+
innerEntryStackIds
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
let argIds
|
|
503
|
+
let resultIds
|
|
504
|
+
if (R > L) {
|
|
505
|
+
argIds = []
|
|
506
|
+
const delta = R - L
|
|
507
|
+
resultIds = stackIds.concat(
|
|
508
|
+
Array.from({ length: delta }, () => ctx.makeId('q'))
|
|
509
|
+
)
|
|
510
|
+
const newQuoteIds = resultIds.slice(stackIds.length)
|
|
511
|
+
for (const qid of newQuoteIds) {
|
|
512
|
+
ctx.quoteRegistry.set(qid, {
|
|
513
|
+
innerSteps: Array.isArray(innerSteps) ? innerSteps : [],
|
|
514
|
+
innerEntryStackIds: [...innerEntryStackIds]
|
|
515
|
+
})
|
|
516
|
+
}
|
|
517
|
+
} else if (R < L) {
|
|
518
|
+
const drop = L - R
|
|
519
|
+
argIds = stackIds.slice(-drop)
|
|
520
|
+
resultIds = stackIds.slice(0, R)
|
|
521
|
+
} else {
|
|
522
|
+
argIds = []
|
|
523
|
+
resultIds = [...stackIds]
|
|
524
|
+
}
|
|
525
|
+
if (resultIds.length !== R) {
|
|
526
|
+
if (ctx.strict) {
|
|
527
|
+
throw new Error('bind-values: Quotation: несовпадение длин')
|
|
528
|
+
}
|
|
529
|
+
resyncStack(ctx, R)
|
|
530
|
+
break
|
|
531
|
+
}
|
|
532
|
+
attachBindings(node, argIds, resultIds)
|
|
533
|
+
ctx.stack = resultIds
|
|
534
|
+
break
|
|
535
|
+
}
|
|
536
|
+
case 'Slot': {
|
|
537
|
+
const dir = node.direction
|
|
538
|
+
const slotName = /** @type {string} */ (node.slotName)
|
|
539
|
+
if (dir === 'write') {
|
|
540
|
+
const argIds = stackIds.slice(-1)
|
|
541
|
+
const base = stackIds.slice(0, stackIds.length - 1)
|
|
542
|
+
const resultIds = base
|
|
543
|
+
if (resultIds.length !== R || argIds.length !== 1) {
|
|
544
|
+
if (ctx.strict) {
|
|
545
|
+
throw new Error('bind-values: slot_write')
|
|
546
|
+
}
|
|
547
|
+
resyncStack(ctx, R)
|
|
548
|
+
break
|
|
549
|
+
}
|
|
550
|
+
attachBindings(node, argIds, resultIds)
|
|
551
|
+
ctx.stack = resultIds
|
|
552
|
+
} else if (dir === 'read') {
|
|
553
|
+
const argIds = []
|
|
554
|
+
const readId = ctx.makeId(`slot_${slotName}`)
|
|
555
|
+
const resultIds = stackIds.concat([readId])
|
|
556
|
+
if (resultIds.length !== R) {
|
|
557
|
+
if (ctx.strict) {
|
|
558
|
+
throw new Error('bind-values: slot_read')
|
|
559
|
+
}
|
|
560
|
+
resyncStack(ctx, R)
|
|
561
|
+
break
|
|
562
|
+
}
|
|
563
|
+
attachBindings(node, argIds, resultIds)
|
|
564
|
+
ctx.stack = resultIds
|
|
565
|
+
} else if (ctx.strict) {
|
|
566
|
+
throw new Error('bind-values: Slot без direction')
|
|
567
|
+
} else {
|
|
568
|
+
resyncStack(ctx, R)
|
|
569
|
+
}
|
|
570
|
+
break
|
|
571
|
+
}
|
|
572
|
+
default: {
|
|
573
|
+
if (ctx.strict) {
|
|
574
|
+
throw new Error(`bind-values: неподдержанный kind ${node.kind}`)
|
|
575
|
+
}
|
|
576
|
+
resyncStack(ctx, R)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Заполняет `valueBindings` на узлах `irSteps` (и рекурсивно в `Quotation.steps`).
|
|
584
|
+
*
|
|
585
|
+
* @param {Record<string, unknown>[]} irSteps
|
|
586
|
+
* @param {BindValueBindingsOptions} options
|
|
587
|
+
* @returns {string[] | null} id входного стека слова (дно→вершина) или null
|
|
588
|
+
*/
|
|
589
|
+
export function bindValueBindingsForIrSteps (irSteps, options) {
|
|
590
|
+
if (!Array.isArray(irSteps)) return null
|
|
591
|
+
const ctx = createCtx(options)
|
|
592
|
+
bindRecursive(irSteps, ctx, true)
|
|
593
|
+
return ctx.entryStackIds
|
|
594
|
+
}
|