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