@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.
- package/bin/sail.mjs +4 -0
- package/cli/run-cli.js +176 -0
- package/index.js +11 -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 +33 -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,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L5 этап 8: JSDoc с `@sail` в сгенерированном ESM (RFC-compile-0.1 §11, RFC-0.1 §10.1).
|
|
3
|
+
* Тот же контрактный формат, что у ручного FFI: произвольный префикс до `@sail`, затем только Sail.
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} line
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
function escapeJSDocLine (line) {
|
|
12
|
+
return line.replace(/\*\//g, '*\\/')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string[]} docEntries inner text of each `-- … --` block
|
|
17
|
+
* @returns {string} Sail `doc_block` fragments
|
|
18
|
+
*/
|
|
19
|
+
export function formatDocBlocksAsSail (docEntries) {
|
|
20
|
+
if (!Array.isArray(docEntries) || docEntries.length === 0) return ''
|
|
21
|
+
return docEntries.map((t) => `-- ${String(t)} --`).join(' ')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string[]} lines prose without leading `*`
|
|
26
|
+
* @returns {string[]}
|
|
27
|
+
*/
|
|
28
|
+
function proseToStarLines (lines) {
|
|
29
|
+
const out = []
|
|
30
|
+
for (const raw of lines) {
|
|
31
|
+
for (const part of String(raw).split('\n')) {
|
|
32
|
+
out.push(escapeJSDocLine(part))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {{ proseLines?: string[], sailText: string }} opts
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
export function formatJSDocSailBlock ({ proseLines = [], sailText }) {
|
|
43
|
+
const sail = String(sailText).trim()
|
|
44
|
+
const body = []
|
|
45
|
+
const prose = proseToStarLines(proseLines)
|
|
46
|
+
for (const pl of prose) {
|
|
47
|
+
body.push(` * ${pl}`)
|
|
48
|
+
}
|
|
49
|
+
if (prose.length > 0) body.push(' *')
|
|
50
|
+
body.push(' * @sail')
|
|
51
|
+
for (const line of sail.split('\n')) {
|
|
52
|
+
body.push(` * ${line}`)
|
|
53
|
+
}
|
|
54
|
+
return `/**\n${body.join('\n')}\n */`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {object | null | undefined} expr
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
export function formatAstTypeExpr (expr) {
|
|
62
|
+
if (!expr || typeof expr !== 'object') return '_'
|
|
63
|
+
switch (expr.kind) {
|
|
64
|
+
case 'type_name':
|
|
65
|
+
return String(expr.name)
|
|
66
|
+
case 'type_var':
|
|
67
|
+
return String(expr.name)
|
|
68
|
+
case 'module_type_ref':
|
|
69
|
+
return `~${expr.module}/${expr.type}`
|
|
70
|
+
case 'paren_type':
|
|
71
|
+
return `(${formatAstTypeExpr(expr.inner)})`
|
|
72
|
+
case 'type_app': {
|
|
73
|
+
const ctor = String(expr.ctor)
|
|
74
|
+
const args = (expr.args ?? []).map(formatAstTypeExpr)
|
|
75
|
+
return `${ctor} ${args.join(' ')}`.trim()
|
|
76
|
+
}
|
|
77
|
+
case 'quotation_type':
|
|
78
|
+
return `Quote (${formatAstSignatureText(expr.inner)})`
|
|
79
|
+
default:
|
|
80
|
+
return '_'
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {object | null | undefined} item
|
|
86
|
+
* @returns {string}
|
|
87
|
+
*/
|
|
88
|
+
export function formatAstSigStackItem (item) {
|
|
89
|
+
if (!item || typeof item !== 'object') return '_'
|
|
90
|
+
switch (item.kind) {
|
|
91
|
+
case 'stack_var':
|
|
92
|
+
return `~${item.name}`
|
|
93
|
+
case 'sig_type_expr':
|
|
94
|
+
return formatAstTypeExpr(item.type)
|
|
95
|
+
case 'quotation_sig':
|
|
96
|
+
return `(${formatAstSignatureText(item.inner)})`
|
|
97
|
+
case 'named_quotation_sig': {
|
|
98
|
+
const inner = formatAstSignatureText(item.quotation.inner)
|
|
99
|
+
return `${item.prefix}: (${inner})`
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
return '_'
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {{ offset: number }} a
|
|
108
|
+
* @param {{ offset: number }} b
|
|
109
|
+
* @returns {number}
|
|
110
|
+
*/
|
|
111
|
+
function cmpSpan (a, b) {
|
|
112
|
+
return (a?.offset ?? 0) - (b?.offset ?? 0)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {object} sig AstSignature
|
|
117
|
+
* @returns {string}
|
|
118
|
+
*/
|
|
119
|
+
export function formatAstSignatureText (sig) {
|
|
120
|
+
if (!sig || typeof sig !== 'object') return '->'
|
|
121
|
+
const left = (sig.left ?? []).map(formatAstSigStackItem).join(' ')
|
|
122
|
+
/** @type {{ span: object, emit: () => string }[]} */
|
|
123
|
+
const rightParts = []
|
|
124
|
+
for (const it of sig.right ?? []) {
|
|
125
|
+
const sp = it.span?.start ?? { offset: 0 }
|
|
126
|
+
rightParts.push({ span: sp, emit: () => formatAstSigStackItem(it) })
|
|
127
|
+
}
|
|
128
|
+
for (const e of sig.effectsAdd ?? []) {
|
|
129
|
+
if (e.side !== 'right') continue
|
|
130
|
+
const sp = e.span?.start ?? { offset: 0 }
|
|
131
|
+
rightParts.push({ span: sp, emit: () => `+${e.name}` })
|
|
132
|
+
}
|
|
133
|
+
for (const e of sig.effectsRemove ?? []) {
|
|
134
|
+
if (e.side !== 'right') continue
|
|
135
|
+
const sp = e.span?.start ?? { offset: 0 }
|
|
136
|
+
rightParts.push({ span: sp, emit: () => `-${e.name}` })
|
|
137
|
+
}
|
|
138
|
+
rightParts.sort((a, b) => cmpSpan(a.span, b.span))
|
|
139
|
+
const right = rightParts.map((p) => p.emit()).join(' ')
|
|
140
|
+
if (left.length === 0 && right.length === 0) return '->'
|
|
141
|
+
if (left.length === 0) return `-> ${right}`.trim()
|
|
142
|
+
if (right.length === 0) return `${left} ->`.trim()
|
|
143
|
+
return `${left} -> ${right}`
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {object | null | undefined} slot normalized / wire slot
|
|
148
|
+
* @returns {string}
|
|
149
|
+
*/
|
|
150
|
+
export function formatWireTypeSlot (slot) {
|
|
151
|
+
if (!slot || typeof slot !== 'object') return '_'
|
|
152
|
+
switch (slot.kind) {
|
|
153
|
+
case 'prim':
|
|
154
|
+
case 'opaque':
|
|
155
|
+
return String(slot.name)
|
|
156
|
+
case 'tvar':
|
|
157
|
+
return String(slot.name)
|
|
158
|
+
case 'stack_label':
|
|
159
|
+
return `~${slot.name}`
|
|
160
|
+
case 'mod_adt':
|
|
161
|
+
return `~${slot.module}/${slot.type}`
|
|
162
|
+
case 'adt':
|
|
163
|
+
return String(slot.type)
|
|
164
|
+
case 'app': {
|
|
165
|
+
const args = (slot.args ?? []).map(formatWireTypeSlot)
|
|
166
|
+
return `${slot.ctor} ${args.join(' ')}`.trim()
|
|
167
|
+
}
|
|
168
|
+
case 'quote':
|
|
169
|
+
return `(${formatWireSignatureText(slot.inner)})`
|
|
170
|
+
case 'named_quote': {
|
|
171
|
+
const inner = formatWireSignatureText(slot.inner)
|
|
172
|
+
return `${slot.prefix}: (${inner})`
|
|
173
|
+
}
|
|
174
|
+
default:
|
|
175
|
+
return '_'
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @param {object | null | undefined} sig wire normalizedSig
|
|
181
|
+
* @returns {string}
|
|
182
|
+
*/
|
|
183
|
+
export function formatWireSignatureText (sig) {
|
|
184
|
+
if (!sig || typeof sig !== 'object') return '->'
|
|
185
|
+
const left = (sig.left ?? []).map(formatWireTypeSlot).join(' ')
|
|
186
|
+
const right = (sig.right ?? []).map(formatWireTypeSlot).join(' ')
|
|
187
|
+
const adds = (sig.effectsAdd ?? [])
|
|
188
|
+
.filter((e) => e.side !== 'left')
|
|
189
|
+
.map((e) => `+${e.name}`)
|
|
190
|
+
const subs = (sig.effectsRemove ?? [])
|
|
191
|
+
.filter((e) => e.side !== 'left')
|
|
192
|
+
.map((e) => `-${e.name}`)
|
|
193
|
+
const rightAll = [right, ...adds, ...subs].filter(Boolean).join(' ')
|
|
194
|
+
if (left.length === 0 && rightAll.length === 0) return '->'
|
|
195
|
+
if (left.length === 0) return `-> ${rightAll}`.trim()
|
|
196
|
+
if (rightAll.length === 0) return `${left} ->`.trim()
|
|
197
|
+
return `${left} -> ${rightAll}`
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {object} item sum_type AST
|
|
202
|
+
* @returns {string}
|
|
203
|
+
*/
|
|
204
|
+
export function formatSumTypeDeclSail (item) {
|
|
205
|
+
const params =
|
|
206
|
+
Array.isArray(item.typeParams) && item.typeParams.length > 0
|
|
207
|
+
? ` ${item.typeParams.join(' ')}`
|
|
208
|
+
: ''
|
|
209
|
+
const head = `& ${item.name}${params} ${formatDocBlocksAsSail(item.doc ?? [])}`.trimEnd()
|
|
210
|
+
const lines = [head]
|
|
211
|
+
for (const tag of item.tags ?? []) {
|
|
212
|
+
const pl = tag.payload ? ` ${formatAstTypeExpr(tag.payload)}` : ''
|
|
213
|
+
const piece =
|
|
214
|
+
`| ${tag.name}${pl} ${formatDocBlocksAsSail(tag.doc ?? [])}`.trimEnd()
|
|
215
|
+
lines.push(piece)
|
|
216
|
+
}
|
|
217
|
+
return lines.join('\n')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @param {object} item product_type AST
|
|
222
|
+
* @returns {string}
|
|
223
|
+
*/
|
|
224
|
+
export function formatProductTypeDeclSail (item) {
|
|
225
|
+
const params =
|
|
226
|
+
Array.isArray(item.typeParams) && item.typeParams.length > 0
|
|
227
|
+
? ` ${item.typeParams.join(' ')}`
|
|
228
|
+
: ''
|
|
229
|
+
const head = `& ${item.name}${params} ${formatDocBlocksAsSail(item.doc ?? [])}`.trimEnd()
|
|
230
|
+
const lines = [head]
|
|
231
|
+
for (const f of item.fields ?? []) {
|
|
232
|
+
const piece =
|
|
233
|
+
`: ${f.name} ${formatAstTypeExpr(f.type)} ${formatDocBlocksAsSail(f.doc ?? [])}`.trimEnd()
|
|
234
|
+
lines.push(piece)
|
|
235
|
+
}
|
|
236
|
+
return lines.join('\n')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Собирает строки «человеческой» документации до `@sail` (JSDoc префикс).
|
|
241
|
+
*
|
|
242
|
+
* @param {string[]} doc
|
|
243
|
+
* @returns {string[]}
|
|
244
|
+
*/
|
|
245
|
+
function proseFromDocEntries (doc) {
|
|
246
|
+
if (!Array.isArray(doc) || doc.length === 0) return []
|
|
247
|
+
return doc.flatMap((d) => String(d).split('\n'))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @param {string} modulePath
|
|
252
|
+
* @param {import('../typecheck/build-type-env.js').TypecheckEnv} env
|
|
253
|
+
* @returns {{ ok: true, block: string } | { ok: false, message: string }}
|
|
254
|
+
*/
|
|
255
|
+
export function formatModuleSailHeaderBlock (modulePath, env) {
|
|
256
|
+
const normPath = path.normalize(modulePath)
|
|
257
|
+
const snap = env.snapshots?.get(normPath)
|
|
258
|
+
const scope = env.scopeByPath?.get(normPath)
|
|
259
|
+
if (!snap?.ok || !Array.isArray(snap.items) || !scope?.exportTypes) {
|
|
260
|
+
return { ok: true, block: '' }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** @type {string[]} */
|
|
264
|
+
const proseLines = []
|
|
265
|
+
/** @type {string[]} */
|
|
266
|
+
const sailChunks = []
|
|
267
|
+
|
|
268
|
+
for (const it of snap.items) {
|
|
269
|
+
if (it.kind === 'sum_type' && scope.exportTypes.has(it.name)) {
|
|
270
|
+
proseLines.push(...proseFromDocEntries(it.doc))
|
|
271
|
+
try {
|
|
272
|
+
sailChunks.push(formatSumTypeDeclSail(it))
|
|
273
|
+
} catch (e) {
|
|
274
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
275
|
+
return { ok: false, message: `sum &${it.name}: ${msg}` }
|
|
276
|
+
}
|
|
277
|
+
} else if (it.kind === 'product_type' && scope.exportTypes.has(it.name)) {
|
|
278
|
+
proseLines.push(...proseFromDocEntries(it.doc))
|
|
279
|
+
try {
|
|
280
|
+
sailChunks.push(formatProductTypeDeclSail(it))
|
|
281
|
+
} catch (e) {
|
|
282
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
283
|
+
return { ok: false, message: `product &${it.name}: ${msg}` }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (sailChunks.length === 0) {
|
|
289
|
+
return { ok: true, block: '' }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const sailText = sailChunks.join('\n\n')
|
|
293
|
+
try {
|
|
294
|
+
const block = formatJSDocSailBlock({
|
|
295
|
+
proseLines,
|
|
296
|
+
sailText
|
|
297
|
+
})
|
|
298
|
+
return { ok: true, block }
|
|
299
|
+
} catch (e) {
|
|
300
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
301
|
+
return { ok: false, message: msg }
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @param {object | null | undefined} snap
|
|
307
|
+
* @param {string} wordName
|
|
308
|
+
* @returns {object | null}
|
|
309
|
+
*/
|
|
310
|
+
export function findWordItemInSnapshot (snap, wordName) {
|
|
311
|
+
if (!snap?.ok || !Array.isArray(snap.items)) return null
|
|
312
|
+
for (const it of snap.items) {
|
|
313
|
+
if (it.kind === 'word' && it.name === wordName) return it
|
|
314
|
+
}
|
|
315
|
+
return null
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* @param {{
|
|
320
|
+
* wordName: string
|
|
321
|
+
* wordAst: object | null
|
|
322
|
+
* wireSig: object | null | undefined
|
|
323
|
+
* exportAsync?: boolean
|
|
324
|
+
* asyncDefinition?: boolean
|
|
325
|
+
* mayFail?: boolean
|
|
326
|
+
* }} opts
|
|
327
|
+
* @returns {{ ok: true, block: string } | { ok: false, message: string }}
|
|
328
|
+
*/
|
|
329
|
+
export function formatWordSailBlock ({
|
|
330
|
+
wordName,
|
|
331
|
+
wordAst,
|
|
332
|
+
wireSig,
|
|
333
|
+
exportAsync,
|
|
334
|
+
asyncDefinition,
|
|
335
|
+
mayFail
|
|
336
|
+
}) {
|
|
337
|
+
/** @type {string[]} */
|
|
338
|
+
const proseLines = []
|
|
339
|
+
if (wordAst && Array.isArray(wordAst.doc)) {
|
|
340
|
+
proseLines.push(...proseFromDocEntries(wordAst.doc))
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let sigText
|
|
344
|
+
try {
|
|
345
|
+
if (wordAst?.signature) {
|
|
346
|
+
sigText = formatAstSignatureText(wordAst.signature)
|
|
347
|
+
} else if (wireSig) {
|
|
348
|
+
sigText = formatWireSignatureText(wireSig)
|
|
349
|
+
} else {
|
|
350
|
+
return { ok: false, message: `нет сигнатуры для @${wordName}` }
|
|
351
|
+
}
|
|
352
|
+
} catch (e) {
|
|
353
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
354
|
+
return { ok: false, message: msg }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const wantAsync =
|
|
358
|
+
exportAsync === true ||
|
|
359
|
+
asyncDefinition === true
|
|
360
|
+
if (wantAsync && !/\+Async\b/.test(sigText)) {
|
|
361
|
+
sigText = `${sigText} +Async`.trim()
|
|
362
|
+
}
|
|
363
|
+
if (mayFail === true && !/\+Fail\b/.test(sigText)) {
|
|
364
|
+
sigText = `${sigText} +Fail`.trim()
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const sailDoc = formatDocBlocksAsSail(
|
|
368
|
+
wordAst && Array.isArray(wordAst.doc) ? wordAst.doc : []
|
|
369
|
+
)
|
|
370
|
+
const sailBody = [`@${wordName} ( ${sigText} )`, sailDoc]
|
|
371
|
+
.filter((s) => s.length > 0)
|
|
372
|
+
.join(' ')
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
return {
|
|
376
|
+
ok: true,
|
|
377
|
+
block: formatJSDocSailBlock({ proseLines, sailText: sailBody })
|
|
378
|
+
}
|
|
379
|
+
} catch (e) {
|
|
380
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
381
|
+
return { ok: false, message: msg }
|
|
382
|
+
}
|
|
383
|
+
}
|