@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.
Files changed (179) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/scripts/generate_error_codes.js +3 -0
  3. package/dist/scripts/generate_error_codes.js.map +1 -1
  4. package/dist/src/browser.d.ts +16 -2
  5. package/dist/src/browser.d.ts.map +1 -1
  6. package/dist/src/browser.js +12 -19
  7. package/dist/src/browser.js.map +1 -1
  8. package/dist/src/cli/utils/error-handler.js.map +1 -1
  9. package/dist/src/config/lexicons/diagnostic-overlays.generated.d.ts.map +1 -1
  10. package/dist/src/config/lexicons/diagnostic-overlays.generated.js +120 -0
  11. package/dist/src/config/lexicons/diagnostic-overlays.generated.js.map +1 -1
  12. package/dist/src/config/lexicons/identifiers/index.d.ts +1 -0
  13. package/dist/src/config/lexicons/identifiers/index.d.ts.map +1 -1
  14. package/dist/src/config/lexicons/identifiers/index.js +1 -0
  15. package/dist/src/config/lexicons/identifiers/index.js.map +1 -1
  16. package/dist/src/config/lexicons/identifiers/spelling.d.ts +25 -0
  17. package/dist/src/config/lexicons/identifiers/spelling.d.ts.map +1 -0
  18. package/dist/src/config/lexicons/identifiers/spelling.js +44 -0
  19. package/dist/src/config/lexicons/identifiers/spelling.js.map +1 -0
  20. package/dist/src/core/interpreter.js +4 -0
  21. package/dist/src/core/interpreter.js.map +1 -1
  22. package/dist/src/core/resolve-typed-references.d.ts.map +1 -1
  23. package/dist/src/core/resolve-typed-references.js +34 -22
  24. package/dist/src/core/resolve-typed-references.js.map +1 -1
  25. package/dist/src/diagnostics/diagnostics.d.ts +27 -2
  26. package/dist/src/diagnostics/diagnostics.d.ts.map +1 -1
  27. package/dist/src/diagnostics/diagnostics.js +37 -1
  28. package/dist/src/diagnostics/diagnostics.js.map +1 -1
  29. package/dist/src/diagnostics/error_codes.d.ts +21 -1
  30. package/dist/src/diagnostics/error_codes.d.ts.map +1 -1
  31. package/dist/src/diagnostics/error_codes.js +160 -0
  32. package/dist/src/diagnostics/error_codes.js.map +1 -1
  33. package/dist/src/frontend/canonicalizer.d.ts.map +1 -1
  34. package/dist/src/frontend/canonicalizer.js +44 -1
  35. package/dist/src/frontend/canonicalizer.js.map +1 -1
  36. package/dist/src/frontend/lexer.js +1 -1
  37. package/dist/src/frontend/lexer.js.map +1 -1
  38. package/dist/src/frontend/resolve-declarations.d.ts.map +1 -1
  39. package/dist/src/frontend/resolve-declarations.js +24 -1
  40. package/dist/src/frontend/resolve-declarations.js.map +1 -1
  41. package/dist/src/frontend/resolve-policy-types.d.ts +2 -0
  42. package/dist/src/frontend/resolve-policy-types.d.ts.map +1 -1
  43. package/dist/src/frontend/resolve-policy-types.js +2 -1
  44. package/dist/src/frontend/resolve-policy-types.js.map +1 -1
  45. package/dist/src/index.d.ts +2 -2
  46. package/dist/src/index.d.ts.map +1 -1
  47. package/dist/src/index.js +1 -1
  48. package/dist/src/index.js.map +1 -1
  49. package/dist/src/lsp/completion.d.ts +2 -2
  50. package/dist/src/lsp/completion.d.ts.map +1 -1
  51. package/dist/src/lsp/completion.js +65 -55
  52. package/dist/src/lsp/completion.js.map +1 -1
  53. package/dist/src/lsp/diagnostics.d.ts.map +1 -1
  54. package/dist/src/lsp/diagnostics.js +2 -0
  55. package/dist/src/lsp/diagnostics.js.map +1 -1
  56. package/dist/src/lsp/member-completion.d.ts +10 -0
  57. package/dist/src/lsp/member-completion.d.ts.map +1 -1
  58. package/dist/src/lsp/member-completion.js +25 -10
  59. package/dist/src/lsp/member-completion.js.map +1 -1
  60. package/dist/src/parser/constraint-parser.d.ts +2 -1
  61. package/dist/src/parser/constraint-parser.d.ts.map +1 -1
  62. package/dist/src/parser/constraint-parser.js +9 -7
  63. package/dist/src/parser/constraint-parser.js.map +1 -1
  64. package/dist/src/parser/context.d.ts.map +1 -1
  65. package/dist/src/parser/context.js +1 -0
  66. package/dist/src/parser/context.js.map +1 -1
  67. package/dist/src/parser/decl-parser.d.ts +4 -3
  68. package/dist/src/parser/decl-parser.d.ts.map +1 -1
  69. package/dist/src/parser/decl-parser.js +17 -16
  70. package/dist/src/parser/decl-parser.js.map +1 -1
  71. package/dist/src/parser/expr-stmt-parser.d.ts +10 -9
  72. package/dist/src/parser/expr-stmt-parser.d.ts.map +1 -1
  73. package/dist/src/parser/expr-stmt-parser.js +96 -92
  74. package/dist/src/parser/expr-stmt-parser.js.map +1 -1
  75. package/dist/src/parser/field-variant-parser.d.ts +4 -3
  76. package/dist/src/parser/field-variant-parser.d.ts.map +1 -1
  77. package/dist/src/parser/field-variant-parser.js +3 -2
  78. package/dist/src/parser/field-variant-parser.js.map +1 -1
  79. package/dist/src/parser/import-parser.d.ts +5 -5
  80. package/dist/src/parser/import-parser.d.ts.map +1 -1
  81. package/dist/src/parser/import-parser.js +4 -3
  82. package/dist/src/parser/import-parser.js.map +1 -1
  83. package/dist/src/parser/parse-issue.d.ts +44 -0
  84. package/dist/src/parser/parse-issue.d.ts.map +1 -0
  85. package/dist/src/parser/parse-issue.js +37 -0
  86. package/dist/src/parser/parse-issue.js.map +1 -0
  87. package/dist/src/parser/parser-tools.d.ts +5 -5
  88. package/dist/src/parser/parser-tools.d.ts.map +1 -1
  89. package/dist/src/parser/parser-tools.js +19 -7
  90. package/dist/src/parser/parser-tools.js.map +1 -1
  91. package/dist/src/parser/type-parser.d.ts +4 -3
  92. package/dist/src/parser/type-parser.d.ts.map +1 -1
  93. package/dist/src/parser/type-parser.js +19 -16
  94. package/dist/src/parser/type-parser.js.map +1 -1
  95. package/dist/src/typecheck/browser.d.ts.map +1 -1
  96. package/dist/src/typecheck/browser.js +4 -11
  97. package/dist/src/typecheck/browser.js.map +1 -1
  98. package/dist/src/typecheck/expression.js +2 -2
  99. package/dist/src/typecheck/expression.js.map +1 -1
  100. package/dist/src/typecheck/generics.d.ts.map +1 -1
  101. package/dist/src/typecheck/generics.js +2 -26
  102. package/dist/src/typecheck/generics.js.map +1 -1
  103. package/dist/src/typecheck/module.d.ts.map +1 -1
  104. package/dist/src/typecheck/module.js +4 -10
  105. package/dist/src/typecheck/module.js.map +1 -1
  106. package/dist/src/typecheck/pattern.d.ts.map +1 -1
  107. package/dist/src/typecheck/pattern.js +5 -4
  108. package/dist/src/typecheck/pattern.js.map +1 -1
  109. package/dist/src/typecheck/pure.d.ts +2 -0
  110. package/dist/src/typecheck/pure.d.ts.map +1 -1
  111. package/dist/src/typecheck/pure.js +4 -0
  112. package/dist/src/typecheck/pure.js.map +1 -1
  113. package/dist/src/typecheck/statement.d.ts +8 -0
  114. package/dist/src/typecheck/statement.d.ts.map +1 -1
  115. package/dist/src/typecheck/statement.js +60 -3
  116. package/dist/src/typecheck/statement.js.map +1 -1
  117. package/dist/src/typecheck/workflow.d.ts.map +1 -1
  118. package/dist/src/typecheck/workflow.js +11 -33
  119. package/dist/src/typecheck/workflow.js.map +1 -1
  120. package/dist/test/integration/lsp/lsp-completion.test.js +3 -1
  121. package/dist/test/integration/lsp/lsp-completion.test.js.map +1 -1
  122. package/dist/test/integration/scripts/typecheck-cli-repl.test.js +2 -1
  123. package/dist/test/integration/scripts/typecheck-cli-repl.test.js.map +1 -1
  124. package/dist/test/integration/vocabulary-resolution.test.js +10 -1
  125. package/dist/test/integration/vocabulary-resolution.test.js.map +1 -1
  126. package/dist/test/unit/canonicalizer/article-declaration-names.test.d.ts +2 -0
  127. package/dist/test/unit/canonicalizer/article-declaration-names.test.d.ts.map +1 -0
  128. package/dist/test/unit/canonicalizer/article-declaration-names.test.js +53 -0
  129. package/dist/test/unit/canonicalizer/article-declaration-names.test.js.map +1 -0
  130. package/dist/test/unit/diagnostics/diagnostic-overlays.test.js.map +1 -1
  131. package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.d.ts +9 -0
  132. package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.d.ts.map +1 -0
  133. package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.js +121 -0
  134. package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.js.map +1 -0
  135. package/dist/test/unit/diagnostics/parse-issue-codes.test.d.ts +6 -0
  136. package/dist/test/unit/diagnostics/parse-issue-codes.test.d.ts.map +1 -0
  137. package/dist/test/unit/diagnostics/parse-issue-codes.test.js +82 -0
  138. package/dist/test/unit/diagnostics/parse-issue-codes.test.js.map +1 -0
  139. package/dist/test/unit/frontend/resolve-declarations.test.js +47 -0
  140. package/dist/test/unit/frontend/resolve-declarations.test.js.map +1 -1
  141. package/dist/test/unit/identifiers/spelling.test.d.ts +2 -0
  142. package/dist/test/unit/identifiers/spelling.test.d.ts.map +1 -0
  143. package/dist/test/unit/identifiers/spelling.test.js +51 -0
  144. package/dist/test/unit/identifiers/spelling.test.js.map +1 -0
  145. package/dist/test/unit/int-pattern-decimal.test.d.ts +2 -0
  146. package/dist/test/unit/int-pattern-decimal.test.d.ts.map +1 -0
  147. package/dist/test/unit/int-pattern-decimal.test.js +54 -0
  148. package/dist/test/unit/int-pattern-decimal.test.js.map +1 -0
  149. package/dist/test/unit/lsp/completion-lexicon.test.js +1 -1
  150. package/dist/test/unit/lsp/completion-lexicon.test.js.map +1 -1
  151. package/dist/test/unit/lsp/completion-list.test.d.ts +2 -0
  152. package/dist/test/unit/lsp/completion-list.test.d.ts.map +1 -0
  153. package/dist/test/unit/lsp/completion-list.test.js +64 -0
  154. package/dist/test/unit/lsp/completion-list.test.js.map +1 -0
  155. package/dist/test/unit/lsp/member-completion.test.js +99 -1
  156. package/dist/test/unit/lsp/member-completion.test.js.map +1 -1
  157. package/dist/test/unit/lsp/resolution-completion.test.js +2 -2
  158. package/dist/test/unit/lsp/resolution-completion.test.js.map +1 -1
  159. package/dist/test/unit/lsp/resolution-diagnostics.test.js +4 -2
  160. package/dist/test/unit/lsp/resolution-diagnostics.test.js.map +1 -1
  161. package/dist/test/unit/parser/depth-limit.test.js +3 -4
  162. package/dist/test/unit/parser/depth-limit.test.js.map +1 -1
  163. package/dist/test/unit/parser/parser.test.js +36 -6
  164. package/dist/test/unit/parser/parser.test.js.map +1 -1
  165. package/dist/test/unit/typecheck/effects-and-capabilities.test.js +19 -0
  166. package/dist/test/unit/typecheck/effects-and-capabilities.test.js.map +1 -1
  167. package/dist/test/unit/typecheck/java-parity-281.test.d.ts +2 -0
  168. package/dist/test/unit/typecheck/java-parity-281.test.d.ts.map +1 -0
  169. package/dist/test/unit/typecheck/java-parity-281.test.js +87 -0
  170. package/dist/test/unit/typecheck/java-parity-281.test.js.map +1 -0
  171. package/dist/test/unit/typecheck/regression-fixes.test.js +2 -2
  172. package/dist/test/unit/typecheck/regression-fixes.test.js.map +1 -1
  173. package/dist/test/unit/typecheck/return-paths.test.d.ts +2 -0
  174. package/dist/test/unit/typecheck/return-paths.test.d.ts.map +1 -0
  175. package/dist/test/unit/typecheck/return-paths.test.js +54 -0
  176. package/dist/test/unit/typecheck/return-paths.test.js.map +1 -0
  177. package/dist/test/unit/typecheck/typecheck-advanced.test.js +2 -2
  178. package/dist/test/unit/typecheck/typecheck-advanced.test.js.map +1 -1
  179. 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
