@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.
Files changed (145) hide show
  1. package/bin/sail.mjs +4 -0
  2. package/cli/run-cli.js +176 -0
  3. package/index.js +11 -2
  4. package/lib/codegen/README.md +230 -0
  5. package/lib/codegen/codegen-diagnostics.js +164 -0
  6. package/lib/codegen/compile-graph.js +107 -0
  7. package/lib/codegen/emit-adt.js +177 -0
  8. package/lib/codegen/emit-body.js +1265 -0
  9. package/lib/codegen/emit-builtin.js +371 -0
  10. package/lib/codegen/emit-jsdoc-sail.js +383 -0
  11. package/lib/codegen/emit-module.js +498 -0
  12. package/lib/codegen/esm-imports.js +26 -0
  13. package/lib/codegen/index.js +69 -0
  14. package/lib/codegen/out-layout.js +102 -0
  15. package/lib/ffi/extract-jsdoc-sail.js +34 -0
  16. package/lib/io-node/index.js +4 -0
  17. package/lib/io-node/package-root.js +18 -0
  18. package/lib/io-node/read-file.js +12 -0
  19. package/lib/io-node/resolve-package.js +24 -0
  20. package/lib/io-node/resolve-sail-names-from-disk.js +21 -0
  21. package/lib/ir/assert-json-serializable.js +30 -0
  22. package/lib/ir/attach-call-effects.js +108 -0
  23. package/lib/ir/bind-values.js +594 -0
  24. package/lib/ir/build-module-ir.js +290 -0
  25. package/lib/ir/index.js +31 -0
  26. package/lib/ir/lower-body-steps.js +170 -0
  27. package/lib/ir/module-metadata.js +65 -0
  28. package/lib/ir/schema-version.js +15 -0
  29. package/lib/ir/serialize.js +202 -0
  30. package/lib/ir/stitch-types.js +92 -0
  31. package/lib/names/adt-autogen.js +22 -0
  32. package/lib/names/import-path.js +28 -0
  33. package/lib/names/index.js +1 -0
  34. package/lib/names/local-declarations.js +127 -0
  35. package/lib/names/lower-first.js +6 -0
  36. package/lib/names/module-scope.js +120 -0
  37. package/lib/names/resolve-sail.js +365 -0
  38. package/lib/names/walk-ast-refs.js +91 -0
  39. package/lib/parse/ast-build.js +51 -0
  40. package/lib/parse/ast-spec.js +212 -0
  41. package/lib/parse/builtins-set.js +12 -0
  42. package/lib/parse/diagnostics.js +180 -0
  43. package/lib/parse/index.js +46 -0
  44. package/lib/parse/lexer.js +390 -0
  45. package/lib/parse/parse-source.js +912 -0
  46. package/lib/typecheck/adt-autogen-sigs.js +345 -0
  47. package/lib/typecheck/build-type-env.js +148 -0
  48. package/lib/typecheck/builtin-signatures.js +183 -0
  49. package/lib/typecheck/check-word-body.js +1021 -0
  50. package/lib/typecheck/effect-decl.js +124 -0
  51. package/lib/typecheck/index.js +55 -0
  52. package/lib/typecheck/normalize-sig.js +369 -0
  53. package/lib/typecheck/stack-step-snapshots.js +56 -0
  54. package/lib/typecheck/unify-type.js +665 -0
  55. package/lib/typecheck/validate-adt.js +201 -0
  56. package/package.json +4 -9
  57. package/scripts/regen-demo-full-syntax-ast.mjs +22 -0
  58. package/test/cli/sail-cli.test.js +64 -0
  59. package/test/codegen/compile-bracket-ffi-e2e.test.js +64 -0
  60. package/test/codegen/compile-stage0.test.js +128 -0
  61. package/test/codegen/compile-stage4-layout.test.js +124 -0
  62. package/test/codegen/e2e-prelude-ffi-adt/app/extra.sail +6 -0
  63. package/test/codegen/e2e-prelude-ffi-adt/app/lib.sail +33 -0
  64. package/test/codegen/e2e-prelude-ffi-adt/app/main.sail +28 -0
  65. package/test/codegen/e2e-prelude-ffi-adt/artifacts/.gitignore +2 -0
  66. package/test/codegen/e2e-prelude-ffi-adt/ffi/helpers.js +27 -0
  67. package/test/codegen/e2e-prelude-ffi-adt.test.js +100 -0
  68. package/test/codegen/emit-adt-stage6.test.js +168 -0
  69. package/test/codegen/emit-async-stage5.test.js +164 -0
  70. package/test/codegen/emit-body-stage2.test.js +139 -0
  71. package/test/codegen/emit-body.test.js +163 -0
  72. package/test/codegen/emit-builtins-stage7.test.js +258 -0
  73. package/test/codegen/emit-diagnostics-stage9.test.js +90 -0
  74. package/test/codegen/emit-jsdoc-stage8.test.js +113 -0
  75. package/test/codegen/emit-module-stage3.test.js +78 -0
  76. package/test/conformance/conformance-ir-l4.test.js +38 -0
  77. package/test/conformance/conformance-l5-codegen.test.js +111 -0
  78. package/test/conformance/conformance-runner.js +91 -0
  79. package/test/conformance/conformance-suite-l3.test.js +32 -0
  80. package/test/ffi/prelude-jsdoc.test.js +49 -0
  81. package/test/fixtures/demo-full-syntax.ast.json +1471 -0
  82. package/test/fixtures/demo-full-syntax.sail +35 -0
  83. package/test/fixtures/io-node-ffi-adt/ffi.js +7 -0
  84. package/test/fixtures/io-node-ffi-adt/use.sail +4 -0
  85. package/test/fixtures/io-node-mini/dep.sail +2 -0
  86. package/test/fixtures/io-node-mini/entry.sail +4 -0
  87. package/test/fixtures/io-node-prelude/entry.sail +4 -0
  88. package/test/fixtures/io-node-reexport-chain/a.sail +4 -0
  89. package/test/fixtures/io-node-reexport-chain/b.sail +2 -0
  90. package/test/fixtures/io-node-reexport-chain/c.sail +2 -0
  91. package/test/io-node/resolve-disk.test.js +59 -0
  92. package/test/ir/bind-values.test.js +84 -0
  93. package/test/ir/build-module-ir.test.js +100 -0
  94. package/test/ir/call-effects.test.js +97 -0
  95. package/test/ir/ffi-bracket-ir.test.js +59 -0
  96. package/test/ir/full-ir-document.test.js +51 -0
  97. package/test/ir/ir-document-assert.js +67 -0
  98. package/test/ir/lower-body-steps.test.js +90 -0
  99. package/test/ir/module-metadata.test.js +42 -0
  100. package/test/ir/serialization-model.test.js +172 -0
  101. package/test/ir/stitch-types.test.js +74 -0
  102. package/test/names/l2-resolve-adt-autogen.test.js +155 -0
  103. package/test/names/l2-resolve-bracket-ffi.test.js +108 -0
  104. package/test/names/l2-resolve-declaration-and-bracket-errors.test.js +276 -0
  105. package/test/names/l2-resolve-graph.test.js +105 -0
  106. package/test/names/l2-resolve-single-file.test.js +79 -0
  107. package/test/parse/ast-spec.test.js +56 -0
  108. package/test/parse/ast.test.js +476 -0
  109. package/test/parse/contract.test.js +37 -0
  110. package/test/parse/fixtures-full-syntax.test.js +24 -0
  111. package/test/parse/helpers.js +27 -0
  112. package/test/parse/l0-lex-diagnostics-matrix.test.js +59 -0
  113. package/test/parse/l0-lex.test.js +40 -0
  114. package/test/parse/l1-diagnostics.test.js +77 -0
  115. package/test/parse/l1-import.test.js +28 -0
  116. package/test/parse/l1-parse-diagnostics-matrix.test.js +32 -0
  117. package/test/parse/l1-top-level.test.js +47 -0
  118. package/test/parse/l1-types.test.js +31 -0
  119. package/test/parse/l1-words.test.js +49 -0
  120. package/test/parse/l2-diagnostics-contract.test.js +67 -0
  121. package/test/parse/l3-diagnostics-contract.test.js +66 -0
  122. package/test/typecheck/adt-decl-stage2.test.js +83 -0
  123. package/test/typecheck/container-contract-e1309.test.js +258 -0
  124. package/test/typecheck/ffi-bracket-l3.test.js +61 -0
  125. package/test/typecheck/l3-diagnostics-matrix.test.js +248 -0
  126. package/test/typecheck/l3-partial-pipeline.test.js +74 -0
  127. package/test/typecheck/opaque-ffi-type.test.js +78 -0
  128. package/test/typecheck/sig-type-stage3.test.js +190 -0
  129. package/test/typecheck/stack-check-stage4.test.js +149 -0
  130. package/test/typecheck/stack-check-stage5.test.js +74 -0
  131. package/test/typecheck/stack-check-stage6.test.js +56 -0
  132. package/test/typecheck/stack-check-stage7.test.js +160 -0
  133. package/test/typecheck/stack-check-stage8.test.js +146 -0
  134. package/test/typecheck/stack-check-stage9.test.js +105 -0
  135. package/test/typecheck/typecheck-env.test.js +53 -0
  136. package/test/typecheck/typecheck-pipeline.test.js +37 -0
  137. package/README.md +0 -37
  138. package/cli/sail.js +0 -151
  139. package/cli/typecheck.js +0 -39
  140. package/docs/ARCHITECTURE.md +0 -50
  141. package/docs/CHANGELOG.md +0 -18
  142. package/docs/FFI-GUIDE.md +0 -65
  143. package/docs/RELEASE.md +0 -36
  144. package/docs/TESTING.md +0 -86
  145. package/test/integration.test.js +0 -61
