@aster-cloud/aster-lang-ts 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/CHANGELOG.md +68 -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 +18 -0
  5. package/dist/src/browser.d.ts.map +1 -1
  6. package/dist/src/browser.js +13 -18
  7. package/dist/src/browser.js.map +1 -1
  8. package/dist/src/cli/utils/error-handler.js +1 -1
  9. package/dist/src/cli/utils/error-handler.js.map +1 -1
  10. package/dist/src/config/semantic.d.ts +12 -1
  11. package/dist/src/config/semantic.d.ts.map +1 -1
  12. package/dist/src/config/semantic.js +20 -1
  13. package/dist/src/config/semantic.js.map +1 -1
  14. package/dist/src/core/interpreter.js +28 -3
  15. package/dist/src/core/interpreter.js.map +1 -1
  16. package/dist/src/core/resolve-typed-references.d.ts.map +1 -1
  17. package/dist/src/core/resolve-typed-references.js +34 -22
  18. package/dist/src/core/resolve-typed-references.js.map +1 -1
  19. package/dist/src/diagnostics/diagnostics.d.ts +33 -2
  20. package/dist/src/diagnostics/diagnostics.d.ts.map +1 -1
  21. package/dist/src/diagnostics/diagnostics.js +47 -1
  22. package/dist/src/diagnostics/diagnostics.js.map +1 -1
  23. package/dist/src/diagnostics/error_codes.d.ts +21 -1
  24. package/dist/src/diagnostics/error_codes.d.ts.map +1 -1
  25. package/dist/src/diagnostics/error_codes.js +160 -0
  26. package/dist/src/diagnostics/error_codes.js.map +1 -1
  27. package/dist/src/frontend/lexer.js +1 -1
  28. package/dist/src/frontend/lexer.js.map +1 -1
  29. package/dist/src/frontend/resolve-declarations.d.ts.map +1 -1
  30. package/dist/src/frontend/resolve-declarations.js +24 -1
  31. package/dist/src/frontend/resolve-declarations.js.map +1 -1
  32. package/dist/src/lsp/completion.d.ts +2 -2
  33. package/dist/src/lsp/completion.d.ts.map +1 -1
  34. package/dist/src/lsp/completion.js +65 -55
  35. package/dist/src/lsp/completion.js.map +1 -1
  36. package/dist/src/lsp/diagnostics.d.ts.map +1 -1
  37. package/dist/src/lsp/diagnostics.js +4 -0
  38. package/dist/src/lsp/diagnostics.js.map +1 -1
  39. package/dist/src/lsp/member-completion.d.ts +10 -0
  40. package/dist/src/lsp/member-completion.d.ts.map +1 -1
  41. package/dist/src/lsp/member-completion.js +25 -10
  42. package/dist/src/lsp/member-completion.js.map +1 -1
  43. package/dist/src/parser/constraint-parser.d.ts +2 -1
  44. package/dist/src/parser/constraint-parser.d.ts.map +1 -1
  45. package/dist/src/parser/constraint-parser.js +9 -7
  46. package/dist/src/parser/constraint-parser.js.map +1 -1
  47. package/dist/src/parser/context.d.ts.map +1 -1
  48. package/dist/src/parser/context.js +1 -0
  49. package/dist/src/parser/context.js.map +1 -1
  50. package/dist/src/parser/decl-parser.d.ts +4 -3
  51. package/dist/src/parser/decl-parser.d.ts.map +1 -1
  52. package/dist/src/parser/decl-parser.js +19 -17
  53. package/dist/src/parser/decl-parser.js.map +1 -1
  54. package/dist/src/parser/expr-stmt-parser.d.ts +10 -9
  55. package/dist/src/parser/expr-stmt-parser.d.ts.map +1 -1
  56. package/dist/src/parser/expr-stmt-parser.js +96 -90
  57. package/dist/src/parser/expr-stmt-parser.js.map +1 -1
  58. package/dist/src/parser/field-variant-parser.d.ts +4 -3
  59. package/dist/src/parser/field-variant-parser.d.ts.map +1 -1
  60. package/dist/src/parser/field-variant-parser.js +3 -2
  61. package/dist/src/parser/field-variant-parser.js.map +1 -1
  62. package/dist/src/parser/import-parser.d.ts +5 -5
  63. package/dist/src/parser/import-parser.d.ts.map +1 -1
  64. package/dist/src/parser/import-parser.js +4 -3
  65. package/dist/src/parser/import-parser.js.map +1 -1
  66. package/dist/src/parser/parse-issue.d.ts +44 -0
  67. package/dist/src/parser/parse-issue.d.ts.map +1 -0
  68. package/dist/src/parser/parse-issue.js +37 -0
  69. package/dist/src/parser/parse-issue.js.map +1 -0
  70. package/dist/src/parser/parser-tools.d.ts +5 -5
  71. package/dist/src/parser/parser-tools.d.ts.map +1 -1
  72. package/dist/src/parser/parser-tools.js +20 -7
  73. package/dist/src/parser/parser-tools.js.map +1 -1
  74. package/dist/src/parser/type-parser.d.ts +4 -3
  75. package/dist/src/parser/type-parser.d.ts.map +1 -1
  76. package/dist/src/parser/type-parser.js +18 -16
  77. package/dist/src/parser/type-parser.js.map +1 -1
  78. package/dist/src/typecheck/browser.d.ts.map +1 -1
  79. package/dist/src/typecheck/browser.js +4 -10
  80. package/dist/src/typecheck/browser.js.map +1 -1
  81. package/dist/src/typecheck/context.d.ts.map +1 -1
  82. package/dist/src/typecheck/context.js +5 -2
  83. package/dist/src/typecheck/context.js.map +1 -1
  84. package/dist/src/typecheck/expression.d.ts.map +1 -1
  85. package/dist/src/typecheck/expression.js +12 -9
  86. package/dist/src/typecheck/expression.js.map +1 -1
  87. package/dist/src/typecheck/generics.d.ts.map +1 -1
  88. package/dist/src/typecheck/generics.js +2 -26
  89. package/dist/src/typecheck/generics.js.map +1 -1
  90. package/dist/src/typecheck/module.d.ts.map +1 -1
  91. package/dist/src/typecheck/module.js +4 -9
  92. package/dist/src/typecheck/module.js.map +1 -1
  93. package/dist/src/typecheck/pattern.d.ts.map +1 -1
  94. package/dist/src/typecheck/pattern.js +5 -4
  95. package/dist/src/typecheck/pattern.js.map +1 -1
  96. package/dist/src/typecheck/pure.d.ts +6 -2
  97. package/dist/src/typecheck/pure.d.ts.map +1 -1
  98. package/dist/src/typecheck/pure.js +19 -2
  99. package/dist/src/typecheck/pure.js.map +1 -1
  100. package/dist/src/typecheck/statement.d.ts +8 -0
  101. package/dist/src/typecheck/statement.d.ts.map +1 -1
  102. package/dist/src/typecheck/statement.js +60 -3
  103. package/dist/src/typecheck/statement.js.map +1 -1
  104. package/dist/src/typecheck/workflow.d.ts.map +1 -1
  105. package/dist/src/typecheck/workflow.js +23 -34
  106. package/dist/src/typecheck/workflow.js.map +1 -1
  107. package/dist/test/integration/lsp/lsp-completion.test.js +3 -1
  108. package/dist/test/integration/lsp/lsp-completion.test.js.map +1 -1
  109. package/dist/test/integration/scripts/typecheck-cli-repl.test.js +2 -1
  110. package/dist/test/integration/scripts/typecheck-cli-repl.test.js.map +1 -1
  111. package/dist/test/unit/diagnostics/diagnostic-overlays.test.js +8 -0
  112. package/dist/test/unit/diagnostics/diagnostic-overlays.test.js.map +1 -1
  113. package/dist/test/unit/diagnostics/legacy-codes.test.d.ts +6 -0
  114. package/dist/test/unit/diagnostics/legacy-codes.test.d.ts.map +1 -0
  115. package/dist/test/unit/diagnostics/legacy-codes.test.js +81 -0
  116. package/dist/test/unit/diagnostics/legacy-codes.test.js.map +1 -0
  117. package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.d.ts +9 -0
  118. package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.d.ts.map +1 -0
  119. package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.js +121 -0
  120. package/dist/test/unit/diagnostics/parse-diagnostics-parity.test.js.map +1 -0
  121. package/dist/test/unit/frontend/resolve-declarations.test.js +47 -0
  122. package/dist/test/unit/frontend/resolve-declarations.test.js.map +1 -1
  123. package/dist/test/unit/int-pattern-decimal.test.d.ts +2 -0
  124. package/dist/test/unit/int-pattern-decimal.test.d.ts.map +1 -0
  125. package/dist/test/unit/int-pattern-decimal.test.js +54 -0
  126. package/dist/test/unit/int-pattern-decimal.test.js.map +1 -0
  127. package/dist/test/unit/lsp/completion-lexicon.test.js +1 -1
  128. package/dist/test/unit/lsp/completion-lexicon.test.js.map +1 -1
  129. package/dist/test/unit/lsp/completion-list.test.d.ts +2 -0
  130. package/dist/test/unit/lsp/completion-list.test.d.ts.map +1 -0
  131. package/dist/test/unit/lsp/completion-list.test.js +64 -0
  132. package/dist/test/unit/lsp/completion-list.test.js.map +1 -0
  133. package/dist/test/unit/lsp/member-completion.test.js +99 -1
  134. package/dist/test/unit/lsp/member-completion.test.js.map +1 -1
  135. package/dist/test/unit/lsp/resolution-completion.test.js +2 -2
  136. package/dist/test/unit/lsp/resolution-completion.test.js.map +1 -1
  137. package/dist/test/unit/lsp/resolution-diagnostics.test.js +4 -2
  138. package/dist/test/unit/lsp/resolution-diagnostics.test.js.map +1 -1
  139. package/dist/test/unit/parser/depth-limit.test.js +3 -3
  140. package/dist/test/unit/parser/depth-limit.test.js.map +1 -1
  141. package/dist/test/unit/parser/parser.test.js +4 -1
  142. package/dist/test/unit/parser/parser.test.js.map +1 -1
  143. package/dist/test/unit/rule-call-checks.test.js +11 -5
  144. package/dist/test/unit/rule-call-checks.test.js.map +1 -1
  145. package/dist/test/unit/typecheck/java-parity-281.test.d.ts +2 -0
  146. package/dist/test/unit/typecheck/java-parity-281.test.d.ts.map +1 -0
  147. package/dist/test/unit/typecheck/java-parity-281.test.js +87 -0
  148. package/dist/test/unit/typecheck/java-parity-281.test.js.map +1 -0
  149. package/dist/test/unit/typecheck/regression-fixes.test.js +2 -2
  150. package/dist/test/unit/typecheck/regression-fixes.test.js.map +1 -1
  151. package/dist/test/unit/typecheck/return-paths.test.d.ts +2 -0
  152. package/dist/test/unit/typecheck/return-paths.test.d.ts.map +1 -0
  153. package/dist/test/unit/typecheck/return-paths.test.js +54 -0
  154. package/dist/test/unit/typecheck/return-paths.test.js.map +1 -0
  155. package/dist/test/unit/typecheck/typecheck-advanced.test.js +2 -2
  156. package/dist/test/unit/typecheck/typecheck-advanced.test.js.map +1 -1
  157. package/dist/test/unit/typecheck-followups.test.d.ts +2 -0
  158. package/dist/test/unit/typecheck-followups.test.d.ts.map +1 -0
  159. package/dist/test/unit/typecheck-followups.test.js +165 -0
  160. package/dist/test/unit/typecheck-followups.test.js.map +1 -0
  161. 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,10 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
