@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,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
|
+
}
|