@aster-cloud/aster-lang-ts 1.2.5 → 1.2.6
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/CHANGELOG.md +42 -0
- package/dist/scripts/generate_error_codes.js +3 -0
- package/dist/scripts/generate_error_codes.js.map +1 -1
- package/dist/src/browser.d.ts +18 -0
- package/dist/src/browser.d.ts.map +1 -1
- package/dist/src/browser.js +13 -18
- package/dist/src/browser.js.map +1 -1
- package/dist/src/cli/utils/error-handler.js +1 -1
- package/dist/src/cli/utils/error-handler.js.map +1 -1
- package/dist/src/core/interpreter.js +4 -0
- package/dist/src/core/interpreter.js.map +1 -1
- package/dist/src/core/resolve-typed-references.d.ts.map +1 -1
- package/dist/src/core/resolve-typed-references.js +34 -22
- package/dist/src/core/resolve-typed-references.js.map +1 -1
- package/dist/src/diagnostics/diagnostics.d.ts +33 -2
- package/dist/src/diagnostics/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics/diagnostics.js +47 -1
- package/dist/src/diagnostics/diagnostics.js.map +1 -1
- package/dist/src/diagnostics/error_codes.d.ts +21 -1
- package/dist/src/diagnostics/error_codes.d.ts.map +1 -1
- package/dist/src/diagnostics/error_codes.js +160 -0
- package/dist/src/diagnostics/error_codes.js.map +1 -1
- package/dist/src/frontend/lexer.js +1 -1
- package/dist/src/frontend/lexer.js.map +1 -1
- package/dist/src/frontend/resolve-declarations.d.ts.map +1 -1
- package/dist/src/frontend/resolve-declarations.js +24 -1
- package/dist/src/frontend/resolve-declarations.js.map +1 -1
- package/dist/src/lsp/completion.d.ts +2 -2
- package/dist/src/lsp/completion.d.ts.map +1 -1
- package/dist/src/lsp/completion.js +65 -55
- package/dist/src/lsp/completion.js.map +1 -1
- package/dist/src/lsp/diagnostics.d.ts.map +1 -1
- package/dist/src/lsp/diagnostics.js +4 -0
- package/dist/src/lsp/diagnostics.js.map +1 -1
- package/dist/src/lsp/member-completion.d.ts +10 -0
- package/dist/src/lsp/member-completion.d.ts.map +1 -1
- package/dist/src/lsp/member-completion.js +25 -10
- package/dist/src/lsp/member-completion.js.map +1 -1
- package/dist/src/parser/constraint-parser.d.ts +2 -1
- package/dist/src/parser/constraint-parser.d.ts.map +1 -1
- package/dist/src/parser/constraint-parser.js +9 -7
- package/dist/src/parser/constraint-parser.js.map +1 -1
- package/dist/src/parser/context.d.ts.map +1 -1
- package/dist/src/parser/context.js +1 -0
- package/dist/src/parser/context.js.map +1 -1
- package/dist/src/parser/decl-parser.d.ts +4 -3
- package/dist/src/parser/decl-parser.d.ts.map +1 -1
- package/dist/src/parser/decl-parser.js +17 -16
- package/dist/src/parser/decl-parser.js.map +1 -1
- package/dist/src/parser/expr-stmt-parser.d.ts +10 -9
- package/dist/src/parser/expr-stmt-parser.d.ts.map +1 -1
- package/dist/src/parser/expr-stmt-parser.js +96 -90
- package/dist/src/parser/expr-stmt-parser.js.map +1 -1
- package/dist/src/parser/field-variant-parser.d.ts +4 -3
- package/dist/src/parser/field-variant-parser.d.ts.map +1 -1
- package/dist/src/parser/field-variant-parser.js +3 -2
- package/dist/src/parser/field-variant-parser.js.map +1 -1
- package/dist/src/parser/import-parser.d.ts +5 -5
- package/dist/src/parser/import-parser.d.ts.map +1 -1
- package/dist/src/parser/import-parser.js +4 -3
- package/dist/src/parser/import-parser.js.map +1 -1
- package/dist/src/parser/parse-issue.d.ts +44 -0
- package/dist/src/parser/parse-issue.d.ts.map +1 -0
- package/dist/src/parser/parse-issue.js +37 -0
- package/dist/src/parser/parse-issue.js.map +1 -0
- package/dist/src/parser/parser-tools.d.ts +5 -5
- package/dist/src/parser/parser-tools.d.ts.map +1 -1
- package/dist/src/parser/parser-tools.js +20 -7
- package/dist/src/parser/parser-tools.js.map +1 -1
- package/dist/src/parser/type-parser.d.ts +4 -3
- package/dist/src/parser/type-parser.d.ts.map +1 -1
- package/dist/src/parser/type-parser.js +18 -16
- package/dist/src/parser/type-parser.js.map +1 -1
- package/dist/src/typecheck/browser.d.ts.map +1 -1
- package/dist/src/typecheck/browser.js +4 -11
- package/dist/src/typecheck/browser.js.map +1 -1
- package/dist/src/typecheck/expression.js +2 -2
- package/dist/src/typecheck/expression.js.map +1 -1
- package/dist/src/typecheck/generics.d.ts.map +1 -1
- package/dist/src/typecheck/generics.js +2 -26
- package/dist/src/typecheck/generics.js.map +1 -1
- package/dist/src/typecheck/module.d.ts.map +1 -1
- package/dist/src/typecheck/module.js +4 -10
- package/dist/src/typecheck/module.js.map +1 -1
- package/dist/src/typecheck/pattern.d.ts.map +1 -1
- package/dist/src/typecheck/pattern.js +5 -4
- package/dist/src/typecheck/pattern.js.map +1 -1
- package/dist/src/typecheck/pure.d.ts +2 -0
- package/dist/src/typecheck/pure.d.ts.map +1 -1
- package/dist/src/typecheck/pure.js +4 -0
- package/dist/src/typecheck/pure.js.map +1 -1
- package/dist/src/typecheck/statement.d.ts +8 -0
- package/dist/src/typecheck/statement.d.ts.map +1 -1
- package/dist/src/typecheck/statement.js +60 -3
- package/dist/src/typecheck/statement.js.map +1 -1
- package/dist/src/typecheck/workflow.d.ts.map +1 -1
- package/dist/src/typecheck/workflow.js +11 -33
- package/dist/src/typecheck/workflow.js.map +1 -1
- package/dist/test/integration/lsp/lsp-completion.test.js +3 -1
- package/dist/test/integration/lsp/lsp-completion.test.js.map +1 -1
- package/dist/test/integration/scripts/typecheck-cli-repl.test.js +2 -1
- package/dist/test/integration/scripts/typecheck-cli-repl.test.js.map +1 -1
- package/dist/test/unit/diagnostics/diagnostic-overlays.test.js +8 -0
- package/dist/test/unit/diagnostics/diagnostic-overlays.test.js.map +1 -1
- package/dist/test/unit/diagnostics/legacy-codes.test.d.ts +6 -0
- package/dist/test/unit/diagnostics/legacy-codes.test.d.ts.map +1 -0
- package/dist/test/unit/diagnostics/legacy-codes.test.js +81 -0
- package/dist/test/unit/diagnostics/legacy-codes.test.js.map +1 -0
- package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.d.ts +9 -0
- package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.d.ts.map +1 -0
- package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.js +121 -0
- package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.js.map +1 -0
- package/dist/test/unit/frontend/resolve-declarations.test.js +47 -0
- package/dist/test/unit/frontend/resolve-declarations.test.js.map +1 -1
- package/dist/test/unit/int-pattern-decimal.test.d.ts +2 -0
- package/dist/test/unit/int-pattern-decimal.test.d.ts.map +1 -0
- package/dist/test/unit/int-pattern-decimal.test.js +54 -0
- package/dist/test/unit/int-pattern-decimal.test.js.map +1 -0
- package/dist/test/unit/lsp/completion-lexicon.test.js +1 -1
- package/dist/test/unit/lsp/completion-lexicon.test.js.map +1 -1
- package/dist/test/unit/lsp/completion-list.test.d.ts +2 -0
- package/dist/test/unit/lsp/completion-list.test.d.ts.map +1 -0
- package/dist/test/unit/lsp/completion-list.test.js +64 -0
- package/dist/test/unit/lsp/completion-list.test.js.map +1 -0
- package/dist/test/unit/lsp/member-completion.test.js +99 -1
- package/dist/test/unit/lsp/member-completion.test.js.map +1 -1
- package/dist/test/unit/lsp/resolution-completion.test.js +2 -2
- package/dist/test/unit/lsp/resolution-completion.test.js.map +1 -1
- package/dist/test/unit/lsp/resolution-diagnostics.test.js +4 -2
- package/dist/test/unit/lsp/resolution-diagnostics.test.js.map +1 -1
- package/dist/test/unit/parser/depth-limit.test.js +3 -3
- package/dist/test/unit/parser/depth-limit.test.js.map +1 -1
- package/dist/test/unit/parser/parser.test.js +4 -1
- package/dist/test/unit/parser/parser.test.js.map +1 -1
- package/dist/test/unit/typecheck/java-parity-281.test.d.ts +2 -0
- package/dist/test/unit/typecheck/java-parity-281.test.d.ts.map +1 -0
- package/dist/test/unit/typecheck/java-parity-281.test.js +87 -0
- package/dist/test/unit/typecheck/java-parity-281.test.js.map +1 -0
- package/dist/test/unit/typecheck/regression-fixes.test.js +2 -2
- package/dist/test/unit/typecheck/regression-fixes.test.js.map +1 -1
- package/dist/test/unit/typecheck/return-paths.test.d.ts +2 -0
- package/dist/test/unit/typecheck/return-paths.test.d.ts.map +1 -0
- package/dist/test/unit/typecheck/return-paths.test.js +54 -0
- package/dist/test/unit/typecheck/return-paths.test.js.map +1 -0
- package/dist/test/unit/typecheck/typecheck-advanced.test.js +2 -2
- package/dist/test/unit/typecheck/typecheck-advanced.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ import { parseType, parseEffectList } from './type-parser.js';
|
|
|
7
7
|
import { parseConstraints } from './constraint-parser.js';
|
|
8
8
|
import { inferFieldType, refineInferredType } from './type-inference.js';
|
|
9
9
|
import { assignSpan, cloneSpan, lastNonLayoutToken, spanFromSources, spanFromTokens, } from './span-utils.js';
|
|
10
|
+
import { Issue, kwParam, lit, tokenLit, tokenName } from './parse-issue.js';
|
|
10
11
|
const WAIT_FOR_PARTS = kwParts(KW.WAIT_FOR);
|
|
11
12
|
const MAX_ATTEMPTS_PARTS = kwParts(KW.MAX_ATTEMPTS);
|
|
12
13
|
function peekKeywordIgnoringLayout(ctx, startIndex = ctx.index) {
|
|
@@ -43,28 +44,31 @@ const SUGAR_OUTCOMES = new Map([
|
|
|
43
44
|
[`${KW.ESCALATE} _`, 'Verdict.escalate'],
|
|
44
45
|
[`${KW.REQUIRE_APPROVAL_BY} _ ${KW.BECAUSE} _`, 'Verdict.require_approval'],
|
|
45
46
|
]);
|
|
46
|
-
const SUGAR_OUTCOME_ERROR = 'Expected allow, deny, escalate or require approval by … because … after When/Otherwise';
|
|
47
|
-
const OTHERWISE_NOT_LAST = 'Otherwise must be the last statement of its block';
|
|
47
|
+
const SUGAR_OUTCOME_ERROR = Issue.expectedAfter(tokenName('verdictOutcome'), tokenName('whenOrOtherwise'), 'Expected allow, deny, escalate or require approval by … because … after When/Otherwise');
|
|
48
|
+
const OTHERWISE_NOT_LAST = Issue.misplaced(kwParam(KW.OTHERWISE), tokenName('lastInBlock'), 'Otherwise must be the last statement of its block');
|
|
48
49
|
/**
|
|
49
50
|
* 块内语句解析器,错误次序对齐 Java AstBuilder:先查本块的 Otherwise 收尾(requireOtherwiseLast),
|
|
50
51
|
* 再校验各语句的语法糖结论。因此 Otherwise 之后再有语句立即报错;本块结论非法的错误先记下,
|
|
51
52
|
* 由 finish 在块结束时报出(只报第一条)。状态都在本块闭包里,随块解析结束而释放。
|
|
52
53
|
*/
|
|
53
54
|
function blockStatementParser(ctx, error, statements) {
|
|
54
|
-
|
|
55
|
+
// Otherwise 收尾的位置:其后再有语句时错误落在该 Otherwise 上(与 Java requireOtherwiseLast 一致)
|
|
56
|
+
let closedBy;
|
|
57
|
+
// 记下 token:错误落在结论起点(与 Java AstBuilder 报在 sugarOutcome 上下文一致),而非块末
|
|
55
58
|
let pendingOutcomeError;
|
|
56
|
-
const deferOutcomeError = (
|
|
59
|
+
const deferOutcomeError = (issue, tok) => { pendingOutcomeError ??= { issue, tok }; };
|
|
57
60
|
return {
|
|
58
61
|
next: () => {
|
|
59
|
-
if (
|
|
60
|
-
error(OTHERWISE_NOT_LAST);
|
|
62
|
+
if (closedBy !== undefined)
|
|
63
|
+
error(OTHERWISE_NOT_LAST, closedBy);
|
|
61
64
|
// 语句开头的 Otherwise/else 必走 parseOtherwiseSugar(If 的 else 分支由 If 自身消费)
|
|
62
|
-
|
|
65
|
+
if (isElseWord(ctx))
|
|
66
|
+
closedBy = ctx.peek();
|
|
63
67
|
statements.push(parseStatement(ctx, error, deferOutcomeError));
|
|
64
68
|
},
|
|
65
69
|
finish: () => {
|
|
66
70
|
if (pendingOutcomeError !== undefined)
|
|
67
|
-
error(pendingOutcomeError);
|
|
71
|
+
error(pendingOutcomeError.issue, pendingOutcomeError.tok);
|
|
68
72
|
},
|
|
69
73
|
};
|
|
70
74
|
}
|
|
@@ -91,7 +95,7 @@ export function parseBlock(ctx, error) {
|
|
|
91
95
|
ctx.consumeNewlines();
|
|
92
96
|
}
|
|
93
97
|
if (!ctx.at(TokenKind.DEDENT))
|
|
94
|
-
error('Expected dedent');
|
|
98
|
+
error(Issue.expected(tokenName('dedent'), 'Expected dedent'));
|
|
95
99
|
blockParser.finish();
|
|
96
100
|
const endTok = ctx.peek();
|
|
97
101
|
ctx.next();
|
|
@@ -118,7 +122,7 @@ export function parseBlock(ctx, error) {
|
|
|
118
122
|
ctx.consumeNewlines();
|
|
119
123
|
}
|
|
120
124
|
if (statements.length === 0)
|
|
121
|
-
error('Expected at least one statement in function body');
|
|
125
|
+
error(Issue.emptyBlock(tokenName('functionBody'), tokenName('statement'), 'Expected at least one statement in function body'));
|
|
122
126
|
blockParser.finish();
|
|
123
127
|
const endTok = ctx.tokens[ctx.index - 1] || startTok;
|
|
124
128
|
const b = Node.Block(statements);
|
|
@@ -151,9 +155,9 @@ export function parseExplicitBlock(ctx, error) {
|
|
|
151
155
|
ctx.consumeNewlines();
|
|
152
156
|
}
|
|
153
157
|
if (!ctx.at(TokenKind.BLOCK_END))
|
|
154
|
-
error("Expected explicit block end word (e.g. '毕')");
|
|
158
|
+
error(Issue.expected(tokenName('blockEndWord'), "Expected explicit block end word (e.g. '毕')"));
|
|
155
159
|
if (statements.length === 0)
|
|
156
|
-
error('Expected at least one statement in explicit block');
|
|
160
|
+
error(Issue.emptyBlock(tokenName('explicitBlock'), tokenName('statement'), 'Expected at least one statement in explicit block'));
|
|
157
161
|
blockParser.finish();
|
|
158
162
|
ctx.next(); // consume BLOCK_END
|
|
159
163
|
const b = Node.Block(statements);
|
|
@@ -169,7 +173,7 @@ export function parseExplicitBlock(ctx, error) {
|
|
|
169
173
|
*/
|
|
170
174
|
function expectPeriodEnd(ctx, error) {
|
|
171
175
|
if (!ctx.at(TokenKind.DOT))
|
|
172
|
-
error("Expected '.' at end of statement");
|
|
176
|
+
error(Issue.expectedStatementEnd(lit('.'), "Expected '.' at end of statement"));
|
|
173
177
|
ctx.next();
|
|
174
178
|
}
|
|
175
179
|
/**
|
|
@@ -212,11 +216,11 @@ function skipInlineElseLayout(ctx) {
|
|
|
212
216
|
function parseInlineIf(ctx, error, cond) {
|
|
213
217
|
skipInlineThenLayout(ctx);
|
|
214
218
|
if (!ctx.isKeyword(KW.THEN))
|
|
215
|
-
error(`Expected ${kwq(ctx, KW.THEN)} in inline if`);
|
|
219
|
+
error(Issue.expectedIn(kwParam(KW.THEN), tokenName('inlineIf'), `Expected ${kwq(ctx, KW.THEN)} in inline if`));
|
|
216
220
|
ctx.nextWord();
|
|
217
221
|
// then 分支:单个 return(内联,不带句点;句点由整条 inline-if 末尾的 return 承担)。
|
|
218
222
|
if (!ctx.isKeyword(KW.RETURN))
|
|
219
|
-
error(`Expected ${kwq(ctx, KW.RETURN)} after ${kwq(ctx, KW.THEN)}`);
|
|
223
|
+
error(Issue.expectedAfter(kwParam(KW.RETURN), kwParam(KW.THEN), `Expected ${kwq(ctx, KW.RETURN)} after ${kwq(ctx, KW.THEN)}`));
|
|
220
224
|
const thenRetTok = ctx.peek();
|
|
221
225
|
ctx.nextWord();
|
|
222
226
|
const thenExpr = parseExpr(ctx, error);
|
|
@@ -245,7 +249,7 @@ function parseInlineIf(ctx, error, cond) {
|
|
|
245
249
|
else {
|
|
246
250
|
// else return X.(末尾 return,带句点)。
|
|
247
251
|
if (!ctx.isKeyword(KW.RETURN))
|
|
248
|
-
error(`Expected ${kwq(ctx, KW.RETURN)} or ${kwq(ctx, KW.IF)} after ${kwKindq(ctx, SemanticTokenKind.OTHERWISE, 'else')}`);
|
|
252
|
+
error(Issue.expectedAfter(tokenName('returnOrIf'), kwParam(KW.OTHERWISE), `Expected ${kwq(ctx, KW.RETURN)} or ${kwq(ctx, KW.IF)} after ${kwKindq(ctx, SemanticTokenKind.OTHERWISE, 'else')}`));
|
|
249
253
|
const elseRetTok = ctx.peek();
|
|
250
254
|
ctx.nextWord();
|
|
251
255
|
const elseExpr = parseExpr(ctx, error);
|
|
@@ -293,7 +297,7 @@ function parseSugarOutcome(ctx, error, reportOutcomeError) {
|
|
|
293
297
|
const startTok = ctx.peek();
|
|
294
298
|
const badStart = OUTCOME_BAD_START.get(startTok.kind);
|
|
295
299
|
if (badStart !== undefined)
|
|
296
|
-
error(`Unexpected '${badStart}' where an outcome was expected`);
|
|
300
|
+
error(Issue.unexpected(lit(badStart), `Unexpected '${badStart}' where an outcome was expected`));
|
|
297
301
|
const shape = [];
|
|
298
302
|
const args = [];
|
|
299
303
|
for (let tok = ctx.peek(); !OUTCOME_END.has(tok.kind); tok = ctx.peek()) {
|
|
@@ -309,7 +313,7 @@ function parseSugarOutcome(ctx, error, reportOutcomeError) {
|
|
|
309
313
|
// 查表落空时 target 为空串:错误若被延后,整次解析终将失败,该节点不会流出
|
|
310
314
|
const target = SUGAR_OUTCOMES.get(shape.join(' ')) ?? '';
|
|
311
315
|
if (target === '')
|
|
312
|
-
reportOutcomeError(SUGAR_OUTCOME_ERROR);
|
|
316
|
+
reportOutcomeError(SUGAR_OUTCOME_ERROR, startTok);
|
|
313
317
|
// 与 Java 一致:合成的 Name/Call/Return 共用结论词范围(不含句点)
|
|
314
318
|
const span = spanFromTokens(startTok, lastNonLayoutToken(ctx));
|
|
315
319
|
const call = assignSpan(Node.Call(assignSpan(Node.Name(target), span), args), span);
|
|
@@ -320,7 +324,7 @@ function parseWhenSugar(ctx, error, reportOutcomeError) {
|
|
|
320
324
|
const whenTok = ctx.nextWord();
|
|
321
325
|
const cond = parseExpr(ctx, error);
|
|
322
326
|
if (!ctx.at(TokenKind.COMMA))
|
|
323
|
-
error("Expected ',' after When condition");
|
|
327
|
+
error(Issue.expectedAfter(lit(','), tokenName('whenCondition'), "Expected ',' after When condition"));
|
|
324
328
|
ctx.next();
|
|
325
329
|
const ret = parseSugarOutcome(ctx, error, reportOutcomeError);
|
|
326
330
|
expectPeriodEnd(ctx, error);
|
|
@@ -346,7 +350,7 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
346
350
|
ctx.nextWord();
|
|
347
351
|
const nameTok = ctx.peek();
|
|
348
352
|
const name = parseIdent(ctx, error);
|
|
349
|
-
expectKeyword(ctx, error, KW.BE, `Use ${kwq(ctx, KW.BE)} in bindings: '${kw(ctx, KW.LET, 'Let')} x ${kw(ctx, KW.BE)} ...'.`);
|
|
353
|
+
expectKeyword(ctx, error, KW.BE, Issue.expectedIn(kwParam(KW.BE), tokenName('binding'), `Use ${kwq(ctx, KW.BE)} in bindings: '${kw(ctx, KW.LET, 'Let')} x ${kw(ctx, KW.BE)} ...'.`));
|
|
350
354
|
// Special-case lambda block form to avoid trailing '.'
|
|
351
355
|
if ((ctx.isKeyword('a') && tokLowerAt(ctx, ctx.index + 1) === 'function') || ctx.isKeyword('function')) {
|
|
352
356
|
if (ctx.isKeyword('a'))
|
|
@@ -354,7 +358,7 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
354
358
|
const functionTok = ctx.nextWord(); // 'function'
|
|
355
359
|
const params = parseParamList(ctx, error);
|
|
356
360
|
expectCommaOr(ctx);
|
|
357
|
-
expectKeyword(ctx, error, KW.PRODUCE, `Expected ${kwq(ctx, KW.PRODUCE)} and return type`);
|
|
361
|
+
expectKeyword(ctx, error, KW.PRODUCE, Issue.expected(kwParam(KW.PRODUCE), `Expected ${kwq(ctx, KW.PRODUCE)} and return type`));
|
|
358
362
|
let retType;
|
|
359
363
|
if (ctx.at(TokenKind.COLON)) {
|
|
360
364
|
retType = Node.TypeName('Unknown');
|
|
@@ -364,7 +368,7 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
364
368
|
retType = parseType(ctx, error);
|
|
365
369
|
}
|
|
366
370
|
if (!ctx.at(TokenKind.COLON))
|
|
367
|
-
error("Expected ':' after return type in lambda");
|
|
371
|
+
error(Issue.expectedAfterIn(lit(':'), tokenName('returnType'), tokenName('lambda'), "Expected ':' after return type in lambda"));
|
|
368
372
|
ctx.next();
|
|
369
373
|
expectNewline(ctx, error);
|
|
370
374
|
const body = parseBlock(ctx, error);
|
|
@@ -390,7 +394,7 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
390
394
|
const setTok = ctx.peek();
|
|
391
395
|
ctx.nextWord();
|
|
392
396
|
const name = parseIdent(ctx, error);
|
|
393
|
-
expectKeyword(ctx, error, KW.TO_WORD, `Use ${kwq(ctx, KW.TO_WORD)} in assignments: '${kw(ctx, KW.SET, 'Set')} x ${kw(ctx, KW.TO_WORD)} ...'.`);
|
|
397
|
+
expectKeyword(ctx, error, KW.TO_WORD, Issue.expectedIn(kwParam(KW.TO_WORD), tokenName('assignment'), `Use ${kwq(ctx, KW.TO_WORD)} in assignments: '${kw(ctx, KW.SET, 'Set')} x ${kw(ctx, KW.TO_WORD)} ...'.`));
|
|
394
398
|
const expr = parseExpr(ctx, error);
|
|
395
399
|
expectPeriodEnd(ctx, error);
|
|
396
400
|
const nd = Node.Set(name, expr);
|
|
@@ -425,10 +429,10 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
425
429
|
if (ctx.isKeyword(KW.AWAIT)) {
|
|
426
430
|
const awaitTok = ctx.nextWord();
|
|
427
431
|
if (!ctx.at(TokenKind.LPAREN))
|
|
428
|
-
error("Expected '(' after await");
|
|
432
|
+
error(Issue.expectedAfter(lit('('), lit('await'), "Expected '(' after await"));
|
|
429
433
|
const args = parseArgList(ctx, error);
|
|
430
434
|
if (args.length !== 1)
|
|
431
|
-
error('await(expr) takes exactly one argument');
|
|
435
|
+
error(Issue.callArity(lit('await'), 1, args.length, 'await(expr) takes exactly one argument'));
|
|
432
436
|
const target = assignTokenSpan(Node.Name('await'), awaitTok);
|
|
433
437
|
const callSpanEnd = lastNonLayoutToken(ctx);
|
|
434
438
|
const aw = Node.Call(target, args);
|
|
@@ -476,7 +480,7 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
476
480
|
ctx.nextWord();
|
|
477
481
|
const expr = parseExpr(ctx, error);
|
|
478
482
|
if (!ctx.at(TokenKind.COLON))
|
|
479
|
-
error("Expected ':' after match expression");
|
|
483
|
+
error(Issue.expectedAfter(lit(':'), tokenName('matchExpression'), "Expected ':' after match expression"));
|
|
480
484
|
ctx.next();
|
|
481
485
|
expectNewline(ctx, error);
|
|
482
486
|
const cases = parseCases(ctx, error);
|
|
@@ -509,9 +513,9 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
509
513
|
}
|
|
510
514
|
if (ctx.isKeyword(KW.WITHIN)) {
|
|
511
515
|
ctx.nextWord();
|
|
512
|
-
expectKeyword(ctx, error, KW.SCOPE, `Expected ${kwq(ctx, KW.SCOPE)} after ${kwq(ctx, KW.WITHIN, 'Within')}`);
|
|
516
|
+
expectKeyword(ctx, error, KW.SCOPE, Issue.expectedAfter(kwParam(KW.SCOPE), kwParam(KW.WITHIN), `Expected ${kwq(ctx, KW.SCOPE)} after ${kwq(ctx, KW.WITHIN, 'Within')}`));
|
|
513
517
|
if (!ctx.at(TokenKind.COLON))
|
|
514
|
-
error(`Expected ':' after ${kwq(ctx, KW.SCOPE)}`);
|
|
518
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.SCOPE), `Expected ':' after ${kwq(ctx, KW.SCOPE)}`));
|
|
515
519
|
ctx.next();
|
|
516
520
|
expectNewline(ctx, error);
|
|
517
521
|
return parseBlock(ctx, error); // Lowering later
|
|
@@ -519,8 +523,8 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
519
523
|
if (ctx.isKeyword(KW.START)) {
|
|
520
524
|
const startTok = ctx.nextWord();
|
|
521
525
|
const name = parseIdent(ctx, error);
|
|
522
|
-
expectKeyword(ctx, error, KW.AS, `Expected ${kwq(ctx, KW.AS)} after name`);
|
|
523
|
-
expectKeyword(ctx, error, KW.ASYNC, `Expected ${kwq(ctx, KW.ASYNC)}`);
|
|
526
|
+
expectKeyword(ctx, error, KW.AS, Issue.expectedAfter(kwParam(KW.AS), tokenName('name'), `Expected ${kwq(ctx, KW.AS)} after name`));
|
|
527
|
+
expectKeyword(ctx, error, KW.ASYNC, Issue.expected(kwParam(KW.ASYNC), `Expected ${kwq(ctx, KW.ASYNC)}`));
|
|
524
528
|
const expr = parseExpr(ctx, error);
|
|
525
529
|
expectPeriodEnd(ctx, error);
|
|
526
530
|
const nd = Node.Start(name, expr);
|
|
@@ -549,17 +553,17 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
549
553
|
assignSpan(nd, spanFromTokens(waitStart, endTok));
|
|
550
554
|
return nd;
|
|
551
555
|
}
|
|
552
|
-
error('Unknown statement');
|
|
556
|
+
error(Issue.unexpectedConstruct(tokenName('statement'), 'Unknown statement'));
|
|
553
557
|
}
|
|
554
558
|
function parseWorkflow(ctx, error) {
|
|
555
559
|
const workflowTok = ctx.peek();
|
|
556
|
-
expectKeyword(ctx, error, KW.WORKFLOW, `Expected ${kwq(ctx, KW.WORKFLOW)}`);
|
|
560
|
+
expectKeyword(ctx, error, KW.WORKFLOW, Issue.expected(kwParam(KW.WORKFLOW), `Expected ${kwq(ctx, KW.WORKFLOW)}`));
|
|
557
561
|
if (!ctx.at(TokenKind.COLON))
|
|
558
|
-
error(`Expected ':' after ${kwq(ctx, KW.WORKFLOW)}`);
|
|
562
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.WORKFLOW), `Expected ':' after ${kwq(ctx, KW.WORKFLOW)}`));
|
|
559
563
|
ctx.next();
|
|
560
564
|
expectNewline(ctx, error);
|
|
561
565
|
if (!ctx.at(TokenKind.INDENT))
|
|
562
|
-
error('Expected indent after workflow header');
|
|
566
|
+
error(Issue.expectedAfter(tokenName('indent'), tokenName('workflowHeader'), 'Expected indent after workflow header'));
|
|
563
567
|
ctx.next();
|
|
564
568
|
ctx.consumeNewlines();
|
|
565
569
|
const steps = [];
|
|
@@ -568,7 +572,7 @@ function parseWorkflow(ctx, error) {
|
|
|
568
572
|
ctx.consumeNewlines();
|
|
569
573
|
}
|
|
570
574
|
if (steps.length === 0)
|
|
571
|
-
error('Workflow must declare at least one step');
|
|
575
|
+
error(Issue.emptyBlock(kwParam(KW.WORKFLOW), kwParam(KW.STEP), 'Workflow must declare at least one step'));
|
|
572
576
|
let retry;
|
|
573
577
|
if (ctx.isKeyword(KW.RETRY)) {
|
|
574
578
|
retry = parseRetryPolicy(ctx, error);
|
|
@@ -580,7 +584,7 @@ function parseWorkflow(ctx, error) {
|
|
|
580
584
|
ctx.consumeNewlines();
|
|
581
585
|
}
|
|
582
586
|
if (!ctx.at(TokenKind.DEDENT))
|
|
583
|
-
error('Expected dedent after workflow body');
|
|
587
|
+
error(Issue.expectedAfter(tokenName('dedent'), tokenName('workflowBody'), 'Expected dedent after workflow body'));
|
|
584
588
|
ctx.next();
|
|
585
589
|
ctx.consumeNewlines();
|
|
586
590
|
expectPeriodEnd(ctx, error);
|
|
@@ -591,19 +595,19 @@ function parseWorkflow(ctx, error) {
|
|
|
591
595
|
}
|
|
592
596
|
function parseStep(ctx, error) {
|
|
593
597
|
const stepTok = ctx.peek();
|
|
594
|
-
expectKeyword(ctx, error, KW.STEP, `Expected ${kwq(ctx, KW.STEP)}`);
|
|
598
|
+
expectKeyword(ctx, error, KW.STEP, Issue.expected(kwParam(KW.STEP), `Expected ${kwq(ctx, KW.STEP)}`));
|
|
595
599
|
const name = parseIdent(ctx, error);
|
|
596
600
|
const dependencies = [];
|
|
597
601
|
if (ctx.isKeyword(KW.DEPENDS)) {
|
|
598
602
|
ctx.nextWord();
|
|
599
|
-
expectKeyword(ctx, error, KW.ON, `Expected ${kwq(ctx, KW.ON)} after ${kwq(ctx, KW.DEPENDS)}`);
|
|
603
|
+
expectKeyword(ctx, error, KW.ON, Issue.expectedAfter(kwParam(KW.ON), kwParam(KW.DEPENDS), `Expected ${kwq(ctx, KW.ON)} after ${kwq(ctx, KW.DEPENDS)}`));
|
|
600
604
|
if (!ctx.at(TokenKind.LBRACKET))
|
|
601
|
-
error(`Expected '[' after '${kw(ctx, KW.DEPENDS)} ${kw(ctx, KW.ON)}'`);
|
|
605
|
+
error(Issue.expectedAfter(lit('['), kwParam(KW.DEPENDS), `Expected '[' after '${kw(ctx, KW.DEPENDS)} ${kw(ctx, KW.ON)}'`));
|
|
602
606
|
ctx.next();
|
|
603
607
|
if (!ctx.at(TokenKind.RBRACKET)) {
|
|
604
608
|
while (true) {
|
|
605
609
|
if (!ctx.at(TokenKind.STRING))
|
|
606
|
-
error('Expected string dependency name');
|
|
610
|
+
error(Issue.expected(tokenName('dependencyName'), 'Expected string dependency name'));
|
|
607
611
|
const depTok = ctx.next();
|
|
608
612
|
dependencies.push(depTok.value);
|
|
609
613
|
if (ctx.at(TokenKind.COMMA)) {
|
|
@@ -613,15 +617,15 @@ function parseStep(ctx, error) {
|
|
|
613
617
|
if (ctx.at(TokenKind.RBRACKET)) {
|
|
614
618
|
break;
|
|
615
619
|
}
|
|
616
|
-
error("Expected ',' or ']' after dependency name");
|
|
620
|
+
error(Issue.expectedAfter(tokenName('commaOrCloseBracket'), tokenName('dependencyName'), "Expected ',' or ']' after dependency name"));
|
|
617
621
|
}
|
|
618
622
|
}
|
|
619
623
|
if (!ctx.at(TokenKind.RBRACKET))
|
|
620
|
-
error("Expected ']' to close dependency list");
|
|
624
|
+
error(Issue.expectedIn(lit(']'), tokenName('dependencyList'), "Expected ']' to close dependency list"));
|
|
621
625
|
ctx.next();
|
|
622
626
|
}
|
|
623
627
|
if (!ctx.at(TokenKind.COLON))
|
|
624
|
-
error("Expected ':' after step name");
|
|
628
|
+
error(Issue.expectedAfter(lit(':'), tokenName('stepName'), "Expected ':' after step name"));
|
|
625
629
|
ctx.next();
|
|
626
630
|
expectNewline(ctx, error);
|
|
627
631
|
const body = parseBlock(ctx, error);
|
|
@@ -630,7 +634,7 @@ function parseStep(ctx, error) {
|
|
|
630
634
|
if (ctx.isKeyword(KW.COMPENSATE)) {
|
|
631
635
|
ctx.nextWord();
|
|
632
636
|
if (!ctx.at(TokenKind.COLON))
|
|
633
|
-
error(`Expected ':' after ${kwq(ctx, KW.COMPENSATE)}`);
|
|
637
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.COMPENSATE), `Expected ':' after ${kwq(ctx, KW.COMPENSATE)}`));
|
|
634
638
|
ctx.next();
|
|
635
639
|
expectNewline(ctx, error);
|
|
636
640
|
compensate = parseBlock(ctx, error);
|
|
@@ -642,13 +646,15 @@ function parseStep(ctx, error) {
|
|
|
642
646
|
return step;
|
|
643
647
|
}
|
|
644
648
|
function parseRetryPolicy(ctx, error) {
|
|
645
|
-
|
|
649
|
+
// 缺必填指令的错误落在 retry 关键词上(与 Java AstBuilder 报在 retrySection 上一致),而非区块末尾
|
|
650
|
+
const retryTok = ctx.peek();
|
|
651
|
+
expectKeyword(ctx, error, KW.RETRY, Issue.expected(kwParam(KW.RETRY), `Expected ${kwq(ctx, KW.RETRY)}`));
|
|
646
652
|
if (!ctx.at(TokenKind.COLON))
|
|
647
|
-
error(`Expected ':' after ${kwq(ctx, KW.RETRY)}`);
|
|
653
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.RETRY), `Expected ':' after ${kwq(ctx, KW.RETRY)}`));
|
|
648
654
|
ctx.next();
|
|
649
655
|
expectNewline(ctx, error);
|
|
650
656
|
if (!ctx.at(TokenKind.INDENT))
|
|
651
|
-
error('Expected indent for retry policy');
|
|
657
|
+
error(Issue.expectedIn(tokenName('indent'), tokenName('retryPolicy'), 'Expected indent for retry policy'));
|
|
652
658
|
ctx.next();
|
|
653
659
|
ctx.consumeNewlines();
|
|
654
660
|
let maxAttempts = null;
|
|
@@ -657,56 +663,56 @@ function parseRetryPolicy(ctx, error) {
|
|
|
657
663
|
if (ctx.isKeywordSeq(MAX_ATTEMPTS_PARTS)) {
|
|
658
664
|
ctx.nextWords(MAX_ATTEMPTS_PARTS);
|
|
659
665
|
if (!ctx.at(TokenKind.COLON))
|
|
660
|
-
error(`Expected ':' after ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
|
|
666
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.MAX_ATTEMPTS), `Expected ':' after ${kwq(ctx, KW.MAX_ATTEMPTS)}`));
|
|
661
667
|
ctx.next();
|
|
662
668
|
if (!ctx.at(TokenKind.INT))
|
|
663
|
-
error(`Expected integer after ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
|
|
669
|
+
error(Issue.expectedAfter(tokenName('integer'), kwParam(KW.MAX_ATTEMPTS), `Expected integer after ${kwq(ctx, KW.MAX_ATTEMPTS)}`));
|
|
664
670
|
const attempts = ctx.next().value;
|
|
665
671
|
if (attempts <= 0)
|
|
666
|
-
error(`${kwq(ctx, KW.MAX_ATTEMPTS)} must be greater than zero`);
|
|
672
|
+
error(Issue.invalidValue(kwParam(KW.MAX_ATTEMPTS), attempts, tokenName('positiveInteger'), `${kwq(ctx, KW.MAX_ATTEMPTS)} must be greater than zero`));
|
|
667
673
|
maxAttempts = attempts;
|
|
668
674
|
expectPeriodEnd(ctx, error);
|
|
669
675
|
}
|
|
670
676
|
else if (ctx.isKeyword(KW.BACKOFF)) {
|
|
671
677
|
ctx.nextWord();
|
|
672
678
|
if (!ctx.at(TokenKind.COLON))
|
|
673
|
-
error(`Expected ':' after ${kwq(ctx, KW.BACKOFF)}`);
|
|
679
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.BACKOFF), `Expected ':' after ${kwq(ctx, KW.BACKOFF)}`));
|
|
674
680
|
ctx.next();
|
|
675
681
|
const modeTok = ctx.nextWord();
|
|
676
682
|
const mode = String(modeTok.value ?? '').toLowerCase();
|
|
677
683
|
if (mode !== 'exponential' && mode !== 'linear') {
|
|
678
|
-
error("Backoff must be either 'exponential' or 'linear'");
|
|
684
|
+
error(Issue.invalidValue(kwParam(KW.BACKOFF), tokenLit(modeTok), tokenName('exponentialOrLinear'), "Backoff must be either 'exponential' or 'linear'"));
|
|
679
685
|
}
|
|
680
686
|
backoff = mode;
|
|
681
687
|
expectPeriodEnd(ctx, error);
|
|
682
688
|
}
|
|
683
689
|
else {
|
|
684
|
-
error('Unknown retry directive');
|
|
690
|
+
error(Issue.unknownName(tokenName('retryDirective'), tokenLit(ctx.peek()), 'Unknown retry directive'));
|
|
685
691
|
}
|
|
686
692
|
ctx.consumeNewlines();
|
|
687
693
|
}
|
|
688
694
|
ctx.next();
|
|
689
695
|
if (maxAttempts === null)
|
|
690
|
-
error(`Retry section must include ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
|
|
696
|
+
error(Issue.emptyBlock(kwParam(KW.RETRY), kwParam(KW.MAX_ATTEMPTS), `Retry section must include ${kwq(ctx, KW.MAX_ATTEMPTS)}`), retryTok);
|
|
691
697
|
if (backoff === null)
|
|
692
|
-
error(`Retry section must include ${kwq(ctx, KW.BACKOFF)}`);
|
|
698
|
+
error(Issue.emptyBlock(kwParam(KW.RETRY), kwParam(KW.BACKOFF), `Retry section must include ${kwq(ctx, KW.BACKOFF)}`), retryTok);
|
|
693
699
|
return { maxAttempts, backoff };
|
|
694
700
|
}
|
|
695
701
|
function parseTimeout(ctx, error) {
|
|
696
|
-
expectKeyword(ctx, error, KW.TIMEOUT, `Expected ${kwq(ctx, KW.TIMEOUT)}`);
|
|
702
|
+
expectKeyword(ctx, error, KW.TIMEOUT, Issue.expected(kwParam(KW.TIMEOUT), `Expected ${kwq(ctx, KW.TIMEOUT)}`));
|
|
697
703
|
if (!ctx.at(TokenKind.COLON))
|
|
698
|
-
error(`Expected ':' after ${kwq(ctx, KW.TIMEOUT)}`);
|
|
704
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.TIMEOUT), `Expected ':' after ${kwq(ctx, KW.TIMEOUT)}`));
|
|
699
705
|
ctx.next();
|
|
700
706
|
if (!ctx.at(TokenKind.INT))
|
|
701
|
-
error('Expected integer timeout value');
|
|
707
|
+
error(Issue.expected(tokenName('timeoutInteger'), 'Expected integer timeout value'));
|
|
702
708
|
const secondsTok = ctx.next();
|
|
703
709
|
const seconds = secondsTok.value;
|
|
704
710
|
if (seconds < 0)
|
|
705
|
-
error('Timeout value must be non-negative');
|
|
711
|
+
error(Issue.invalidValue(kwParam(KW.TIMEOUT), seconds, tokenName('nonNegativeInteger'), 'Timeout value must be non-negative'));
|
|
706
712
|
if (!(ctx.isKeyword('seconds') || ctx.isKeyword('second'))) {
|
|
707
713
|
// 'seconds' 不做本地化:所有 locale 的词表都没有时间单位的本地拼写,
|
|
708
714
|
// zh/de 作者同样只能写英文 seconds,报错里的 'seconds' 正是作者该写的词。
|
|
709
|
-
error("Timeout must specify time unit 'seconds'");
|
|
715
|
+
error(Issue.invalidValue(tokenName('timeoutUnit'), tokenLit(ctx.peek()), lit('seconds'), "Timeout must specify time unit 'seconds'"));
|
|
710
716
|
}
|
|
711
717
|
ctx.nextWord();
|
|
712
718
|
expectPeriodEnd(ctx, error);
|
|
@@ -724,17 +730,17 @@ function parseCases(ctx, error) {
|
|
|
724
730
|
ctx.pushCompoundContext(KW.MATCH);
|
|
725
731
|
try {
|
|
726
732
|
if (!ctx.at(TokenKind.INDENT))
|
|
727
|
-
error('Expected indent for cases');
|
|
733
|
+
error(Issue.expectedIn(tokenName('indent'), tokenName('matchCases'), 'Expected indent for cases'));
|
|
728
734
|
ctx.next();
|
|
729
735
|
while (!ctx.at(TokenKind.DEDENT)) {
|
|
730
736
|
if (!ctx.isKeyword(KW.WHEN)) {
|
|
731
737
|
// 改进的错误消息,提示复合模式关系
|
|
732
|
-
error(`Expected ${kwq(ctx, KW.WHEN, 'When')} (若...为 pattern requires '为' after '若')`);
|
|
738
|
+
error(Issue.expectedIn(kwParam(KW.WHEN), tokenName('matchCase'), `Expected ${kwq(ctx, KW.WHEN, 'When')} (若...为 pattern requires '为' after '若')`));
|
|
733
739
|
}
|
|
734
740
|
const whenTok = ctx.nextWord();
|
|
735
741
|
const pat = parsePattern(ctx, error);
|
|
736
742
|
if (!ctx.at(TokenKind.COMMA))
|
|
737
|
-
error("Expected ',' after pattern");
|
|
743
|
+
error(Issue.expectedAfter(lit(','), tokenName('pattern'), "Expected ',' after pattern"));
|
|
738
744
|
ctx.next();
|
|
739
745
|
const body = parseCaseBody(ctx, error);
|
|
740
746
|
const caseNode = Node.Case(pat, body);
|
|
@@ -806,13 +812,13 @@ function parseIfExpr(ctx, error) {
|
|
|
806
812
|
const cond = parseOr(ctx, error);
|
|
807
813
|
skipInlineThenLayout(ctx);
|
|
808
814
|
if (!ctx.isKeyword(KW.THEN))
|
|
809
|
-
error(`Expected ${kwq(ctx, KW.THEN)} in if-expression`);
|
|
815
|
+
error(Issue.expectedIn(kwParam(KW.THEN), tokenName('ifExpression'), `Expected ${kwq(ctx, KW.THEN)} in if-expression`));
|
|
810
816
|
ctx.nextWord(); // 消费 THEN
|
|
811
817
|
const thenE = parseExpr(ctx, error);
|
|
812
818
|
skipInlineElseLayout(ctx);
|
|
813
819
|
if (!ctx.isKeyword(KW.ELSE) && !ctx.isKeyword(KW.OTHERWISE)) {
|
|
814
|
-
error(`Expected ${kwKindq(ctx, SemanticTokenKind.OTHERWISE, 'else')} in if-expression ` +
|
|
815
|
-
`(${kwKind(ctx, SemanticTokenKind.OTHERWISE, 'else')} is mandatory for expression-level if)`);
|
|
820
|
+
error(Issue.expectedIn(kwParam(KW.OTHERWISE), tokenName('ifExpression'), `Expected ${kwKindq(ctx, SemanticTokenKind.OTHERWISE, 'else')} in if-expression ` +
|
|
821
|
+
`(${kwKind(ctx, SemanticTokenKind.OTHERWISE, 'else')} is mandatory for expression-level if)`));
|
|
816
822
|
}
|
|
817
823
|
ctx.nextWord(); // 消费 ELSE / OTHERWISE
|
|
818
824
|
const elseE = parseExpr(ctx, error);
|
|
@@ -1284,7 +1290,7 @@ function parseApplyOrPrimary(ctx, error) {
|
|
|
1284
1290
|
if (ctx.isKeyword(KW.APPLY) && applyLooksLikeCall(ctx)) {
|
|
1285
1291
|
const applyTok = ctx.nextWord();
|
|
1286
1292
|
const fn = parseCallTargetName(ctx, error);
|
|
1287
|
-
expectKeyword(ctx, error, KW.TO_WORD, `Use '${kw(ctx, KW.APPLY)} <fn> ${kw(ctx, KW.TO_WORD)} <arg>' (expected ${kwq(ctx, KW.TO_WORD)}).`);
|
|
1293
|
+
expectKeyword(ctx, error, KW.TO_WORD, Issue.expectedIn(kwParam(KW.TO_WORD), kwParam(KW.APPLY), `Use '${kw(ctx, KW.APPLY)} <fn> ${kw(ctx, KW.TO_WORD)} <arg>' (expected ${kwq(ctx, KW.TO_WORD)}).`));
|
|
1288
1294
|
const arg = parseExpr(ctx, error);
|
|
1289
1295
|
const call = Node.Call(fn, [arg]);
|
|
1290
1296
|
assignSpan(call, spanFromSources(applyTok, arg));
|
|
@@ -1323,7 +1329,7 @@ function applyLooksLikeCall(ctx) {
|
|
|
1323
1329
|
function parseCallTargetName(ctx, error) {
|
|
1324
1330
|
const startTok = ctx.peek();
|
|
1325
1331
|
if (!ctx.at(TokenKind.IDENT) && !ctx.at(TokenKind.TYPE_IDENT)) {
|
|
1326
|
-
error(`Expected a function name after ${kwq(ctx, KW.APPLY)}.`);
|
|
1332
|
+
error(Issue.expectedAfter(tokenName('functionName'), kwParam(KW.APPLY), `Expected a function name after ${kwq(ctx, KW.APPLY)}.`));
|
|
1327
1333
|
}
|
|
1328
1334
|
const consumed = [ctx.peek()];
|
|
1329
1335
|
let full = ctx.at(TokenKind.IDENT) ? parseIdent(ctx, error) : ctx.next().value;
|
|
@@ -1393,7 +1399,7 @@ function operatorWordBeforeParen(ctx, english) {
|
|
|
1393
1399
|
function parseOperatorWordCall(ctx, error, word, english) {
|
|
1394
1400
|
const symbol = english ? null : OPERATOR_WORD_SYMBOLS.get(word);
|
|
1395
1401
|
if (!symbol) {
|
|
1396
|
-
error(`运算符词 ${kwq(ctx, word)} 不能用作前缀调用;只有符号运算符 ${PREFIX_SYMBOL_LIST} 可以写成 op(a, b)`);
|
|
1402
|
+
error(Issue.invalidValue(tokenName('prefixOperator'), kwParam(word), lit(PREFIX_SYMBOL_LIST), `运算符词 ${kwq(ctx, word)} 不能用作前缀调用;只有符号运算符 ${PREFIX_SYMBOL_LIST} 可以写成 op(a, b)`));
|
|
1397
1403
|
}
|
|
1398
1404
|
return parsePrefixOperatorCall(ctx, error, symbol, kwParts(word).length);
|
|
1399
1405
|
}
|
|
@@ -1404,7 +1410,7 @@ function parsePrefixOperatorCall(ctx, error, op, opTokenCount = 1) {
|
|
|
1404
1410
|
ctx.next();
|
|
1405
1411
|
const args = parseArgList(ctx, error);
|
|
1406
1412
|
if (args.length !== 2)
|
|
1407
|
-
error(`前缀操作符调用需要 2 个参数,但实际为 ${args.length}`);
|
|
1413
|
+
error(Issue.callArity(tokenName('prefixOperatorCall'), 2, args.length, `前缀操作符调用需要 2 个参数,但实际为 ${args.length}`));
|
|
1408
1414
|
const target = assignTokenSpan(Node.Name(op), opTok);
|
|
1409
1415
|
return assignSpan(Node.Call(target, args), spanFromTokens(opTok, lastNonLayoutToken(ctx)));
|
|
1410
1416
|
}
|
|
@@ -1425,7 +1431,7 @@ function parsePrimary(ctx, error) {
|
|
|
1425
1431
|
const functionTok = ctx.nextWord(); // 'function'
|
|
1426
1432
|
const params = parseParamList(ctx, error);
|
|
1427
1433
|
expectCommaOr(ctx);
|
|
1428
|
-
expectKeyword(ctx, error, KW.PRODUCE, `Expected ${kwq(ctx, KW.PRODUCE)} and return type`);
|
|
1434
|
+
expectKeyword(ctx, error, KW.PRODUCE, Issue.expected(kwParam(KW.PRODUCE), `Expected ${kwq(ctx, KW.PRODUCE)} and return type`));
|
|
1429
1435
|
let retType;
|
|
1430
1436
|
if (ctx.at(TokenKind.COLON)) {
|
|
1431
1437
|
retType = Node.TypeName('Unknown');
|
|
@@ -1435,7 +1441,7 @@ function parsePrimary(ctx, error) {
|
|
|
1435
1441
|
retType = parseType(ctx, error);
|
|
1436
1442
|
}
|
|
1437
1443
|
if (!ctx.at(TokenKind.COLON))
|
|
1438
|
-
error("Expected ':' after return type in lambda");
|
|
1444
|
+
error(Issue.expectedAfterIn(lit(':'), tokenName('returnType'), tokenName('lambda'), "Expected ':' after return type in lambda"));
|
|
1439
1445
|
ctx.next();
|
|
1440
1446
|
expectNewline(ctx, error);
|
|
1441
1447
|
const body = parseBlock(ctx, error);
|
|
@@ -1591,7 +1597,7 @@ function parsePrimary(ctx, error) {
|
|
|
1591
1597
|
const awaitTok = ctx.nextWord();
|
|
1592
1598
|
const args = parseArgList(ctx, error);
|
|
1593
1599
|
if (args.length !== 1)
|
|
1594
|
-
error('await(expr) takes exactly one argument');
|
|
1600
|
+
error(Issue.callArity(lit('await'), 1, args.length, 'await(expr) takes exactly one argument'));
|
|
1595
1601
|
const target = assignTokenSpan(Node.Name('await'), awaitTok);
|
|
1596
1602
|
const call = Node.Call(target, args);
|
|
1597
1603
|
const endTok = lastNonLayoutToken(ctx);
|
|
@@ -1616,7 +1622,7 @@ function parsePrimary(ctx, error) {
|
|
|
1616
1622
|
}
|
|
1617
1623
|
}
|
|
1618
1624
|
if (!ctx.at(TokenKind.RBRACKET))
|
|
1619
|
-
error("Expected ']' to close list literal");
|
|
1625
|
+
error(Issue.expectedIn(lit(']'), tokenName('listLiteral'), "Expected ']' to close list literal"));
|
|
1620
1626
|
const rbrackTok = ctx.next();
|
|
1621
1627
|
const list = Node.ListLit(elements);
|
|
1622
1628
|
assignSpan(list, spanFromSources(lbrackTok, rbrackTok));
|
|
@@ -1627,7 +1633,7 @@ function parsePrimary(ctx, error) {
|
|
|
1627
1633
|
const lparenTok = ctx.next();
|
|
1628
1634
|
const expr = parseExpr(ctx, error);
|
|
1629
1635
|
if (!ctx.at(TokenKind.RPAREN))
|
|
1630
|
-
error("Expected ')' after expression");
|
|
1636
|
+
error(Issue.expectedAfter(lit(')'), tokenName('expression'), "Expected ')' after expression"));
|
|
1631
1637
|
const rparenTok = ctx.next();
|
|
1632
1638
|
assignSpanFromSources(expr, lparenTok, rparenTok, expr);
|
|
1633
1639
|
return expr;
|
|
@@ -1645,7 +1651,7 @@ function parsePrimary(ctx, error) {
|
|
|
1645
1651
|
const withTok = ctx.nextWord();
|
|
1646
1652
|
const fields = [];
|
|
1647
1653
|
let hasMore = true;
|
|
1648
|
-
const setToMissing = `Expected '${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)}' in construction
|
|
1654
|
+
const setToMissing = Issue.expectedIn(kwParam(KW.SET), tokenName('construction'), `Expected '${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)}' in construction`);
|
|
1649
1655
|
while (hasMore) {
|
|
1650
1656
|
const nameTok = ctx.peek();
|
|
1651
1657
|
// 字段位置不是名字(如 `helper with 10` 这种 with 调用后缀):与 Java 引擎
|
|
@@ -1659,7 +1665,7 @@ function parsePrimary(ctx, error) {
|
|
|
1659
1665
|
const equalsTok = ctx.peek();
|
|
1660
1666
|
ctx.nextWord();
|
|
1661
1667
|
if (!ctx.isKeyword(KW.TO_WORD))
|
|
1662
|
-
error(`Expected ${kwq(ctx, KW.TO_WORD)} after ${kwq(ctx, KW.SET)} in construction`);
|
|
1668
|
+
error(Issue.expectedAfterIn(kwParam(KW.TO_WORD), kwParam(KW.SET), tokenName('construction'), `Expected ${kwq(ctx, KW.TO_WORD)} after ${kwq(ctx, KW.SET)} in construction`));
|
|
1663
1669
|
ctx.nextWord();
|
|
1664
1670
|
const e = parseExpr(ctx, error);
|
|
1665
1671
|
const fld = {
|
|
@@ -1703,7 +1709,7 @@ function parsePrimary(ctx, error) {
|
|
|
1703
1709
|
full += '.' + partTok.value;
|
|
1704
1710
|
}
|
|
1705
1711
|
else {
|
|
1706
|
-
error('Expected identifier after dot');
|
|
1712
|
+
error(Issue.expectedAfter(tokenName('identifier'), lit('.'), 'Expected identifier after dot'));
|
|
1707
1713
|
}
|
|
1708
1714
|
}
|
|
1709
1715
|
const target = Node.Name(full);
|
|
@@ -1714,8 +1720,8 @@ function parsePrimary(ctx, error) {
|
|
|
1714
1720
|
// 直到运行时才抛 Undefined function;在此于编译期就报错并给出正确写法。
|
|
1715
1721
|
// 仅拦截裸类型名(无点链后缀),点链形态(如 SomeType.method(...))不受影响。
|
|
1716
1722
|
if (full === typeName && ctx.declaredRecordTypes.has(typeName)) {
|
|
1717
|
-
error(`Cannot construct '${typeName}' with positional arguments. ` +
|
|
1718
|
-
`Use named fields instead: '${typeName} ${kw(ctx, KW.WITH)} <field> ${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)} <value> ${kw(ctx, KW.AND)} ...'`);
|
|
1723
|
+
error(Issue.unexpectedConstruct(tokenName('positionalConstruction'), `Cannot construct '${typeName}' with positional arguments. ` +
|
|
1724
|
+
`Use named fields instead: '${typeName} ${kw(ctx, KW.WITH)} <field> ${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)} <value> ${kw(ctx, KW.AND)} ...'`));
|
|
1719
1725
|
}
|
|
1720
1726
|
const args = parseArgList(ctx, error);
|
|
1721
1727
|
const call = Node.Call(target, args);
|
|
@@ -1752,8 +1758,8 @@ function parsePrimary(ctx, error) {
|
|
|
1752
1758
|
if (ctx.at(TokenKind.LPAREN)) {
|
|
1753
1759
|
// 同 TYPE_IDENT 分支:拦截对已声明类型的位置式构造(无大小写语言里类型名是 IDENT)。
|
|
1754
1760
|
if (full === name && ctx.declaredRecordTypes.has(name)) {
|
|
1755
|
-
error(`Cannot construct '${name}' with positional arguments. ` +
|
|
1756
|
-
`Use named fields instead: '${name} ${kw(ctx, KW.WITH)} <field> ${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)} <value> ${kw(ctx, KW.AND)} ...'`);
|
|
1761
|
+
error(Issue.unexpectedConstruct(tokenName('positionalConstruction'), `Cannot construct '${name}' with positional arguments. ` +
|
|
1762
|
+
`Use named fields instead: '${name} ${kw(ctx, KW.WITH)} <field> ${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)} <value> ${kw(ctx, KW.AND)} ...'`));
|
|
1757
1763
|
}
|
|
1758
1764
|
const args = parseArgList(ctx, error);
|
|
1759
1765
|
const call = Node.Call(target, args);
|
|
@@ -1763,7 +1769,7 @@ function parsePrimary(ctx, error) {
|
|
|
1763
1769
|
}
|
|
1764
1770
|
return target;
|
|
1765
1771
|
}
|
|
1766
|
-
error('Unexpected expression');
|
|
1772
|
+
error(Issue.unexpectedConstruct(tokenName('expression'), 'Unexpected expression'));
|
|
1767
1773
|
}
|
|
1768
1774
|
/**
|
|
1769
1775
|
* 解析参数列表
|
|
@@ -1773,7 +1779,7 @@ function parsePrimary(ctx, error) {
|
|
|
1773
1779
|
*/
|
|
1774
1780
|
export function parseArgList(ctx, error) {
|
|
1775
1781
|
if (!ctx.at(TokenKind.LPAREN))
|
|
1776
|
-
error("Expected '('");
|
|
1782
|
+
error(Issue.expected(lit('('), "Expected '('"));
|
|
1777
1783
|
ctx.next();
|
|
1778
1784
|
const args = [];
|
|
1779
1785
|
while (!ctx.at(TokenKind.RPAREN)) {
|
|
@@ -1786,7 +1792,7 @@ export function parseArgList(ctx, error) {
|
|
|
1786
1792
|
break;
|
|
1787
1793
|
}
|
|
1788
1794
|
if (!ctx.at(TokenKind.RPAREN))
|
|
1789
|
-
error("Expected ')'");
|
|
1795
|
+
error(Issue.expected(lit(')'), "Expected ')'"));
|
|
1790
1796
|
ctx.next();
|
|
1791
1797
|
return args;
|
|
1792
1798
|
}
|
|
@@ -1832,7 +1838,7 @@ export function parsePattern(ctx, error) {
|
|
|
1832
1838
|
break;
|
|
1833
1839
|
}
|
|
1834
1840
|
if (!ctx.at(TokenKind.RPAREN))
|
|
1835
|
-
error("Expected ')' in pattern");
|
|
1841
|
+
error(Issue.expectedIn(lit(')'), tokenName('pattern'), "Expected ')' in pattern"));
|
|
1836
1842
|
const rparenTok = ctx.next();
|
|
1837
1843
|
const node = Node.PatternCtor(typeName, names);
|
|
1838
1844
|
assignSpan(node, spanFromSources(typeTok, lparenTok, rparenTok, ...nameTokens));
|
|
@@ -2036,7 +2042,7 @@ function parseIdent(ctx, error) {
|
|
|
2036
2042
|
if (recovered !== null)
|
|
2037
2043
|
return recovered;
|
|
2038
2044
|
if (!ctx.at(TokenKind.IDENT)) {
|
|
2039
|
-
error('Expected identifier');
|
|
2045
|
+
error(Issue.expected(tokenName('identifier'), 'Expected identifier'));
|
|
2040
2046
|
}
|
|
2041
2047
|
return ctx.next().value;
|
|
2042
2048
|
}
|
|
@@ -2047,9 +2053,9 @@ function parseIdent(ctx, error) {
|
|
|
2047
2053
|
* @param kw 关键字
|
|
2048
2054
|
* @param msg 错误消息
|
|
2049
2055
|
*/
|
|
2050
|
-
function expectKeyword(ctx, error, kw,
|
|
2056
|
+
function expectKeyword(ctx, error, kw, issue) {
|
|
2051
2057
|
if (!ctx.isKeyword(kw))
|
|
2052
|
-
error(
|
|
2058
|
+
error(issue);
|
|
2053
2059
|
ctx.nextWord();
|
|
2054
2060
|
}
|
|
2055
2061
|
/**
|
|
@@ -2068,7 +2074,7 @@ function expectCommaOr(ctx) {
|
|
|
2068
2074
|
*/
|
|
2069
2075
|
function expectNewline(ctx, error) {
|
|
2070
2076
|
if (!ctx.at(TokenKind.NEWLINE))
|
|
2071
|
-
error('Expected newline');
|
|
2077
|
+
error(Issue.expected(tokenName('newline'), 'Expected newline'));
|
|
2072
2078
|
ctx.next();
|
|
2073
2079
|
}
|
|
2074
2080
|
//# sourceMappingURL=expr-stmt-parser.js.map
|