- let closed = false;
55
+ // Otherwise 收尾的位置:其后再有语句时错误落在该 Otherwise 上(与 Java requireOtherwiseLast 一致)
56
+ let closedBy;
57
+ // 记下 token:错误落在结论起点(与 Java AstBuilder 报在 sugarOutcome 上下文一致),而非块末
55
58
  let pendingOutcomeError;
56
- const deferOutcomeError = (msg) => { pendingOutcomeError ??= msg; };
59
+ const deferOutcomeError = (issue, tok) => { pendingOutcomeError ??= { issue, tok }; };
57
60
  return {
58
61
  next: () => {
59
- if (closed)
60
- error(OTHERWISE_NOT_LAST);
62
+ if (closedBy !== undefined)
63
+ error(OTHERWISE_NOT_LAST, closedBy);
61
64
  // 语句开头的 Otherwise/else 必走 parseOtherwiseSugar(If 的 else 分支由 If 自身消费)
62
- closed = isElseWord(ctx);
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
- expectKeyword(ctx, error, KW.RETRY, `Expected ${kwq(ctx, KW.RETRY)}`);
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, msg) {
2054
+ function expectKeyword(ctx, error, kw, issue) {
2051
2055
  if (!ctx.isKeyword(kw))
2052
- error(msg);
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