@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,912 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phrase parser RFC-0.1 §12.2 (L1) + построение AST (ast-spec.js).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createLexerState, nextToken, readPathToken, skipWs } from './lexer.js'
|
|
6
|
+
import * as diag from './diagnostics.js'
|
|
7
|
+
import * as ab from './ast-build.js'
|
|
8
|
+
|
|
9
|
+
function skipInlineWs (state) {
|
|
10
|
+
while (state.i < state.source.length) {
|
|
11
|
+
const c = state.source[state.i]
|
|
12
|
+
if (c === ' ' || c === '\t') {
|
|
13
|
+
state.i++
|
|
14
|
+
state.col++
|
|
15
|
+
} else break
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createCtx (state) {
|
|
20
|
+
return {
|
|
21
|
+
state,
|
|
22
|
+
diagnostics: [],
|
|
23
|
+
regexpOk: false,
|
|
24
|
+
buf: null,
|
|
25
|
+
pushback: [],
|
|
26
|
+
items: [],
|
|
27
|
+
pendingDoc: []
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function peek (ctx) {
|
|
32
|
+
if (ctx.pushback.length) return ctx.pushback[ctx.pushback.length - 1]
|
|
33
|
+
if (ctx.buf === null) ctx.buf = nextToken(ctx.state, ctx)
|
|
34
|
+
return ctx.buf
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function eat (ctx) {
|
|
38
|
+
if (ctx.pushback.length) return ctx.pushback.pop()
|
|
39
|
+
const t = peek(ctx)
|
|
40
|
+
ctx.buf = null
|
|
41
|
+
return t
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function unget (ctx, t) {
|
|
45
|
+
ctx.pushback.push(t)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function pushE1001 (ctx, t, tokenText) {
|
|
49
|
+
ctx.diagnostics.push({
|
|
50
|
+
code: 'E1001',
|
|
51
|
+
message: diag.e1001UnexpectedToken(tokenText),
|
|
52
|
+
offset: t.offset,
|
|
53
|
+
line: t.line,
|
|
54
|
+
column: t.column
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function pushDiag (ctx, code, message, t) {
|
|
59
|
+
ctx.diagnostics.push({
|
|
60
|
+
code,
|
|
61
|
+
message,
|
|
62
|
+
offset: t.offset,
|
|
63
|
+
line: t.line,
|
|
64
|
+
column: t.column
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function tokenSpan (ctx, tok) {
|
|
69
|
+
const sp = ab.openSpan(tok)
|
|
70
|
+
ab.closeSpan(ctx.state, sp)
|
|
71
|
+
return sp
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function drainComments (ctx) {
|
|
75
|
+
const out = []
|
|
76
|
+
while (peek(ctx).type === 'COMMENT') out.push(eat(ctx).value)
|
|
77
|
+
return out
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function forkCtx (ctx) {
|
|
81
|
+
const state = {
|
|
82
|
+
source: ctx.state.source,
|
|
83
|
+
i: ctx.state.i,
|
|
84
|
+
line: ctx.state.line,
|
|
85
|
+
col: ctx.state.col
|
|
86
|
+
}
|
|
87
|
+
const sub = createCtx(state)
|
|
88
|
+
sub.regexpOk = ctx.regexpOk
|
|
89
|
+
return sub
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* `(` … парная `)` содержит хотя бы один `->` при depth === 1 внутри этой пары
|
|
94
|
+
* (стрелка «между In и Out» этой скобочной группы, не внутри вложенных `(` … `)`).
|
|
95
|
+
* Тогда это quotation_sig / quotation_type; иначе — paren_type + type_expr.
|
|
96
|
+
*/
|
|
97
|
+
function sigParenHasTopLevelArrow (ctx) {
|
|
98
|
+
const t = peek(ctx)
|
|
99
|
+
if (t.type !== '(') return false
|
|
100
|
+
const sub = forkCtx(ctx)
|
|
101
|
+
// peek() мог оставить buf и продвинуть state.i за «(»; fork иначе начнёт не с той скобки.
|
|
102
|
+
sub.state.i = t.offset
|
|
103
|
+
sub.state.line = t.line
|
|
104
|
+
sub.state.col = t.column
|
|
105
|
+
sub.buf = null
|
|
106
|
+
eat(sub)
|
|
107
|
+
let depth = 1
|
|
108
|
+
let sawArrowAtDepth1 = false
|
|
109
|
+
while (depth > 0 && peek(sub).type !== 'EOF') {
|
|
110
|
+
const u = eat(sub)
|
|
111
|
+
if (u.type === '(') depth++
|
|
112
|
+
else if (u.type === ')') depth--
|
|
113
|
+
else if (u.type === 'ARROW' && depth === 1) sawArrowAtDepth1 = true
|
|
114
|
+
}
|
|
115
|
+
return sawArrowAtDepth1
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function skipBodyComments (ctx) {
|
|
119
|
+
while (peek(ctx).type === 'COMMENT') eat(ctx)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function wrapSigType (inner) {
|
|
123
|
+
return ab.node('sig_type_expr', inner.span, { type: inner })
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function effectNameFromAdd (t) {
|
|
127
|
+
const s = String(t.value)
|
|
128
|
+
return s.startsWith('+') ? s.slice(1) : s
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function effectNameFromSub (t) {
|
|
132
|
+
const s = String(t.value)
|
|
133
|
+
return s.startsWith('-') ? s.slice(1) : s
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function sigTypeArgFollows (ctx) {
|
|
137
|
+
const t = peek(ctx)
|
|
138
|
+
return (
|
|
139
|
+
t.type === '(' ||
|
|
140
|
+
t.type === 'MODULE_TYPE_REF' ||
|
|
141
|
+
t.type === 'TYPE_NAME' ||
|
|
142
|
+
t.type === 'LOWER_IDENT'
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function typeExprDelimiter (ctx) {
|
|
147
|
+
const t = peek(ctx)
|
|
148
|
+
return (
|
|
149
|
+
t.type === 'EOF' ||
|
|
150
|
+
t.type === ')' ||
|
|
151
|
+
t.type === '|' ||
|
|
152
|
+
t.type === 'ARROW' ||
|
|
153
|
+
t.type === 'MODULE' ||
|
|
154
|
+
t.type === 'AMP' ||
|
|
155
|
+
t.type === 'WORD_DEF' ||
|
|
156
|
+
t.type === 'COMMENT'
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* TypeName без параметров на слоте операнда `List` / `Dict` / `Map` (RFC-0.1 list_type, dict_type, map_type):
|
|
162
|
+
* иначе `Map Str Num` ошибочно разбиралось как `Map (Str Num)`.
|
|
163
|
+
* Параметризованные формы (`Maybe a`, `Vec T`) и группировка — через полный `buildTypeExpr` или `( … )`.
|
|
164
|
+
*/
|
|
165
|
+
const PRIMITIVE_TYPE_NAMES = new Set([
|
|
166
|
+
'Str', 'String', 'Num', 'Int', 'Bool', 'Float', 'Double', 'Char', 'Unit', 'Nil'
|
|
167
|
+
])
|
|
168
|
+
|
|
169
|
+
/** @returns {object | null} AstTypeExpr */
|
|
170
|
+
function buildTypeExprCombinatorOperand (ctx) {
|
|
171
|
+
const t = peek(ctx)
|
|
172
|
+
if (t.type === '(') {
|
|
173
|
+
const o = eat(ctx)
|
|
174
|
+
const sp = ab.openSpan(o)
|
|
175
|
+
const inner = buildTypeExpr(ctx)
|
|
176
|
+
if (!inner) return null
|
|
177
|
+
if (peek(ctx).type !== ')') {
|
|
178
|
+
const u = peek(ctx)
|
|
179
|
+
pushE1001(ctx, u, String(u.value || u.type))
|
|
180
|
+
return null
|
|
181
|
+
}
|
|
182
|
+
eat(ctx)
|
|
183
|
+
ab.closeSpan(ctx.state, sp)
|
|
184
|
+
return ab.node('paren_type', sp, { inner })
|
|
185
|
+
}
|
|
186
|
+
if (t.type === 'MODULE_TYPE_REF') {
|
|
187
|
+
const m = eat(ctx)
|
|
188
|
+
return ab.node('module_type_ref', tokenSpan(ctx, m), { module: m.module, type: m.typeName })
|
|
189
|
+
}
|
|
190
|
+
if (t.type === 'LOWER_IDENT') {
|
|
191
|
+
const id = eat(ctx)
|
|
192
|
+
const sp = tokenSpan(ctx, id)
|
|
193
|
+
return ab.node('type_var', sp, { name: String(id.value) })
|
|
194
|
+
}
|
|
195
|
+
if (t.type !== 'TYPE_NAME') {
|
|
196
|
+
pushE1001(ctx, t, String(t.value || t.type))
|
|
197
|
+
return null
|
|
198
|
+
}
|
|
199
|
+
const nm = String(t.value)
|
|
200
|
+
if (PRIMITIVE_TYPE_NAMES.has(nm)) {
|
|
201
|
+
const idtok = eat(ctx)
|
|
202
|
+
return ab.node('type_name', tokenSpan(ctx, idtok), { name: nm })
|
|
203
|
+
}
|
|
204
|
+
return buildTypeExpr(ctx)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** @returns {object | null} AstTypeExpr */
|
|
208
|
+
function buildTypeExpr (ctx) {
|
|
209
|
+
const t = peek(ctx)
|
|
210
|
+
if (t.type === '(') {
|
|
211
|
+
const o = eat(ctx)
|
|
212
|
+
const sp = ab.openSpan(o)
|
|
213
|
+
const inner = buildTypeExpr(ctx)
|
|
214
|
+
if (!inner) return null
|
|
215
|
+
if (peek(ctx).type !== ')') {
|
|
216
|
+
const u = peek(ctx)
|
|
217
|
+
pushE1001(ctx, u, String(u.value || u.type))
|
|
218
|
+
return null
|
|
219
|
+
}
|
|
220
|
+
eat(ctx)
|
|
221
|
+
ab.closeSpan(ctx.state, sp)
|
|
222
|
+
return ab.node('paren_type', sp, { inner })
|
|
223
|
+
}
|
|
224
|
+
if (t.type === 'MODULE_TYPE_REF') {
|
|
225
|
+
const m = eat(ctx)
|
|
226
|
+
return ab.node('module_type_ref', tokenSpan(ctx, m), { module: m.module, type: m.typeName })
|
|
227
|
+
}
|
|
228
|
+
if (t.type !== 'LOWER_IDENT' && t.type !== 'TYPE_NAME') {
|
|
229
|
+
pushE1001(ctx, t, String(t.value || t.type))
|
|
230
|
+
return null
|
|
231
|
+
}
|
|
232
|
+
const first = eat(ctx)
|
|
233
|
+
const sp = ab.openSpan(first)
|
|
234
|
+
if (first.value === 'List') {
|
|
235
|
+
const arg = buildTypeExprCombinatorOperand(ctx)
|
|
236
|
+
if (!arg) return null
|
|
237
|
+
ab.closeSpan(ctx.state, sp)
|
|
238
|
+
return ab.node('type_app', sp, { ctor: 'List', args: [arg] })
|
|
239
|
+
}
|
|
240
|
+
if (first.value === 'Dict') {
|
|
241
|
+
const arg = buildTypeExprCombinatorOperand(ctx)
|
|
242
|
+
if (!arg) return null
|
|
243
|
+
ab.closeSpan(ctx.state, sp)
|
|
244
|
+
return ab.node('type_app', sp, { ctor: 'Dict', args: [arg] })
|
|
245
|
+
}
|
|
246
|
+
if (first.value === 'Map') {
|
|
247
|
+
const a = buildTypeExprCombinatorOperand(ctx)
|
|
248
|
+
if (!a) return null
|
|
249
|
+
const b = buildTypeExprCombinatorOperand(ctx)
|
|
250
|
+
if (!b) return null
|
|
251
|
+
ab.closeSpan(ctx.state, sp)
|
|
252
|
+
return ab.node('type_app', sp, { ctor: 'Map', args: [a, b] })
|
|
253
|
+
}
|
|
254
|
+
if (first.type === 'TYPE_NAME') {
|
|
255
|
+
const args = []
|
|
256
|
+
while (!typeExprDelimiter(ctx) && sigTypeArgFollows(ctx)) {
|
|
257
|
+
const a = buildTypeExpr(ctx)
|
|
258
|
+
if (!a) return null
|
|
259
|
+
args.push(a)
|
|
260
|
+
}
|
|
261
|
+
ab.closeSpan(ctx.state, sp)
|
|
262
|
+
if (args.length === 0) return ab.node('type_name', sp, { name: first.value })
|
|
263
|
+
return ab.node('type_app', sp, { ctor: first.value, args })
|
|
264
|
+
}
|
|
265
|
+
ab.closeSpan(ctx.state, sp)
|
|
266
|
+
return ab.node('type_var', sp, { name: first.value })
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** @returns {object | null} AstTypeExpr (core of sig_type_expr) */
|
|
270
|
+
function buildSigTypeExprCore (ctx) {
|
|
271
|
+
const t = peek(ctx)
|
|
272
|
+
if (t.type === '(') {
|
|
273
|
+
if (sigParenHasTopLevelArrow(ctx)) {
|
|
274
|
+
const q = parseQuotSigAst(ctx)
|
|
275
|
+
if (!q) return null
|
|
276
|
+
return ab.node('quotation_type', q.node.span, { inner: q.node.inner })
|
|
277
|
+
}
|
|
278
|
+
const o = eat(ctx)
|
|
279
|
+
const sp = ab.openSpan(o)
|
|
280
|
+
const inner = buildTypeExpr(ctx)
|
|
281
|
+
if (!inner) return null
|
|
282
|
+
if (peek(ctx).type !== ')') {
|
|
283
|
+
const u = peek(ctx)
|
|
284
|
+
pushE1001(ctx, u, String(u.value || u.type))
|
|
285
|
+
return null
|
|
286
|
+
}
|
|
287
|
+
eat(ctx)
|
|
288
|
+
ab.closeSpan(ctx.state, sp)
|
|
289
|
+
return ab.node('paren_type', sp, { inner })
|
|
290
|
+
}
|
|
291
|
+
if (t.type === 'MODULE_TYPE_REF') {
|
|
292
|
+
const m = eat(ctx)
|
|
293
|
+
return ab.node('module_type_ref', tokenSpan(ctx, m), { module: m.module, type: m.typeName })
|
|
294
|
+
}
|
|
295
|
+
if (t.type !== 'LOWER_IDENT' && t.type !== 'TYPE_NAME') {
|
|
296
|
+
pushE1001(ctx, t, String(t.value || t.type))
|
|
297
|
+
return null
|
|
298
|
+
}
|
|
299
|
+
const first = eat(ctx)
|
|
300
|
+
const sp = ab.openSpan(first)
|
|
301
|
+
if (first.value === 'List') {
|
|
302
|
+
const arg = buildTypeExprCombinatorOperand(ctx)
|
|
303
|
+
if (!arg) return null
|
|
304
|
+
ab.closeSpan(ctx.state, sp)
|
|
305
|
+
return ab.node('type_app', sp, { ctor: 'List', args: [arg] })
|
|
306
|
+
}
|
|
307
|
+
if (first.value === 'Dict') {
|
|
308
|
+
const arg = buildTypeExprCombinatorOperand(ctx)
|
|
309
|
+
if (!arg) return null
|
|
310
|
+
ab.closeSpan(ctx.state, sp)
|
|
311
|
+
return ab.node('type_app', sp, { ctor: 'Dict', args: [arg] })
|
|
312
|
+
}
|
|
313
|
+
if (first.value === 'Map') {
|
|
314
|
+
const a = buildTypeExprCombinatorOperand(ctx)
|
|
315
|
+
if (!a) return null
|
|
316
|
+
const b = buildTypeExprCombinatorOperand(ctx)
|
|
317
|
+
if (!b) return null
|
|
318
|
+
ab.closeSpan(ctx.state, sp)
|
|
319
|
+
return ab.node('type_app', sp, { ctor: 'Map', args: [a, b] })
|
|
320
|
+
}
|
|
321
|
+
if (first.type === 'TYPE_NAME') {
|
|
322
|
+
const head = String(first.value)
|
|
323
|
+
// Примитив — один слот; соседний `TYPE_NAME` — следующий sig_item, не аргумент (RFC-0.1: скобки только у параметризованных форм).
|
|
324
|
+
if (PRIMITIVE_TYPE_NAMES.has(head)) {
|
|
325
|
+
ab.closeSpan(ctx.state, sp)
|
|
326
|
+
return ab.node('type_name', sp, { name: head })
|
|
327
|
+
}
|
|
328
|
+
const args = []
|
|
329
|
+
while (sigTypeArgFollows(ctx)) {
|
|
330
|
+
const a = buildSigTypeExprCore(ctx)
|
|
331
|
+
if (!a) return null
|
|
332
|
+
args.push(a)
|
|
333
|
+
}
|
|
334
|
+
ab.closeSpan(ctx.state, sp)
|
|
335
|
+
if (args.length === 0) return ab.node('type_name', sp, { name: first.value })
|
|
336
|
+
return ab.node('type_app', sp, { ctor: first.value, args })
|
|
337
|
+
}
|
|
338
|
+
ab.closeSpan(ctx.state, sp)
|
|
339
|
+
return ab.node('type_var', sp, { name: first.value })
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function parseSigItemInto (ctx, side, stack, effectsAdd, effectsRemove) {
|
|
343
|
+
const t = peek(ctx)
|
|
344
|
+
if (t.type === 'COMMENT') {
|
|
345
|
+
pushE1001(ctx, t, 'comment')
|
|
346
|
+
return false
|
|
347
|
+
}
|
|
348
|
+
if (t.type === 'EFFECT_ADD') {
|
|
349
|
+
const e = eat(ctx)
|
|
350
|
+
if (side === 'left') {
|
|
351
|
+
pushDiag(ctx, 'E1007', diag.e1007EffectOnlyAfterArrow(String(e.value)), e)
|
|
352
|
+
return false
|
|
353
|
+
}
|
|
354
|
+
const sp = tokenSpan(ctx, e)
|
|
355
|
+
effectsAdd.push(ab.node('sig_effect_add', sp, { name: effectNameFromAdd(e), side }))
|
|
356
|
+
return true
|
|
357
|
+
}
|
|
358
|
+
if (t.type === 'EFFECT_SUB') {
|
|
359
|
+
const e = eat(ctx)
|
|
360
|
+
if (side === 'left') {
|
|
361
|
+
pushDiag(ctx, 'E1007', diag.e1007EffectOnlyAfterArrow(String(e.value)), e)
|
|
362
|
+
return false
|
|
363
|
+
}
|
|
364
|
+
const sp = tokenSpan(ctx, e)
|
|
365
|
+
effectsRemove.push(ab.node('sig_effect_remove', sp, { name: effectNameFromSub(e), side }))
|
|
366
|
+
return true
|
|
367
|
+
}
|
|
368
|
+
if (t.type === 'STACK_VAR') {
|
|
369
|
+
const s = eat(ctx)
|
|
370
|
+
const sp = tokenSpan(ctx, s)
|
|
371
|
+
stack.push(ab.node('stack_var', sp, { name: String(s.value) }))
|
|
372
|
+
return true
|
|
373
|
+
}
|
|
374
|
+
if (t.type === '(') {
|
|
375
|
+
if (sigParenHasTopLevelArrow(ctx)) {
|
|
376
|
+
const q = parseQuotSigAst(ctx)
|
|
377
|
+
if (!q) return false
|
|
378
|
+
stack.push(q.node)
|
|
379
|
+
return true
|
|
380
|
+
}
|
|
381
|
+
const o = eat(ctx)
|
|
382
|
+
const sp = ab.openSpan(o)
|
|
383
|
+
const inner = buildTypeExpr(ctx)
|
|
384
|
+
if (!inner) return false
|
|
385
|
+
if (peek(ctx).type !== ')') {
|
|
386
|
+
const u = peek(ctx)
|
|
387
|
+
pushE1001(ctx, u, String(u.value || u.type))
|
|
388
|
+
return false
|
|
389
|
+
}
|
|
390
|
+
eat(ctx)
|
|
391
|
+
ab.closeSpan(ctx.state, sp)
|
|
392
|
+
stack.push(wrapSigType(ab.node('paren_type', sp, { inner })))
|
|
393
|
+
return true
|
|
394
|
+
}
|
|
395
|
+
if (t.type === 'MODULE_TYPE_REF') {
|
|
396
|
+
const inner = buildSigTypeExprCore(ctx)
|
|
397
|
+
if (!inner) return false
|
|
398
|
+
stack.push(wrapSigType(inner))
|
|
399
|
+
return true
|
|
400
|
+
}
|
|
401
|
+
if (t.type !== 'LOWER_IDENT' && t.type !== 'TYPE_NAME') {
|
|
402
|
+
pushE1001(ctx, t, String(t.value || t.type))
|
|
403
|
+
return false
|
|
404
|
+
}
|
|
405
|
+
const a = eat(ctx)
|
|
406
|
+
if (peek(ctx).type === ':') {
|
|
407
|
+
const prefixSp = tokenSpan(ctx, a)
|
|
408
|
+
eat(ctx)
|
|
409
|
+
const res = parseQuotSigAst(ctx)
|
|
410
|
+
if (!res) return false
|
|
411
|
+
const sp = ab.mergeSpan(prefixSp, res.node.span)
|
|
412
|
+
const named = ab.node('named_quotation_sig', sp, { prefix: String(a.value), quotation: res.node })
|
|
413
|
+
stack.push(named)
|
|
414
|
+
return true
|
|
415
|
+
}
|
|
416
|
+
if (
|
|
417
|
+
a.type === 'TYPE_NAME' &&
|
|
418
|
+
String(a.value) === 'Quote' &&
|
|
419
|
+
peek(ctx).type === '('
|
|
420
|
+
) {
|
|
421
|
+
const quoteSp = tokenSpan(ctx, a)
|
|
422
|
+
const q = parseQuotSigAst(ctx)
|
|
423
|
+
if (!q) return false
|
|
424
|
+
const sp = ab.mergeSpan(quoteSp, q.node.span)
|
|
425
|
+
stack.push(ab.node('quotation_sig', sp, { inner: q.node.inner }))
|
|
426
|
+
return true
|
|
427
|
+
}
|
|
428
|
+
unget(ctx, a)
|
|
429
|
+
const inner = buildSigTypeExprCore(ctx)
|
|
430
|
+
if (!inner) return false
|
|
431
|
+
stack.push(wrapSigType(inner))
|
|
432
|
+
return true
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** @returns {{ node: object, sawOuterArrow: boolean } | null} */
|
|
436
|
+
function parseQuotSigAst (ctx) {
|
|
437
|
+
const o = eat(ctx)
|
|
438
|
+
if (o.type !== '(') {
|
|
439
|
+
unget(ctx, o)
|
|
440
|
+
return null
|
|
441
|
+
}
|
|
442
|
+
const sp = ab.openSpan(o)
|
|
443
|
+
const left = []
|
|
444
|
+
const right = []
|
|
445
|
+
const effectsAdd = []
|
|
446
|
+
const effectsRemove = []
|
|
447
|
+
const half = (side, st) => {
|
|
448
|
+
while (peek(ctx).type !== 'ARROW' && peek(ctx).type !== ')') {
|
|
449
|
+
if (!parseSigItemInto(ctx, side, st, effectsAdd, effectsRemove)) return false
|
|
450
|
+
}
|
|
451
|
+
return true
|
|
452
|
+
}
|
|
453
|
+
if (!half('left', left)) return null
|
|
454
|
+
let sawOuterArrow = false
|
|
455
|
+
if (peek(ctx).type === 'ARROW') {
|
|
456
|
+
sawOuterArrow = true
|
|
457
|
+
eat(ctx)
|
|
458
|
+
if (!half('right', right)) return null
|
|
459
|
+
}
|
|
460
|
+
if (peek(ctx).type !== ')') {
|
|
461
|
+
const u = peek(ctx)
|
|
462
|
+
pushE1001(ctx, u, String(u.value || u.type))
|
|
463
|
+
return null
|
|
464
|
+
}
|
|
465
|
+
const c = eat(ctx)
|
|
466
|
+
ab.closeSpan(ctx.state, sp)
|
|
467
|
+
const inner = ab.node('signature', sp, {
|
|
468
|
+
left,
|
|
469
|
+
right,
|
|
470
|
+
effectsAdd,
|
|
471
|
+
effectsRemove
|
|
472
|
+
})
|
|
473
|
+
const node = ab.node('quotation_sig', sp, { inner })
|
|
474
|
+
return { node, sawOuterArrow }
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function parseParenSignatureAst (ctx) {
|
|
478
|
+
const res = parseQuotSigAst(ctx)
|
|
479
|
+
if (!res) return null
|
|
480
|
+
const { node, sawOuterArrow } = res
|
|
481
|
+
const sig = node.inner
|
|
482
|
+
// RFC-0.1 §6.1.1: лишняя пара скобок вокруг sugar-цитаты `( ( In -> Out ) )` не нормируется;
|
|
483
|
+
// без верхнего `->` у слова это один stack-item `quotation_sig` — отклоняем (E1001).
|
|
484
|
+
if (
|
|
485
|
+
!sawOuterArrow &&
|
|
486
|
+
sig.left.length === 1 &&
|
|
487
|
+
sig.right.length === 0 &&
|
|
488
|
+
sig.left[0].kind === 'quotation_sig'
|
|
489
|
+
) {
|
|
490
|
+
const st = sig.left[0].span.start
|
|
491
|
+
pushE1001(ctx, st, '(')
|
|
492
|
+
return null
|
|
493
|
+
}
|
|
494
|
+
return sig
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function parseSignatureAst (ctx) {
|
|
498
|
+
return parseParenSignatureAst(ctx)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function parseImportAst (ctx) {
|
|
502
|
+
const modTok = eat(ctx)
|
|
503
|
+
ctx.regexpOk = false
|
|
504
|
+
ctx.buf = null
|
|
505
|
+
const sp = ab.openSpan(modTok)
|
|
506
|
+
const moduleName = String(modTok.value)
|
|
507
|
+
let bracket = null
|
|
508
|
+
const st = ctx.state
|
|
509
|
+
skipWs(st)
|
|
510
|
+
if (st.i < st.source.length && st.source[st.i] === '(') {
|
|
511
|
+
bracket = { words: [], types: [] }
|
|
512
|
+
st.i += 1
|
|
513
|
+
st.col += 1
|
|
514
|
+
while (true) {
|
|
515
|
+
skipWs(st)
|
|
516
|
+
if (st.i >= st.source.length) {
|
|
517
|
+
pushE1001(ctx, { type: 'EOF', offset: st.i, line: st.line, column: st.col }, 'EOF')
|
|
518
|
+
return null
|
|
519
|
+
}
|
|
520
|
+
if (st.source[st.i] === ')') {
|
|
521
|
+
st.i += 1
|
|
522
|
+
st.col += 1
|
|
523
|
+
break
|
|
524
|
+
}
|
|
525
|
+
ctx.buf = null
|
|
526
|
+
const u = peek(ctx)
|
|
527
|
+
if (u.type === 'WORD_DEF') {
|
|
528
|
+
const wd = eat(ctx)
|
|
529
|
+
bracket.words.push(String(wd.value))
|
|
530
|
+
} else if (u.type === 'AMP') {
|
|
531
|
+
eat(ctx)
|
|
532
|
+
const tn = eat(ctx)
|
|
533
|
+
if (tn.type !== 'TYPE_NAME') {
|
|
534
|
+
pushE1001(ctx, tn, String(tn.value || tn.type))
|
|
535
|
+
return null
|
|
536
|
+
}
|
|
537
|
+
bracket.types.push(String(tn.value))
|
|
538
|
+
} else {
|
|
539
|
+
pushE1001(ctx, u, String(u.value || u.type))
|
|
540
|
+
return null
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const pathTok = readPathToken(ctx.state, ctx)
|
|
545
|
+
ctx.buf = null
|
|
546
|
+
if (!pathTok) {
|
|
547
|
+
ctx.diagnostics.push({
|
|
548
|
+
code: 'E1003',
|
|
549
|
+
message: diag.e1003BadImportPath(''),
|
|
550
|
+
offset: modTok.offset,
|
|
551
|
+
line: modTok.line,
|
|
552
|
+
column: modTok.column
|
|
553
|
+
})
|
|
554
|
+
return null
|
|
555
|
+
}
|
|
556
|
+
const src = ctx.state.source
|
|
557
|
+
skipInlineWs(ctx.state)
|
|
558
|
+
if (ctx.state.i < src.length) {
|
|
559
|
+
const ch = src[ctx.state.i]
|
|
560
|
+
if (ch !== '\n' && ch !== '\r') {
|
|
561
|
+
const lineEnd = src.indexOf('\n', ctx.state.i)
|
|
562
|
+
const end = lineEnd === -1 ? src.length : lineEnd
|
|
563
|
+
const bad = src.slice(pathTok.offset, end).replace(/\s+$/, '')
|
|
564
|
+
ctx.diagnostics.push({
|
|
565
|
+
code: 'E1003',
|
|
566
|
+
message: diag.e1003BadImportPath(bad),
|
|
567
|
+
offset: pathTok.offset,
|
|
568
|
+
line: pathTok.line,
|
|
569
|
+
column: pathTok.column
|
|
570
|
+
})
|
|
571
|
+
ctx.state.i = end
|
|
572
|
+
return null
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
ab.closeSpan(ctx.state, sp)
|
|
576
|
+
const doc = ctx.pendingDoc ? [...ctx.pendingDoc] : []
|
|
577
|
+
return ab.node('import', sp, {
|
|
578
|
+
module: moduleName,
|
|
579
|
+
path: String(pathTok.value),
|
|
580
|
+
bracket,
|
|
581
|
+
doc
|
|
582
|
+
})
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function parseTypeDeclAst (ctx) {
|
|
586
|
+
if (peek(ctx).type !== 'AMP') return null
|
|
587
|
+
const amp = eat(ctx)
|
|
588
|
+
const declSp = ab.openSpan(amp)
|
|
589
|
+
const name = eat(ctx)
|
|
590
|
+
if (name.type !== 'TYPE_NAME') {
|
|
591
|
+
pushE1001(ctx, name, String(name.value || name.type))
|
|
592
|
+
return null
|
|
593
|
+
}
|
|
594
|
+
const typeParams = []
|
|
595
|
+
while (peek(ctx).type === 'LOWER_IDENT' || peek(ctx).type === 'TYPE_NAME') {
|
|
596
|
+
const p = eat(ctx)
|
|
597
|
+
typeParams.push(String(p.value))
|
|
598
|
+
}
|
|
599
|
+
const headerDoc = drainComments(ctx)
|
|
600
|
+
const u = peek(ctx)
|
|
601
|
+
if (u.type === '|') {
|
|
602
|
+
const tags = []
|
|
603
|
+
while (peek(ctx).type === '|') {
|
|
604
|
+
eat(ctx)
|
|
605
|
+
const tag = eat(ctx)
|
|
606
|
+
if (tag.type !== 'TYPE_NAME') {
|
|
607
|
+
pushE1001(ctx, tag, String(tag.value || tag.type))
|
|
608
|
+
return null
|
|
609
|
+
}
|
|
610
|
+
const tagSp = ab.openSpan(tag)
|
|
611
|
+
let payload = null
|
|
612
|
+
const v = peek(ctx)
|
|
613
|
+
if (
|
|
614
|
+
v.type !== '|' &&
|
|
615
|
+
v.type !== 'EOF' &&
|
|
616
|
+
v.type !== 'AMP' &&
|
|
617
|
+
v.type !== 'WORD_DEF' &&
|
|
618
|
+
v.type !== 'MODULE' &&
|
|
619
|
+
v.type !== 'COMMENT'
|
|
620
|
+
) {
|
|
621
|
+
payload = buildTypeExpr(ctx)
|
|
622
|
+
if (!payload) return null
|
|
623
|
+
tagSp.end = payload.span.end
|
|
624
|
+
}
|
|
625
|
+
const tagDoc = drainComments(ctx)
|
|
626
|
+
ab.closeSpan(ctx.state, tagSp)
|
|
627
|
+
tags.push({
|
|
628
|
+
name: String(tag.value),
|
|
629
|
+
payload,
|
|
630
|
+
span: tagSp,
|
|
631
|
+
doc: tagDoc
|
|
632
|
+
})
|
|
633
|
+
}
|
|
634
|
+
ab.closeSpan(ctx.state, declSp)
|
|
635
|
+
return ab.node('sum_type', declSp, {
|
|
636
|
+
name: String(name.value),
|
|
637
|
+
typeParams,
|
|
638
|
+
doc: headerDoc,
|
|
639
|
+
tags
|
|
640
|
+
})
|
|
641
|
+
}
|
|
642
|
+
if (u.type === ':') {
|
|
643
|
+
const fields = []
|
|
644
|
+
while (peek(ctx).type === ':') {
|
|
645
|
+
eat(ctx)
|
|
646
|
+
const fn = eat(ctx)
|
|
647
|
+
if (fn.type !== 'LOWER_IDENT') {
|
|
648
|
+
pushE1001(ctx, fn, String(fn.value || fn.type))
|
|
649
|
+
return null
|
|
650
|
+
}
|
|
651
|
+
const fnSp = tokenSpan(ctx, fn)
|
|
652
|
+
const ty = buildTypeExpr(ctx)
|
|
653
|
+
if (!ty) return null
|
|
654
|
+
const fieldDoc = drainComments(ctx)
|
|
655
|
+
const fsp = ab.mergeSpan(fnSp, ty.span)
|
|
656
|
+
fields.push({
|
|
657
|
+
name: String(fn.value),
|
|
658
|
+
type: ty,
|
|
659
|
+
span: fsp,
|
|
660
|
+
doc: fieldDoc
|
|
661
|
+
})
|
|
662
|
+
}
|
|
663
|
+
ab.closeSpan(ctx.state, declSp)
|
|
664
|
+
return ab.node('product_type', declSp, {
|
|
665
|
+
name: String(name.value),
|
|
666
|
+
typeParams,
|
|
667
|
+
doc: headerDoc,
|
|
668
|
+
fields
|
|
669
|
+
})
|
|
670
|
+
}
|
|
671
|
+
pushE1001(ctx, u, String(u.value || u.type))
|
|
672
|
+
return null
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function litKindFromToken (t) {
|
|
676
|
+
if (t.type === 'STRING') return 'string'
|
|
677
|
+
if (t.type === 'NUMBER') return 'number'
|
|
678
|
+
if (t.type === 'BIGINT') return 'bigint'
|
|
679
|
+
if (t.type === 'REGEXP') return 'regexp'
|
|
680
|
+
if (t.type === 'NIL') return 'nil'
|
|
681
|
+
if (t.type === 'TRUE' || t.type === 'FALSE') return 'bool'
|
|
682
|
+
return 'string'
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function isLiteralToken (t) {
|
|
686
|
+
return (
|
|
687
|
+
t.type === 'STRING' ||
|
|
688
|
+
t.type === 'NUMBER' ||
|
|
689
|
+
t.type === 'BIGINT' ||
|
|
690
|
+
t.type === 'REGEXP' ||
|
|
691
|
+
t.type === 'NIL' ||
|
|
692
|
+
t.type === 'TRUE' ||
|
|
693
|
+
t.type === 'FALSE'
|
|
694
|
+
)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function parseListLiteralAst (ctx) {
|
|
698
|
+
const o = eat(ctx)
|
|
699
|
+
const sp = ab.openSpan(o)
|
|
700
|
+
const elements = []
|
|
701
|
+
while (peek(ctx).type !== ']') {
|
|
702
|
+
if (peek(ctx).type === 'EOF') return null
|
|
703
|
+
if (peek(ctx).type === 'COMMENT') {
|
|
704
|
+
pushE1001(ctx, peek(ctx), 'comment')
|
|
705
|
+
return null
|
|
706
|
+
}
|
|
707
|
+
ctx.regexpOk = true
|
|
708
|
+
const t = peek(ctx)
|
|
709
|
+
if (!isLiteralToken(t)) {
|
|
710
|
+
pushE1001(ctx, t, String(t.value || t.type))
|
|
711
|
+
return null
|
|
712
|
+
}
|
|
713
|
+
const lt = eat(ctx)
|
|
714
|
+
elements.push(ab.node('literal', tokenSpan(ctx, lt), { litKind: litKindFromToken(lt), raw: String(lt.value) }))
|
|
715
|
+
}
|
|
716
|
+
eat(ctx)
|
|
717
|
+
ab.closeSpan(ctx.state, sp)
|
|
718
|
+
return ab.node('list_literal', sp, { elements })
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function parseExecutableQuotAst (ctx) {
|
|
722
|
+
const o = eat(ctx)
|
|
723
|
+
const sp = ab.openSpan(o)
|
|
724
|
+
const body = []
|
|
725
|
+
while (peek(ctx).type !== ')') {
|
|
726
|
+
if (peek(ctx).type === 'EOF') return null
|
|
727
|
+
if (peek(ctx).type === 'ARROW') {
|
|
728
|
+
const a = eat(ctx)
|
|
729
|
+
pushDiag(ctx, 'E1005', diag.e1005ArrowInQuotation(), a)
|
|
730
|
+
return null
|
|
731
|
+
}
|
|
732
|
+
skipBodyComments(ctx)
|
|
733
|
+
if (peek(ctx).type === ')') break
|
|
734
|
+
ctx.regexpOk = true
|
|
735
|
+
const step = parseWordStepAst(ctx)
|
|
736
|
+
if (!step) return null
|
|
737
|
+
body.push(step)
|
|
738
|
+
}
|
|
739
|
+
eat(ctx)
|
|
740
|
+
ab.closeSpan(ctx.state, sp)
|
|
741
|
+
return ab.node('quotation', sp, { body })
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function parseWordStepAst (ctx) {
|
|
745
|
+
const t = peek(ctx)
|
|
746
|
+
if (t.type === 'EFFECT_ADD') {
|
|
747
|
+
pushDiag(ctx, 'E1006', diag.e1006EffectOutsideSignature(t.value), t)
|
|
748
|
+
eat(ctx)
|
|
749
|
+
return null
|
|
750
|
+
}
|
|
751
|
+
if (t.type === 'EFFECT_SUB') {
|
|
752
|
+
pushDiag(ctx, 'E1006', diag.e1006EffectOutsideSignature(t.value), t)
|
|
753
|
+
eat(ctx)
|
|
754
|
+
return null
|
|
755
|
+
}
|
|
756
|
+
if (t.type === 'WORD_DEF') {
|
|
757
|
+
pushE1001(ctx, t, '@')
|
|
758
|
+
return null
|
|
759
|
+
}
|
|
760
|
+
if (t.type === '(') return parseExecutableQuotAst(ctx)
|
|
761
|
+
if (t.type === '[') return parseListLiteralAst(ctx)
|
|
762
|
+
if (isLiteralToken(t)) {
|
|
763
|
+
const lt = eat(ctx)
|
|
764
|
+
return ab.node('literal', tokenSpan(ctx, lt), { litKind: litKindFromToken(lt), raw: String(lt.value) })
|
|
765
|
+
}
|
|
766
|
+
if (t.type === 'WORD_REF') {
|
|
767
|
+
const w = eat(ctx)
|
|
768
|
+
return ab.node('word_ref', tokenSpan(ctx, w), { name: String(w.value) })
|
|
769
|
+
}
|
|
770
|
+
if (t.type === 'MODULE_WORD_REF') {
|
|
771
|
+
const w = eat(ctx)
|
|
772
|
+
return ab.node('module_word_ref', tokenSpan(ctx, w), { module: w.module, word: w.word })
|
|
773
|
+
}
|
|
774
|
+
if (t.type === 'LOWER_IDENT') {
|
|
775
|
+
const id = eat(ctx)
|
|
776
|
+
const sp = tokenSpan(ctx, id)
|
|
777
|
+
const nm = String(id.value)
|
|
778
|
+
return ab.node('builtin', sp, { name: nm })
|
|
779
|
+
}
|
|
780
|
+
if (t.type === ':') {
|
|
781
|
+
eat(ctx)
|
|
782
|
+
const n = eat(ctx)
|
|
783
|
+
if (n.type !== 'LOWER_IDENT') {
|
|
784
|
+
pushE1001(ctx, n, String(n.value || n.type))
|
|
785
|
+
return null
|
|
786
|
+
}
|
|
787
|
+
return ab.node('slot_write', tokenSpan(ctx, n), { name: String(n.value) })
|
|
788
|
+
}
|
|
789
|
+
if (t.type === ';') {
|
|
790
|
+
eat(ctx)
|
|
791
|
+
const n = eat(ctx)
|
|
792
|
+
if (n.type !== 'LOWER_IDENT') {
|
|
793
|
+
pushE1001(ctx, n, String(n.value || n.type))
|
|
794
|
+
return null
|
|
795
|
+
}
|
|
796
|
+
return ab.node('slot_read', tokenSpan(ctx, n), { name: String(n.value) })
|
|
797
|
+
}
|
|
798
|
+
pushE1001(ctx, t, String(t.value || t.type))
|
|
799
|
+
return null
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function parseWordAst (ctx) {
|
|
803
|
+
const at = eat(ctx)
|
|
804
|
+
if (at.type !== 'WORD_DEF') {
|
|
805
|
+
unget(ctx, at)
|
|
806
|
+
return null
|
|
807
|
+
}
|
|
808
|
+
ctx.regexpOk = false
|
|
809
|
+
const wspan = ab.openSpan(at)
|
|
810
|
+
const signature = parseSignatureAst(ctx)
|
|
811
|
+
if (!signature) {
|
|
812
|
+
if (ctx.diagnostics.length === 0) {
|
|
813
|
+
const t = peek(ctx)
|
|
814
|
+
pushE1001(ctx, t, String(t.value || t.type))
|
|
815
|
+
}
|
|
816
|
+
return null
|
|
817
|
+
}
|
|
818
|
+
ctx.regexpOk = true
|
|
819
|
+
const wordDoc = drainComments(ctx)
|
|
820
|
+
const body = []
|
|
821
|
+
for (;;) {
|
|
822
|
+
skipBodyComments(ctx)
|
|
823
|
+
ctx.regexpOk = true
|
|
824
|
+
const t = peek(ctx)
|
|
825
|
+
if (t.type === 'EOF') break
|
|
826
|
+
if (
|
|
827
|
+
t.type === 'MODULE' ||
|
|
828
|
+
t.type === 'AMP' ||
|
|
829
|
+
t.type === 'WORD_DEF'
|
|
830
|
+
) {
|
|
831
|
+
if (t.column === 1) break
|
|
832
|
+
}
|
|
833
|
+
const step = parseWordStepAst(ctx)
|
|
834
|
+
if (!step) return null
|
|
835
|
+
body.push(step)
|
|
836
|
+
}
|
|
837
|
+
ab.closeSpan(ctx.state, wspan)
|
|
838
|
+
return ab.node('word', wspan, {
|
|
839
|
+
name: String(at.value),
|
|
840
|
+
signature,
|
|
841
|
+
doc: wordDoc,
|
|
842
|
+
body
|
|
843
|
+
})
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function parseTopLevelAst (ctx) {
|
|
847
|
+
const t = peek(ctx)
|
|
848
|
+
ctx.regexpOk = false
|
|
849
|
+
if (t.type === 'MODULE') {
|
|
850
|
+
const n = parseImportAst(ctx)
|
|
851
|
+
if (n) {
|
|
852
|
+
ctx.items.push(n)
|
|
853
|
+
return true
|
|
854
|
+
}
|
|
855
|
+
return false
|
|
856
|
+
}
|
|
857
|
+
if (t.type === 'AMP') {
|
|
858
|
+
const n = parseTypeDeclAst(ctx)
|
|
859
|
+
if (n) {
|
|
860
|
+
ctx.items.push(n)
|
|
861
|
+
return true
|
|
862
|
+
}
|
|
863
|
+
return false
|
|
864
|
+
}
|
|
865
|
+
if (t.type === 'WORD_DEF') {
|
|
866
|
+
const n = parseWordAst(ctx)
|
|
867
|
+
if (n) {
|
|
868
|
+
ctx.items.push(n)
|
|
869
|
+
return true
|
|
870
|
+
}
|
|
871
|
+
return false
|
|
872
|
+
}
|
|
873
|
+
if (t.type === 'LOWER_IDENT' || t.type === 'TYPE_NAME') {
|
|
874
|
+
pushDiag(ctx, 'E1004', diag.e1004UnknownBlock(t.value), t)
|
|
875
|
+
eat(ctx)
|
|
876
|
+
return false
|
|
877
|
+
}
|
|
878
|
+
pushE1001(ctx, t, String(t.value || t.type))
|
|
879
|
+
return false
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function finishSourceFileAst (ctx) {
|
|
883
|
+
const end = ab.endPosFromState(ctx.state)
|
|
884
|
+
const span = {
|
|
885
|
+
start: { offset: 0, line: 1, column: 1 },
|
|
886
|
+
end
|
|
887
|
+
}
|
|
888
|
+
return ab.node('source_file', span, { items: ctx.items })
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
export function parseSourceText (sourceText) {
|
|
892
|
+
const state = createLexerState(sourceText)
|
|
893
|
+
const ctx = createCtx(state)
|
|
894
|
+
let pendingDoc = []
|
|
895
|
+
while (peek(ctx).type !== 'EOF') {
|
|
896
|
+
pendingDoc = pendingDoc.concat(drainComments(ctx))
|
|
897
|
+
if (peek(ctx).type === 'EOF') break
|
|
898
|
+
ctx.pendingDoc = pendingDoc
|
|
899
|
+
pendingDoc = []
|
|
900
|
+
const ok = parseTopLevelAst(ctx)
|
|
901
|
+
ctx.pendingDoc = []
|
|
902
|
+
if (ctx.diagnostics.length || !ok) break
|
|
903
|
+
}
|
|
904
|
+
drainComments(ctx)
|
|
905
|
+
if (peek(ctx).type !== 'EOF' && ctx.diagnostics.length === 0) {
|
|
906
|
+
const t = peek(ctx)
|
|
907
|
+
pushE1001(ctx, t, String(t.value || t.type))
|
|
908
|
+
}
|
|
909
|
+
const ast =
|
|
910
|
+
ctx.diagnostics.length === 0 ? finishSourceFileAst(ctx) : undefined
|
|
911
|
+
return { diagnostics: ctx.diagnostics, ast }
|
|
912
|
+
}
|