@aster-cloud/aster-lang-ts 1.2.5 → 1.3.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/CHANGELOG.md +73 -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 +16 -2
- package/dist/src/browser.d.ts.map +1 -1
- package/dist/src/browser.js +12 -19
- package/dist/src/browser.js.map +1 -1
- package/dist/src/cli/utils/error-handler.js.map +1 -1
- package/dist/src/config/lexicons/diagnostic-overlays.generated.d.ts.map +1 -1
- package/dist/src/config/lexicons/diagnostic-overlays.generated.js +120 -0
- package/dist/src/config/lexicons/diagnostic-overlays.generated.js.map +1 -1
- package/dist/src/config/lexicons/identifiers/index.d.ts +1 -0
- package/dist/src/config/lexicons/identifiers/index.d.ts.map +1 -1
- package/dist/src/config/lexicons/identifiers/index.js +1 -0
- package/dist/src/config/lexicons/identifiers/index.js.map +1 -1
- package/dist/src/config/lexicons/identifiers/spelling.d.ts +25 -0
- package/dist/src/config/lexicons/identifiers/spelling.d.ts.map +1 -0
- package/dist/src/config/lexicons/identifiers/spelling.js +44 -0
- package/dist/src/config/lexicons/identifiers/spelling.js.map +1 -0
- 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 +27 -2
- package/dist/src/diagnostics/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics/diagnostics.js +37 -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/canonicalizer.d.ts.map +1 -1
- package/dist/src/frontend/canonicalizer.js +44 -1
- package/dist/src/frontend/canonicalizer.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/frontend/resolve-policy-types.d.ts +2 -0
- package/dist/src/frontend/resolve-policy-types.d.ts.map +1 -1
- package/dist/src/frontend/resolve-policy-types.js +2 -1
- package/dist/src/frontend/resolve-policy-types.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.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 +2 -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 -92
- 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 +19 -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 +19 -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/integration/vocabulary-resolution.test.js +10 -1
- package/dist/test/integration/vocabulary-resolution.test.js.map +1 -1
- package/dist/test/unit/canonicalizer/article-declaration-names.test.d.ts +2 -0
- package/dist/test/unit/canonicalizer/article-declaration-names.test.d.ts.map +1 -0
- package/dist/test/unit/canonicalizer/article-declaration-names.test.js +53 -0
- package/dist/test/unit/canonicalizer/article-declaration-names.test.js.map +1 -0
- package/dist/test/unit/diagnostics/diagnostic-overlays.test.js.map +1 -1
- 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/diagnostics/parse-issue-codes.test.d.ts +6 -0
- package/dist/test/unit/diagnostics/parse-issue-codes.test.d.ts.map +1 -0
- package/dist/test/unit/diagnostics/parse-issue-codes.test.js +82 -0
- package/dist/test/unit/diagnostics/parse-issue-codes.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/identifiers/spelling.test.d.ts +2 -0
- package/dist/test/unit/identifiers/spelling.test.d.ts.map +1 -0
- package/dist/test/unit/identifiers/spelling.test.js +51 -0
- package/dist/test/unit/identifiers/spelling.test.js.map +1 -0
- 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 -4
- package/dist/test/unit/parser/depth-limit.test.js.map +1 -1
- package/dist/test/unit/parser/parser.test.js +36 -6
- package/dist/test/unit/parser/parser.test.js.map +1 -1
- package/dist/test/unit/typecheck/effects-and-capabilities.test.js +19 -0
- package/dist/test/unit/typecheck/effects-and-capabilities.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,9 @@ 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"));
|
|
433
|
+
// 参数个数不在解析期检查(与 aster-lang-core 一致;个数不符由运行时 await 内置报错,#295)
|
|
429
434
|
const args = parseArgList(ctx, error);
|
|
430
|
-
if (args.length !== 1)
|
|
431
|
-
error('await(expr) takes exactly one argument');
|
|
432
435
|
const target = assignTokenSpan(Node.Name('await'), awaitTok);
|
|
433
436
|
const callSpanEnd = lastNonLayoutToken(ctx);
|
|
434
437
|
const aw = Node.Call(target, args);
|
|
@@ -476,7 +479,7 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
476
479
|
ctx.nextWord();
|
|
477
480
|
const expr = parseExpr(ctx, error);
|
|
478
481
|
if (!ctx.at(TokenKind.COLON))
|
|
479
|
-
error("Expected ':' after match expression");
|
|
482
|
+
error(Issue.expectedAfter(lit(':'), tokenName('matchExpression'), "Expected ':' after match expression"));
|
|
480
483
|
ctx.next();
|
|
481
484
|
expectNewline(ctx, error);
|
|
482
485
|
const cases = parseCases(ctx, error);
|
|
@@ -509,9 +512,9 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
509
512
|
}
|
|
510
513
|
if (ctx.isKeyword(KW.WITHIN)) {
|
|
511
514
|
ctx.nextWord();
|
|
512
|
-
expectKeyword(ctx, error, KW.SCOPE, `Expected ${kwq(ctx, KW.SCOPE)} after ${kwq(ctx, KW.WITHIN, 'Within')}`);
|
|
515
|
+
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
516
|
if (!ctx.at(TokenKind.COLON))
|
|
514
|
-
error(`Expected ':' after ${kwq(ctx, KW.SCOPE)}`);
|
|
517
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.SCOPE), `Expected ':' after ${kwq(ctx, KW.SCOPE)}`));
|
|
515
518
|
ctx.next();
|
|
516
519
|
expectNewline(ctx, error);
|
|
517
520
|
return parseBlock(ctx, error); // Lowering later
|
|
@@ -519,8 +522,8 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
519
522
|
if (ctx.isKeyword(KW.START)) {
|
|
520
523
|
const startTok = ctx.nextWord();
|
|
521
524
|
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)}`);
|
|
525
|
+
expectKeyword(ctx, error, KW.AS, Issue.expectedAfter(kwParam(KW.AS), tokenName('name'), `Expected ${kwq(ctx, KW.AS)} after name`));
|
|
526
|
+
expectKeyword(ctx, error, KW.ASYNC, Issue.expected(kwParam(KW.ASYNC), `Expected ${kwq(ctx, KW.ASYNC)}`));
|
|
524
527
|
const expr = parseExpr(ctx, error);
|
|
525
528
|
expectPeriodEnd(ctx, error);
|
|
526
529
|
const nd = Node.Start(name, expr);
|
|
@@ -549,17 +552,17 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
549
552
|
assignSpan(nd, spanFromTokens(waitStart, endTok));
|
|
550
553
|
return nd;
|
|
551
554
|
}
|
|
552
|
-
error('Unknown statement');
|
|
555
|
+
error(Issue.unexpectedConstruct(tokenName('statement'), 'Unknown statement'));
|
|
553
556
|
}
|
|
554
557
|
function parseWorkflow(ctx, error) {
|
|
555
558
|
const workflowTok = ctx.peek();
|
|
556
|
-
expectKeyword(ctx, error, KW.WORKFLOW, `Expected ${kwq(ctx, KW.WORKFLOW)}`);
|
|
559
|
+
expectKeyword(ctx, error, KW.WORKFLOW, Issue.expected(kwParam(KW.WORKFLOW), `Expected ${kwq(ctx, KW.WORKFLOW)}`));
|
|
557
560
|
if (!ctx.at(TokenKind.COLON))
|
|
558
|
-
error(`Expected ':' after ${kwq(ctx, KW.WORKFLOW)}`);
|
|
561
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.WORKFLOW), `Expected ':' after ${kwq(ctx, KW.WORKFLOW)}`));
|
|
559
562
|
ctx.next();
|
|
560
563
|
expectNewline(ctx, error);
|
|
561
564
|
if (!ctx.at(TokenKind.INDENT))
|
|
562
|
-
error('Expected indent after workflow header');
|
|
565
|
+
error(Issue.expectedAfter(tokenName('indent'), tokenName('workflowHeader'), 'Expected indent after workflow header'));
|
|
563
566
|
ctx.next();
|
|
564
567
|
ctx.consumeNewlines();
|
|
565
568
|
const steps = [];
|
|
@@ -568,7 +571,7 @@ function parseWorkflow(ctx, error) {
|
|
|
568
571
|
ctx.consumeNewlines();
|
|
569
572
|
}
|
|
570
573
|
if (steps.length === 0)
|
|
571
|
-
error('Workflow must declare at least one step');
|
|
574
|
+
error(Issue.emptyBlock(kwParam(KW.WORKFLOW), kwParam(KW.STEP), 'Workflow must declare at least one step'));
|
|
572
575
|
let retry;
|
|
573
576
|
if (ctx.isKeyword(KW.RETRY)) {
|
|
574
577
|
retry = parseRetryPolicy(ctx, error);
|
|
@@ -580,7 +583,7 @@ function parseWorkflow(ctx, error) {
|
|
|
580
583
|
ctx.consumeNewlines();
|
|
581
584
|
}
|
|
582
585
|
if (!ctx.at(TokenKind.DEDENT))
|
|
583
|
-
error('Expected dedent after workflow body');
|
|
586
|
+
error(Issue.expectedAfter(tokenName('dedent'), tokenName('workflowBody'), 'Expected dedent after workflow body'));
|
|
584
587
|
ctx.next();
|
|
585
588
|
ctx.consumeNewlines();
|
|
586
589
|
expectPeriodEnd(ctx, error);
|
|
@@ -591,19 +594,19 @@ function parseWorkflow(ctx, error) {
|
|
|
591
594
|
}
|
|
592
595
|
function parseStep(ctx, error) {
|
|
593
596
|
const stepTok = ctx.peek();
|
|
594
|
-
expectKeyword(ctx, error, KW.STEP, `Expected ${kwq(ctx, KW.STEP)}`);
|
|
597
|
+
expectKeyword(ctx, error, KW.STEP, Issue.expected(kwParam(KW.STEP), `Expected ${kwq(ctx, KW.STEP)}`));
|
|
595
598
|
const name = parseIdent(ctx, error);
|
|
596
599
|
const dependencies = [];
|
|
597
600
|
if (ctx.isKeyword(KW.DEPENDS)) {
|
|
598
601
|
ctx.nextWord();
|
|
599
|
-
expectKeyword(ctx, error, KW.ON, `Expected ${kwq(ctx, KW.ON)} after ${kwq(ctx, KW.DEPENDS)}`);
|
|
602
|
+
expectKeyword(ctx, error, KW.ON, Issue.expectedAfter(kwParam(KW.ON), kwParam(KW.DEPENDS), `Expected ${kwq(ctx, KW.ON)} after ${kwq(ctx, KW.DEPENDS)}`));
|
|
600
603
|
if (!ctx.at(TokenKind.LBRACKET))
|
|
601
|
-
error(`Expected '[' after '${kw(ctx, KW.DEPENDS)} ${kw(ctx, KW.ON)}'`);
|
|
604
|
+
error(Issue.expectedAfter(lit('['), kwParam(KW.DEPENDS), `Expected '[' after '${kw(ctx, KW.DEPENDS)} ${kw(ctx, KW.ON)}'`));
|
|
602
605
|
ctx.next();
|
|
603
606
|
if (!ctx.at(TokenKind.RBRACKET)) {
|
|
604
607
|
while (true) {
|
|
605
608
|
if (!ctx.at(TokenKind.STRING))
|
|
606
|
-
error('Expected string dependency name');
|
|
609
|
+
error(Issue.expected(tokenName('dependencyName'), 'Expected string dependency name'));
|
|
607
610
|
const depTok = ctx.next();
|
|
608
611
|
dependencies.push(depTok.value);
|
|
609
612
|
if (ctx.at(TokenKind.COMMA)) {
|
|
@@ -613,15 +616,15 @@ function parseStep(ctx, error) {
|
|
|
613
616
|
if (ctx.at(TokenKind.RBRACKET)) {
|
|
614
617
|
break;
|
|
615
618
|
}
|
|
616
|
-
error("Expected ',' or ']' after dependency name");
|
|
619
|
+
error(Issue.expectedAfter(tokenName('commaOrCloseBracket'), tokenName('dependencyName'), "Expected ',' or ']' after dependency name"));
|
|
617
620
|
}
|
|
618
621
|
}
|
|
619
622
|
if (!ctx.at(TokenKind.RBRACKET))
|
|
620
|
-
error("Expected ']' to close dependency list");
|
|
623
|
+
error(Issue.expectedIn(lit(']'), tokenName('dependencyList'), "Expected ']' to close dependency list"));
|
|
621
624
|
ctx.next();
|
|
622
625
|
}
|
|
623
626
|
if (!ctx.at(TokenKind.COLON))
|
|
624
|
-
error("Expected ':' after step name");
|
|
627
|
+
error(Issue.expectedAfter(lit(':'), tokenName('stepName'), "Expected ':' after step name"));
|
|
625
628
|
ctx.next();
|
|
626
629
|
expectNewline(ctx, error);
|
|
627
630
|
const body = parseBlock(ctx, error);
|
|
@@ -630,7 +633,7 @@ function parseStep(ctx, error) {
|
|
|
630
633
|
if (ctx.isKeyword(KW.COMPENSATE)) {
|
|
631
634
|
ctx.nextWord();
|
|
632
635
|
if (!ctx.at(TokenKind.COLON))
|
|
633
|
-
error(`Expected ':' after ${kwq(ctx, KW.COMPENSATE)}`);
|
|
636
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.COMPENSATE), `Expected ':' after ${kwq(ctx, KW.COMPENSATE)}`));
|
|
634
637
|
ctx.next();
|
|
635
638
|
expectNewline(ctx, error);
|
|
636
639
|
compensate = parseBlock(ctx, error);
|
|
@@ -642,13 +645,15 @@ function parseStep(ctx, error) {
|
|
|
642
645
|
return step;
|
|
643
646
|
}
|
|
644
647
|
function parseRetryPolicy(ctx, error) {
|
|
645
|
-
|
|
648
|
+
// 缺必填指令的错误落在 retry 关键词上(与 Java AstBuilder 报在 retrySection 上一致),而非区块末尾
|
|
649
|
+
const retryTok = ctx.peek();
|
|
650
|
+
expectKeyword(ctx, error, KW.RETRY, Issue.expected(kwParam(KW.RETRY), `Expected ${kwq(ctx, KW.RETRY)}`));
|
|
646
651
|
if (!ctx.at(TokenKind.COLON))
|
|
647
|
-
error(`Expected ':' after ${kwq(ctx, KW.RETRY)}`);
|
|
652
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.RETRY), `Expected ':' after ${kwq(ctx, KW.RETRY)}`));
|
|
648
653
|
ctx.next();
|
|
649
654
|
expectNewline(ctx, error);
|
|
650
655
|
if (!ctx.at(TokenKind.INDENT))
|
|
651
|
-
error('Expected indent for retry policy');
|
|
656
|
+
error(Issue.expectedIn(tokenName('indent'), tokenName('retryPolicy'), 'Expected indent for retry policy'));
|
|
652
657
|
ctx.next();
|
|
653
658
|
ctx.consumeNewlines();
|
|
654
659
|
let maxAttempts = null;
|
|
@@ -657,56 +662,56 @@ function parseRetryPolicy(ctx, error) {
|
|
|
657
662
|
if (ctx.isKeywordSeq(MAX_ATTEMPTS_PARTS)) {
|
|
658
663
|
ctx.nextWords(MAX_ATTEMPTS_PARTS);
|
|
659
664
|
if (!ctx.at(TokenKind.COLON))
|
|
660
|
-
error(`Expected ':' after ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
|
|
665
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.MAX_ATTEMPTS), `Expected ':' after ${kwq(ctx, KW.MAX_ATTEMPTS)}`));
|
|
661
666
|
ctx.next();
|
|
662
667
|
if (!ctx.at(TokenKind.INT))
|
|
663
|
-
error(`Expected integer after ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
|
|
668
|
+
error(Issue.expectedAfter(tokenName('integer'), kwParam(KW.MAX_ATTEMPTS), `Expected integer after ${kwq(ctx, KW.MAX_ATTEMPTS)}`));
|
|
664
669
|
const attempts = ctx.next().value;
|
|
665
670
|
if (attempts <= 0)
|
|
666
|
-
error(`${kwq(ctx, KW.MAX_ATTEMPTS)} must be greater than zero`);
|
|
671
|
+
error(Issue.invalidValue(kwParam(KW.MAX_ATTEMPTS), attempts, tokenName('positiveInteger'), `${kwq(ctx, KW.MAX_ATTEMPTS)} must be greater than zero`));
|
|
667
672
|
maxAttempts = attempts;
|
|
668
673
|
expectPeriodEnd(ctx, error);
|
|
669
674
|
}
|
|
670
675
|
else if (ctx.isKeyword(KW.BACKOFF)) {
|
|
671
676
|
ctx.nextWord();
|
|
672
677
|
if (!ctx.at(TokenKind.COLON))
|
|
673
|
-
error(`Expected ':' after ${kwq(ctx, KW.BACKOFF)}`);
|
|
678
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.BACKOFF), `Expected ':' after ${kwq(ctx, KW.BACKOFF)}`));
|
|
674
679
|
ctx.next();
|
|
675
680
|
const modeTok = ctx.nextWord();
|
|
676
681
|
const mode = String(modeTok.value ?? '').toLowerCase();
|
|
677
682
|
if (mode !== 'exponential' && mode !== 'linear') {
|
|
678
|
-
error("Backoff must be either 'exponential' or 'linear'");
|
|
683
|
+
error(Issue.invalidValue(kwParam(KW.BACKOFF), tokenLit(modeTok), tokenName('exponentialOrLinear'), "Backoff must be either 'exponential' or 'linear'"));
|
|
679
684
|
}
|
|
680
685
|
backoff = mode;
|
|
681
686
|
expectPeriodEnd(ctx, error);
|
|
682
687
|
}
|
|
683
688
|
else {
|
|
684
|
-
error('Unknown retry directive');
|
|
689
|
+
error(Issue.unknownName(tokenName('retryDirective'), tokenLit(ctx.peek()), 'Unknown retry directive'));
|
|
685
690
|
}
|
|
686
691
|
ctx.consumeNewlines();
|
|
687
692
|
}
|
|
688
693
|
ctx.next();
|
|
689
694
|
if (maxAttempts === null)
|
|
690
|
-
error(`Retry section must include ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
|
|
695
|
+
error(Issue.emptyBlock(kwParam(KW.RETRY), kwParam(KW.MAX_ATTEMPTS), `Retry section must include ${kwq(ctx, KW.MAX_ATTEMPTS)}`), retryTok);
|
|
691
696
|
if (backoff === null)
|
|
692
|
-
error(`Retry section must include ${kwq(ctx, KW.BACKOFF)}`);
|
|
697
|
+
error(Issue.emptyBlock(kwParam(KW.RETRY), kwParam(KW.BACKOFF), `Retry section must include ${kwq(ctx, KW.BACKOFF)}`), retryTok);
|
|
693
698
|
return { maxAttempts, backoff };
|
|
694
699
|
}
|
|
695
700
|
function parseTimeout(ctx, error) {
|
|
696
|
-
expectKeyword(ctx, error, KW.TIMEOUT, `Expected ${kwq(ctx, KW.TIMEOUT)}`);
|
|
701
|
+
expectKeyword(ctx, error, KW.TIMEOUT, Issue.expected(kwParam(KW.TIMEOUT), `Expected ${kwq(ctx, KW.TIMEOUT)}`));
|
|
697
702
|
if (!ctx.at(TokenKind.COLON))
|
|
698
|
-
error(`Expected ':' after ${kwq(ctx, KW.TIMEOUT)}`);
|
|
703
|
+
error(Issue.expectedAfter(lit(':'), kwParam(KW.TIMEOUT), `Expected ':' after ${kwq(ctx, KW.TIMEOUT)}`));
|
|
699
704
|
ctx.next();
|
|
700
705
|
if (!ctx.at(TokenKind.INT))
|
|
701
|
-
error('Expected integer timeout value');
|
|
706
|
+
error(Issue.expected(tokenName('timeoutInteger'), 'Expected integer timeout value'));
|
|
702
707
|
const secondsTok = ctx.next();
|
|
703
708
|
const seconds = secondsTok.value;
|
|
704
709
|
if (seconds < 0)
|
|
705
|
-
error('Timeout value must be non-negative');
|
|
710
|
+
error(Issue.invalidValue(kwParam(KW.TIMEOUT), seconds, tokenName('nonNegativeInteger'), 'Timeout value must be non-negative'));
|
|
706
711
|
if (!(ctx.isKeyword('seconds') || ctx.isKeyword('second'))) {
|
|
707
712
|
// 'seconds' 不做本地化:所有 locale 的词表都没有时间单位的本地拼写,
|
|
708
713
|
// zh/de 作者同样只能写英文 seconds,报错里的 'seconds' 正是作者该写的词。
|
|
709
|
-
error("Timeout must specify time unit 'seconds'");
|
|
714
|
+
error(Issue.invalidValue(tokenName('timeoutUnit'), tokenLit(ctx.peek()), lit('seconds'), "Timeout must specify time unit 'seconds'"));
|
|
710
715
|
}
|
|
711
716
|
ctx.nextWord();
|
|
712
717
|
expectPeriodEnd(ctx, error);
|
|
@@ -724,17 +729,17 @@ function parseCases(ctx, error) {
|
|
|
724
729
|
ctx.pushCompoundContext(KW.MATCH);
|
|
725
730
|
try {
|
|
726
731
|
if (!ctx.at(TokenKind.INDENT))
|
|
727
|
-
error('Expected indent for cases');
|
|
732
|
+
error(Issue.expectedIn(tokenName('indent'), tokenName('matchCases'), 'Expected indent for cases'));
|
|
728
733
|
ctx.next();
|
|
729
734
|
while (!ctx.at(TokenKind.DEDENT)) {
|
|
730
735
|
if (!ctx.isKeyword(KW.WHEN)) {
|
|
731
736
|
// 改进的错误消息,提示复合模式关系
|
|
732
|
-
error(`Expected ${kwq(ctx, KW.WHEN, 'When')} (若...为 pattern requires '为' after '若')`);
|
|
737
|
+
error(Issue.expectedIn(kwParam(KW.WHEN), tokenName('matchCase'), `Expected ${kwq(ctx, KW.WHEN, 'When')} (若...为 pattern requires '为' after '若')`));
|
|
733
738
|
}
|
|
734
739
|
const whenTok = ctx.nextWord();
|
|
735
740
|
const pat = parsePattern(ctx, error);
|
|
736
741
|
if (!ctx.at(TokenKind.COMMA))
|
|
737
|
-
error("Expected ',' after pattern");
|
|
742
|
+
error(Issue.expectedAfter(lit(','), tokenName('pattern'), "Expected ',' after pattern"));
|
|
738
743
|
ctx.next();
|
|
739
744
|
const body = parseCaseBody(ctx, error);
|
|
740
745
|
const caseNode = Node.Case(pat, body);
|
|
@@ -806,13 +811,13 @@ function parseIfExpr(ctx, error) {
|
|
|
806
811
|
const cond = parseOr(ctx, error);
|
|
807
812
|
skipInlineThenLayout(ctx);
|
|
808
813
|
if (!ctx.isKeyword(KW.THEN))
|
|
809
|
-
error(`Expected ${kwq(ctx, KW.THEN)} in if-expression`);
|
|
814
|
+
error(Issue.expectedIn(kwParam(KW.THEN), tokenName('ifExpression'), `Expected ${kwq(ctx, KW.THEN)} in if-expression`));
|
|
810
815
|
ctx.nextWord(); // 消费 THEN
|
|
811
816
|
const thenE = parseExpr(ctx, error);
|
|
812
817
|
skipInlineElseLayout(ctx);
|
|
813
818
|
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)`);
|
|
819
|
+
error(Issue.expectedIn(kwParam(KW.OTHERWISE), tokenName('ifExpression'), `Expected ${kwKindq(ctx, SemanticTokenKind.OTHERWISE, 'else')} in if-expression ` +
|
|
820
|
+
`(${kwKind(ctx, SemanticTokenKind.OTHERWISE, 'else')} is mandatory for expression-level if)`));
|
|
816
821
|
}
|
|
817
822
|
ctx.nextWord(); // 消费 ELSE / OTHERWISE
|
|
818
823
|
const elseE = parseExpr(ctx, error);
|
|
@@ -1284,7 +1289,7 @@ function parseApplyOrPrimary(ctx, error) {
|
|
|
1284
1289
|
if (ctx.isKeyword(KW.APPLY) && applyLooksLikeCall(ctx)) {
|
|
1285
1290
|
const applyTok = ctx.nextWord();
|
|
1286
1291
|
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)}).`);
|
|
1292
|
+
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
1293
|
const arg = parseExpr(ctx, error);
|
|
1289
1294
|
const call = Node.Call(fn, [arg]);
|
|
1290
1295
|
assignSpan(call, spanFromSources(applyTok, arg));
|
|
@@ -1323,7 +1328,7 @@ function applyLooksLikeCall(ctx) {
|
|
|
1323
1328
|
function parseCallTargetName(ctx, error) {
|
|
1324
1329
|
const startTok = ctx.peek();
|
|
1325
1330
|
if (!ctx.at(TokenKind.IDENT) && !ctx.at(TokenKind.TYPE_IDENT)) {
|
|
1326
|
-
error(`Expected a function name after ${kwq(ctx, KW.APPLY)}.`);
|
|
1331
|
+
error(Issue.expectedAfter(tokenName('functionName'), kwParam(KW.APPLY), `Expected a function name after ${kwq(ctx, KW.APPLY)}.`));
|
|
1327
1332
|
}
|
|
1328
1333
|
const consumed = [ctx.peek()];
|
|
1329
1334
|
let full = ctx.at(TokenKind.IDENT) ? parseIdent(ctx, error) : ctx.next().value;
|
|
@@ -1393,7 +1398,7 @@ function operatorWordBeforeParen(ctx, english) {
|
|
|
1393
1398
|
function parseOperatorWordCall(ctx, error, word, english) {
|
|
1394
1399
|
const symbol = english ? null : OPERATOR_WORD_SYMBOLS.get(word);
|
|
1395
1400
|
if (!symbol) {
|
|
1396
|
-
error(`运算符词 ${kwq(ctx, word)} 不能用作前缀调用;只有符号运算符 ${PREFIX_SYMBOL_LIST} 可以写成 op(a, b)`);
|
|
1401
|
+
error(Issue.invalidValue(tokenName('prefixOperator'), kwParam(word), lit(PREFIX_SYMBOL_LIST), `运算符词 ${kwq(ctx, word)} 不能用作前缀调用;只有符号运算符 ${PREFIX_SYMBOL_LIST} 可以写成 op(a, b)`));
|
|
1397
1402
|
}
|
|
1398
1403
|
return parsePrefixOperatorCall(ctx, error, symbol, kwParts(word).length);
|
|
1399
1404
|
}
|
|
@@ -1404,7 +1409,7 @@ function parsePrefixOperatorCall(ctx, error, op, opTokenCount = 1) {
|
|
|
1404
1409
|
ctx.next();
|
|
1405
1410
|
const args = parseArgList(ctx, error);
|
|
1406
1411
|
if (args.length !== 2)
|
|
1407
|
-
error(`前缀操作符调用需要 2 个参数,但实际为 ${args.length}`);
|
|
1412
|
+
error(Issue.callArity(tokenName('prefixOperatorCall'), 2, args.length, `前缀操作符调用需要 2 个参数,但实际为 ${args.length}`));
|
|
1408
1413
|
const target = assignTokenSpan(Node.Name(op), opTok);
|
|
1409
1414
|
return assignSpan(Node.Call(target, args), spanFromTokens(opTok, lastNonLayoutToken(ctx)));
|
|
1410
1415
|
}
|
|
@@ -1425,7 +1430,7 @@ function parsePrimary(ctx, error) {
|
|
|
1425
1430
|
const functionTok = ctx.nextWord(); // 'function'
|
|
1426
1431
|
const params = parseParamList(ctx, error);
|
|
1427
1432
|
expectCommaOr(ctx);
|
|
1428
|
-
expectKeyword(ctx, error, KW.PRODUCE, `Expected ${kwq(ctx, KW.PRODUCE)} and return type`);
|
|
1433
|
+
expectKeyword(ctx, error, KW.PRODUCE, Issue.expected(kwParam(KW.PRODUCE), `Expected ${kwq(ctx, KW.PRODUCE)} and return type`));
|
|
1429
1434
|
let retType;
|
|
1430
1435
|
if (ctx.at(TokenKind.COLON)) {
|
|
1431
1436
|
retType = Node.TypeName('Unknown');
|
|
@@ -1435,7 +1440,7 @@ function parsePrimary(ctx, error) {
|
|
|
1435
1440
|
retType = parseType(ctx, error);
|
|
1436
1441
|
}
|
|
1437
1442
|
if (!ctx.at(TokenKind.COLON))
|
|
1438
|
-
error("Expected ':' after return type in lambda");
|
|
1443
|
+
error(Issue.expectedAfterIn(lit(':'), tokenName('returnType'), tokenName('lambda'), "Expected ':' after return type in lambda"));
|
|
1439
1444
|
ctx.next();
|
|
1440
1445
|
expectNewline(ctx, error);
|
|
1441
1446
|
const body = parseBlock(ctx, error);
|
|
@@ -1589,9 +1594,8 @@ function parsePrimary(ctx, error) {
|
|
|
1589
1594
|
}
|
|
1590
1595
|
if (ctx.isKeyword(KW.AWAIT)) {
|
|
1591
1596
|
const awaitTok = ctx.nextWord();
|
|
1597
|
+
// 参数个数不在解析期检查(与 aster-lang-core 一致;个数不符由运行时 await 内置报错,#295)
|
|
1592
1598
|
const args = parseArgList(ctx, error);
|
|
1593
|
-
if (args.length !== 1)
|
|
1594
|
-
error('await(expr) takes exactly one argument');
|
|
1595
1599
|
const target = assignTokenSpan(Node.Name('await'), awaitTok);
|
|
1596
1600
|
const call = Node.Call(target, args);
|
|
1597
1601
|
const endTok = lastNonLayoutToken(ctx);
|
|
@@ -1616,7 +1620,7 @@ function parsePrimary(ctx, error) {
|
|
|
1616
1620
|
}
|
|
1617
1621
|
}
|
|
1618
1622
|
if (!ctx.at(TokenKind.RBRACKET))
|
|
1619
|
-
error("Expected ']' to close list literal");
|
|
1623
|
+
error(Issue.expectedIn(lit(']'), tokenName('listLiteral'), "Expected ']' to close list literal"));
|
|
1620
1624
|
const rbrackTok = ctx.next();
|
|
1621
1625
|
const list = Node.ListLit(elements);
|
|
1622
1626
|
assignSpan(list, spanFromSources(lbrackTok, rbrackTok));
|
|
@@ -1627,7 +1631,7 @@ function parsePrimary(ctx, error) {
|
|
|
1627
1631
|
const lparenTok = ctx.next();
|
|
1628
1632
|
const expr = parseExpr(ctx, error);
|
|
1629
1633
|
if (!ctx.at(TokenKind.RPAREN))
|
|
1630
|
-
error("Expected ')' after expression");
|
|
1634
|
+
error(Issue.expectedAfter(lit(')'), tokenName('expression'), "Expected ')' after expression"));
|
|
1631
1635
|
const rparenTok = ctx.next();
|
|
1632
1636
|
assignSpanFromSources(expr, lparenTok, rparenTok, expr);
|
|
1633
1637
|
return expr;
|
|
@@ -1645,7 +1649,7 @@ function parsePrimary(ctx, error) {
|
|
|
1645
1649
|
const withTok = ctx.nextWord();
|
|
1646
1650
|
const fields = [];
|
|
1647
1651
|
let hasMore = true;
|
|
1648
|
-
const setToMissing = `Expected '${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)}' in construction
|
|
1652
|
+
const setToMissing = Issue.expectedIn(kwParam(KW.SET), tokenName('construction'), `Expected '${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)}' in construction`);
|
|
1649
1653
|
while (hasMore) {
|
|
1650
1654
|
const nameTok = ctx.peek();
|
|
1651
1655
|
// 字段位置不是名字(如 `helper with 10` 这种 with 调用后缀):与 Java 引擎
|
|
@@ -1659,7 +1663,7 @@ function parsePrimary(ctx, error) {
|
|
|
1659
1663
|
const equalsTok = ctx.peek();
|
|
1660
1664
|
ctx.nextWord();
|
|
1661
1665
|
if (!ctx.isKeyword(KW.TO_WORD))
|
|
1662
|
-
error(`Expected ${kwq(ctx, KW.TO_WORD)} after ${kwq(ctx, KW.SET)} in construction`);
|
|
1666
|
+
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
1667
|
ctx.nextWord();
|
|
1664
1668
|
const e = parseExpr(ctx, error);
|
|
1665
1669
|
const fld = {
|
|
@@ -1703,7 +1707,7 @@ function parsePrimary(ctx, error) {
|
|
|
1703
1707
|
full += '.' + partTok.value;
|
|
1704
1708
|
}
|
|
1705
1709
|
else {
|
|
1706
|
-
error('Expected identifier after dot');
|
|
1710
|
+
error(Issue.expectedAfter(tokenName('identifier'), lit('.'), 'Expected identifier after dot'));
|
|
1707
1711
|
}
|
|
1708
1712
|
}
|
|
1709
1713
|
const target = Node.Name(full);
|
|
@@ -1714,8 +1718,8 @@ function parsePrimary(ctx, error) {
|
|
|
1714
1718
|
// 直到运行时才抛 Undefined function;在此于编译期就报错并给出正确写法。
|
|
1715
1719
|
// 仅拦截裸类型名(无点链后缀),点链形态(如 SomeType.method(...))不受影响。
|
|
1716
1720
|
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)} ...'`);
|
|
1721
|
+
error(Issue.unexpectedConstruct(tokenName('positionalConstruction'), `Cannot construct '${typeName}' with positional arguments. ` +
|
|
1722
|
+
`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
1723
|
}
|
|
1720
1724
|
const args = parseArgList(ctx, error);
|
|
1721
1725
|
const call = Node.Call(target, args);
|
|
@@ -1752,8 +1756,8 @@ function parsePrimary(ctx, error) {
|
|
|
1752
1756
|
if (ctx.at(TokenKind.LPAREN)) {
|
|
1753
1757
|
// 同 TYPE_IDENT 分支:拦截对已声明类型的位置式构造(无大小写语言里类型名是 IDENT)。
|
|
1754
1758
|
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)} ...'`);
|
|
1759
|
+
error(Issue.unexpectedConstruct(tokenName('positionalConstruction'), `Cannot construct '${name}' with positional arguments. ` +
|
|
1760
|
+
`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
1761
|
}
|
|
1758
1762
|
const args = parseArgList(ctx, error);
|
|
1759
1763
|
const call = Node.Call(target, args);
|
|
@@ -1763,7 +1767,7 @@ function parsePrimary(ctx, error) {
|
|
|
1763
1767
|
}
|
|
1764
1768
|
return target;
|
|
1765
1769
|
}
|
|
1766
|
-
error('Unexpected expression');
|
|
1770
|
+
error(Issue.unexpectedConstruct(tokenName('expression'), 'Unexpected expression'));
|
|
1767
1771
|
}
|
|
1768
1772
|
/**
|
|
1769
1773
|
* 解析参数列表
|
|
@@ -1773,7 +1777,7 @@ function parsePrimary(ctx, error) {
|
|
|
1773
1777
|
*/
|
|
1774
1778
|
export function parseArgList(ctx, error) {
|
|
1775
1779
|
if (!ctx.at(TokenKind.LPAREN))
|
|
1776
|
-
error("Expected '('");
|
|
1780
|
+
error(Issue.expected(lit('('), "Expected '('"));
|
|
1777
1781
|
ctx.next();
|
|
1778
1782
|
const args = [];
|
|
1779
1783
|
while (!ctx.at(TokenKind.RPAREN)) {
|
|
@@ -1786,7 +1790,7 @@ export function parseArgList(ctx, error) {
|
|
|
1786
1790
|
break;
|
|
1787
1791
|
}
|
|
1788
1792
|
if (!ctx.at(TokenKind.RPAREN))
|
|
1789
|
-
error("Expected ')'");
|
|
1793
|
+
error(Issue.expected(lit(')'), "Expected ')'"));
|
|
1790
1794
|
ctx.next();
|
|
1791
1795
|
return args;
|
|
1792
1796
|
}
|
|
@@ -1832,7 +1836,7 @@ export function parsePattern(ctx, error) {
|
|
|
1832
1836
|
break;
|
|
1833
1837
|
}
|
|
1834
1838
|
if (!ctx.at(TokenKind.RPAREN))
|
|
1835
|
-
error("Expected ')' in pattern");
|
|
1839
|
+
error(Issue.expectedIn(lit(')'), tokenName('pattern'), "Expected ')' in pattern"));
|
|
1836
1840
|
const rparenTok = ctx.next();
|
|
1837
1841
|
const node = Node.PatternCtor(typeName, names);
|
|
1838
1842
|
assignSpan(node, spanFromSources(typeTok, lparenTok, rparenTok, ...nameTokens));
|
|
@@ -2036,7 +2040,7 @@ function parseIdent(ctx, error) {
|
|
|
2036
2040
|
if (recovered !== null)
|
|
2037
2041
|
return recovered;
|
|
2038
2042
|
if (!ctx.at(TokenKind.IDENT)) {
|
|
2039
|
-
error('Expected identifier');
|
|
2043
|
+
error(Issue.expected(tokenName('identifier'), 'Expected identifier'));
|
|
2040
2044
|
}
|
|
2041
2045
|
return ctx.next().value;
|
|
2042
2046
|
}
|
|
@@ -2047,9 +2051,9 @@ function parseIdent(ctx, error) {
|
|
|
2047
2051
|
* @param kw 关键字
|
|
2048
2052
|
* @param msg 错误消息
|
|
2049
2053
|
*/
|
|
2050
|
-
function expectKeyword(ctx, error, kw,
|
|
2054
|
+
function expectKeyword(ctx, error, kw, issue) {
|
|
2051
2055
|
if (!ctx.isKeyword(kw))
|
|
2052
|
-
error(
|
|
2056
|
+
error(issue);
|
|
2053
2057
|
ctx.nextWord();
|
|
2054
2058
|
}
|
|
2055
2059
|
/**
|
|
@@ -2068,7 +2072,7 @@ function expectCommaOr(ctx) {
|
|
|
2068
2072
|
*/
|
|
2069
2073
|
function expectNewline(ctx, error) {
|
|
2070
2074
|
if (!ctx.at(TokenKind.NEWLINE))
|
|
2071
|
-
error('Expected newline');
|
|
2075
|
+
error(Issue.expected(tokenName('newline'), 'Expected newline'));
|
|
2072
2076
|
ctx.next();
|
|
2073
2077
|
}
|
|
2074
2078
|
//# sourceMappingURL=expr-stmt-parser.js.map
|