@@ -0,0 +1,345 @@
1
+ /**
2
+ * L3 этап 8: сигнатуры автоген-слов ADT (RFC-0.1 §7, RFC-typecheck §5.8).
3
+ */
4
+ import { lowerFirst } from '../names/lower-first.js'
5
+ import {
6
+ modulePathForAstNode,
7
+ PRIMITIVE_TYPE_NAMES
8
+ } from './normalize-sig.js'
9
+ import { shareTvarsInSignature } from './unify-type.js'
10
+
11
+ /** @returns {{ kind: 'tvar', name: string, source: null, decl: null }} */
12
+ function tv (name) {
13
+ return { kind: 'tvar', name, source: null, decl: null }
14
+ }
15
+
16
+ /** @returns {{ kind: 'prim', name: string, source: null, decl: null }} */
17
+ function prim (name) {
18
+ return { kind: 'prim', name, source: null, decl: null }
19
+ }
20
+
21
+ /** @returns {{ kind: 'stack_label', name: string, source: null }} */
22
+ function sl (name) {
23
+ return { kind: 'stack_label', name, source: null }
24
+ }
25
+
26
+ /**
27
+ * @param {import('./normalize-sig.js').NormalizedSignature} inner
28
+ */
29
+ function q (inner) {
30
+ return { kind: 'quote', source: null, inner }
31
+ }
32
+
33
+ /**
34
+ * @param {object[]} left
35
+ * @param {object[]} right
36
+ * @param {object} [meta]
37
+ * @returns {import('./normalize-sig.js').NormalizedSignature}
38
+ */
39
+ function normSig (left, right, meta = {}) {
40
+ return {
41
+ left,
42
+ right,
43
+ effectsAdd: [],
44
+ effectsRemove: [],
45
+ ...meta
46
+ }
47
+ }
48
+
49
+ /**
50
+ * shareTvarsInSignature даёт каждой вложенной quotation новую область stack_label,
51
+ * из-за чего общий ~b веток eliminator-а (RFC-typecheck §5.8) распадается на разные узлы.
52
+ * После share выравниваем правый stack_label всех веточных Quote на один объект.
53
+ *
54
+ * @param {import('./normalize-sig.js').NormalizedSignature} sig
55
+ */
56
+ function mergeSumEliminatorBranchOutputLabels (sig) {
57
+ if (sig.adtEliminator !== 'sum' || !sig.left || sig.left.length < 2) return
58
+ const quotes = sig.left.slice(1)
59
+ if (quotes.length < 2) return
60
+ const first = quotes[0]
61
+ if (first.kind !== 'quote' || !first.inner?.right?.length) return
62
+ const canon = first.inner.right[0]
63
+ if (!canon || canon.kind !== 'stack_label') return
64
+ for (let i = 1; i < quotes.length; i++) {
65
+ const qn = quotes[i]
66
+ if (qn.kind !== 'quote' || !qn.inner?.right?.length) continue
67
+ qn.inner.right[0] = canon
68
+ }
69
+ }
70
+
71
+ /**
72
+ * @param {string} modulePath
73
+ * @param {object} node
74
+ */
75
+ function declRef (modulePath, node) {
76
+ return { path: modulePath, node }
77
+ }
78
+
79
+ /**
80
+ * @param {import('./build-type-env.js').TypecheckEnv} env
81
+ * @param {string} modulePath
82
+ * @param {object} expr
83
+ * @param {Map<string, object>} tvarByParam
84
+ * @returns {object | null}
85
+ */
86
+ function payloadTypeToIr (env, modulePath, expr, tvarByParam) {
87
+ if (!expr) return null
88
+ const scopeInfo = env.scopeByPath.get(modulePath)
89
+ const typeIndex = scopeInfo?.typeIndex ?? new Map()
90
+ switch (expr.kind) {
91
+ case 'type_var': {
92
+ const t = tvarByParam.get(expr.name)
93
+ if (t) return t
94
+ return tv(expr.name)
95
+ }
96
+ case 'type_name': {
97
+ const nm = expr.name
98
+ if (PRIMITIVE_TYPE_NAMES.has(nm)) return prim(nm)
99
+ const def = typeIndex.get(nm)
100
+ if (def && (def.kind === 'sum_type' || def.kind === 'product_type')) {
101
+ const defPath =
102
+ modulePathForAstNode(env.snapshots, def) ?? modulePath
103
+ return {
104
+ kind: 'adt',
105
+ path: defPath,
106
+ type: nm,
107
+ source: null,
108
+ decl: declRef(defPath, def)
109
+ }
110
+ }
111
+ return { kind: 'opaque', name: nm, source: null, decl: null }
112
+ }
113
+ case 'paren_type':
114
+ return payloadTypeToIr(env, modulePath, expr.inner, tvarByParam)
115
+ case 'type_app': {
116
+ const ctor = expr.ctor
117
+ const args = []
118
+ for (const a of expr.args) {
119
+ const na = payloadTypeToIr(env, modulePath, a, tvarByParam)
120
+ if (!na) return null
121
+ args.push(na)
122
+ }
123
+ if (['List', 'Dict', 'Map'].includes(ctor)) {
124
+ return {
125
+ kind: 'app',
126
+ ctor,
127
+ args,
128
+ source: null,
129
+ decl: null
130
+ }
131
+ }
132
+ const def = typeIndex.get(ctor)
133
+ if (def && (def.kind === 'sum_type' || def.kind === 'product_type')) {
134
+ const defPath =
135
+ modulePathForAstNode(env.snapshots, def) ?? modulePath
136
+ return {
137
+ kind: 'app',
138
+ ctor,
139
+ args,
140
+ source: null,
141
+ decl: declRef(defPath, def)
142
+ }
143
+ }
144
+ return null
145
+ }
146
+ default:
147
+ return null
148
+ }
149
+ }
150
+
151
+ /**
152
+ * @param {object} sumAst
153
+ * @param {string} modulePath
154
+ * @param {Map<string, object>} tvarByParam
155
+ */
156
+ function sumTypeIr (sumAst, modulePath, tvarByParam) {
157
+ const names = sumAst.typeParams ?? []
158
+ const ref = declRef(modulePath, sumAst)
159
+ if (names.length === 0) {
160
+ return {
161
+ kind: 'adt',
162
+ path: modulePath,
163
+ type: sumAst.name,
164
+ source: null,
165
+ decl: ref
166
+ }
167
+ }
168
+ const args = names.map((p) => tvarByParam.get(p))
169
+ return {
170
+ kind: 'app',
171
+ ctor: sumAst.name,
172
+ args,
173
+ source: null,
174
+ decl: ref
175
+ }
176
+ }
177
+
178
+ /**
179
+ * @param {import('./build-type-env.js').TypecheckEnv} env
180
+ * @param {object} sumAst
181
+ * @param {string} modulePath
182
+ * @returns {Map<string, import('./normalize-sig.js').NormalizedSignature>}
183
+ */
184
+ function signaturesForSum (env, sumAst, modulePath) {
185
+ /** @type {Map<string, import('./normalize-sig.js').NormalizedSignature>} */
186
+ const m = new Map()
187
+ const tvarByParam = new Map()
188
+ for (const p of sumAst.typeParams ?? []) {
189
+ tvarByParam.set(p, tv(p))
190
+ }
191
+ const sumIr = sumTypeIr(sumAst, modulePath, tvarByParam)
192
+
193
+ for (const tag of sumAst.tags ?? []) {
194
+ const wn = lowerFirst(tag.name)
195
+ if (tag.payload) {
196
+ const pay = payloadTypeToIr(env, modulePath, tag.payload, tvarByParam)
197
+ if (!pay) continue
198
+ const s = normSig([pay], [sumIr])
199
+ m.set(wn, shareTvarsInSignature(s))
200
+ } else {
201
+ const s = normSig([], [sumIr])
202
+ m.set(wn, shareTvarsInSignature(s))
203
+ }
204
+ }
205
+
206
+ const elimName = lowerFirst(sumAst.name)
207
+ const sLab = sl('s')
208
+ const bLab = sl('b')
209
+ /** @type {object[]} */
210
+ const branchQuotes = []
211
+ for (const tag of sumAst.tags ?? []) {
212
+ /** @type {object[]} */
213
+ const leftIn = [sLab]
214
+ if (tag.payload) {
215
+ const pay = payloadTypeToIr(env, modulePath, tag.payload, tvarByParam)
216
+ if (!pay) {
217
+ return m
218
+ }
219
+ leftIn.push(pay)
220
+ }
221
+ branchQuotes.push(q(normSig(leftIn, [bLab])))
222
+ }
223
+ const elimSig = normSig([sumIr, ...branchQuotes], [], {
224
+ adtEliminator: 'sum'
225
+ })
226
+ const elimShared = shareTvarsInSignature(elimSig)
227
+ mergeSumEliminatorBranchOutputLabels(elimShared)
228
+ m.set(elimName, elimShared)
229
+
230
+ return m
231
+ }
232
+
233
+ /**
234
+ * @param {object} prodAst
235
+ * @param {string} modulePath
236
+ * @param {Map<string, object>} tvarByParam
237
+ */
238
+ function productTypeIr (prodAst, modulePath, tvarByParam) {
239
+ const names = prodAst.typeParams ?? []
240
+ const ref = declRef(modulePath, prodAst)
241
+ if (names.length === 0) {
242
+ return {
243
+ kind: 'adt',
244
+ path: modulePath,
245
+ type: prodAst.name,
246
+ source: null,
247
+ decl: ref
248
+ }
249
+ }
250
+ const args = names.map((p) => tvarByParam.get(p))
251
+ return {
252
+ kind: 'app',
253
+ ctor: prodAst.name,
254
+ args,
255
+ source: null,
256
+ decl: ref
257
+ }
258
+ }
259
+
260
+ /**
261
+ * @param {import('./build-type-env.js').TypecheckEnv} env
262
+ * @param {object} prodAst
263
+ * @param {string} modulePath
264
+ * @returns {Map<string, import('./normalize-sig.js').NormalizedSignature>}
265
+ */
266
+ function signaturesForProduct (env, prodAst, modulePath) {
267
+ /** @type {Map<string, import('./normalize-sig.js').NormalizedSignature>} */
268
+ const m = new Map()
269
+ const tvarByParam = new Map()
270
+ for (const p of prodAst.typeParams ?? []) {
271
+ tvarByParam.set(p, tv(p))
272
+ }
273
+ const prodIr = productTypeIr(prodAst, modulePath, tvarByParam)
274
+
275
+ const ctorName = lowerFirst(prodAst.name)
276
+ /** @type {object[]} */
277
+ const fieldIrs = []
278
+ for (const f of prodAst.fields ?? []) {
279
+ const ft = payloadTypeToIr(env, modulePath, f.type, tvarByParam)
280
+ if (!ft) return m
281
+ fieldIrs.push(ft)
282
+ }
283
+ m.set(ctorName, shareTvarsInSignature(normSig(fieldIrs, [prodIr])))
284
+
285
+ for (const f of prodAst.fields ?? []) {
286
+ const ft = payloadTypeToIr(env, modulePath, f.type, tvarByParam)
287
+ if (!ft) return m
288
+ const getter = lowerFirst(f.name + prodAst.name)
289
+ m.set(getter, shareTvarsInSignature(normSig([prodIr], [ft])))
290
+ }
291
+
292
+ const withName = lowerFirst(`with${prodAst.name}`)
293
+ const sLab = sl('s')
294
+ const bLab = sl('b')
295
+ const qInner = normSig([sLab, ...fieldIrs], [bLab])
296
+ const elimSig = normSig([prodIr, q(qInner)], [], {
297
+ adtEliminator: 'product'
298
+ })
299
+ m.set(withName, shareTvarsInSignature(elimSig))
300
+
301
+ return m
302
+ }
303
+
304
+ /**
305
+ * @param {import('./build-type-env.js').TypecheckEnv} env
306
+ * @param {string} modulePath
307
+ * @returns {Map<string, import('./normalize-sig.js').NormalizedSignature>}
308
+ */
309
+ export function buildAutogenSignaturesForModule (env, modulePath) {
310
+ /** @type {Map<string, import('./normalize-sig.js').NormalizedSignature>} */
311
+ const out = new Map()
312
+ const adtMap = env.adtByPath?.get(modulePath)
313
+ if (!adtMap) return out
314
+
315
+ for (const entry of adtMap.values()) {
316
+ const ast = entry.ast
317
+ if (entry.kind === 'sum') {
318
+ const part = signaturesForSum(env, ast, modulePath)
319
+ for (const [k, v] of part) out.set(k, v)
320
+ } else if (entry.kind === 'product') {
321
+ const part = signaturesForProduct(env, ast, modulePath)
322
+ for (const [k, v] of part) out.set(k, v)
323
+ }
324
+ }
325
+ return out
326
+ }
327
+
328
+ /**
329
+ * Не перезаписывает существующие `@word` в sigIrByPath.
330
+ *
331
+ * @param {import('./build-type-env.js').TypecheckEnv} env
332
+ * @param {Map<string, Map<string, import('./normalize-sig.js').NormalizedSignature>>} sigIrByPath
333
+ */
334
+ export function mergeAdtAutogenIntoSigIrByPath (env, sigIrByPath) {
335
+ for (const p of env.modulePathsOrdered) {
336
+ const perPath = sigIrByPath.get(p)
337
+ if (!perPath) continue
338
+ const auto = buildAutogenSignaturesForModule(env, p)
339
+ for (const [name, sig] of auto) {
340
+ if (!perPath.has(name)) {
341
+ perPath.set(name, sig)
342
+ }
343
+ }
344
+ }
345
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * L3 этап 1: окружение из снимков L2 (RFC-typecheck-0.1 §5.1, RFC-0.1 §11).
3
+ *
4
+ * @typedef {object} TypecheckEnv
5
+ * @property {string} entryPath
6
+ * @property {string[]} modulePathsOrdered
7
+ * @property {Map<string, object>} snapshots
8
+ * @property {Map<string, Map<string, object>>} wordDeclByPath
9
+ * @property {Map<string, { importMap: Map<string, object>, unq: Set<string>, exportWords: Set<string>, exportTypes: Set<string>, typeIndex: Map<string, object> }>} scopeByPath
10
+ * @property {Map<string, Map<string, object>>} [adtByPath] этап 2: только **валидные** объявления `&` (частично при диагностиках на других `&`; RFC-typecheck §5.1)
11
+ * @property {Map<string, Map<string, object>>} [sigIrByPath] этап 3: IR сигнатур для слов **без** ошибок нормализации (частичная карта при `ok: false`); слоты включают `prim`, `opaque` (FFI §5.4), `adt`, `app`, …
12
+ * @property {Map<string, Map<string, { asyncDefinition: boolean, mayFail: boolean }>>} [definitionEffectsByPath] этап 7: RFC-IR §6 (объявление)
13
+ * @property {object[]} [callSiteEffectMarks] этап 7: calleeAsync / calleeMayFail по шагам (RFC-typecheck §3, RFC-IR §6)
14
+ * @property {Map<string, Map<string, object>>} [stackSnapshotsByPath] этап 9: запись слова — см. [stack-step-snapshots.js](stack-step-snapshots.js) (`steps`, `nestedByParentStep`); для успешного слова — полная цепь; при ошибке в теле — префикс и `pre` на шаге сбоя (RFC-typecheck §3); при ошибке выхода слова — полная цепь по шагам
15
+ */
16
+ import path from 'node:path'
17
+ import { buildScopeForSnapshot } from '../names/module-scope.js'
18
+ import { resolveImportPath } from '../names/import-path.js'
19
+
20
+ /**
21
+ * Явное `@word` в экспортируемом модуле (не autogen).
22
+ *
23
+ * @param {object} depSnapshot
24
+ * @param {string} name
25
+ * @returns {object | null}
26
+ */
27
+ export function resolveExportedWord (depSnapshot, name) {
28
+ if (!depSnapshot?.ok) return null
29
+ for (const it of depSnapshot.items) {
30
+ if (it.kind === 'word' && it.name === name) return it
31
+ }
32
+ return null
33
+ }
34
+
35
+ /**
36
+ * Топологический порядок: зависимость раньше импортёра. Только `snap.ok === true`.
37
+ *
38
+ * @param {Map<string, object>} snapshots
39
+ * @param {(p: string) => string | null | undefined} readFile
40
+ * @param {((spec: string, fromPath: string) => string | null) | undefined} resolvePackage
41
+ * @returns {string[]}
42
+ */
43
+ function topoSortOkModules (snapshots, readFile, resolvePackage) {
44
+ /** @type {string[]} */
45
+ const vertices = []
46
+ for (const [p, snap] of snapshots) {
47
+ if (snap?.ok === true) vertices.push(p)
48
+ }
49
+ vertices.sort()
50
+ const vertexSet = new Set(vertices)
51
+ /** @type {Map<string, number>} */
52
+ const indegree = new Map()
53
+ /** @type {Map<string, string[]>} */
54
+ const adj = new Map()
55
+ for (const p of vertices) {
56
+ indegree.set(p, 0)
57
+ adj.set(p, [])
58
+ }
59
+ for (const p of vertices) {
60
+ const snap = snapshots.get(p)
61
+ for (const item of snap.items) {
62
+ if (item.kind !== 'import') continue
63
+ const r = resolveImportPath(p, item.path, readFile, resolvePackage)
64
+ if (!r.resolvedPath || !vertexSet.has(r.resolvedPath)) continue
65
+ const dep = snapshots.get(r.resolvedPath)
66
+ if (!dep?.ok) continue
67
+ indegree.set(p, indegree.get(p) + 1)
68
+ adj.get(r.resolvedPath).push(p)
69
+ }
70
+ }
71
+ const visited = new Set()
72
+ /** @type {string[]} */
73
+ const out = []
74
+ while (visited.size < vertices.length) {
75
+ /** @type {string[]} */
76
+ const zeros = []
77
+ for (const v of vertices) {
78
+ if (!visited.has(v) && indegree.get(v) === 0) zeros.push(v)
79
+ }
80
+ if (zeros.length === 0) break
81
+ zeros.sort()
82
+ const u = zeros[0]
83
+ visited.add(u)
84
+ out.push(u)
85
+ for (const v of adj.get(u)) {
86
+ indegree.set(v, indegree.get(v) - 1)
87
+ }
88
+ }
89
+ if (out.length < vertices.length) {
90
+ const rest = vertices.filter((p) => !visited.has(p)).sort()
91
+ out.push(...rest)
92
+ }
93
+ return out
94
+ }
95
+
96
+ /**
97
+ * @param {{
98
+ * snapshots: Map<string, object>,
99
+ * entryPath: string,
100
+ * readFile: (p: string) => string | null | undefined,
101
+ * resolvePackage?: (spec: string, fromPath: string) => string | null
102
+ * }} ctx
103
+ * @returns {{
104
+ * entryPath: string,
105
+ * modulePathsOrdered: string[],
106
+ * snapshots: Map<string, object>,
107
+ * wordDeclByPath: Map<string, Map<string, object>>,
108
+ * scopeByPath: Map<string, { importMap: Map<string, object>, unq: Set<string>, exportWords: Set<string>, exportTypes: Set<string>, typeIndex: Map<string, object> }>
109
+ * }}
110
+ */
111
+ export function buildTypecheckEnv (ctx) {
112
+ const { snapshots, entryPath, readFile, resolvePackage } = ctx
113
+ const normEntry = path.normalize(entryPath)
114
+ const ctxScope = { readFile, resolvePackage, snapshots }
115
+
116
+ /** @type {Map<string, Map<string, object>>} */
117
+ const wordDeclByPath = new Map()
118
+ /** @type {Map<string, { importMap: Map<string, object>, unq: Set<string>, exportWords: Set<string>, exportTypes: Set<string>, typeIndex: Map<string, object> }>} */
119
+ const scopeByPath = new Map()
120
+
121
+ for (const [p, snap] of snapshots) {
122
+ if (!snap?.ok) continue
123
+ /** @type {Map<string, object>} */
124
+ const words = new Map()
125
+ for (const it of snap.items) {
126
+ if (it.kind === 'word') words.set(it.name, it)
127
+ }
128
+ wordDeclByPath.set(p, words)
129
+ const scope = buildScopeForSnapshot(snap, ctxScope)
130
+ scopeByPath.set(p, {
131
+ importMap: scope.importMap,
132
+ unq: scope.unq,
133
+ exportWords: snap.exportWords,
134
+ exportTypes: snap.exportTypes,
135
+ typeIndex: snap.typeIndex
136
+ })
137
+ }
138
+
139
+ const modulePathsOrdered = topoSortOkModules(snapshots, readFile, resolvePackage)
140
+
141
+ return {
142
+ entryPath: normEntry,
143
+ modulePathsOrdered,
144
+ snapshots,
145
+ wordDeclByPath,
146
+ scopeByPath
147
+ }
148
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * L3: IR сигнатуры встроенных слов (RFC-builtins-0.1 §4.1–4.5).
3
+ */
4
+ import { BUILTIN_WORDS } from '../parse/builtins-set.js'
5
+
6
+ /** @returns {{ kind: 'tvar', name: string, source: null, decl: null }} */
7
+ function tv (name) {
8
+ return { kind: 'tvar', name, source: null, decl: null }
9
+ }
10
+
11
+ /** @returns {{ kind: 'stack_label', name: string, source: null }} */
12
+ function sl (name) {
13
+ return { kind: 'stack_label', name, source: null }
14
+ }
15
+
16
+ /**
17
+ * @param {import('./normalize-sig.js').NormalizedSignature} inner
18
+ * @returns {{ kind: 'quote', source: null, inner }}
19
+ */
20
+ function q (inner) {
21
+ return { kind: 'quote', source: null, inner }
22
+ }
23
+
24
+ /**
25
+ * @param {object[]} left
26
+ * @param {object[]} right
27
+ * @returns {import('./normalize-sig.js').NormalizedSignature}
28
+ */
29
+ function sig (left, right) {
30
+ return { left, right, effectsAdd: [], effectsRemove: [] }
31
+ }
32
+
33
+ /** У каждой лексемы — свои объекты tvar (не делить между встроенными). */
34
+ function buildStackOnlySignatures () {
35
+ return new Map([
36
+ (() => {
37
+ const a = tv('a')
38
+ return ['dup', sig([a], [a, a])]
39
+ })(),
40
+ (() => {
41
+ const a = tv('a')
42
+ return ['drop', sig([a], [])]
43
+ })(),
44
+ (() => {
45
+ const a = tv('a')
46
+ const b = tv('b')
47
+ return ['swap', sig([a, b], [b, a])]
48
+ })(),
49
+ (() => {
50
+ const a = tv('a')
51
+ const b = tv('b')
52
+ return ['over', sig([a, b], [a, b, a])]
53
+ })(),
54
+ (() => {
55
+ const a = tv('a')
56
+ const b = tv('b')
57
+ return ['dup2', sig([a, b], [a, b, a, b])]
58
+ })(),
59
+ (() => {
60
+ const a = tv('a')
61
+ const b = tv('b')
62
+ return ['drop2', sig([a, b], [])]
63
+ })(),
64
+ (() => {
65
+ const a = tv('a')
66
+ const b = tv('b')
67
+ const c = tv('c')
68
+ return ['rot', sig([a, b, c], [b, c, a])]
69
+ })(),
70
+ (() => {
71
+ const a = tv('a')
72
+ const b = tv('b')
73
+ const c = tv('c')
74
+ return ['reverse', sig([a, b, c], [c, a, b])]
75
+ })(),
76
+ (() => {
77
+ const a = tv('a')
78
+ const b = tv('b')
79
+ return ['nip', sig([a, b], [b])]
80
+ })(),
81
+ (() => {
82
+ const a = tv('a')
83
+ const b = tv('b')
84
+ return ['tuck', sig([a, b], [b, a, b])]
85
+ })(),
86
+ (() => {
87
+ const x1 = tv('x1')
88
+ const x2 = tv('x2')
89
+ const y1 = tv('y1')
90
+ const y2 = tv('y2')
91
+ return ['swap2', sig([x1, x2, y1, y2], [y1, y2, x1, x2])]
92
+ })()
93
+ ])
94
+ }
95
+
96
+ /**
97
+ * §4.3–4.5: call, dip, keep, bi, tri, spread, both.
98
+ * Порядок в left / right: снизу стека → вершина (слева направо в массиве).
99
+ */
100
+ function buildStage6Signatures () {
101
+ return new Map([
102
+ (() => {
103
+ const s = sl('s')
104
+ const t = sl('t')
105
+ const a = tv('a')
106
+ const inner = sig([s], [t])
107
+ return ['dip', sig([s, a, q(inner)], [t, a])]
108
+ })(),
109
+ (() => {
110
+ const s = sl('s')
111
+ const t = sl('t')
112
+ const a = tv('a')
113
+ const inner = sig([s, a], [t])
114
+ return ['keep', sig([s, a, q(inner)], [t, a])]
115
+ })(),
116
+ (() => {
117
+ const s = sl('s')
118
+ const a = tv('a')
119
+ const b = tv('b')
120
+ const c = tv('c')
121
+ const q1 = q(sig([a], [b]))
122
+ const q2 = q(sig([a], [c]))
123
+ return ['bi', sig([s, a, q1, q2], [s, b, c])]
124
+ })(),
125
+ (() => {
126
+ const s = sl('s')
127
+ const a = tv('a')
128
+ const b = tv('b')
129
+ const c = tv('c')
130
+ const d = tv('d')
131
+ const q1 = q(sig([a], [b]))
132
+ const q2 = q(sig([a], [c]))
133
+ const q3 = q(sig([a], [d]))
134
+ return ['tri', sig([s, a, q1, q2, q3], [s, b, c, d])]
135
+ })(),
136
+ (() => {
137
+ const s = sl('s')
138
+ const a = tv('a')
139
+ const b = tv('b')
140
+ const c = tv('c')
141
+ const d = tv('d')
142
+ const q1 = q(sig([a], [c]))
143
+ const q2 = q(sig([b], [d]))
144
+ return ['spread', sig([s, a, b, q1, q2], [s, c, d])]
145
+ })(),
146
+ (() => {
147
+ const s = sl('s')
148
+ const x = tv('x')
149
+ const y = tv('y')
150
+ const inner = sig([x], [y])
151
+ return ['both', sig([s, x, x, q(inner)], [s, y, y])]
152
+ })()
153
+ ])
154
+ }
155
+
156
+ const STACK_ONLY = buildStackOnlySignatures()
157
+ const STAGE6 = buildStage6Signatures()
158
+
159
+ const ALL_STACK = new Map([...STACK_ONLY, ...STAGE6])
160
+
161
+ /**
162
+ * @param {string} name
163
+ * @returns {import('./normalize-sig.js').NormalizedSignature | null}
164
+ */
165
+ export function getBuiltinStackSignature (name) {
166
+ return ALL_STACK.get(name) ?? null
167
+ }
168
+
169
+ /**
170
+ * @param {string} name
171
+ * @returns {boolean}
172
+ */
173
+ export function isBuiltinSupportedStage4 (name) {
174
+ return BUILTIN_WORDS.has(name) && STACK_ONLY.has(name)
175
+ }
176
+
177
+ /**
178
+ * @param {string} name
179
+ * @returns {boolean}
180
+ */
181
+ export function isBuiltinDeferredStage6 (name) {
182
+ return false
183
+ }