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