@algosail/lang 0.2.12 → 0.5.1
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 +12 -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 +34 -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,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Извлечение текста Sail из JSDoc FFI (RFC-0.1 §10.1): JSDoc-блоки от открывающего до закрывающего
|
|
3
|
+
* ограничителя комментария, тег @sail, хвост после снятия префиксов строк со звёздочкой.
|
|
4
|
+
*
|
|
5
|
+
* Не полноценный JS-лексер: закрытие комментария — по первому вхождению пары символов star-slash.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} sourceText
|
|
10
|
+
* @returns {string[]}
|
|
11
|
+
*/
|
|
12
|
+
export function extractSailFragmentsFromJs (sourceText) {
|
|
13
|
+
const out = []
|
|
14
|
+
let i = 0
|
|
15
|
+
while (i < sourceText.length) {
|
|
16
|
+
const open = sourceText.indexOf('/**', i)
|
|
17
|
+
if (open === -1) break
|
|
18
|
+
const close = sourceText.indexOf('*/', open + 3)
|
|
19
|
+
if (close === -1) break
|
|
20
|
+
const block = sourceText.slice(open + 3, close)
|
|
21
|
+
const tag = block.match(/@sail\b/)
|
|
22
|
+
if (tag && tag.index !== undefined) {
|
|
23
|
+
const afterTag = block.slice(tag.index + tag[0].length)
|
|
24
|
+
const lines = afterTag.split(/\r?\n/)
|
|
25
|
+
const cleaned = lines
|
|
26
|
+
.map(line => line.replace(/^\s*\* ?/, ''))
|
|
27
|
+
.join('\n')
|
|
28
|
+
.trim()
|
|
29
|
+
if (cleaned.length > 0) out.push(cleaned)
|
|
30
|
+
}
|
|
31
|
+
i = close + 2
|
|
32
|
+
}
|
|
33
|
+
return out
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} startAbsPath — абсолютный путь к файлу или каталогу
|
|
6
|
+
* @returns {string | null} — каталог с package.json или null
|
|
7
|
+
*/
|
|
8
|
+
export function findPackageRoot (startAbsPath) {
|
|
9
|
+
let current = path.dirname(startAbsPath)
|
|
10
|
+
for (;;) {
|
|
11
|
+
if (fs.existsSync(path.join(current, 'package.json'))) {
|
|
12
|
+
return path.resolve(current)
|
|
13
|
+
}
|
|
14
|
+
const parent = path.dirname(current)
|
|
15
|
+
if (parent === current) return null
|
|
16
|
+
current = parent
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { findPackageRoot } from './package-root.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {{ projectRoot?: string }} [options]
|
|
7
|
+
* @returns {(spec: string, fromPath: string) => string | null}
|
|
8
|
+
*/
|
|
9
|
+
export function createResolvePackage (options) {
|
|
10
|
+
const fixedRoot =
|
|
11
|
+
options?.projectRoot != null ? path.resolve(options.projectRoot) : null
|
|
12
|
+
|
|
13
|
+
return (spec, fromPath) => {
|
|
14
|
+
const root = fixedRoot ?? findPackageRoot(fromPath)
|
|
15
|
+
if (root === null) return null
|
|
16
|
+
try {
|
|
17
|
+
const req = createRequire(path.join(root, 'package.json'))
|
|
18
|
+
const resolved = req.resolve(spec, { paths: [path.dirname(fromPath)] })
|
|
19
|
+
return path.normalize(resolved)
|
|
20
|
+
} catch {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { resolveSailNames } from '../names/resolve-sail.js'
|
|
3
|
+
import { createReadFileUtf8 } from './read-file.js'
|
|
4
|
+
import { createResolvePackage } from './resolve-package.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {{ entryPath: string, projectRoot?: string, withSnapshots?: boolean }} opts
|
|
8
|
+
*/
|
|
9
|
+
export function resolveSailNamesFromDisk (opts) {
|
|
10
|
+
const { entryPath, projectRoot, ...rest } = opts
|
|
11
|
+
const readFile = createReadFileUtf8()
|
|
12
|
+
const resolvePackage = createResolvePackage(
|
|
13
|
+
projectRoot != null ? { projectRoot } : undefined
|
|
14
|
+
)
|
|
15
|
+
return resolveSailNames({
|
|
16
|
+
entryPath: path.resolve(entryPath),
|
|
17
|
+
readFile,
|
|
18
|
+
resolvePackage,
|
|
19
|
+
...rest
|
|
20
|
+
})
|
|
21
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Проверка, что значение без потерь уйдёт в JSON (для отладки и тестов IR wire).
|
|
3
|
+
*
|
|
4
|
+
* @param {unknown} value
|
|
5
|
+
* @param {string} [path]
|
|
6
|
+
*/
|
|
7
|
+
export function assertJsonSerializable (value, path = 'root') {
|
|
8
|
+
if (value === undefined) {
|
|
9
|
+
throw new Error(`IR JSON: недопустимо undefined в ${path}`)
|
|
10
|
+
}
|
|
11
|
+
const t = typeof value
|
|
12
|
+
if (t === 'function' || t === 'symbol') {
|
|
13
|
+
throw new Error(`IR JSON: недопустимый тип в ${path}`)
|
|
14
|
+
}
|
|
15
|
+
if (value === null || t === 'string' || t === 'number' || t === 'boolean') {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
if (value instanceof Map || value instanceof WeakMap) {
|
|
19
|
+
throw new Error(`IR JSON: Map/WeakMap недопустимы в ${path}`)
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
value.forEach((item, i) => assertJsonSerializable(item, `${path}[${i}]`))
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
if (t === 'object') {
|
|
26
|
+
for (const k of Object.keys(value)) {
|
|
27
|
+
assertJsonSerializable(/** @type {Record<string, unknown>} */ (value)[k], `${path}.${k}`)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L4 фаза 5: эффекты +Async / +Fail на wire IR (RFC-IR section 6).
|
|
3
|
+
* Данные берутся из `env.callSiteEffectMarks` (L3 этап 7) без повторного анализа эффектов.
|
|
4
|
+
*
|
|
5
|
+
* На узлах шагов (и вложенных `Quotation.steps`) опционально:
|
|
6
|
+
* - **`calleeAsync`**: маркер L3 «ожидать на codegen»; при поглощении `-Async` у вызывающего может отсутствовать (вариант B), тогда `await` решается по сигнатуре callee в L5.
|
|
7
|
+
* - **`calleeMayFail`**: у вызываемого +Fail (опционально для инструментов, RFC-IR §6.2).
|
|
8
|
+
*
|
|
9
|
+
* Сопоставление: `span.start` wire-узла (как в {@link lower-body-steps.js}) с `span` маркера;
|
|
10
|
+
* плюс `kind` маркера и имя builtin/слова.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {unknown} wireSpan из IR-узла
|
|
15
|
+
* @param {unknown} astSpan из маркера L3 (тот же объект, что у шага парсера)
|
|
16
|
+
* @returns {boolean}
|
|
17
|
+
*/
|
|
18
|
+
function spanStartEqual (wireSpan, astSpan) {
|
|
19
|
+
if (
|
|
20
|
+
wireSpan == null ||
|
|
21
|
+
astSpan == null ||
|
|
22
|
+
typeof wireSpan !== 'object' ||
|
|
23
|
+
typeof astSpan !== 'object'
|
|
24
|
+
) {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
const w = /** @type {{ start?: { offset?: number, line?: number, column?: number } }} */ (
|
|
28
|
+
wireSpan
|
|
29
|
+
)
|
|
30
|
+
const a = /** @type {{ start?: { offset?: number, line?: number, column?: number } }} */ (
|
|
31
|
+
astSpan
|
|
32
|
+
)
|
|
33
|
+
const ws = w.start
|
|
34
|
+
const as = a.start
|
|
35
|
+
if (ws == null || as == null) return false
|
|
36
|
+
return (
|
|
37
|
+
ws.offset === as.offset &&
|
|
38
|
+
ws.line === as.line &&
|
|
39
|
+
ws.column === as.column
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {object} mark элемент `callSiteEffectMarks`
|
|
45
|
+
* @param {Record<string, unknown>} node узел `irSteps`
|
|
46
|
+
* @returns {boolean}
|
|
47
|
+
*/
|
|
48
|
+
function markMatchesNode (mark, node) {
|
|
49
|
+
if (!spanStartEqual(node.span, mark.span)) return false
|
|
50
|
+
const kind = mark.kind
|
|
51
|
+
switch (kind) {
|
|
52
|
+
case 'word_ref':
|
|
53
|
+
return (
|
|
54
|
+
node.kind === 'Word' &&
|
|
55
|
+
node.ref === 'local' &&
|
|
56
|
+
node.name === mark.name
|
|
57
|
+
)
|
|
58
|
+
case 'module_word_ref':
|
|
59
|
+
return (
|
|
60
|
+
node.kind === 'Word' &&
|
|
61
|
+
node.ref === 'qualified' &&
|
|
62
|
+
node.module === mark.module &&
|
|
63
|
+
node.word === mark.word
|
|
64
|
+
)
|
|
65
|
+
case 'call':
|
|
66
|
+
return node.kind === 'Builtin' && node.name === 'call'
|
|
67
|
+
case 'builtin_quote':
|
|
68
|
+
return node.kind === 'Builtin' && node.name === mark.builtin
|
|
69
|
+
default:
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Прикрепляет к узлам `irSteps` поля `calleeAsync` / `calleeMayFail` по отфильтрованным маркерам.
|
|
76
|
+
*
|
|
77
|
+
* @param {Record<string, unknown>[]} irSteps
|
|
78
|
+
* @param {object[]} marks `env.callSiteEffectMarks`, уже отфильтрованные по `modulePath` и `callerWord`
|
|
79
|
+
*/
|
|
80
|
+
export function attachCallSiteEffectMarksToIrSteps (irSteps, marks) {
|
|
81
|
+
if (!Array.isArray(irSteps) || !Array.isArray(marks) || marks.length === 0) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {Record<string, unknown>[]} steps
|
|
87
|
+
*/
|
|
88
|
+
function walk (steps) {
|
|
89
|
+
for (const raw of steps) {
|
|
90
|
+
if (raw == null || typeof raw !== 'object') continue
|
|
91
|
+
const node = /** @type {Record<string, unknown>} */ (raw)
|
|
92
|
+
const matching = marks.filter((m) => markMatchesNode(m, node))
|
|
93
|
+
if (matching.length > 0) {
|
|
94
|
+
if (matching.some((m) => m.calleeAsync === true)) {
|
|
95
|
+
node.calleeAsync = true
|
|
96
|
+
}
|
|
97
|
+
if (matching.some((m) => m.calleeMayFail === true)) {
|
|
98
|
+
node.calleeMayFail = true
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (node.kind === 'Quotation' && Array.isArray(node.steps)) {
|
|
102
|
+
walk(/** @type {Record<string, unknown>[]} */ (node.steps))
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
walk(irSteps)
|
|
108
|
+
}
|