@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,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L4: каркас `module` из L2/L3 + wire-шаги тела (RFC-IR-0.1).
|
|
3
|
+
* `words[].irSteps` — узлы section 5; на них же (фаза 3) копируются `preTypes`/`postTypes` из `stackSteps`;
|
|
4
|
+
* фаза 5: `asyncDefinition`/`mayFail` на записи слова, `calleeAsync`/`calleeMayFail` на узлах вызова (RFC-IR §6);
|
|
5
|
+
* фаза 6: `imports[].resolvedPath`, на `Word` qualified — `resolvedModulePath` (RFC-IR §7, [module-metadata.js](./module-metadata.js)).
|
|
6
|
+
*/
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
import { attachCallSiteEffectMarksToIrSteps } from './attach-call-effects.js'
|
|
9
|
+
import {
|
|
10
|
+
attachResolvedModulePathToQualifiedWords,
|
|
11
|
+
enrichImportsWithResolvedPaths
|
|
12
|
+
} from './module-metadata.js'
|
|
13
|
+
import { lookupWordSignatureIr } from '../typecheck/check-word-body.js'
|
|
14
|
+
import { lowerBodySteps } from './lower-body-steps.js'
|
|
15
|
+
import { attachStackTypesToIrSteps } from './stitch-types.js'
|
|
16
|
+
import { bindValueBindingsForIrSteps } from './bind-values.js'
|
|
17
|
+
import { createSerializedIrDocument } from './serialize.js'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {import('../typecheck/build-type-env.js').TypecheckEnv} TypecheckEnv
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {object | null | undefined} t
|
|
25
|
+
* @returns {unknown}
|
|
26
|
+
*/
|
|
27
|
+
function irTypeSlotToWire (t) {
|
|
28
|
+
if (t == null || typeof t !== 'object') return t
|
|
29
|
+
switch (t.kind) {
|
|
30
|
+
case 'prim':
|
|
31
|
+
return { kind: 'prim', name: t.name }
|
|
32
|
+
case 'tvar':
|
|
33
|
+
return { kind: 'tvar', name: t.name }
|
|
34
|
+
case 'stack_label':
|
|
35
|
+
return { kind: 'stack_label', name: t.name }
|
|
36
|
+
case 'opaque':
|
|
37
|
+
return { kind: 'opaque', name: t.name }
|
|
38
|
+
case 'app':
|
|
39
|
+
return {
|
|
40
|
+
kind: 'app',
|
|
41
|
+
ctor: t.ctor,
|
|
42
|
+
args: (t.args ?? []).map(irTypeSlotToWire)
|
|
43
|
+
}
|
|
44
|
+
case 'mod_adt':
|
|
45
|
+
return { kind: 'mod_adt', module: t.module, type: t.type }
|
|
46
|
+
case 'adt':
|
|
47
|
+
return { kind: 'adt', path: t.path, type: t.type }
|
|
48
|
+
case 'quote':
|
|
49
|
+
return { kind: 'quote', inner: normalizedSigToWire(t.inner) }
|
|
50
|
+
case 'named_quote':
|
|
51
|
+
return {
|
|
52
|
+
kind: 'named_quote',
|
|
53
|
+
prefix: t.prefix,
|
|
54
|
+
inner: normalizedSigToWire(t.inner)
|
|
55
|
+
}
|
|
56
|
+
default:
|
|
57
|
+
return { kind: String(t.kind ?? 'unknown') }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {import('../typecheck/normalize-sig.js').NormalizedSignature} sig
|
|
63
|
+
*/
|
|
64
|
+
function normalizedSigToWire (sig) {
|
|
65
|
+
if (!sig || typeof sig !== 'object') return sig
|
|
66
|
+
const out = {
|
|
67
|
+
left: (sig.left ?? []).map(irTypeSlotToWire),
|
|
68
|
+
right: (sig.right ?? []).map(irTypeSlotToWire),
|
|
69
|
+
effectsAdd: (sig.effectsAdd ?? []).map((e) => ({
|
|
70
|
+
kind: e.kind,
|
|
71
|
+
name: e.name,
|
|
72
|
+
side: e.side
|
|
73
|
+
})),
|
|
74
|
+
effectsRemove: (sig.effectsRemove ?? []).map((e) => ({
|
|
75
|
+
kind: e.kind,
|
|
76
|
+
name: e.name,
|
|
77
|
+
side: e.side
|
|
78
|
+
}))
|
|
79
|
+
}
|
|
80
|
+
if (sig.adtEliminator != null) {
|
|
81
|
+
out.adtEliminator = sig.adtEliminator
|
|
82
|
+
}
|
|
83
|
+
return out
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {{ steps: object[], nestedByParentStep: Map<number, object> }} record
|
|
88
|
+
* @returns {object}
|
|
89
|
+
*/
|
|
90
|
+
function stackSnapshotRecordToWire (record) {
|
|
91
|
+
const steps = []
|
|
92
|
+
const rawSteps = record.steps ?? []
|
|
93
|
+
for (let i = 0; i < rawSteps.length; i++) {
|
|
94
|
+
const s = rawSteps[i]
|
|
95
|
+
if (s == null || typeof s !== 'object') {
|
|
96
|
+
steps.push(null)
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
const stepOut = {
|
|
100
|
+
preTypes: (s.pre ?? []).map(irTypeSlotToWire)
|
|
101
|
+
}
|
|
102
|
+
if (s.post != null) {
|
|
103
|
+
stepOut.postTypes = s.post.map(irTypeSlotToWire)
|
|
104
|
+
}
|
|
105
|
+
steps.push(stepOut)
|
|
106
|
+
}
|
|
107
|
+
/** @type {{ parentStepIndex: number, record: object }[]} */
|
|
108
|
+
const nested = []
|
|
109
|
+
if (record.nestedByParentStep instanceof Map) {
|
|
110
|
+
for (const [parentStepIndex, inner] of record.nestedByParentStep) {
|
|
111
|
+
nested.push({
|
|
112
|
+
parentStepIndex,
|
|
113
|
+
record: stackSnapshotRecordToWire(inner)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const out = { steps }
|
|
118
|
+
if (nested.length > 0) {
|
|
119
|
+
out.nestedByParentStep = nested
|
|
120
|
+
}
|
|
121
|
+
return out
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {object} item import AST item
|
|
126
|
+
*/
|
|
127
|
+
function importItemToWire (item) {
|
|
128
|
+
const w = {
|
|
129
|
+
module: item.module,
|
|
130
|
+
path: item.path
|
|
131
|
+
}
|
|
132
|
+
if (item.bracket) {
|
|
133
|
+
w.bracket = {
|
|
134
|
+
words: [...(item.bracket.words ?? [])],
|
|
135
|
+
types: [...(item.bracket.types ?? [])]
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return w
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {TypecheckEnv} env
|
|
143
|
+
* @param {string} modulePath
|
|
144
|
+
* @param {{
|
|
145
|
+
* moduleStatus?: 'ok' | 'error'
|
|
146
|
+
* valueBindings?: boolean
|
|
147
|
+
* valueBindingsStrict?: boolean
|
|
148
|
+
* }} [options] при `moduleStatus` дублирует итог L3 в payload (корень документа — `meta.status` в {@link createSerializedIrDocument}).
|
|
149
|
+
* При `valueBindings: true` после фазы 3 заполняется `irSteps[*].valueBindings` (RFC-IR §4).
|
|
150
|
+
* @returns {import('./serialize.js').IrModulePayload}
|
|
151
|
+
*/
|
|
152
|
+
export function buildModuleIr (env, modulePath, options = {}) {
|
|
153
|
+
const normPath = path.normalize(modulePath)
|
|
154
|
+
const snap = env.snapshots?.get(normPath)
|
|
155
|
+
const scope = env.scopeByPath?.get(normPath)
|
|
156
|
+
const wordsMap = env.wordDeclByPath?.get(normPath)
|
|
157
|
+
|
|
158
|
+
/** @type {import('./serialize.js').IrModulePayload} */
|
|
159
|
+
const payload = {
|
|
160
|
+
path: normPath,
|
|
161
|
+
imports: [],
|
|
162
|
+
exportWords: [],
|
|
163
|
+
exportTypes: [],
|
|
164
|
+
adts: [],
|
|
165
|
+
words: []
|
|
166
|
+
}
|
|
167
|
+
if (options.moduleStatus != null) {
|
|
168
|
+
payload.status = options.moduleStatus
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (snap?.ok && Array.isArray(snap.items)) {
|
|
172
|
+
for (const it of snap.items) {
|
|
173
|
+
if (it.kind === 'import') {
|
|
174
|
+
payload.imports.push(importItemToWire(it))
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
enrichImportsWithResolvedPaths(payload.imports, snap, scope)
|
|
179
|
+
|
|
180
|
+
if (scope) {
|
|
181
|
+
payload.exportWords = Array.from(scope.exportWords ?? []).sort()
|
|
182
|
+
payload.exportTypes = Array.from(scope.exportTypes ?? []).sort()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const adtMap = env.adtByPath?.get(normPath)
|
|
186
|
+
if (adtMap instanceof Map) {
|
|
187
|
+
const names = [...adtMap.keys()].sort()
|
|
188
|
+
for (const name of names) {
|
|
189
|
+
const e = adtMap.get(name)
|
|
190
|
+
if (!e) continue
|
|
191
|
+
payload.adts.push({
|
|
192
|
+
kind: e.kind,
|
|
193
|
+
name: e.name,
|
|
194
|
+
typeParams: [...(e.typeParams ?? [])]
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const sigsForPath = env.sigIrByPath?.get(normPath)
|
|
200
|
+
const snapsForPath = env.stackSnapshotsByPath?.get(normPath)
|
|
201
|
+
|
|
202
|
+
if (wordsMap instanceof Map) {
|
|
203
|
+
const names = [...wordsMap.keys()].sort()
|
|
204
|
+
for (const name of names) {
|
|
205
|
+
const word = wordsMap.get(name)
|
|
206
|
+
const exported = scope?.exportWords?.has(name) ?? false
|
|
207
|
+
const sigAvailable = sigsForPath instanceof Map && sigsForPath.has(name)
|
|
208
|
+
/** @type {Record<string, unknown>} */
|
|
209
|
+
const entry = {
|
|
210
|
+
name,
|
|
211
|
+
exported,
|
|
212
|
+
sigAvailable
|
|
213
|
+
}
|
|
214
|
+
if (sigAvailable) {
|
|
215
|
+
const sig = sigsForPath.get(name)
|
|
216
|
+
entry.normalizedSig = normalizedSigToWire(sig)
|
|
217
|
+
}
|
|
218
|
+
const defFx = env.definitionEffectsByPath?.get(normPath)
|
|
219
|
+
const fx =
|
|
220
|
+
defFx instanceof Map ? defFx.get(name) : undefined
|
|
221
|
+
entry.asyncDefinition = fx?.asyncDefinition === true
|
|
222
|
+
entry.mayFail = fx?.mayFail === true
|
|
223
|
+
if (snapsForPath instanceof Map && snapsForPath.has(name)) {
|
|
224
|
+
entry.stackSteps = stackSnapshotRecordToWire(snapsForPath.get(name))
|
|
225
|
+
}
|
|
226
|
+
if (Array.isArray(word.body)) {
|
|
227
|
+
entry.irSteps = lowerBodySteps(normPath, name, word.body)
|
|
228
|
+
if (scope?.importMap instanceof Map) {
|
|
229
|
+
attachResolvedModulePathToQualifiedWords(
|
|
230
|
+
entry.irSteps,
|
|
231
|
+
scope.importMap
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
if (entry.stackSteps != null) {
|
|
235
|
+
attachStackTypesToIrSteps(entry.irSteps, entry.stackSteps)
|
|
236
|
+
}
|
|
237
|
+
const marks = env.callSiteEffectMarks ?? []
|
|
238
|
+
const marksForWord = marks.filter(
|
|
239
|
+
(m) => path.normalize(m.modulePath) === normPath && m.callerWord === name
|
|
240
|
+
)
|
|
241
|
+
attachCallSiteEffectMarksToIrSteps(entry.irSteps, marksForWord)
|
|
242
|
+
if (options.valueBindings === true && Array.isArray(entry.irSteps)) {
|
|
243
|
+
const entryStackIds = bindValueBindingsForIrSteps(entry.irSteps, {
|
|
244
|
+
wordName: name,
|
|
245
|
+
modulePath: normPath,
|
|
246
|
+
strict: options.valueBindingsStrict === true,
|
|
247
|
+
resolveLocalWordSig: (wn) =>
|
|
248
|
+
lookupWordSignatureIr(env, normPath, null, wn),
|
|
249
|
+
resolveQualifiedWordSig: (modAlias, w) =>
|
|
250
|
+
lookupWordSignatureIr(env, normPath, modAlias, w)
|
|
251
|
+
})
|
|
252
|
+
if (entryStackIds != null) {
|
|
253
|
+
entry.entryStackIds = entryStackIds
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
payload.words.push(entry)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return payload
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @param {TypecheckEnv} env
|
|
266
|
+
* @param {string} modulePath
|
|
267
|
+
* @param {{
|
|
268
|
+
* moduleStatus: 'ok' | 'error',
|
|
269
|
+
* producer?: { name: string, version?: string },
|
|
270
|
+
* sources?: { path?: string, uri?: string, sha256?: string }[],
|
|
271
|
+
* valueBindings?: boolean,
|
|
272
|
+
* valueBindingsStrict?: boolean
|
|
273
|
+
* }} opts
|
|
274
|
+
*/
|
|
275
|
+
export function buildSerializedIrDocumentFromEnv (env, modulePath, opts) {
|
|
276
|
+
const modulePayload = buildModuleIr(env, modulePath, {
|
|
277
|
+
moduleStatus: opts.moduleStatus,
|
|
278
|
+
valueBindings: opts.valueBindings === true,
|
|
279
|
+
valueBindingsStrict: opts.valueBindingsStrict === true
|
|
280
|
+
})
|
|
281
|
+
/** @type {{ status: 'ok' | 'error', producer?: object, sources?: object[] }} */
|
|
282
|
+
const meta = { status: opts.moduleStatus }
|
|
283
|
+
if (opts.producer != null) {
|
|
284
|
+
meta.producer = opts.producer
|
|
285
|
+
}
|
|
286
|
+
if (opts.sources != null) {
|
|
287
|
+
meta.sources = opts.sources
|
|
288
|
+
}
|
|
289
|
+
return createSerializedIrDocument(modulePayload, { meta })
|
|
290
|
+
}
|
package/lib/ir/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L4: typed IR после lowering — [RFC-IR-0.1.md](../../../../RFC-IR-0.1.md).
|
|
3
|
+
*
|
|
4
|
+
* Входные данные: любой итог `typecheckSail` и `TypecheckEnv`
|
|
5
|
+
* (`ok: true` или частичный L3 при ошибках); статус модуля в IR (`meta.status`, опционально
|
|
6
|
+
* `module.status` в payload) задаёт вызывающий код — см. {@link buildModuleIr}.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { IR_SCHEMA_VERSION } from './schema-version.js'
|
|
10
|
+
export { attachCallSiteEffectMarksToIrSteps } from './attach-call-effects.js'
|
|
11
|
+
export {
|
|
12
|
+
attachResolvedModulePathToQualifiedWords,
|
|
13
|
+
enrichImportsWithResolvedPaths
|
|
14
|
+
} from './module-metadata.js'
|
|
15
|
+
export { assertJsonSerializable } from './assert-json-serializable.js'
|
|
16
|
+
export { bindValueBindingsForIrSteps } from './bind-values.js'
|
|
17
|
+
export { buildModuleIr, buildSerializedIrDocumentFromEnv } from './build-module-ir.js'
|
|
18
|
+
export { lowerBodySteps, irBodyStepNodeId } from './lower-body-steps.js'
|
|
19
|
+
export {
|
|
20
|
+
attachStackTypesToIrSteps,
|
|
21
|
+
verifyWireStackSnapshot,
|
|
22
|
+
wireStackSnapshotToRecord
|
|
23
|
+
} from './stitch-types.js'
|
|
24
|
+
export {
|
|
25
|
+
createSerializedIrDocument,
|
|
26
|
+
unwrapSerializedIrDocument,
|
|
27
|
+
unwrapSerializedIrDocumentFull,
|
|
28
|
+
stringifyIrDocument,
|
|
29
|
+
parseIrDocument,
|
|
30
|
+
parseIrDocumentFull
|
|
31
|
+
} from './serialize.js'
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L4 фаза 2: `word.body` (AST) → wire-узлы RFC-IR section 5.
|
|
3
|
+
* Без стыковки с `stackSteps` (фаза 3). JSON-safe: только примитивы, массивы, plain objects.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Стабильный id шага: `{modulePath}#{wordName}#{indexPath}`; `indexPath` — индексы через `.`
|
|
8
|
+
* (корень `0`, вложение `( … )` у шага `2`: `2.0`, `2.1`, …). Совместимо с [serialize.js](./serialize.js).
|
|
9
|
+
*
|
|
10
|
+
* @param {string} modulePath нормализованный путь модуля
|
|
11
|
+
* @param {string} wordName
|
|
12
|
+
* @param {number[]} indexPath
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
export function irBodyStepNodeId (modulePath, wordName, indexPath) {
|
|
16
|
+
const tail = indexPath.length ? indexPath.join('.') : ''
|
|
17
|
+
return tail
|
|
18
|
+
? `${modulePath}#${wordName}#${tail}`
|
|
19
|
+
: `${modulePath}#${wordName}#`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {{ start: object, end?: object } | null | undefined} span
|
|
24
|
+
* @returns {{ start: object, end?: object } | undefined}
|
|
25
|
+
*/
|
|
26
|
+
function spanToWire (span) {
|
|
27
|
+
if (span == null || typeof span !== 'object' || span.start == null) {
|
|
28
|
+
return undefined
|
|
29
|
+
}
|
|
30
|
+
const out = {
|
|
31
|
+
start: {
|
|
32
|
+
offset: span.start.offset,
|
|
33
|
+
line: span.start.line,
|
|
34
|
+
column: span.start.column
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (span.end != null) {
|
|
38
|
+
out.end = {
|
|
39
|
+
offset: span.end.offset,
|
|
40
|
+
line: span.end.line,
|
|
41
|
+
column: span.end.column
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {object} step AST word_step
|
|
49
|
+
* @param {string} modulePath
|
|
50
|
+
* @param {string} wordName
|
|
51
|
+
* @param {number[]} indexPath
|
|
52
|
+
* @returns {Record<string, unknown>}
|
|
53
|
+
*/
|
|
54
|
+
function lowerOneStep (step, modulePath, wordName, indexPath) {
|
|
55
|
+
const nodeId = irBodyStepNodeId(modulePath, wordName, indexPath)
|
|
56
|
+
const span = spanToWire(step.span)
|
|
57
|
+
|
|
58
|
+
switch (step.kind) {
|
|
59
|
+
case 'literal':
|
|
60
|
+
return {
|
|
61
|
+
kind: 'Literal',
|
|
62
|
+
nodeId,
|
|
63
|
+
litKind: step.litKind,
|
|
64
|
+
raw: step.raw,
|
|
65
|
+
span
|
|
66
|
+
}
|
|
67
|
+
case 'builtin':
|
|
68
|
+
return {
|
|
69
|
+
kind: 'Builtin',
|
|
70
|
+
nodeId,
|
|
71
|
+
name: step.name,
|
|
72
|
+
span
|
|
73
|
+
}
|
|
74
|
+
case 'word_ref':
|
|
75
|
+
return {
|
|
76
|
+
kind: 'Word',
|
|
77
|
+
nodeId,
|
|
78
|
+
ref: 'local',
|
|
79
|
+
name: step.name,
|
|
80
|
+
span
|
|
81
|
+
}
|
|
82
|
+
case 'module_word_ref':
|
|
83
|
+
return {
|
|
84
|
+
kind: 'Word',
|
|
85
|
+
nodeId,
|
|
86
|
+
ref: 'qualified',
|
|
87
|
+
module: step.module,
|
|
88
|
+
word: step.word,
|
|
89
|
+
span
|
|
90
|
+
}
|
|
91
|
+
case 'quotation': {
|
|
92
|
+
const inner = step.body ?? []
|
|
93
|
+
/** @type {Record<string, unknown>[]} */
|
|
94
|
+
const steps = []
|
|
95
|
+
for (let j = 0; j < inner.length; j++) {
|
|
96
|
+
steps.push(
|
|
97
|
+
lowerOneStep(inner[j], modulePath, wordName, [...indexPath, j])
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
kind: 'Quotation',
|
|
102
|
+
nodeId,
|
|
103
|
+
span,
|
|
104
|
+
steps
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
case 'slot_read':
|
|
108
|
+
return {
|
|
109
|
+
kind: 'Slot',
|
|
110
|
+
nodeId,
|
|
111
|
+
direction: 'read',
|
|
112
|
+
slotName: step.name,
|
|
113
|
+
span
|
|
114
|
+
}
|
|
115
|
+
case 'slot_write':
|
|
116
|
+
return {
|
|
117
|
+
kind: 'Slot',
|
|
118
|
+
nodeId,
|
|
119
|
+
direction: 'write',
|
|
120
|
+
slotName: step.name,
|
|
121
|
+
span
|
|
122
|
+
}
|
|
123
|
+
case 'list_literal': {
|
|
124
|
+
const elements = step.elements ?? []
|
|
125
|
+
/** @type {Record<string, unknown>[]} */
|
|
126
|
+
const wireElements = []
|
|
127
|
+
for (let j = 0; j < elements.length; j++) {
|
|
128
|
+
const el = elements[j]
|
|
129
|
+
wireElements.push({
|
|
130
|
+
kind: 'Literal',
|
|
131
|
+
nodeId: `${nodeId}.e${j}`,
|
|
132
|
+
litKind: el.litKind,
|
|
133
|
+
raw: el.raw,
|
|
134
|
+
span: spanToWire(el.span)
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
kind: 'ListLiteral',
|
|
139
|
+
nodeId,
|
|
140
|
+
span,
|
|
141
|
+
elements: wireElements
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
default:
|
|
145
|
+
return {
|
|
146
|
+
kind: 'Unknown',
|
|
147
|
+
nodeId,
|
|
148
|
+
astKind: String(step.kind ?? 'unknown'),
|
|
149
|
+
span
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {string} modulePath нормализованный путь (как ключи `TypecheckEnv`)
|
|
156
|
+
* @param {string} wordName
|
|
157
|
+
* @param {object[]} bodySteps `word.body` из AST
|
|
158
|
+
* @returns {Record<string, unknown>[]}
|
|
159
|
+
*/
|
|
160
|
+
export function lowerBodySteps (modulePath, wordName, bodySteps) {
|
|
161
|
+
if (!Array.isArray(bodySteps)) {
|
|
162
|
+
return []
|
|
163
|
+
}
|
|
164
|
+
/** @type {Record<string, unknown>[]} */
|
|
165
|
+
const out = []
|
|
166
|
+
for (let i = 0; i < bodySteps.length; i++) {
|
|
167
|
+
out.push(lowerOneStep(bodySteps[i], modulePath, wordName, [i]))
|
|
168
|
+
}
|
|
169
|
+
return out
|
|
170
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L4 фаза 6: метаданные модуля (RFC-IR section 7).
|
|
3
|
+
* Резолв путей импорта и квалифицированных вызовов из `scopeByPath` (тот же граф, что после `resolveSailNames`), без повторного `resolveImportPath`.
|
|
4
|
+
*
|
|
5
|
+
* - **`imports[].resolvedPath`**: нормализованный путь модуля-зависимости (`dep.path` из `importMap.get(алиас)`).
|
|
6
|
+
* - **`Word` с `ref === 'qualified'`**: опционально **`resolvedModulePath`** — тот же путь для алиаса `module`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from 'node:path'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {Record<string, unknown>[]} imports wire-записи в порядке импортов в снимке
|
|
13
|
+
* @param {{ ok?: boolean, items?: object[] } | null | undefined} snap снимок текущего модуля
|
|
14
|
+
* @param {{ importMap?: Map<string, { path?: string }> } | null | undefined} scope из `scopeByPath`
|
|
15
|
+
*/
|
|
16
|
+
export function enrichImportsWithResolvedPaths (imports, snap, scope) {
|
|
17
|
+
if (!Array.isArray(imports) || imports.length === 0) return
|
|
18
|
+
const im = scope?.importMap
|
|
19
|
+
if (!(im instanceof Map)) return
|
|
20
|
+
if (!snap?.ok || !Array.isArray(snap.items)) return
|
|
21
|
+
let idx = 0
|
|
22
|
+
for (const item of snap.items) {
|
|
23
|
+
if (item.kind !== 'import') continue
|
|
24
|
+
const wire = imports[idx]
|
|
25
|
+
if (wire == null || typeof wire !== 'object') break
|
|
26
|
+
const dep = im.get(item.module)
|
|
27
|
+
if (dep != null && typeof dep.path === 'string' && dep.path.length > 0) {
|
|
28
|
+
wire.resolvedPath = path.normalize(dep.path)
|
|
29
|
+
}
|
|
30
|
+
idx++
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {Record<string, unknown>[]} irSteps
|
|
36
|
+
* @param {Map<string, { path?: string }>} importMap
|
|
37
|
+
*/
|
|
38
|
+
export function attachResolvedModulePathToQualifiedWords (irSteps, importMap) {
|
|
39
|
+
if (!Array.isArray(irSteps) || !(importMap instanceof Map)) return
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {Record<string, unknown>[]} steps
|
|
43
|
+
*/
|
|
44
|
+
function walk (steps) {
|
|
45
|
+
for (const raw of steps) {
|
|
46
|
+
if (raw == null || typeof raw !== 'object') continue
|
|
47
|
+
const node = /** @type {Record<string, unknown>} */ (raw)
|
|
48
|
+
if (
|
|
49
|
+
node.kind === 'Word' &&
|
|
50
|
+
node.ref === 'qualified' &&
|
|
51
|
+
typeof node.module === 'string'
|
|
52
|
+
) {
|
|
53
|
+
const dep = importMap.get(node.module)
|
|
54
|
+
if (dep != null && typeof dep.path === 'string' && dep.path.length > 0) {
|
|
55
|
+
node.resolvedModulePath = path.normalize(dep.path)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (node.kind === 'Quotation' && Array.isArray(node.steps)) {
|
|
59
|
+
walk(/** @type {Record<string, unknown>[]} */ (node.steps))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
walk(irSteps)
|
|
65
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Версия логической схемы wire-формата IR (RFC-IR-0.1 §8).
|
|
3
|
+
* Инкремент при ломающих изменениях обязательных полей корня или семантики `module`.
|
|
4
|
+
*
|
|
5
|
+
* Опциональные ключи корня (`views`, `meta`) не требуют bump, пока читатели совместимы
|
|
6
|
+
* с тем же значением строки версии.
|
|
7
|
+
*
|
|
8
|
+
* 0.1.1 — опциональное `module.words[].irSteps` (фаза 2, RFC-IR section 5 wire).
|
|
9
|
+
* 0.1.2 — `stackSteps.steps[]` с выравниванием по индексу тела (`null` при отсутствии снимка);
|
|
10
|
+
* `irSteps[*].preTypes`/`postTypes` после фазы 3 ([stitch-types.js](./stitch-types.js)).
|
|
11
|
+
* 0.1.3 — опционально `irSteps[*].valueBindings` (фаза 4, RFC-IR section 4, [bind-values.js](./bind-values.js)).
|
|
12
|
+
* 0.1.4 — `module.words[].asyncDefinition`/`mayFail` и на шагах `calleeAsync`/`calleeMayFail` (фаза 5, RFC-IR §6, [attach-call-effects.js](./attach-call-effects.js)).
|
|
13
|
+
* 0.1.5 — `module.imports[].resolvedPath`, на `Word` qualified — `resolvedModulePath` (фаза 6, RFC-IR §7, [module-metadata.js](./module-metadata.js)).
|
|
14
|
+
*/
|
|
15
|
+
export const IR_SCHEMA_VERSION = '0.1.5'
|