@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.
Files changed (145) hide show
  1. package/bin/sail.mjs +4 -0
  2. package/cli/run-cli.js +176 -0
  3. package/index.js +12 -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 +34 -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,78 @@
1
+ /**
2
+ * L3: номинальные opaque-типы FFI (RFC-0.1 §5.4–5.4.1).
3
+ */
4
+ import test from 'brittle'
5
+ import path from 'node:path'
6
+ import { typecheckSail } from '../../index.js'
7
+
8
+ const root = path.resolve('/virtual/sail-opaque-ffi')
9
+ function vfs (files) {
10
+ const norm = Object.fromEntries(
11
+ Object.entries(files).map(([k, v]) => [path.normalize(k), v])
12
+ )
13
+ return (p) => norm[path.normalize(p)] ?? null
14
+ }
15
+
16
+ test('opaque FFI: свободный TypeName — IR kind opaque', function (t) {
17
+ const main = path.join(root, 'opaque-buffer.sail')
18
+ const src = ['@pass ( Buffer -> Buffer )', ''].join('\n')
19
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
20
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
21
+ const slot = r.env.sigIrByPath.get(main).get('pass').left[0]
22
+ t.is(slot.kind, 'opaque')
23
+ t.is(slot.name, 'Buffer')
24
+ t.end()
25
+ })
26
+
27
+ test('opaque FFI: пустое тело при ( T -> T )', function (t) {
28
+ const main = path.join(root, 'opaque-id.sail')
29
+ const src = ['@id ( GhostType -> GhostType )', ''].join('\n')
30
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
31
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
32
+ t.end()
33
+ })
34
+
35
+ test('opaque FFI: фактический верх стека Num при объявленном Buffer — E1304', function (t) {
36
+ const main = path.join(root, 'opaque-mismatch.sail')
37
+ const src = [
38
+ '@f ( Buffer -> Buffer )',
39
+ '',
40
+ ' drop',
41
+ '',
42
+ ' 1',
43
+ ''
44
+ ].join('\n')
45
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
46
+ t.ok(r.ok === false)
47
+ t.ok(r.diagnostics.some((d) => d.code === 'E1304'), r.diagnostics.map((d) => d.code).join(','))
48
+ t.end()
49
+ })
50
+
51
+ test('opaque FFI: пользовательский ADT перекрывает имя — IR kind adt', function (t) {
52
+ const main = path.join(root, 'opaque-shadow-adt.sail')
53
+ const src = [
54
+ '&GdkDisplay',
55
+ '| Inst',
56
+ '',
57
+ '@x ( GdkDisplay -> GdkDisplay )',
58
+ ''
59
+ ].join('\n')
60
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
61
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
62
+ const slot = r.env.sigIrByPath.get(main).get('x').left[0]
63
+ t.is(slot.kind, 'adt')
64
+ t.is(slot.type, 'GdkDisplay')
65
+ t.end()
66
+ })
67
+
68
+ test('L3 sig IR: неизвестный ctor в type_app — E1304 (opaque только без параметров)', function (t) {
69
+ const main = path.join(root, 'unknown-ctor-app.sail')
70
+ const src = ['@m ( UnknownCtor Num -> )', '', ' drop', ''].join('\n')
71
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
72
+ t.ok(r.ok === false)
73
+ const d = r.diagnostics.find((x) => x.code === 'E1304')
74
+ t.ok(d, 'E1304')
75
+ t.ok(d.message.includes('UnknownCtor'), 'mentions ctor')
76
+ t.ok(!r.env.sigIrByPath?.get(main)?.get('m'), 'no sig IR for failed word')
77
+ t.end()
78
+ })
@@ -0,0 +1,190 @@
1
+ /**
2
+ * L3 этап 3: IR сигнатур (нормализация типовых выражений), provenance source/decl.
3
+ */
4
+ import test from 'brittle'
5
+ import path from 'node:path'
6
+ import { parseSource } from '../../lib/parse/index.js'
7
+ import { normalizeSigItemWithEnv } from '../../lib/typecheck/normalize-sig.js'
8
+ import { typecheckSail } from '../../index.js'
9
+
10
+ const root = path.resolve('/virtual/sail-l3-sig3')
11
+ function vfs (files) {
12
+ const norm = Object.fromEntries(
13
+ Object.entries(files).map(([k, v]) => [path.normalize(k), v])
14
+ )
15
+ return (p) => norm[path.normalize(p)] ?? null
16
+ }
17
+
18
+ test('L3 sig IR: Num -> Str, source на type_name', function (t) {
19
+ const main = path.join(root, 'prim.sail')
20
+ const src = ['@f ( Num -> Str )', '', ' drop', ' ""', ''].join('\n')
21
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
22
+ t.ok(r.ok, String(r.diagnostics?.map((d) => d.code)))
23
+ const w = r.env.wordDeclByPath.get(main).get('f')
24
+ const sig = r.env.sigIrByPath.get(main).get('f')
25
+ t.ok(w && sig, 'word + sig IR')
26
+ t.is(sig.left[0].kind, 'prim')
27
+ t.is(sig.left[0].name, 'Num')
28
+ t.is(sig.left[0].source.kind, 'type_name')
29
+ t.is(sig.left[0].source.name, 'Num')
30
+ t.is(sig.left[0].decl, null)
31
+ t.is(sig.right[0].kind, 'prim')
32
+ t.is(sig.right[0].name, 'Str')
33
+ t.is(sig.right[0].source.kind, 'type_name')
34
+ t.end()
35
+ })
36
+
37
+ test('L3 sig IR: (List Num) -> как type_app + prim с source', function (t) {
38
+ const main = path.join(root, 'list.sail')
39
+ const src = ['@g ( (List Num) -> )', '', ' drop', ''].join('\n')
40
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
41
+ t.ok(r.ok, String(r.diagnostics?.map((d) => d.code)))
42
+ const sig = r.env.sigIrByPath.get(main).get('g')
43
+ const app = sig.left[0]
44
+ t.is(app.kind, 'app')
45
+ t.is(app.ctor, 'List')
46
+ t.is(app.source.kind, 'type_app')
47
+ t.is(app.args.length, 1)
48
+ t.is(app.args[0].kind, 'prim')
49
+ t.is(app.args[0].name, 'Num')
50
+ t.is(app.args[0].source.kind, 'type_name')
51
+ t.end()
52
+ })
53
+
54
+ test('L3 sig IR: локальный &Point — adt, path, decl на sum_type', function (t) {
55
+ const main = path.join(root, 'point.sail')
56
+ const src = [
57
+ '&Point',
58
+ ' :x Num',
59
+ ' :y Num',
60
+ '@m ( Point -> Point )',
61
+ ''
62
+ ].join('\n')
63
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
64
+ t.ok(r.ok, String(r.diagnostics?.map((d) => d.code)))
65
+ const sig = r.env.sigIrByPath.get(main).get('m')
66
+ t.is(sig.left[0].kind, 'adt')
67
+ t.is(sig.left[0].type, 'Point')
68
+ t.is(sig.left[0].path, path.normalize(main))
69
+ t.is(sig.left[0].decl.path, path.normalize(main))
70
+ t.is(sig.left[0].decl.node.kind, 'product_type')
71
+ t.is(sig.left[0].decl.node.name, 'Point')
72
+ t.is(sig.left[0].source.kind, 'type_name')
73
+ t.is(sig.left[0].source.name, 'Point')
74
+ t.is(sig.right[0].kind, 'adt')
75
+ t.end()
76
+ })
77
+
78
+ test('L3 sig IR: ~L/SomeType — mod_adt + decl из зависимости', function (t) {
79
+ const main = path.join(root, 'use-lib.sail')
80
+ const lib = path.join(root, 'dep-lib.sail')
81
+ const files = {
82
+ [main]: ['+L ./dep-lib.sail', '@m ( ~L/SomeType -> )', '', ' drop', ''].join('\n'),
83
+ [lib]: ['&SomeType', ' |Tag', ''].join('\n')
84
+ }
85
+ const r = typecheckSail({ entryPath: main, readFile: vfs(files) })
86
+ t.ok(r.ok, String(r.diagnostics?.map((d) => d.code)))
87
+ const sig = r.env.sigIrByPath.get(main).get('m')
88
+ t.is(sig.left[0].kind, 'mod_adt')
89
+ t.is(sig.left[0].module, 'L')
90
+ t.is(sig.left[0].type, 'SomeType')
91
+ t.is(sig.left[0].source.kind, 'module_type_ref')
92
+ t.is(sig.left[0].decl.path, path.normalize(lib))
93
+ t.is(sig.left[0].decl.node.kind, 'sum_type')
94
+ t.is(sig.left[0].decl.node.name, 'SomeType')
95
+ t.end()
96
+ })
97
+
98
+ test('L3 sig IR: скобочный импорт +T ( &T ), в сигнатуре T без ~ — adt path = модуль определения', function (t) {
99
+ const main = path.join(root, 'br-main.sail')
100
+ const lib = path.join(root, 'br-lib.sail')
101
+ const files = {
102
+ [main]: ['+L ( &T ) ./br-lib.sail', '@m ( T -> )', '', ' drop', ''].join('\n'),
103
+ [lib]: ['&T', ' |A', ''].join('\n')
104
+ }
105
+ const r = typecheckSail({ entryPath: main, readFile: vfs(files) })
106
+ t.ok(r.ok, String(r.diagnostics?.map((d) => d.code)))
107
+ const sig = r.env.sigIrByPath.get(main).get('m')
108
+ t.is(sig.left[0].kind, 'adt')
109
+ t.is(sig.left[0].type, 'T')
110
+ t.is(sig.left[0].path, path.normalize(lib))
111
+ t.is(sig.left[0].decl.path, path.normalize(lib))
112
+ t.is(sig.left[0].decl.node.kind, 'sum_type')
113
+ t.is(sig.left[0].source.kind, 'type_name')
114
+ t.is(sig.left[0].source.name, 'T')
115
+ t.end()
116
+ })
117
+
118
+ test('L3 sig IR: свободный TypeName без ADT — opaque FFI (RFC-0.1 §5.4)', function (t) {
119
+ const main = path.join(root, 'ghost-opaque.sail')
120
+ const src = ['@m ( GhostType -> )', '', ' drop', ''].join('\n')
121
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
122
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
123
+ const slot = r.env.sigIrByPath.get(main).get('m').left[0]
124
+ t.is(slot.kind, 'opaque')
125
+ t.is(slot.name, 'GhostType')
126
+ t.end()
127
+ })
128
+
129
+ test('L3 sig IR: quotation_sig (внутри named) — kind quote + source', function (t) {
130
+ const pr = parseSource('@x ( q: ( Num -> Str ) -> )\n\n')
131
+ t.ok(pr.ok, pr.diagnostics?.[0]?.message)
132
+ const quotation = pr.ast.items[0].signature.left[0].quotation
133
+ t.is(quotation.kind, 'quotation_sig')
134
+ const main = path.join(root, 'quote-ctx.sail')
135
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: '@f ( -> )\n\n' }) })
136
+ t.ok(r.ok)
137
+ const { ir, diagnostics } = normalizeSigItemWithEnv(quotation, r.env, main)
138
+ t.is(diagnostics.length, 0)
139
+ t.is(ir.kind, 'quote')
140
+ t.is(ir.source.kind, 'quotation_sig')
141
+ t.is(ir.inner.left[0].kind, 'prim')
142
+ t.is(ir.inner.left[0].source.name, 'Num')
143
+ t.is(ir.inner.right[0].kind, 'prim')
144
+ t.is(ir.inner.right[0].source.name, 'Str')
145
+ t.end()
146
+ })
147
+
148
+ test('L3 sig IR: named_quotation_sig — prefix + вложенная сигнатура', function (t) {
149
+ const main = path.join(root, 'namedq.sail')
150
+ const src = ['@w ( just: ( Num -> Num ) -> )', '', ' drop', ''].join('\n')
151
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
152
+ t.ok(r.ok, String(r.diagnostics?.map((d) => d.code)))
153
+ const sig = r.env.sigIrByPath.get(main).get('w')
154
+ const n = sig.left[0]
155
+ t.is(n.kind, 'named_quote')
156
+ t.is(n.prefix, 'just')
157
+ t.is(n.source.kind, 'named_quotation_sig')
158
+ t.is(n.inner.left[0].kind, 'prim')
159
+ t.is(n.inner.left[0].name, 'Num')
160
+ t.end()
161
+ })
162
+
163
+ test('L3 sig stage3: List Num в одном слоте — один type_app (E1311 не из парсера v1)', function (t) {
164
+ const main = path.join(root, 'list-one-slot.sail')
165
+ const src = ['@f ( List Num -> )', '', ' drop', ''].join('\n')
166
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
167
+ t.ok(r.ok, String(r.diagnostics?.map((d) => d.code)))
168
+ const ir = r.env.sigIrByPath.get(main).get('f')
169
+ t.is(ir.left[0].kind, 'app')
170
+ t.is(ir.left[0].ctor, 'List')
171
+ t.end()
172
+ })
173
+
174
+ test('L3 sig stage3: параметризованный sum без аргументов в слоте — E1311 (§4.4)', function (t) {
175
+ const main = path.join(root, 'bare-maybe.sail')
176
+ const src = [
177
+ '&Maybe a',
178
+ '| Nothing',
179
+ '| Just a',
180
+ '',
181
+ '@f ( Maybe -> )',
182
+ '',
183
+ ' drop',
184
+ ''
185
+ ].join('\n')
186
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
187
+ t.ok(r.ok === false)
188
+ t.ok(r.diagnostics.some((d) => d.code === 'E1311'), r.diagnostics.map((x) => x.code).join(','))
189
+ t.end()
190
+ })
@@ -0,0 +1,149 @@
1
+ /**
2
+ * L3 этап 4: симуляция стека по телу слова (RFC-typecheck-0.1 §5.2–5.5).
3
+ */
4
+ import test from 'brittle'
5
+ import path from 'node:path'
6
+ import { typecheckSail } from '../../index.js'
7
+
8
+ const root = path.resolve('/virtual/sail-l3-st4')
9
+ function vfs (files) {
10
+ const norm = Object.fromEntries(
11
+ Object.entries(files).map(([k, v]) => [path.normalize(k), v])
12
+ )
13
+ return (p) => norm[path.normalize(p)] ?? null
14
+ }
15
+
16
+ test('L3 stage4: литерал number — ( -> Num )', function (t) {
17
+ const main = path.join(root, 'lit-num.sail')
18
+ const src = ['@w ( -> Num )', '', ' 42', ''].join('\n')
19
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
20
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
21
+ t.end()
22
+ })
23
+
24
+ test('L3 stage4: литерал string — ( -> Str )', function (t) {
25
+ const main = path.join(root, 'lit-str.sail')
26
+ const src = ['@w ( -> Str )', '', ' "hi"', ''].join('\n')
27
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
28
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
29
+ t.end()
30
+ })
31
+
32
+ test('L3 stage4: литерал bool — ( -> Bool )', function (t) {
33
+ const main = path.join(root, 'lit-bool.sail')
34
+ const src = ['@w ( -> Bool )', '', ' true', ''].join('\n')
35
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
36
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
37
+ t.end()
38
+ })
39
+
40
+ test('L3 stage4: литерал nil — ( -> Nil )', function (t) {
41
+ const main = path.join(root, 'lit-nil.sail')
42
+ const src = ['@w ( -> Nil )', '', ' nil', ''].join('\n')
43
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
44
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
45
+ t.end()
46
+ })
47
+
48
+ test('L3 stage4: литерал string при сигнатуре ( -> Num ) — E1303/E1304', function (t) {
49
+ const main = path.join(root, 'lit-mismatch.sail')
50
+ const src = ['@w ( -> Num )', '', ' "oops"', ''].join('\n')
51
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
52
+ t.ok(r.ok === false)
53
+ const codes = r.diagnostics.map((d) => d.code)
54
+ t.ok(codes.includes('E1303') || codes.includes('E1304'), codes.join(','))
55
+ t.end()
56
+ })
57
+
58
+ test('L3 stage4: литерал number затем dup — ( -> Num Num )', function (t) {
59
+ const main = path.join(root, 'lit-num-dup.sail')
60
+ const src = ['@w ( -> Num Num )', '', ' 1', '', ' dup', ''].join('\n')
61
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
62
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
63
+ t.end()
64
+ })
65
+
66
+ test('L3 stage4: dup и контракт ( Num -> Num Num )', function (t) {
67
+ const main = path.join(root, 'dup-ok.sail')
68
+ const src = ['@w ( Num -> Num Num )', '', ' dup', ''].join('\n')
69
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
70
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
71
+ t.end()
72
+ })
73
+
74
+ test('L3 stage4: dup полиморфно согласуется с ( Str -> Str Str )', function (t) {
75
+ const main = path.join(root, 'dup-str.sail')
76
+ const src = ['@w ( Str -> Str Str )', '', ' dup', ''].join('\n')
77
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
78
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
79
+ t.end()
80
+ })
81
+
82
+ test('L3 stage4: несовпадение выхода ( Num -> Str ) с телом dup — E1303/E1304', function (t) {
83
+ const main = path.join(root, 'dup-bad-out.sail')
84
+ const src = ['@w ( Num -> Str )', '', ' dup', ''].join('\n')
85
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
86
+ t.ok(r.ok === false)
87
+ const codes = r.diagnostics.map((d) => d.code)
88
+ t.ok(codes.includes('E1303') || codes.includes('E1304'), codes.join(','))
89
+ t.end()
90
+ })
91
+
92
+ test('L3 stage4: вызов локального слова /inc', function (t) {
93
+ const main = path.join(root, 'call-inc.sail')
94
+ const src = [
95
+ '@inc ( Num -> Num )',
96
+ '',
97
+ '@main ( Num -> Num )',
98
+ '',
99
+ ' /inc',
100
+ ''
101
+ ].join('\n')
102
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
103
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
104
+ t.end()
105
+ })
106
+
107
+ test('L3 stage4: слоты :x и ;x', function (t) {
108
+ const main = path.join(root, 'slots.sail')
109
+ const src = ['@w ( Num -> Num )', '', ' :x', '', ' ;x', ''].join('\n')
110
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
111
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
112
+ t.end()
113
+ })
114
+
115
+ test('L3 stage4: повторная :x — E1313', function (t) {
116
+ const main = path.join(root, 'slot-dup.sail')
117
+ const src = ['@w ( Num -> Num )', '', ' dup', '', ' :x', '', ' :x', ''].join('\n')
118
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
119
+ t.ok(r.ok === false)
120
+ t.ok(r.diagnostics.some((d) => d.code === 'E1313'))
121
+ t.end()
122
+ })
123
+
124
+ test('L3 stage4: ;x до :x — E1314', function (t) {
125
+ const main = path.join(root, 'slot-read-first.sail')
126
+ const src = ['@w ( Num -> Num )', '', ' ;x', ''].join('\n')
127
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
128
+ t.ok(r.ok === false)
129
+ t.ok(r.diagnostics.some((d) => d.code === 'E1314'))
130
+ t.end()
131
+ })
132
+
133
+ test('L3 stage4: литерал quotation с неверным телом — E1303 (этап 6)', function (t) {
134
+ const main = path.join(root, 'quot-body.sail')
135
+ const src = ['@w ( -> )', '', ' ( dup )', ''].join('\n')
136
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
137
+ t.ok(r.ok === false)
138
+ t.ok(r.diagnostics.some((d) => d.code === 'E1303'))
139
+ t.end()
140
+ })
141
+
142
+ test('L3 stage4: builtin call без quotation — E1303 (этап 6)', function (t) {
143
+ const main = path.join(root, 'builtin-call.sail')
144
+ const src = ['@w ( -> )', '', ' call', ''].join('\n')
145
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
146
+ t.ok(r.ok === false)
147
+ t.ok(r.diagnostics.some((d) => d.code === 'E1303'))
148
+ t.end()
149
+ })
@@ -0,0 +1,74 @@
1
+ /**
2
+ * L3 этап 5: метки стека ~s / ~b в сигнатурах (RFC-typecheck-0.1 §4.4, §5.2).
3
+ */
4
+ import test from 'brittle'
5
+ import path from 'node:path'
6
+ import { typecheckSail } from '../../index.js'
7
+
8
+ const root = path.resolve('/virtual/sail-l3-st5')
9
+ function vfs (files) {
10
+ const norm = Object.fromEntries(
11
+ Object.entries(files).map(([k, v]) => [path.normalize(k), v])
12
+ )
13
+ return (p) => norm[path.normalize(p)] ?? null
14
+ }
15
+
16
+ test('L3 stage5: пустое тело ( ~s -> ~s )', function (t) {
17
+ const main = path.join(root, 'lbl-id.sail')
18
+ const src = ['@w ( ~s -> ~s )', '', ''].join('\n')
19
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
20
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
21
+ t.end()
22
+ })
23
+
24
+ test('L3 stage5: ( ~s Num -> ~s ) с drop', function (t) {
25
+ const main = path.join(root, 'lbl-drop.sail')
26
+ const src = ['@w ( ~s Num -> ~s )', '', ' drop', ''].join('\n')
27
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
28
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
29
+ t.end()
30
+ })
31
+
32
+ test('L3 stage5: две метки ~s в контракте — drop снимает одну вершину', function (t) {
33
+ const main = path.join(root, 'lbl-dup-slot.sail')
34
+ const src = ['@w ( ~s ~s -> ~s )', '', ' drop', ''].join('\n')
35
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
36
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
37
+ t.end()
38
+ })
39
+
40
+ test('L3 stage5: несовместимые ~s и ~b на выходе — E1304', function (t) {
41
+ const main = path.join(root, 'lbl-mismatch-out.sail')
42
+ const src = ['@w ( ~s -> ~b )', '', ''].join('\n')
43
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
44
+ t.ok(r.ok === false)
45
+ t.ok(r.diagnostics.some((d) => d.code === 'E1304'), r.diagnostics.map((d) => d.code).join(','))
46
+ t.end()
47
+ })
48
+
49
+ test('L3 stage5: регрессия ( a -> a ) пустое тело', function (t) {
50
+ const main = path.join(root, 'poly-id-empty.sail')
51
+ const src = ['@id ( a -> a )', '', '@w ( Num -> Num )', '', ' /id', ''].join('\n')
52
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
53
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
54
+ t.end()
55
+ })
56
+
57
+ test('L3 stage5: два вызова полиморфного callee — свежие tvar', function (t) {
58
+ const main = path.join(root, 'poly-twice.sail')
59
+ const src = [
60
+ '@id ( a -> a )',
61
+ '',
62
+ '@w ( Num Num -> Num )',
63
+ '',
64
+ ' /id',
65
+ '',
66
+ ' drop',
67
+ '',
68
+ ' /id',
69
+ ''
70
+ ].join('\n')
71
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
72
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
73
+ t.end()
74
+ })
@@ -0,0 +1,56 @@
1
+ /**
2
+ * L3 этап 6: quotation, call (RFC-typecheck-0.1 §5.6, RFC-builtins §4.3–4.5).
3
+ */
4
+ import test from 'brittle'
5
+ import path from 'node:path'
6
+ import { typecheckSail } from '../../index.js'
7
+
8
+ const root = path.resolve('/virtual/sail-l3-st6')
9
+ function vfs (files) {
10
+ const norm = Object.fromEntries(
11
+ Object.entries(files).map(([k, v]) => [path.normalize(k), v])
12
+ )
13
+ return (p) => norm[path.normalize(p)] ?? null
14
+ }
15
+
16
+ test('L3 stage6: пустая цитата ( ) и сигнатура ( ~s -> ~s ) в Quote', function (t) {
17
+ const main = path.join(root, 'empty-q.sail')
18
+ const src = ['@w ( -> ( ~s -> ~s ) )', '', ' ( )', ''].join('\n')
19
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
20
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
21
+ t.end()
22
+ })
23
+
24
+ test('L3 stage6: ( ) call на пустом стеке', function (t) {
25
+ const main = path.join(root, 'q-call.sail')
26
+ const src = ['@w ( -> )', '', ' ( )', '', ' call', ''].join('\n')
27
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
28
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
29
+ t.end()
30
+ })
31
+
32
+ test('L3 stage6: ( 1 ) — quote ( -> Num )', function (t) {
33
+ const main = path.join(root, 'q-one.sail')
34
+ const src = ['@w ( -> ( -> Num ) )', '', ' ( 1 )', ''].join('\n')
35
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
36
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
37
+ t.end()
38
+ })
39
+
40
+ test('L3 stage6: ( 1 ) call — quote ( -> Num ), call с I пустым', function (t) {
41
+ const main = path.join(root, 'call-num.sail')
42
+ const src = ['@w ( -> Num )', '', ' ( 1 )', '', ' call', ''].join('\n')
43
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
44
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
45
+ t.end()
46
+ })
47
+
48
+ test('L3 stage6: call — несогласованный выход слова — ошибка L3', function (t) {
49
+ const main = path.join(root, 'call-bad.sail')
50
+ const src = ['@w ( -> Num )', '', ' ( )', '', ' call', ''].join('\n')
51
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
52
+ t.ok(r.ok === false)
53
+ const codes = r.diagnostics.map((d) => d.code)
54
+ t.ok(codes.includes('E1303') || codes.includes('E1304'), codes.join(','))
55
+ t.end()
56
+ })