425
429
  if (ctx.isKeyword(KW.AWAIT)) {
426
430
  const awaitTok = ctx.nextWord();
427
431
  if (!ctx.at(TokenKind.LPAREN))
428
- error("Expected '(' after await");
432
+ error(Issue.expectedAfter(lit('('), lit('await'), "Expected '(' after await"));
429
433
  const args = parseArgList(ctx, error);
430
434
  if (args.length !== 1)
431
- error('await(expr) takes exactly one argument');
435
+ error(Issue.callArity(lit('await'), 1, args.length, 'await(expr) takes exactly one argument'));
432
436
  const target = assignTokenSpan(Node.Name('await'), awaitTok);
433
437
  const callSpanEnd = lastNonLayoutToken(ctx);
434
438
  const aw = Node.Call(target, args);
@@ -476,7 +480,7 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
476
480
  ctx.nextWord();
477
481
  const expr = parseExpr(ctx, error);
478
482
  if (!ctx.at(TokenKind.COLON))
479
- error("Expected ':' after match expression");
483
+ error(Issue.expectedAfter(lit(':'), tokenName('matchExpression'), "Expected ':' after match expression"));
480
484
  ctx.next();
481
485
  expectNewline(ctx, error);
482
486
  const cases = parseCases(ctx, error);
@@ -509,9 +513,9 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
509
513
  }
