@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,38 @@
1
+ /**
2
+ * L4 фаза 7: эталонные accept-кейсы (L2/L3) → сериализованный IR (RFC-conformance §5, RFC-IR §8).
3
+ * Не гоняет L5 codegen; только typecheck + buildSerializedIrDocumentFromEnv.
4
+ */
5
+ import test from 'brittle'
6
+ import { typecheckSail } from '../../index.js'
7
+ import { buildSerializedIrDocumentFromEnv } from '../../lib/ir/index.js'
8
+ import {
9
+ absoluteConformanceEntryPath,
10
+ filterCasesForL3TypecheckRunner,
11
+ loadConformanceSuite,
12
+ typecheckConformanceCase
13
+ } from './conformance-runner.js'
14
+ import { assertSerializedIrDocumentOk } from '../ir/ir-document-assert.js'
15
+
16
+ const suite = loadConformanceSuite()
17
+
18
+ const acceptL2L3 = filterCasesForL3TypecheckRunner(suite.cases).filter(
19
+ (c) => c.expect === 'accept'
20
+ )
21
+
22
+ for (const c of acceptL2L3) {
23
+ test(`conformance IR L4 smoke: ${c.id}`, function (t) {
24
+ const r = typecheckConformanceCase(c, typecheckSail)
25
+ t.ok(r.ok === true, `typecheck accept: ${c.id}`)
26
+ const entryPath = absoluteConformanceEntryPath(c)
27
+ const doc = buildSerializedIrDocumentFromEnv(r.env, entryPath, {
28
+ moduleStatus: 'ok'
29
+ })
30
+ assertSerializedIrDocumentOk(t, doc, {
31
+ expectedModulePath: entryPath,
32
+ expectMetaStatus: 'ok',
33
+ requireNonEmptyWords: true,
34
+ requireResolvedImportsWhenPresent: c.id === 'accept-cross-module-word'
35
+ })
36
+ t.end()
37
+ })
38
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Эталонный набор conformance: L5 codegen по `suite.json` (RFC-conformance §5–§7).
3
+ * Кейсы: `expect === 'accept'` и `requireJs === true`; опционально `requireIr` + `expectIrSchema`.
4
+ */
5
+ import test from 'brittle'
6
+ import fs from 'node:fs'
7
+ import os from 'node:os'
8
+ import path from 'node:path'
9
+ import { typecheckSail } from '../../index.js'
10
+ import { compileSailToOutDir } from '../../lib/codegen/index.js'
11
+ import { sailModuleToOutputJsPath } from '../../lib/codegen/out-layout.js'
12
+ import { buildSerializedIrDocumentFromEnv } from '../../lib/ir/index.js'
13
+ import { assertSerializedIrDocumentOk } from '../ir/ir-document-assert.js'
14
+ import {
15
+ EXPECTED_CONFORMANCE_SUITE_VERSION,
16
+ absoluteConformanceEntryPath,
17
+ assertConformanceTypecheckOutcome,
18
+ buildConformanceReadFile,
19
+ conformanceRoot,
20
+ filterCasesForL5CodegenRunner,
21
+ loadConformanceSuite,
22
+ typecheckConformanceCase
23
+ } from './conformance-runner.js'
24
+
25
+ const suite = loadConformanceSuite()
26
+
27
+ test('conformance L5: conformanceSuiteVersion совпадает с ожиданием раннера', function (t) {
28
+ t.is(
29
+ suite.conformanceSuiteVersion,
30
+ EXPECTED_CONFORMANCE_SUITE_VERSION,
31
+ 'обновите EXPECTED_CONFORMANCE_SUITE_VERSION в conformance-runner.js при bump suite.json (RFC-conformance §7)'
32
+ )
33
+ t.end()
34
+ })
35
+
36
+ const l5CodegenCases = filterCasesForL5CodegenRunner(suite.cases)
37
+
38
+ for (const c of l5CodegenCases) {
39
+ test(`conformance L5 codegen: ${c.id}`, async function (t) {
40
+ const baseDir = conformanceRoot()
41
+ const readFile = buildConformanceReadFile(c, baseDir)
42
+ const entryPath = absoluteConformanceEntryPath(c, baseDir)
43
+ const sourceRoot = baseDir
44
+
45
+ const tc = typecheckConformanceCase(c, typecheckSail, baseDir)
46
+ assertConformanceTypecheckOutcome(t, c, tc)
47
+
48
+ if (c.requireIr === true) {
49
+ const doc = buildSerializedIrDocumentFromEnv(tc.env, entryPath, {
50
+ moduleStatus: 'ok'
51
+ })
52
+ if (c.expectIrSchema != null) {
53
+ t.is(
54
+ /** @type {{ irSchemaVersion?: string }} */ (doc).irSchemaVersion,
55
+ c.expectIrSchema,
56
+ 'expectIrSchema из suite.json'
57
+ )
58
+ }
59
+ assertSerializedIrDocumentOk(t, doc, {
60
+ expectedModulePath: entryPath,
61
+ expectMetaStatus: 'ok',
62
+ requireNonEmptyWords: true,
63
+ requireResolvedImportsWhenPresent:
64
+ c.id === 'accept-l5-cross-module-codegen'
65
+ })
66
+ }
67
+
68
+ const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sail-l5-conf-'))
69
+ try {
70
+ const r = await compileSailToOutDir({
71
+ entryPath,
72
+ outDir,
73
+ sourceRoot,
74
+ readFile
75
+ })
76
+ t.ok(r.ok === true, `compileSailToOutDir ok: ${JSON.stringify(r.diagnostics)}`)
77
+ t.ok(
78
+ r.ok === true && Array.isArray(r.emitted) && r.emitted.length > 0,
79
+ 'emitted — непустой список путей'
80
+ )
81
+ if (!r.ok || !Array.isArray(r.emitted)) {
82
+ t.end()
83
+ return
84
+ }
85
+ for (const p of r.emitted) {
86
+ t.ok(fs.existsSync(p), `файл существует: ${p}`)
87
+ const st = fs.statSync(p)
88
+ t.ok(st.isFile() && st.size > 0, `непустой файл: ${p}`)
89
+ const text = fs.readFileSync(p, 'utf8')
90
+ t.ok(text.includes('export function'), 'эвристика ESM: export function')
91
+ t.ok(!/\bimport\s*\(\s*\)/.test(text), 'без динамического import() для sail-графа')
92
+ }
93
+
94
+ if (c.id === 'accept-l5-cross-module-codegen') {
95
+ const layout = { sourceRoot, outDir, entryPath }
96
+ const mainJs = sailModuleToOutputJsPath(layout, entryPath)
97
+ t.ok(mainJs.ok, 'путь JS для entry')
98
+ const mainPath = mainJs.path
99
+ t.ok(r.emitted.includes(mainPath), 'entry .js в emitted')
100
+ const mainText = fs.readFileSync(mainPath, 'utf8')
101
+ t.ok(
102
+ /\bimport\s+/.test(mainText) && /\bfrom\s+['"]/.test(mainText),
103
+ 'статический import (RFC-compile §9)'
104
+ )
105
+ }
106
+ } finally {
107
+ fs.rmSync(outDir, { recursive: true, force: true })
108
+ }
109
+ t.end()
110
+ })
111
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Раннер эталонного набора conformance для `typecheckSail` (RFC-conformance-0.1 §5, §7).
3
+ * Читает `conformance/suite.json` от корня репозитория; VFS из `entry` и `contextFiles`.
4
+ */
5
+ import { readFileSync } from 'node:fs'
6
+ import { fileURLToPath } from 'node:url'
7
+ import path from 'node:path'
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
+
11
+ /** Ожидаемая версия набора; при bump в conformance/suite.json обновить здесь (RFC-conformance §7). */
12
+ export const EXPECTED_CONFORMANCE_SUITE_VERSION = '0.1.7'
13
+
14
+ export function repoRootFromConformanceTest () {
15
+ return path.normalize(path.join(__dirname, '..', '..', '..'))
16
+ }
17
+
18
+ export function conformanceRoot () {
19
+ return path.join(repoRootFromConformanceTest(), 'conformance')
20
+ }
21
+
22
+ export function loadConformanceSuite () {
23
+ const root = conformanceRoot()
24
+ const p = path.join(root, 'suite.json')
25
+ const text = readFileSync(p, 'utf8')
26
+ return JSON.parse(text)
27
+ }
28
+
29
+ /** Кейсы, для которых достаточно resolve + typecheck (без L5 JS/IR). */
30
+ export function filterCasesForL3TypecheckRunner (cases) {
31
+ return cases.filter((c) => c.minLevel === 'L2' || c.minLevel === 'L3')
32
+ }
33
+
34
+ /** Accept-кейсы с L5 codegen: `requireJs` в манифесте (RFC-conformance §5). */
35
+ export function filterCasesForL5CodegenRunner (cases) {
36
+ return cases.filter((c) => c.expect === 'accept' && c.requireJs === true)
37
+ }
38
+
39
+ export function absoluteConformanceEntryPath (caseEntry, baseDir = conformanceRoot()) {
40
+ return path.normalize(path.join(baseDir, caseEntry.entry))
41
+ }
42
+
43
+ /**
44
+ * readFile для typecheckSail: ключи — нормализованные абсолютные пути.
45
+ * @param {object} caseEntry — элемент `cases` из suite.json
46
+ * @param {string} [baseDir] — корень каталога conformance (диск)
47
+ */
48
+ export function buildConformanceReadFile (caseEntry, baseDir = conformanceRoot()) {
49
+ const rels = caseEntry.contextFiles ?? [caseEntry.entry]
50
+ const map = new Map()
51
+ for (const rel of rels) {
52
+ const abs = path.normalize(path.join(baseDir, rel))
53
+ const text = readFileSync(abs, 'utf8')
54
+ map.set(abs, text)
55
+ }
56
+ return (p) => map.get(path.normalize(p)) ?? null
57
+ }
58
+
59
+ /**
60
+ * @param {object} caseEntry
61
+ * @param {(opts: object) => { ok: boolean, diagnostics?: object[] }} typecheckSail
62
+ */
63
+ export function typecheckConformanceCase (caseEntry, typecheckSail, baseDir = conformanceRoot()) {
64
+ const entryPath = absoluteConformanceEntryPath(caseEntry, baseDir)
65
+ const readFile = buildConformanceReadFile(caseEntry, baseDir)
66
+ return typecheckSail({ entryPath, readFile })
67
+ }
68
+
69
+ export function diagnosticCodes (diagnostics) {
70
+ return (diagnostics ?? []).map((d) => d.code)
71
+ }
72
+
73
+ /**
74
+ * @param {*} t — экземпляр brittle test
75
+ * @param {object} caseEntry
76
+ * @param {{ ok: boolean, diagnostics?: object[] }} result
77
+ */
78
+ export function assertConformanceTypecheckOutcome (t, caseEntry, result) {
79
+ if (caseEntry.expect === 'accept') {
80
+ t.ok(result.ok === true, `expected accept, got diagnostics: ${JSON.stringify(result.diagnostics)}`)
81
+ return
82
+ }
83
+ t.ok(result.ok === false, 'expected reject')
84
+ const allowed = caseEntry.codes ?? (caseEntry.code != null ? [caseEntry.code] : [])
85
+ const got = diagnosticCodes(result.diagnostics)
86
+ const hit = allowed.some((code) => got.includes(code))
87
+ t.ok(
88
+ hit,
89
+ `expected a diagnostic with one of [${allowed.join(', ')}], got [${got.join(', ')}]`
90
+ )
91
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Эталонный набор conformance: кейсы с minLevel L2/L3 через typecheckSail.
3
+ * L0/L1/L5 и кейсы с requireJs/requireIr здесь не гоняются (L5 codegen — отдельный этап toolchain).
4
+ */
5
+ import test from 'brittle'
6
+ import { typecheckSail } from '../../index.js'
7
+ import {
8
+ EXPECTED_CONFORMANCE_SUITE_VERSION,
9
+ assertConformanceTypecheckOutcome,
10
+ filterCasesForL3TypecheckRunner,
11
+ loadConformanceSuite,
12
+ typecheckConformanceCase
13
+ } from './conformance-runner.js'
14
+
15
+ const suite = loadConformanceSuite()
16
+
17
+ test('conformance: conformanceSuiteVersion совпадает с ожиданием раннера', function (t) {
18
+ t.is(
19
+ suite.conformanceSuiteVersion,
20
+ EXPECTED_CONFORMANCE_SUITE_VERSION,
21
+ 'обновите EXPECTED_CONFORMANCE_SUITE_VERSION в conformance-runner.js при bump suite.json (RFC-conformance §7)'
22
+ )
23
+ t.end()
24
+ })
25
+
26
+ for (const c of filterCasesForL3TypecheckRunner(suite.cases)) {
27
+ test(`conformance L2/L3 typecheck: ${c.id}`, function (t) {
28
+ const r = typecheckConformanceCase(c, typecheckSail)
29
+ assertConformanceTypecheckOutcome(t, c, r)
30
+ t.end()
31
+ })
32
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * FFI: извлечение @sail из prelude (реальные JS-модули) и разбор фрагментов parseSource.
3
+ */
4
+ import test from 'brittle'
5
+ import { readFileSync } from 'fs'
6
+ import path from 'path'
7
+ import { fileURLToPath } from 'url'
8
+ import { parseSource } from '../../index.js'
9
+ import { extractSailFragmentsFromJs } from '../../lib/ffi/extract-jsdoc-sail.js'
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
12
+ const repoRoot = path.join(__dirname, '..', '..', '..')
13
+ const preludeLib = path.join(repoRoot, 'prelude', 'lib')
14
+
15
+ function readPrelude (name) {
16
+ return readFileSync(path.join(preludeLib, name), 'utf8')
17
+ }
18
+
19
+ test('extract-jsdoc-sail: prelude/lib/num.js — по одному @sail на слово (RFC-0.1 §10.1)', function (t) {
20
+ const src = readPrelude('num.js')
21
+ const frags = extractSailFragmentsFromJs(src)
22
+ t.is(frags.length, 3, 'отдельный JSDoc с @sail у каждого export')
23
+ const names = []
24
+ for (const frag of frags) {
25
+ const r = parseSource(frag)
26
+ t.ok(r.ok, r.diagnostics.map(d => d.message).join('; '))
27
+ t.is(r.ast?.kind, 'source_file')
28
+ t.is(r.ast?.items?.length, 1, 'в блоке ровно одно объявление слова')
29
+ const w = r.ast.items[0]
30
+ t.ok(Array.isArray(w.doc) && w.doc.length === 1, `${w.name}: Sail doc_block в @sail`)
31
+ names.push(w.name)
32
+ }
33
+ t.alike(names.sort(), ['add', 'inc', 'mul'])
34
+ t.end()
35
+ })
36
+
37
+ test('extract-jsdoc-sail: все подмодули prelude парсятся без ошибок', function (t) {
38
+ for (const file of ['num.js', 'list.js', 'str.js', 'bool.js', 'dict.js', 'map.js']) {
39
+ const src = readPrelude(file)
40
+ const frags = extractSailFragmentsFromJs(src)
41
+ t.ok(frags.length >= 1, `${file}: ожидался хотя бы один @sail-блок`)
42
+ for (const frag of frags) {
43
+ const r = parseSource(frag)
44
+ t.ok(r.ok, `${file}: ${r.diagnostics.map(d => d.code + ' ' + d.message).join('; ')}`)
45
+ t.is(r.ast?.items?.length, 1, `${file}: один @sail — одно слово (§10.1)`)
46
+ }
47
+ }
48
+ t.end()
49
+ })