@algosail/lang 0.2.11 → 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,160 @@
1
+ /**
2
+ * L3 этап 7: эффекты +Async / +Fail, всплытие (RFC-0.1 §5.7, RFC-typecheck §5.6–5.7).
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-st7')
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 stage7: +Async вызывает +Async — ок', function (t) {
17
+ const main = path.join(root, 'async-ok.sail')
18
+ const src = [
19
+ '@asyncCallee ( -> +Async )',
20
+ '',
21
+ '@caller ( -> +Async )',
22
+ ' /asyncCallee',
23
+ ''
24
+ ].join('\n')
25
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
26
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
27
+ const fx = r.env?.definitionEffectsByPath?.get(main)?.get('caller')
28
+ t.ok(fx?.asyncDefinition === true)
29
+ t.end()
30
+ })
31
+
32
+ test('L3 stage7: обёртка с исходящим -Async вызывает +Async — ок', function (t) {
33
+ const main = path.join(root, 'async-wrap.sail')
34
+ const src = [
35
+ '@asyncCallee ( -> +Async )',
36
+ '',
37
+ '@wrap ( -> -Async )',
38
+ ' /asyncCallee',
39
+ ''
40
+ ].join('\n')
41
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
42
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
43
+ t.end()
44
+ })
45
+
46
+ test('L3 stage7: обёртка с исходящим -Fail вызывает +Fail — ок', function (t) {
47
+ const main = path.join(root, 'fail-wrap.sail')
48
+ const src = [
49
+ '@risky ( -> +Fail )',
50
+ '',
51
+ '@wrap ( -> -Fail )',
52
+ ' /risky',
53
+ ''
54
+ ].join('\n')
55
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
56
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
57
+ t.end()
58
+ })
59
+
60
+ test('L3 stage7: синхронное слово вызывает +Async — E1310', function (t) {
61
+ const main = path.join(root, 'e1310.sail')
62
+ const src = [
63
+ '@asyncCallee ( -> +Async )',
64
+ '',
65
+ '@sync ( -> )',
66
+ ' /asyncCallee',
67
+ ''
68
+ ].join('\n')
69
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
70
+ t.ok(r.ok === false)
71
+ t.ok(r.diagnostics.some((d) => d.code === 'E1310'), r.diagnostics.map((d) => d.code).join(','))
72
+ t.end()
73
+ })
74
+
75
+ test('L3 stage7: вызов +Fail без +Fail у вызывающего — E1312', function (t) {
76
+ const main = path.join(root, 'e1312.sail')
77
+ const src = [
78
+ '@mayFail ( -> +Fail )',
79
+ '',
80
+ '@sync ( -> )',
81
+ ' /mayFail',
82
+ ''
83
+ ].join('\n')
84
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
85
+ t.ok(r.ok === false)
86
+ t.ok(r.diagnostics.some((d) => d.code === 'E1312'), r.diagnostics.map((d) => d.code).join(','))
87
+ t.end()
88
+ })
89
+
90
+ test('L3 stage7: quotation + call всплывает +Async — без +Async у слова E1310', function (t) {
91
+ const main = path.join(root, 'q-call-async-bad.sail')
92
+ const src = [
93
+ '@asyncCallee ( -> +Async )',
94
+ '',
95
+ '@bad ( -> )',
96
+ ' ( /asyncCallee )',
97
+ ' call',
98
+ ''
99
+ ].join('\n')
100
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
101
+ t.ok(r.ok === false)
102
+ t.ok(r.diagnostics.some((d) => d.code === 'E1310'), r.diagnostics.map((d) => d.code).join(','))
103
+ t.end()
104
+ })
105
+
106
+ test('L3 stage7: quotation + call с +Async у внешнего слова — ок', function (t) {
107
+ const main = path.join(root, 'q-call-async-good.sail')
108
+ const src = [
109
+ '@asyncCallee ( -> +Async )',
110
+ '',
111
+ '@good ( -> +Async )',
112
+ ' ( /asyncCallee )',
113
+ ' call',
114
+ ''
115
+ ].join('\n')
116
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
117
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
118
+ t.end()
119
+ })
120
+
121
+ test('L3 stage7: callSiteEffectMarks — calleeAsync на вызове', function (t) {
122
+ const main = path.join(root, 'marks.sail')
123
+ const src = [
124
+ '@asyncCallee ( -> +Async )',
125
+ '',
126
+ '@caller ( -> +Async )',
127
+ ' /asyncCallee',
128
+ ''
129
+ ].join('\n')
130
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
131
+ t.ok(r.ok)
132
+ const marks = r.env?.callSiteEffectMarks ?? []
133
+ const ref = marks.find(
134
+ (m) => m.kind === 'word_ref' && m.name === 'asyncCallee'
135
+ )
136
+ t.ok(ref?.calleeAsync === true)
137
+ t.ok(ref?.callerWord === 'caller')
138
+ t.end()
139
+ })
140
+
141
+ test('L3 stage7: callSiteEffectMarks — под -Async calleeAsync не ставится (вариант B)', function (t) {
142
+ const main = path.join(root, 'marks-wrap-async.sail')
143
+ const src = [
144
+ '@asyncCallee ( -> +Async )',
145
+ '',
146
+ '@wrap ( -> -Async )',
147
+ ' /asyncCallee',
148
+ ''
149
+ ].join('\n')
150
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
151
+ t.ok(r.ok)
152
+ const marks = r.env?.callSiteEffectMarks ?? []
153
+ const ref = marks.find(
154
+ (m) => m.kind === 'word_ref' && m.name === 'asyncCallee'
155
+ )
156
+ t.ok(ref)
157
+ t.is(ref.calleeAsync, false)
158
+ t.is(ref.callerWord, 'wrap')
159
+ t.end()
160
+ })
@@ -0,0 +1,146 @@
1
+ /**
2
+ * L3 этап 8: автоген ADT и eliminator-ы (RFC-0.1 §7, RFC-typecheck §5.8).
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-st8')
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 stage8: sum /nothing — ( -> (Maybe a) )', function (t) {
17
+ const main = path.join(root, 'sum-nothing.sail')
18
+ const src = [
19
+ '&Maybe a',
20
+ '| Nothing',
21
+ '| Just a',
22
+ '',
23
+ '@w ( -> (Maybe a) )',
24
+ ' /nothing',
25
+ ''
26
+ ].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 stage8: sum /just — ( a -> (Maybe a) )', function (t) {
33
+ const main = path.join(root, 'sum-just.sail')
34
+ const src = [
35
+ '&Maybe a',
36
+ '| Nothing',
37
+ '| Just a',
38
+ '',
39
+ '@w ( Num -> (Maybe a) )',
40
+ ' /just',
41
+ ''
42
+ ].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 stage8: eliminator /maybe — согласованные ветки', function (t) {
49
+ const main = path.join(root, 'sum-maybe-ok.sail')
50
+ const src = [
51
+ '&Maybe a',
52
+ '| Nothing',
53
+ '| Just a',
54
+ '',
55
+ '@nothingQuot ( -> q:( ~s -> ~s ) )',
56
+ ' ( )',
57
+ '',
58
+ '@justQuot ( -> q:( ~s a -> ~s ) )',
59
+ ' ( drop )',
60
+ '',
61
+ '@w ( ~s (Maybe a) -> ~s )',
62
+ ' /nothingQuot',
63
+ ' /justQuot',
64
+ ' /maybe',
65
+ ''
66
+ ].join('\n')
67
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
68
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
69
+ t.end()
70
+ })
71
+
72
+ test('L3 stage8: eliminator — не хватает quotation — E1308', function (t) {
73
+ const main = path.join(root, 'sum-maybe-e1308.sail')
74
+ const src = [
75
+ '&Maybe a',
76
+ '| Nothing',
77
+ '| Just a',
78
+ '',
79
+ '@w ( ~s (Maybe a) -> ~s )',
80
+ ' /maybe',
81
+ ''
82
+ ].join('\n')
83
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
84
+ t.ok(r.ok === false)
85
+ const codes = r.diagnostics.map((d) => d.code)
86
+ t.ok(codes.includes('E1308'), codes.join(','))
87
+ t.end()
88
+ })
89
+
90
+ test('L3 stage8: eliminator — несовместимые ветки — E1307', function (t) {
91
+ const main = path.join(root, 'sum-maybe-e1307.sail')
92
+ const src = [
93
+ '&Maybe a',
94
+ '| Nothing',
95
+ '| Just a',
96
+ '',
97
+ '@w ( ~s (Maybe a) -> ~s Num )',
98
+ ' ( )',
99
+ ' ( )',
100
+ ' /maybe',
101
+ ''
102
+ ].join('\n')
103
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
104
+ t.ok(r.ok === false)
105
+ t.ok(r.diagnostics.some((d) => d.code === 'E1307'), r.diagnostics.map((d) => d.code).join(','))
106
+ t.end()
107
+ })
108
+
109
+ test('L3 stage8: product /point и /withPoint', function (t) {
110
+ const main = path.join(root, 'prod-point.sail')
111
+ const src = [
112
+ '&Point',
113
+ ':x Num',
114
+ ':y Num',
115
+ '',
116
+ '@q ( -> q:( ~s Num Num -> ~s ) )',
117
+ ' ( nip drop )',
118
+ '',
119
+ '@w ( Point -> )',
120
+ ' /q',
121
+ ' /withPoint',
122
+ '',
123
+ '@t ( -> Point )',
124
+ ' 1 2 /point',
125
+ ''
126
+ ].join('\n')
127
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
128
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
129
+ t.end()
130
+ })
131
+
132
+ test('L3 stage8: product геттер /xPoint', function (t) {
133
+ const main = path.join(root, 'prod-getter.sail')
134
+ const src = [
135
+ '&Point',
136
+ ':x Num',
137
+ ':y Num',
138
+ '',
139
+ '@w ( Point -> Num )',
140
+ ' /xPoint',
141
+ ''
142
+ ].join('\n')
143
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
144
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
145
+ t.end()
146
+ })
@@ -0,0 +1,105 @@
1
+ /**
2
+ * L3 этап 9: снимки стека для lowering (RFC-IR-0.1 §3, RFC-typecheck-0.1 §3, §6).
3
+ */
4
+ import test from 'brittle'
5
+ import path from 'node:path'
6
+ import { typecheckSail } from '../../index.js'
7
+ import { verifySnapshotChainStitches } from '../../lib/typecheck/stack-step-snapshots.js'
8
+
9
+ const root = path.resolve('/virtual/sail-l3-st9')
10
+ function vfs (files) {
11
+ const norm = Object.fromEntries(
12
+ Object.entries(files).map(([k, v]) => [path.normalize(k), v])
13
+ )
14
+ return (p) => norm[path.normalize(p)] ?? null
15
+ }
16
+
17
+ test('L3 stage9: цепочка pre/post для литерал + dup', function (t) {
18
+ const main = path.join(root, 'snap-dup.sail')
19
+ const src = ['@w ( -> Num Num )', '', ' 1', '', ' dup', ''].join('\n')
20
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
21
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
22
+ const rec = r.env.stackSnapshotsByPath.get(main).get('w')
23
+ t.is(rec.steps.length, 2)
24
+ t.ok(verifySnapshotChainStitches(rec))
25
+ t.is(rec.steps[0].pre.length, 0)
26
+ t.is(rec.steps[0].post.length, 1)
27
+ t.is(rec.steps[0].post[0].kind, 'prim')
28
+ t.is(rec.steps[0].post[0].name, 'Num')
29
+ t.is(rec.steps[1].pre.length, 1)
30
+ t.is(rec.steps[1].post.length, 2)
31
+ t.end()
32
+ })
33
+
34
+ test('L3 stage9: вложенная quotation — nestedByParentStep', function (t) {
35
+ const main = path.join(root, 'snap-nested.sail')
36
+ const src = ['@w ( -> ( -> Num ) )', '', ' ( 1 )', ''].join('\n')
37
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
38
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
39
+ const rec = r.env.stackSnapshotsByPath.get(main).get('w')
40
+ t.is(rec.steps.length, 1)
41
+ const nested = rec.nestedByParentStep.get(0)
42
+ t.ok(nested)
43
+ t.is(nested.steps.length, 1)
44
+ t.ok(verifySnapshotChainStitches(rec))
45
+ t.ok(verifySnapshotChainStitches(nested))
46
+ t.end()
47
+ })
48
+
49
+ test('L3 stage9: полиморфный вызов — снимок с Num', function (t) {
50
+ const main = path.join(root, 'snap-poly.sail')
51
+ const src = [
52
+ '@id ( a -> a )',
53
+ '',
54
+ '@w ( Num -> Num )',
55
+ '',
56
+ ' /id',
57
+ ''
58
+ ].join('\n')
59
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
60
+ t.ok(r.ok, r.diagnostics?.map((d) => d.message).join('; '))
61
+ const rec = r.env.stackSnapshotsByPath.get(main).get('w')
62
+ t.is(rec.steps.length, 1)
63
+ t.is(rec.steps[0].pre[0].kind, 'prim')
64
+ t.is(rec.steps[0].pre[0].name, 'Num')
65
+ t.is(rec.steps[0].post[0].kind, 'prim')
66
+ t.is(rec.steps[0].post[0].name, 'Num')
67
+ t.end()
68
+ })
69
+
70
+ test('L3 stage9: при ошибке тела — частичный снимок (pre на шаге сбоя) и второе слово проверяется', function (t) {
71
+ const main = path.join(root, 'snap-fail.sail')
72
+ const src = [
73
+ '@bad ( -> )',
74
+ '',
75
+ ' drop',
76
+ '',
77
+ '@ok ( -> )',
78
+ '',
79
+ ''
80
+ ].join('\n')
81
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
82
+ t.ok(r.ok === false)
83
+ const per = r.env.stackSnapshotsByPath.get(main)
84
+ t.ok(per)
85
+ const badRec = per.get('bad')
86
+ t.ok(badRec, 'RFC-typecheck §3: снимок для слова с ошибкой в теле')
87
+ t.ok(badRec.steps[0].pre)
88
+ t.ok(badRec.steps[0].post === undefined, 'post на ошибочном шаге не обязателен')
89
+ t.ok(per.has('ok'))
90
+ t.is(per.get('ok').steps.length, 0)
91
+ t.end()
92
+ })
93
+
94
+ test('L3 stage9: post[last] согласуется с объявленным выходом слова', function (t) {
95
+ const main = path.join(root, 'snap-out.sail')
96
+ const src = ['@w ( -> Num Num )', '', ' 1', '', ' dup', ''].join('\n')
97
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
98
+ t.ok(r.ok)
99
+ const rec = r.env.stackSnapshotsByPath.get(main).get('w')
100
+ const lastPost = rec.steps[rec.steps.length - 1].post
101
+ t.is(lastPost.length, 2)
102
+ t.is(lastPost[0].name, 'Num')
103
+ t.is(lastPost[1].name, 'Num')
104
+ t.end()
105
+ })
@@ -0,0 +1,53 @@
1
+ /**
2
+ * L3 этап 1: окружение typecheck — wordDeclByPath, scopeByPath, топо-порядок модулей.
3
+ */
4
+ import test from 'brittle'
5
+ import path from 'node:path'
6
+ import { typecheckSail } from '../../index.js'
7
+ import { resolveExportedWord } from '../../lib/typecheck/build-type-env.js'
8
+
9
+ const root = path.resolve('/virtual/sail-l3-env')
10
+ function vfs (files) {
11
+ const norm = Object.fromEntries(
12
+ Object.entries(files).map(([k, v]) => [path.normalize(k), v])
13
+ )
14
+ return (p) => norm[path.normalize(p)] ?? null
15
+ }
16
+
17
+ test('typecheck env: один модуль — слова и scope.unq', function (t) {
18
+ const main = path.join(root, 'main.sail')
19
+ const src = ['@noop ( -> )', '', '@main ( -> )', '', ' /noop'].join('\n')
20
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
21
+ t.ok(r.ok)
22
+ const env = r.env
23
+ t.ok(env, 'env')
24
+ const words = env.wordDeclByPath.get(main)
25
+ t.ok(words?.get('main')?.signature, 'main signature')
26
+ t.ok(Array.isArray(words?.get('main')?.body), 'main body')
27
+ t.ok(words?.get('noop')?.signature, 'noop signature')
28
+ const sc = env.scopeByPath.get(main)
29
+ t.ok(sc?.unq?.has('main') && sc.unq.has('noop'), 'unq')
30
+ t.end()
31
+ })
32
+
33
+ test('typecheck env: два модуля — dep раньше entry в modulePathsOrdered, importMap', function (t) {
34
+ const main = path.join(root, 'main.sail')
35
+ const lib = path.join(root, 'lib.sail')
36
+ const files = {
37
+ [main]: ['+Lib ./lib.sail', '@main ( -> )', '', ' ~Lib/hello'].join('\n'),
38
+ [lib]: ['@hello ( -> )', '', ''].join('\n')
39
+ }
40
+ const r = typecheckSail({ entryPath: main, readFile: vfs(files) })
41
+ t.ok(r.ok, r.diagnostics.map((d) => d.code + ' ' + d.message).join('; '))
42
+ const env = r.env
43
+ const order = env.modulePathsOrdered
44
+ const iLib = order.indexOf(lib)
45
+ const iMain = order.indexOf(main)
46
+ t.ok(iLib >= 0 && iMain >= 0)
47
+ t.ok(iLib < iMain, 'lib before main in topo order')
48
+ const dep = env.scopeByPath.get(main).importMap.get('Lib')
49
+ t.ok(dep && dep.path === lib, 'importMap Lib -> lib snapshot')
50
+ const w = resolveExportedWord(dep, 'hello')
51
+ t.ok(w && w.name === 'hello', 'resolveExportedWord')
52
+ t.end()
53
+ })
@@ -0,0 +1,37 @@
1
+ /**
2
+ * L3 этап 0: вход после L2, проброс диагностик при падении имён, заглушка при успехе.
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')
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('typecheckSail: при ошибке L2 те же diagnostics, без snapshots', function (t) {
17
+ const main = path.join(root, 'main.sail')
18
+ const src = '@main ( -> )\n\n /nope\n'
19
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
20
+ t.ok(r.ok === false)
21
+ t.ok(!('snapshots' in r) || r.snapshots === undefined)
22
+ const d = r.diagnostics.find((x) => x.code === 'E1201')
23
+ t.ok(d, 'E1201')
24
+ t.end()
25
+ })
26
+
27
+ test('typecheckSail: при успехе L2 ok и пустые diagnostics L3, есть snapshots', function (t) {
28
+ const main = path.join(root, 'main.sail')
29
+ const src = ['@noop ( -> )', '', '@main ( -> )', '', ' /noop'].join('\n')
30
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
31
+ t.ok(r.ok, r.diagnostics.map((d) => d.code + ' ' + d.message).join('; '))
32
+ t.is(r.diagnostics.length, 0)
33
+ t.ok(r.snapshots instanceof Map)
34
+ t.ok(r.snapshots.has(main))
35
+ t.ok(r.env && r.env.wordDeclByPath?.get(main)?.has('main'), 'env этап 1')
36
+ t.end()
37
+ })
package/README.md DELETED
@@ -1,37 +0,0 @@
1
- # @algosail/lang
2
-
3
- Sail language tooling. Re-exports parser and typecheck.
4
-
5
- ## CLI
6
-
7
- ```bash
8
- sail check <file.sail> # Typecheck
9
- sail compile <file.sail> [options] # Compile to JS
10
- ```
11
-
12
- Options for `compile`: `--out <file.js>`, `--no-source-map`, `-` (stdout).
13
-
14
- ## Exports
15
-
16
- ```javascript
17
- import { createParser, typecheck } from '@algosail/lang'
18
- ```
19
-
20
- ## Docs
21
-
22
- - [FFI-GUIDE.md](docs/FFI-GUIDE.md) — JSDoc format for FFI modules
23
- - [ARCHITECTURE.md](docs/ARCHITECTURE.md) — Pipeline, packages, dependencies
24
- - [TESTING.md](docs/TESTING.md) — Brittle setup, integration tests
25
- - [CHANGELOG.md](docs/CHANGELOG.md) — Version history
26
- - [RELEASE.md](docs/RELEASE.md) — Release checklist
27
-
28
- ## Packages
29
-
30
- | Package | Description |
31
- |---------|-------------|
32
- | @algosail/parser | Parse Sail + JS → symbol table |
33
- | @algosail/typecheck | Type checking |
34
- | @algosail/compiler | Sail → JS (CLI: `sail-compile`) |
35
- | @algosail/builtins | Builtin words |
36
- | @algosail/tree-sitter | Grammar |
37
- | @algosail/lsp | LSP server (hover, completion, go-to-definition) |
package/cli/sail.js DELETED
@@ -1,151 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Sail CLI — sail check | sail compile
5
- */
6
-
7
- const args = process.argv.slice(2)
8
- const cmd = args[0]
9
-
10
- if (!cmd || cmd === '-h' || cmd === '--help') {
11
- console.log(`sail - Sail language tooling
12
-
13
- Usage:
14
- sail check <file.sail> Typecheck a Sail file
15
- sail compile <file.sail> Compile Sail to JavaScript
16
-
17
- Options for compile:
18
- --out <file.js> Output path (default: input.js)
19
- --no-source-map Disable source map generation
20
- - Write to stdout
21
-
22
- Examples:
23
- sail check main.sail
24
- sail compile main.sail --out dist/main.js
25
- sail compile main.sail -
26
- `)
27
- process.exit(cmd === '-h' || cmd === '--help' ? 0 : 1)
28
- }
29
-
30
- const rest = args.slice(1)
31
-
32
- if (cmd === 'check') {
33
- await runCheck(rest)
34
- } else if (cmd === 'compile') {
35
- await runCompile(rest)
36
- } else {
37
- console.error(`sail: unknown command '${cmd}'. Use 'sail check' or 'sail compile'.`)
38
- process.exit(1)
39
- }
40
-
41
- async function runCheck(args) {
42
- const filePath = args.find((a) => !a.startsWith('-'))
43
- if (!filePath) {
44
- console.error('Usage: sail check <path-to-file.sail>')
45
- process.exit(1)
46
- }
47
-
48
- const { createParser } = await import('@algosail/parser')
49
- const { typecheck } = await import('@algosail/typecheck')
50
- const { readFile } = await import('node:fs/promises')
51
- const { pathToFileURL } = await import('node:url')
52
- const { resolve } = await import('node:path')
53
- const { formatError } = await import('@algosail/compiler/lib/formatError.js')
54
- const { addErrorHints } = await import('@algosail/compiler/lib/errorHints.js')
55
-
56
- const absPath = resolve(process.cwd(), filePath)
57
- const uri = pathToFileURL(absPath).href
58
-
59
- let text
60
- try {
61
- text = await readFile(absPath, 'utf8')
62
- } catch (err) {
63
- console.error(`sail check: cannot read ${filePath}: ${err.message}`)
64
- process.exit(1)
65
- }
66
-
67
- const parser = await createParser()
68
- const result = await parser.parseSail(uri, text)
69
- const parseErrors = (result.errors ?? []).map((e) => ({
70
- message: e.type === 'error' ? `Parse error: ${e.text}` : `Parse error: missing ${e.text}`,
71
- startPosition: e.startPosition,
72
- uri,
73
- }))
74
- const typeErrors = typecheck(result).map((e) => {
75
- const hint = addErrorHints(e)
76
- return { ...e, uri, ...(hint && { hint }) }
77
- })
78
- const errors = [...parseErrors, ...typeErrors]
79
-
80
- if (errors.length > 0) {
81
- for (const err of errors) {
82
- console.error(formatError({ ...err, uri }, uri))
83
- }
84
- process.exit(1)
85
- }
86
- }
87
-
88
- async function runCompile(args) {
89
- let inputPath = null
90
- let outPath = null
91
-
92
- for (let i = 0; i < args.length; i++) {
93
- if (args[i] === '--out' && args[i + 1]) {
94
- outPath = args[i + 1]
95
- i++
96
- } else if (!args[i].startsWith('-')) {
97
- inputPath = args[i]
98
- }
99
- }
100
-
101
- if (!inputPath) {
102
- console.error('Usage: sail compile <input.sail> [--out <output.js>] [--no-source-map]')
103
- process.exit(1)
104
- }
105
-
106
- const { compile } = await import('@algosail/compiler')
107
- const { formatError } = await import('@algosail/compiler/lib/formatError.js')
108
- const { readFile, writeFile } = await import('node:fs/promises')
109
- const { dirname, resolve, basename } = await import('node:path')
110
- const { pathToFileURL } = await import('node:url')
111
-
112
- const absPath = resolve(process.cwd(), inputPath)
113
- const uri = pathToFileURL(absPath).href
114
-
115
- let source
116
- try {
117
- source = await readFile(absPath, 'utf8')
118
- } catch (err) {
119
- console.error(`sail compile: cannot read ${inputPath}: ${err.message}`)
120
- process.exit(1)
121
- }
122
-
123
- const outputPath = outPath ?? inputPath.replace(/\.sail$/, '.js')
124
- const compileOpts = {
125
- outPath: outputPath !== '-' ? dirname(resolve(process.cwd(), outputPath)) : dirname(absPath),
126
- sourceMap: outputPath !== '-' && !args.includes('--no-source-map'),
127
- outFile: outputPath !== '-' ? resolve(process.cwd(), outputPath) : null,
128
- }
129
-
130
- const result = await compile(uri, source, compileOpts)
131
-
132
- if (result.errors.length > 0) {
133
- for (const err of result.errors) {
134
- console.error(formatError({ ...err, uri }, uri))
135
- }
136
- process.exit(1)
137
- }
138
-
139
- if (outputPath === '-') {
140
- process.stdout.write(result.js)
141
- } else {
142
- let js = result.js
143
- if (result.sourceMap) {
144
- const mapPath = outputPath + '.map'
145
- await writeFile(mapPath, result.sourceMap, 'utf8')
146
- js += `\n//# sourceMappingURL=${basename(mapPath)}`
147
- }
148
- await writeFile(outputPath, js, 'utf8')
149
- console.log(`Compiled to ${outputPath}`)
150
- }
151
- }