510
514
  if (ctx.isKeyword(KW.WITHIN)) {
511
515
  ctx.nextWord();
512
- expectKeyword(ctx, error, KW.SCOPE, `Expected ${kwq(ctx, KW.SCOPE)} after ${kwq(ctx, KW.WITHIN, 'Within')}`);
516
+ expectKeyword(ctx, error, KW.SCOPE, Issue.expectedAfter(kwParam(KW.SCOPE), kwParam(KW.WITHIN), `Expected ${kwq(ctx, KW.SCOPE)} after ${kwq(ctx, KW.WITHIN, 'Within')}`));
513
517
  if (!ctx.at(TokenKind.COLON))
514
- error(`Expected ':' after ${kwq(ctx, KW.SCOPE)}`);
518
+ error(Issue.expectedAfter(lit(':'), kwParam(KW.SCOPE), `Expected ':' after ${kwq(ctx, KW.SCOPE)}`));
515
519
  ctx.next();
516
520
  expectNewline(ctx, error);
517
521
  return parseBlock(ctx, error); // Lowering later
@@ -519,8 +523,8 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
519
523
  if (ctx.isKeyword(KW.START)) {
520
524
  const startTok = ctx.nextWord();
521
525
  const name = parseIdent(ctx, error);
522
- expectKeyword(ctx, error, KW.AS, `Expected ${kwq(ctx, KW.AS)} after name`);
523
- expectKeyword(ctx, error, KW.ASYNC, `Expected ${kwq(ctx, KW.ASYNC)}`);
526
+ expectKeyword(ctx, error, KW.AS, Issue.expectedAfter(kwParam(KW.AS), tokenName('name'), `Expected ${kwq(ctx, KW.AS)} after name`));
527
+ expectKeyword(ctx, error, KW.ASYNC, Issue.expected(kwParam(KW.ASYNC), `Expected ${kwq(ctx, KW.ASYNC)}`));
524
528
  const expr = parseExpr(ctx, error);
525
529
  expectPeriodEnd(ctx, error);
526
530
  const nd = Node.Start(name, expr);
@@ -549,17 +553,17 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
549
553
  assignSpan(nd, spanFromTokens(waitStart, endTok));
550
554
  return nd;
551
555
  }
552
- error('Unknown statement');
556
+ error(Issue.unexpectedConstruct(tokenName('statement'), 'Unknown statement'));
553
557
  }
