@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,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,4 @@
1
+ export { createReadFileUtf8 } from './read-file.js'
2
+ export { findPackageRoot } from './package-root.js'
3
+ export { createResolvePackage } from './resolve-package.js'
4
+ export { resolveSailNamesFromDisk } from './resolve-sail-names-from-disk.js'
@@ -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,12 @@
1
+ import fs from 'node:fs'
2
+
3
+ /** @returns {(absPath: string) => string | null} */
4
+ export function createReadFileUtf8 () {
5
+ return (absPath) => {
6
+ try {
7
+ return fs.readFileSync(absPath, 'utf8')
8
+ } catch {
9
+ return null
10
+ }
11
+ }
12
+ }
@@ -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
+ }