554
558
  function parseWorkflow(ctx, error) {
555
559
  const workflowTok = ctx.peek();
556
- expectKeyword(ctx, error, KW.WORKFLOW, `Expected ${kwq(ctx, KW.WORKFLOW)}`);
560
+ expectKeyword(ctx, error, KW.WORKFLOW, Issue.expected(kwParam(KW.WORKFLOW), `Expected ${kwq(ctx, KW.WORKFLOW)}`));
557
561
  if (!ctx.at(TokenKind.COLON))
558
- error(`Expected ':' after ${kwq(ctx, KW.WORKFLOW)}`);
562
+ error(Issue.expectedAfter(lit(':'), kwParam(KW.WORKFLOW), `Expected ':' after ${kwq(ctx, KW.WORKFLOW)}`));
559
563
  ctx.next();
560
564
  expectNewline(ctx, error);
561
565
  if (!ctx.at(TokenKind.INDENT))
562
- error('Expected indent after workflow header');
566
+ error(Issue.expectedAfter(tokenName('indent'), tokenName('workflowHeader'), 'Expected indent after workflow header'));
563
567
  ctx.next();
564
568
  ctx.consumeNewlines();
565
569
  const steps = [];
@@ -568,7 +572,7 @@ function parseWorkflow(ctx, error) {
568
572
  ctx.consumeNewlines();
569
573
  }
570
574
  if (steps.length === 0)
571
- error('Workflow must declare at least one step');
575
+ error(Issue.emptyBlock(kwParam(KW.WORKFLOW), kwParam(KW.STEP), 'Workflow must declare at least one step'));
572
576
  let retry;
573
577
  if (ctx.isKeyword(KW.RETRY)) {
574
578
  retry = parseRetryPolicy(ctx, error);
@@ -580,7 +584,7 @@ function parseWorkflow(ctx, error) {
580
584
  ctx.consumeNewlines();
581
585
  }
582
586
  if (!ctx.at(TokenKind.DEDENT))
583
- error('Expected dedent after workflow body');
587
+ error(Issue.expectedAfter(tokenName('dedent'), tokenName('workflowBody'), 'Expected dedent after workflow body'));
584
588
  ctx.next();
585
589
  ctx.consumeNewlines();
586
590
  expectPeriodEnd(ctx, error);
@@ -591,19 +595,19 @@ function parseWorkflow(ctx, error) {
591
595
  }
592
596
  function parseStep(ctx, error) {
593
597
  const stepTok = ctx.peek();
594
- expectKeyword(ctx, error, KW.STEP, `Expected ${kwq(ctx, KW.STEP)}`);
598
+ expectKeyword(ctx, error, KW.STEP, Issue.expected(kwParam(KW.STEP), `Expected ${kwq(ctx, KW.STEP)}`));
595
599
  const name = parseIdent(ctx, error);
596
600
  const dependencies = [];
597
601
  if (ctx.isKeyword(KW.DEPENDS)) {
598
602
  ctx.nextWord();
599
- expectKeyword(ctx, error, KW.ON, `Expected ${kwq(ctx, KW.ON)} after ${kwq(ctx, KW.DEPENDS)}`);
603
+ expectKeyword(ctx, error, KW.ON, Issue.expectedAfter(kwParam(KW.ON), kwParam(KW.DEPENDS), `Expected ${kwq(ctx, KW.ON)} after ${kwq(ctx, KW.DEPENDS)}`));
600
604
  if (!ctx.at(TokenKind.LBRACKET))
601
- error(`Expected '[' after '${kw(ctx, KW.DEPENDS)} ${kw(ctx, KW.ON)}'`);
605
+ error(Issue.expectedAfter(lit('['), kwParam(KW.DEPENDS), `Expected '[' after '${kw(ctx, KW.DEPENDS)} ${kw(ctx, KW.ON)}'`));
602
606
  ctx.next();
603
607
  if (!ctx.at(TokenKind.RBRACKET)) {
604
608
  while (true) {
605
609
  if (!ctx.at(TokenKind.STRING))
606
- error('Expected string dependency name');
610
+ error(Issue.expected(tokenName('dependencyName'), 'Expected string dependency name'));
607
611
  const depTok = ctx.next();
608
612
  dependencies.push(depTok.value);
609
613
  if (ctx.at(TokenKind.COMMA)) {
@@ -613,15 +617,15 @@ function parseStep(ctx, error) {
613
617
  if (ctx.at(TokenKind.RBRACKET)) {
614
618
  break;
615
619
  }
616
- error("Expected ',' or ']' after dependency name");
620
+ error(Issue.expectedAfter(tokenName('commaOrCloseBracket'), tokenName('dependencyName'), "Expected ',' or ']' after dependency name"));
617
621
  }
618
622
  }
619
623
  if (!ctx.at(TokenKind.RBRACKET))
620
- error("Expected ']' to close dependency list");
624
+ error(Issue.expectedIn(lit(']'), tokenName('dependencyList'), "Expected ']' to close dependency list"));
621
625
  ctx.next();
622
626
  }
623
627
  if (!ctx.at(TokenKind.COLON))
624
- error("Expected ':' after step name");
628
+ error(Issue.expectedAfter(lit(':'), tokenName('stepName'), "Expected ':' after step name"));
625
629
  ctx.next();
626
630
  expectNewline(ctx, error);
627
631
  const body = parseBlock(ctx, error);
@@ -630,7 +634,7 @@ function parseStep(ctx, error) {
630
634
  if (ctx.isKeyword(KW.COMPENSATE)) {
631
635
  ctx.nextWord();
632
636
  if (!ctx.at(TokenKind.COLON))
633
- error(`Expected ':' after ${kwq(ctx, KW.COMPENSATE)}`);
637
+ error(Issue.expectedAfter(lit(':'), kwParam(KW.COMPENSATE), `Expected ':' after ${kwq(ctx, KW.COMPENSATE)}`));
634
638
  ctx.next();
635
639
  expectNewline(ctx, error);
636
640
  compensate = parseBlock(ctx, error);
@@ -642,13 +646,15 @@ function parseStep(ctx, error) {
642
646
  return step;
643
647
  }
644
648
  function parseRetryPolicy(ctx, error) {
645
- expectKeyword(ctx, error, KW.RETRY, `Expected ${kwq(ctx, KW.RETRY)}`);
649
+ // 缺必填指令的错误落在 retry 关键词上(与 Java AstBuilder 报在 retrySection 上一致),而非区块末尾
650
+ const retryTok = ctx.peek();
651
+ expectKeyword(ctx, error, KW.RETRY, Issue.expected(kwParam(KW.RETRY), `Expected ${kwq(ctx, KW.RETRY)}`));
646
652
  if (!ctx.at(TokenKind.COLON))
647
- error(`Expected ':' after ${kwq(ctx, KW.RETRY)}`);
653
+ error(Issue.expectedAfter(lit(':'), kwParam(KW.RETRY), `Expected ':' after ${kwq(ctx, KW.RETRY)}`));
648
654
  ctx.next();
649
655
  expectNewline(ctx, error);
650
656
  if (!ctx.at(TokenKind.INDENT))
651
- error('Expected indent for retry policy');
657
+ error(Issue.expectedIn(tokenName('indent'), tokenName('retryPolicy'), 'Expected indent for retry policy'));
652
658
  ctx.next();
653
659
  ctx.consumeNewlines();
654
660
  let maxAttempts = null;
@@ -657,56 +663,56 @@ function parseRetryPolicy(ctx, error) {
657
663
  if (ctx.isKeywordSeq(MAX_ATTEMPTS_PARTS)) {
658
664
  ctx.nextWords(MAX_ATTEMPTS_PARTS);
659
665
  if (!ctx.at(TokenKind.COLON))
660
- error(`Expected ':' after ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
666
+ error(Issue.expectedAfter(lit(':'), kwParam(KW.MAX_ATTEMPTS), `Expected ':' after ${kwq(ctx, KW.MAX_ATTEMPTS)}`));
661
667
  ctx.next();
662
668
  if (!ctx.at(TokenKind.INT))
663
- error(`Expected integer after ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
669
+ error(Issue.expectedAfter(tokenName('integer'), kwParam(KW.MAX_ATTEMPTS), `Expected integer after ${kwq(ctx, KW.MAX_ATTEMPTS)}`));
664
670
  const attempts = ctx.next().value;
665
671
  if (attempts <= 0)
666
- error(`${kwq(ctx, KW.MAX_ATTEMPTS)} must be greater than zero`);
672
+ error(Issue.invalidValue(kwParam(KW.MAX_ATTEMPTS), attempts, tokenName('positiveInteger'), `${kwq(ctx, KW.MAX_ATTEMPTS)} must be greater than zero`));
667
673
  maxAttempts = attempts;
668
674
  expectPeriodEnd(ctx, error);
669
675
  }
670
676
  else if (ctx.isKeyword(KW.BACKOFF)) {
671
677
  ctx.nextWord();
672
678
  if (!ctx.at(TokenKind.COLON))
673
- error(`Expected ':' after ${kwq(ctx, KW.BACKOFF)}`);
679
+ error(Issue.expectedAfter(lit(':'), kwParam(KW.BACKOFF), `Expected ':' after ${kwq(ctx, KW.BACKOFF)}`));
674
680
  ctx.next();
675
681
  const modeTok = ctx.nextWord();
676
682
  const mode = String(modeTok.value ?? '').toLowerCase();
677
683
  if (mode !== 'exponential' && mode !== 'linear') {
678
- error("Backoff must be either 'exponential' or 'linear'");
684
+ error(Issue.invalidValue(kwParam(KW.BACKOFF), tokenLit(modeTok), tokenName('exponentialOrLinear'), "Backoff must be either 'exponential' or 'linear'"));
679
685
  }
680
686
  backoff = mode;
681
687
  expectPeriodEnd(ctx, error);
682
688
  }
683
689
  else {
684
- error('Unknown retry directive');
690
+ error(Issue.unknownName(tokenName('retryDirective'), tokenLit(ctx.peek()), 'Unknown retry directive'));
685
691
  }
686
692
  ctx.consumeNewlines();
687
693
  }
688
694
  ctx.next();
689
695
  if (maxAttempts === null)
690
- error(`Retry section must include ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
696
+ error(Issue.emptyBlock(kwParam(KW.RETRY), kwParam(KW.MAX_ATTEMPTS), `Retry section must include ${kwq(ctx, KW.MAX_ATTEMPTS)}`), retryTok);
691
697
  if (backoff === null)
692
- error(`Retry section must include ${kwq(ctx, KW.BACKOFF)}`);
698
+ error(Issue.emptyBlock(kwParam(KW.RETRY), kwParam(KW.BACKOFF), `Retry section must include ${kwq(ctx, KW.BACKOFF)}`), retryTok);
693
699
  return { maxAttempts, backoff };
694
700
  }
695
701
  function parseTimeout(ctx, error) {
696
- expectKeyword(ctx, error, KW.TIMEOUT, `Expected ${kwq(ctx, KW.TIMEOUT)}`);
702
+ expectKeyword(ctx, error, KW.TIMEOUT, Issue.expected(kwParam(KW.TIMEOUT), `Expected ${kwq(ctx, KW.TIMEOUT)}`));
697
703
  if (!ctx.at(TokenKind.COLON))
698
- error(`Expected ':' after ${kwq(ctx, KW.TIMEOUT)}`);
704
+ error(Issue.expectedAfter(lit(':'), kwParam(KW.TIMEOUT), `Expected ':' after ${kwq(ctx, KW.TIMEOUT)}`));
699
705
  ctx.next();
700
706
  if (!ctx.at(TokenKind.INT))
701
- error('Expected integer timeout value');
707
+ error(Issue.expected(tokenName('timeoutInteger'), 'Expected integer timeout value'));
702
708
  const secondsTok = ctx.next();
703
709
  const seconds = secondsTok.value;
704
710
  if (seconds < 0)
705
- error('Timeout value must be non-negative');
711
+ error(Issue.invalidValue(kwParam(KW.TIMEOUT), seconds, tokenName('nonNegativeInteger'), 'Timeout value must be non-negative'));
706
712
  if (!(ctx.isKeyword('seconds') || ctx.isKeyword('second'))) {
707
713
  // 'seconds' 不做本地化:所有 locale 的词表都没有时间单位的本地拼写,
708
714
  // zh/de 作者同样只能写英文 seconds,报错里的 'seconds' 正是作者该写的词。
709
- error("Timeout must specify time unit 'seconds'");
715
+ error(Issue.invalidValue(tokenName('timeoutUnit'), tokenLit(ctx.peek()), lit('seconds'), "Timeout must specify time unit 'seconds'"));
710
716
  }
711
717
  ctx.nextWord();
712
718
  expectPeriodEnd(ctx, error);
@@ -724,17 +730,17 @@ function parseCases(ctx, error) {
724
730
  ctx.pushCompoundContext(KW.MATCH);
725
731
  try {
726
732
  if (!ctx.at(TokenKind.INDENT))
727
- error('Expected indent for cases');
733
+ error(Issue.expectedIn(tokenName('indent'), tokenName('matchCases'), 'Expected indent for cases'));
728
734
  ctx.next();
729
735
  while (!ctx.at(TokenKind.DEDENT)) {
730
736
  if (!ctx.isKeyword(KW.WHEN)) {
731
737
  // 改进的错误消息,提示复合模式关系
732
- error(`Expected ${kwq(ctx, KW.WHEN, 'When')} (若...为 pattern requires '为' after '若')`);
738
+ error(Issue.expectedIn(kwParam(KW.WHEN), tokenName('matchCase'), `Expected ${kwq(ctx, KW.WHEN, 'When')} (若...为 pattern requires '为' after '若')`));
733
739
  }
734
740
  const whenTok = ctx.nextWord();
735
741
  const pat = parsePattern(ctx, error);
736
742
  if (!ctx.at(TokenKind.COMMA))
737
- error("Expected ',' after pattern");
743
+ error(Issue.expectedAfter(lit(','), tokenName('pattern'), "Expected ',' after pattern"));
738
744
  ctx.next();
739
745
  const body = parseCaseBody(ctx, error);
740
746
  const caseNode = Node.Case(pat, body);
@@ -806,13 +812,13 @@ function parseIfExpr(ctx, error) {
806
812
  const cond = parseOr(ctx, error);
807
813
  skipInlineThenLayout(ctx);
808
814
  if (!ctx.isKeyword(KW.THEN))
809
- error(`Expected ${kwq(ctx, KW.THEN)} in if-expression`);
815
+ error(Issue.expectedIn(kwParam(KW.THEN), tokenName('ifExpression'), `Expected ${kwq(ctx, KW.THEN)} in if-expression`));
810
816
  ctx.nextWord(); // 消费 THEN
811
817
  const thenE = parseExpr(ctx, error);
812
818
  skipInlineElseLayout(ctx);
813
819
  if (!ctx.isKeyword(KW.ELSE) && !ctx.isKeyword(KW.OTHERWISE)) {
814
- error(`Expected ${kwKindq(ctx, SemanticTokenKind.OTHERWISE, 'else')} in if-expression ` +
815
- `(${kwKind(ctx, SemanticTokenKind.OTHERWISE, 'else')} is mandatory for expression-level if)`);
820
+ error(Issue.expectedIn(kwParam(KW.OTHERWISE), tokenName('ifExpression'), `Expected ${kwKindq(ctx, SemanticTokenKind.OTHERWISE, 'else')} in if-expression ` +
821
+ `(${kwKind(ctx, SemanticTokenKind.OTHERWISE, 'else')} is mandatory for expression-level if)`));
816
822
  }
817
823
  ctx.nextWord(); // 消费 ELSE / OTHERWISE
818
824
  const elseE = parseExpr(ctx, error);
@@ -1284,7 +1290,7 @@ function parseApplyOrPrimary(ctx, error) {
1284
1290
  if (ctx.isKeyword(KW.APPLY) && applyLooksLikeCall(ctx)) {
1285
1291
  const applyTok = ctx.nextWord();
1286
1292
  const fn = parseCallTargetName(ctx, error);
1287
- expectKeyword(ctx, error, KW.TO_WORD, `Use '${kw(ctx, KW.APPLY)} <fn> ${kw(ctx, KW.TO_WORD)} <arg>' (expected ${kwq(ctx, KW.TO_WORD)}).`);
1293
+ expectKeyword(ctx, error, KW.TO_WORD, Issue.expectedIn(kwParam(KW.TO_WORD), kwParam(KW.APPLY), `Use '${kw(ctx, KW.APPLY)} <fn> ${kw(ctx, KW.TO_WORD)} <arg>' (expected ${kwq(ctx, KW.TO_WORD)}).`));
1288
1294
  const arg = parseExpr(ctx, error);
1289
1295
  const call = Node.Call(fn, [arg]);
1290
1296
  assignSpan(call, spanFromSources(applyTok, arg));
@@ -1323,7 +1329,7 @@ function applyLooksLikeCall(ctx) {
1323
1329
  function parseCallTargetName(ctx, error) {
1324
1330
  const startTok = ctx.peek();
1325
1331
  if (!ctx.at(TokenKind.IDENT) && !ctx.at(TokenKind.TYPE_IDENT)) {
1326
- error(`Expected a function name after ${kwq(ctx, KW.APPLY)}.`);
1332
+ error(Issue.expectedAfter(tokenName('functionName'), kwParam(KW.APPLY), `Expected a function name after ${kwq(ctx, KW.APPLY)}.`));
1327
1333
  }
1328
1334
  const consumed = [ctx.peek()];
1329
1335
  let full = ctx.at(TokenKind.IDENT) ? parseIdent(ctx, error) : ctx.next().value;
@@ -1393,7 +1399,7 @@ function operatorWordBeforeParen(ctx, english) {
1393
1399
  function parseOperatorWordCall(ctx, error, word, english) {
1394
1400
  const symbol = english ? null : OPERATOR_WORD_SYMBOLS.get(word);
1395
1401
  if (!symbol) {
1396
- error(`运算符词 ${kwq(ctx, word)} 不能用作前缀调用;只有符号运算符 ${PREFIX_SYMBOL_LIST} 可以写成 op(a, b)`);
1402
+ error(Issue.invalidValue(tokenName('prefixOperator'), kwParam(word), lit(PREFIX_SYMBOL_LIST), `运算符词 ${kwq(ctx, word)} 不能用作前缀调用;只有符号运算符 ${PREFIX_SYMBOL_LIST} 可以写成 op(a, b)`));
1397
1403
  }
1398
1404
  return parsePrefixOperatorCall(ctx, error, symbol, kwParts(word).length);
1399
1405
  }
@@ -1404,7 +1410,7 @@ function parsePrefixOperatorCall(ctx, error, op, opTokenCount = 1) {
1404
1410
  ctx.next();
1405
1411
  const args = parseArgList(ctx, error);
1406
1412
  if (args.length !== 2)
1407
- error(`前缀操作符调用需要 2 个参数,但实际为 ${args.length}`);
1413
+ error(Issue.callArity(tokenName('prefixOperatorCall'), 2, args.length, `前缀操作符调用需要 2 个参数,但实际为 ${args.length}`));
1408
1414
  const target = assignTokenSpan(Node.Name(op), opTok);
1409
1415
  return assignSpan(Node.Call(target, args), spanFromTokens(opTok, lastNonLayoutToken(ctx)));
1410
1416
  }
@@ -1425,7 +1431,7 @@ function parsePrimary(ctx, error) {
1425
1431
  const functionTok = ctx.nextWord(); // 'function'
1426
1432
  const params = parseParamList(ctx, error);
1427
1433
  expectCommaOr(ctx);
1428
- expectKeyword(ctx, error, KW.PRODUCE, `Expected ${kwq(ctx, KW.PRODUCE)} and return type`);
1434
+ expectKeyword(ctx, error, KW.PRODUCE, Issue.expected(kwParam(KW.PRODUCE), `Expected ${kwq(ctx, KW.PRODUCE)} and return type`));
1429
1435
  let retType;
1430
1436
  if (ctx.at(TokenKind.COLON)) {
1431
1437
  retType = Node.TypeName('Unknown');
@@ -1435,7 +1441,7 @@ function parsePrimary(ctx, error) {
1435
1441
  retType = parseType(ctx, error);
1436
1442
  }
1437
1443
  if (!ctx.at(TokenKind.COLON))
1438
- error("Expected ':' after return type in lambda");
1444
+ error(Issue.expectedAfterIn(lit(':'), tokenName('returnType'), tokenName('lambda'), "Expected ':' after return type in lambda"));
1439
1445
  ctx.next();
1440
1446
  expectNewline(ctx, error);
1441
1447
  const body = parseBlock(ctx, error);
@@ -1591,7 +1597,7 @@ function parsePrimary(ctx, error) {
1591
1597
  const awaitTok = ctx.nextWord();
1592
1598
  const args = parseArgList(ctx, error);
1593
1599
  if (args.length !== 1)
1594
- error('await(expr) takes exactly one argument');
1600
+ error(Issue.callArity(lit('await'), 1, args.length, 'await(expr) takes exactly one argument'));
1595
1601
  const target = assignTokenSpan(Node.Name('await'), awaitTok);
1596
1602
  const call = Node.Call(target, args);
1597
1603
  const endTok = lastNonLayoutToken(ctx);
@@ -1616,7 +1622,7 @@ function parsePrimary(ctx, error) {
1616
1622
  }
1617
1623
  }
1618
1624
  if (!ctx.at(TokenKind.RBRACKET))
1619
- error("Expected ']' to close list literal");
1625
+ error(Issue.expectedIn(lit(']'), tokenName('listLiteral'), "Expected ']' to close list literal"));
1620
1626
  const rbrackTok = ctx.next();
1621
1627
  const list = Node.ListLit(elements);
1622
1628
  assignSpan(list, spanFromSources(lbrackTok, rbrackTok));
@@ -1627,7 +1633,7 @@ function parsePrimary(ctx, error) {
1627
1633
  const lparenTok = ctx.next();
1628
1634
  const expr = parseExpr(ctx, error);
1629
1635
  if (!ctx.at(TokenKind.RPAREN))
1630
- error("Expected ')' after expression");
1636
+ error(Issue.expectedAfter(lit(')'), tokenName('expression'), "Expected ')' after expression"));
1631
1637
  const rparenTok = ctx.next();
1632
1638
  assignSpanFromSources(expr, lparenTok, rparenTok, expr);
1633
1639
  return expr;
@@ -1645,7 +1651,7 @@ function parsePrimary(ctx, error) {
1645
1651
  const withTok = ctx.nextWord();
1646
1652
  const fields = [];
1647
1653
  let hasMore = true;
1648
- const setToMissing = `Expected '${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)}' in construction`;
1654
+ const setToMissing = Issue.expectedIn(kwParam(KW.SET), tokenName('construction'), `Expected '${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)}' in construction`);
1649
1655
  while (hasMore) {
1650
1656
  const nameTok = ctx.peek();
1651
1657
  // 字段位置不是名字(如 `helper with 10` 这种 with 调用后缀):与 Java 引擎
@@ -1659,7 +1665,7 @@ function parsePrimary(ctx, error) {
1659
1665
  const equalsTok = ctx.peek();
1660
1666
  ctx.nextWord();
1661
1667
  if (!ctx.isKeyword(KW.TO_WORD))
1662
- error(`Expected ${kwq(ctx, KW.TO_WORD)} after ${kwq(ctx, KW.SET)} in construction`);
1668
+ error(Issue.expectedAfterIn(kwParam(KW.TO_WORD), kwParam(KW.SET), tokenName('construction'), `Expected ${kwq(ctx, KW.TO_WORD)} after ${kwq(ctx, KW.SET)} in construction`));
1663
1669
  ctx.nextWord();
1664
1670
  const e = parseExpr(ctx, error);
1665
1671
  const fld = {
@@ -1703,7 +1709,7 @@ function parsePrimary(ctx, error) {
1703
1709
  full += '.' + partTok.value;
1704
1710
  }
1705
1711
  else {
1706
- error('Expected identifier after dot');
1712
+ error(Issue.expectedAfter(tokenName('identifier'), lit('.'), 'Expected identifier after dot'));
1707
1713
  }
1708
1714
  }
1709
1715
  const target = Node.Name(full);
@@ -1714,8 +1720,8 @@ function parsePrimary(ctx, error) {
1714
1720
  // 直到运行时才抛 Undefined function;在此于编译期就报错并给出正确写法。
1715
1721
  // 仅拦截裸类型名(无点链后缀),点链形态(如 SomeType.method(...))不受影响。
1716
1722
  if (full === typeName && ctx.declaredRecordTypes.has(typeName)) {
1717
- error(`Cannot construct '${typeName}' with positional arguments. ` +
1718
- `Use named fields instead: '${typeName} ${kw(ctx, KW.WITH)} <field> ${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)} <value> ${kw(ctx, KW.AND)} ...'`);
1723
+ error(Issue.unexpectedConstruct(tokenName('positionalConstruction'), `Cannot construct '${typeName}' with positional arguments. ` +
1724
+ `Use named fields instead: '${typeName} ${kw(ctx, KW.WITH)} <field> ${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)} <value> ${kw(ctx, KW.AND)} ...'`));
1719
1725
  }
1720
1726
  const args = parseArgList(ctx, error);
1721
1727
  const call = Node.Call(target, args);
@@ -1752,8 +1758,8 @@ function parsePrimary(ctx, error) {
1752
1758
  if (ctx.at(TokenKind.LPAREN)) {
1753
1759
  // 同 TYPE_IDENT 分支:拦截对已声明类型的位置式构造(无大小写语言里类型名是 IDENT)。
1754
1760
  if (full === name && ctx.declaredRecordTypes.has(name)) {
1755
- error(`Cannot construct '${name}' with positional arguments. ` +
1756
- `Use named fields instead: '${name} ${kw(ctx, KW.WITH)} <field> ${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)} <value> ${kw(ctx, KW.AND)} ...'`);
1761
+ error(Issue.unexpectedConstruct(tokenName('positionalConstruction'), `Cannot construct '${name}' with positional arguments. ` +
1762
+ `Use named fields instead: '${name} ${kw(ctx, KW.WITH)} <field> ${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)} <value> ${kw(ctx, KW.AND)} ...'`));
1757
1763
  }
1758
1764
  const args = parseArgList(ctx, error);
1759
1765
  const call = Node.Call(target, args);
@@ -1763,7 +1769,7 @@ function parsePrimary(ctx, error) {
1763
1769
  }
1764
1770
  return target;
1765
1771
  }
1766
- error('Unexpected expression');
1772
+ error(Issue.unexpectedConstruct(tokenName('expression'), 'Unexpected expression'));
1767
1773
  }
1768
1774
  /**
1769
1775
  * 解析参数列表
@@ -1773,7 +1779,7 @@ function parsePrimary(ctx, error) {
1773
1779
  */
1774
1780
  export function parseArgList(ctx, error) {
1775
1781
  if (!ctx.at(TokenKind.LPAREN))
1776
- error("Expected '('");
1782
+ error(Issue.expected(lit('('), "Expected '('"));
1777
1783
  ctx.next();
1778
1784
  const args = [];
1779
1785
  while (!ctx.at(TokenKind.RPAREN)) {
@@ -1786,7 +1792,7 @@ export function parseArgList(ctx, error) {
1786
1792
  break;
1787
1793
  }
1788
1794
  if (!ctx.at(TokenKind.RPAREN))
1789
- error("Expected ')'");
1795
+ error(Issue.expected(lit(')'), "Expected ')'"));
1790
1796
  ctx.next();
1791
1797
  return args;
1792
1798
  }
@@ -1832,7 +1838,7 @@ export function parsePattern(ctx, error) {
1832
1838
  break;
1833
1839
  }
1834
1840
  if (!ctx.at(TokenKind.RPAREN))
1835
- error("Expected ')' in pattern");
1841
+ error(Issue.expectedIn(lit(')'), tokenName('pattern'), "Expected ')' in pattern"));
1836
1842
  const rparenTok = ctx.next();
1837
1843
  const node = Node.PatternCtor(typeName, names);
1838
1844
  assignSpan(node, spanFromSources(typeTok, lparenTok, rparenTok, ...nameTokens));
@@ -2036,7 +2042,7 @@ function parseIdent(ctx, error) {
2036
2042
  if (recovered !== null)
2037
2043
  return recovered;
2038
2044
  if (!ctx.at(TokenKind.IDENT)) {
2039
- error('Expected identifier');
2045
+ error(Issue.expected(tokenName('identifier'), 'Expected identifier'));
2040
2046
  }
2041
2047
  return ctx.next().value;
2042
2048
  }
@@ -2047,9 +2053,9 @@ function parseIdent(ctx, error) {
2047
2053
  * @param kw 关键字
2048
2054
  * @param msg 错误消息
2049
2055
  */
2050
- function expectKeyword(ctx, error, kw, msg) {
2056
+ function expectKeyword(ctx, error, kw, issue) {
2051
2057
  if (!ctx.isKeyword(kw))
2052
- error(msg);
2058
+ error(issue);
2053
2059
  ctx.nextWord();
2054
2060
  }
2055
2061
  /**
@@ -2068,7 +2074,7 @@ function expectCommaOr(ctx) {
2068
2074
  */
2069
2075
  function expectNewline(ctx, error) {
2070
2076
  if (!ctx.at(TokenKind.NEWLINE))
2071
- error('Expected newline');
2077
+ error(Issue.expected(tokenName('newline'), 'Expected newline'));
2072
2078
  ctx.next();
2073
2079
  }
2074
2080
  //# sourceMappingURL=expr-stmt-parser.js.map