@aster-cloud/aster-lang-ts 1.0.31 → 1.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/corpus-loader.d.ts +10 -4
- package/dist/scripts/corpus-loader.d.ts.map +1 -1
- package/dist/scripts/corpus-loader.js +24 -9
- package/dist/scripts/corpus-loader.js.map +1 -1
- package/dist/scripts/lexicon-completeness.d.ts +42 -0
- package/dist/scripts/lexicon-completeness.d.ts.map +1 -0
- package/dist/scripts/lexicon-completeness.js +57 -0
- package/dist/scripts/lexicon-completeness.js.map +1 -0
- package/dist/scripts/lsp-client-helper.d.ts +7 -1
- package/dist/scripts/lsp-client-helper.d.ts.map +1 -1
- package/dist/scripts/lsp-client-helper.js +6 -1
- package/dist/scripts/lsp-client-helper.js.map +1 -1
- package/dist/scripts/verify-lexicon-sync.js +14 -44
- package/dist/scripts/verify-lexicon-sync.js.map +1 -1
- package/dist/src/lsp/codeaction.d.ts.map +1 -1
- package/dist/src/lsp/codeaction.js +12 -4
- package/dist/src/lsp/codeaction.js.map +1 -1
- package/dist/src/lsp/diagnostics.d.ts +2 -0
- package/dist/src/lsp/diagnostics.d.ts.map +1 -1
- package/dist/src/lsp/diagnostics.js +4 -0
- package/dist/src/lsp/diagnostics.js.map +1 -1
- package/dist/src/lsp/navigation/hover.d.ts.map +1 -1
- package/dist/src/lsp/navigation/hover.js +23 -0
- package/dist/src/lsp/navigation/hover.js.map +1 -1
- package/dist/src/lsp/navigation/references.d.ts +3 -1
- package/dist/src/lsp/navigation/references.d.ts.map +1 -1
- package/dist/src/lsp/navigation/references.js +16 -6
- package/dist/src/lsp/navigation/references.js.map +1 -1
- package/dist/src/lsp/navigation/rename.d.ts +1 -1
- package/dist/src/lsp/navigation/rename.d.ts.map +1 -1
- package/dist/src/lsp/navigation/rename.js +31 -42
- package/dist/src/lsp/navigation/rename.js.map +1 -1
- package/dist/src/lsp/navigation/shared.d.ts +34 -2
- package/dist/src/lsp/navigation/shared.d.ts.map +1 -1
- package/dist/src/lsp/navigation/shared.js +88 -10
- package/dist/src/lsp/navigation/shared.js.map +1 -1
- package/dist/src/lsp/navigation.js +2 -2
- package/dist/src/lsp/navigation.js.map +1 -1
- package/dist/src/lsp/parse-document.d.ts +11 -0
- package/dist/src/lsp/parse-document.d.ts.map +1 -0
- package/dist/src/lsp/parse-document.js +51 -0
- package/dist/src/lsp/parse-document.js.map +1 -0
- package/dist/src/lsp/server.js +18 -23
- package/dist/src/lsp/server.js.map +1 -1
- package/dist/src/lsp/settings.d.ts +35 -0
- package/dist/src/lsp/settings.d.ts.map +1 -0
- package/dist/src/lsp/settings.js +31 -0
- package/dist/src/lsp/settings.js.map +1 -0
- package/dist/src/lsp/source-positions.d.ts +47 -0
- package/dist/src/lsp/source-positions.d.ts.map +1 -0
- package/dist/src/lsp/source-positions.js +92 -0
- package/dist/src/lsp/source-positions.js.map +1 -0
- package/dist/src/lsp/workspace/document-indexer.d.ts +3 -1
- package/dist/src/lsp/workspace/document-indexer.d.ts.map +1 -1
- package/dist/src/lsp/workspace/document-indexer.js +5 -7
- package/dist/src/lsp/workspace/document-indexer.js.map +1 -1
- package/dist/src/lsp/workspace/symbol-finder.d.ts +8 -1
- package/dist/src/lsp/workspace/symbol-finder.d.ts.map +1 -1
- package/dist/src/lsp/workspace/symbol-finder.js +10 -13
- package/dist/src/lsp/workspace/symbol-finder.js.map +1 -1
- package/dist/src/parser/constraint-parser.d.ts.map +1 -1
- package/dist/src/parser/constraint-parser.js +18 -7
- package/dist/src/parser/constraint-parser.js.map +1 -1
- package/dist/src/parser/decl-parser.d.ts.map +1 -1
- package/dist/src/parser/decl-parser.js +6 -5
- package/dist/src/parser/decl-parser.js.map +1 -1
- package/dist/src/parser/expr-stmt-parser.d.ts.map +1 -1
- package/dist/src/parser/expr-stmt-parser.js +95 -38
- package/dist/src/parser/expr-stmt-parser.js.map +1 -1
- package/dist/src/parser/import-parser.d.ts.map +1 -1
- package/dist/src/parser/import-parser.js +2 -1
- package/dist/src/parser/import-parser.js.map +1 -1
- package/dist/src/parser/keyword-display.d.ts +40 -0
- package/dist/src/parser/keyword-display.d.ts.map +1 -0
- package/dist/src/parser/keyword-display.js +73 -0
- package/dist/src/parser/keyword-display.js.map +1 -0
- package/dist/src/parser/type-parser.d.ts.map +1 -1
- package/dist/src/parser/type-parser.js +5 -4
- package/dist/src/parser/type-parser.js.map +1 -1
- package/dist/src/parser.d.ts.map +1 -1
- package/dist/src/parser.js +1 -0
- package/dist/src/parser.js.map +1 -1
- package/dist/test/integration/lsp/lsp-rename-safety.test.d.ts +2 -0
- package/dist/test/integration/lsp/lsp-rename-safety.test.d.ts.map +1 -0
- package/dist/test/integration/lsp/lsp-rename-safety.test.js +114 -0
- package/dist/test/integration/lsp/lsp-rename-safety.test.js.map +1 -0
- package/dist/test/integration/lsp/lsp-source-positions.test.d.ts +2 -0
- package/dist/test/integration/lsp/lsp-source-positions.test.d.ts.map +1 -0
- package/dist/test/integration/lsp/lsp-source-positions.test.js +161 -0
- package/dist/test/integration/lsp/lsp-source-positions.test.js.map +1 -0
- package/dist/test/integration/lsp/lsp-zh-inmemory-navigation.test.d.ts +2 -0
- package/dist/test/integration/lsp/lsp-zh-inmemory-navigation.test.d.ts.map +1 -0
- package/dist/test/integration/lsp/lsp-zh-inmemory-navigation.test.js +79 -0
- package/dist/test/integration/lsp/lsp-zh-inmemory-navigation.test.js.map +1 -0
- package/dist/test/unit/eval-regression-poker-straight-high.test.d.ts +2 -0
- package/dist/test/unit/eval-regression-poker-straight-high.test.d.ts.map +1 -0
- package/dist/test/unit/eval-regression-poker-straight-high.test.js +61 -0
- package/dist/test/unit/eval-regression-poker-straight-high.test.js.map +1 -0
- package/dist/test/unit/lexicon-completeness.test.d.ts +2 -0
- package/dist/test/unit/lexicon-completeness.test.d.ts.map +1 -0
- package/dist/test/unit/lexicon-completeness.test.js +42 -0
- package/dist/test/unit/lexicon-completeness.test.js.map +1 -0
- package/dist/test/unit/lsp/codeaction.test.js +28 -0
- package/dist/test/unit/lsp/codeaction.test.js.map +1 -1
- package/dist/test/unit/lsp/lsp-locale-fixes.test.d.ts +2 -0
- package/dist/test/unit/lsp/lsp-locale-fixes.test.d.ts.map +1 -0
- package/dist/test/unit/lsp/lsp-locale-fixes.test.js +105 -0
- package/dist/test/unit/lsp/lsp-locale-fixes.test.js.map +1 -0
- package/dist/test/unit/lsp/parse-document.test.d.ts +2 -0
- package/dist/test/unit/lsp/parse-document.test.d.ts.map +1 -0
- package/dist/test/unit/lsp/parse-document.test.js +30 -0
- package/dist/test/unit/lsp/parse-document.test.js.map +1 -0
- package/dist/test/unit/mapping/redos-budget.test.js +60 -23
- package/dist/test/unit/mapping/redos-budget.test.js.map +1 -1
- package/dist/test/unit/parser/keyword-display.test.d.ts +8 -0
- package/dist/test/unit/parser/keyword-display.test.d.ts.map +1 -0
- package/dist/test/unit/parser/keyword-display.test.js +27 -0
- package/dist/test/unit/parser/keyword-display.test.js.map +1 -0
- package/dist/test/unit/parser/localized-diagnostic-keywords.test.d.ts +15 -0
- package/dist/test/unit/parser/localized-diagnostic-keywords.test.d.ts.map +1 -0
- package/dist/test/unit/parser/localized-diagnostic-keywords.test.js +98 -0
- package/dist/test/unit/parser/localized-diagnostic-keywords.test.js.map +1 -0
- package/dist/test/unit/parser/prefix-operator-call.test.js +119 -1
- package/dist/test/unit/parser/prefix-operator-call.test.js.map +1 -1
- package/dist/test/unit/scripts/corpus-loader.test.js +44 -9
- package/dist/test/unit/scripts/corpus-loader.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { kw, kwKind, kwKindq, kwq } from './keyword-display.js';
|
|
2
|
+
import { SemanticTokenKind } from '../config/token-kind.js';
|
|
1
3
|
import { kwParts, tokLowerAt, tryReadTranslatedIdent } from './context.js';
|
|
2
4
|
import { TokenKind, KW } from '../frontend/tokens.js';
|
|
3
5
|
import { Node } from '../ast/ast.js';
|
|
@@ -225,11 +227,11 @@ function skipInlineElseLayout(ctx) {
|
|
|
225
227
|
function parseInlineIf(ctx, error, cond) {
|
|
226
228
|
skipInlineThenLayout(ctx);
|
|
227
229
|
if (!ctx.isKeyword(KW.THEN))
|
|
228
|
-
error(
|
|
230
|
+
error(`Expected ${kwq(ctx, KW.THEN)} in inline if`);
|
|
229
231
|
ctx.nextWord();
|
|
230
232
|
// then 分支:单个 return(内联,不带句点;句点由整条 inline-if 末尾的 return 承担)。
|
|
231
233
|
if (!ctx.isKeyword(KW.RETURN))
|
|
232
|
-
error(
|
|
234
|
+
error(`Expected ${kwq(ctx, KW.RETURN)} after ${kwq(ctx, KW.THEN)}`);
|
|
233
235
|
const thenRetTok = ctx.peek();
|
|
234
236
|
ctx.nextWord();
|
|
235
237
|
const thenExpr = parseExpr(ctx, error);
|
|
@@ -258,7 +260,7 @@ function parseInlineIf(ctx, error, cond) {
|
|
|
258
260
|
else {
|
|
259
261
|
// else return X.(末尾 return,带句点)。
|
|
260
262
|
if (!ctx.isKeyword(KW.RETURN))
|
|
261
|
-
error(
|
|
263
|
+
error(`Expected ${kwq(ctx, KW.RETURN)} or ${kwq(ctx, KW.IF)} after ${kwKindq(ctx, SemanticTokenKind.OTHERWISE, 'else')}`);
|
|
262
264
|
const elseRetTok = ctx.peek();
|
|
263
265
|
ctx.nextWord();
|
|
264
266
|
const elseExpr = parseExpr(ctx, error);
|
|
@@ -359,7 +361,7 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
359
361
|
ctx.nextWord();
|
|
360
362
|
const nameTok = ctx.peek();
|
|
361
363
|
const name = parseIdent(ctx, error);
|
|
362
|
-
expectKeyword(ctx, error, KW.BE,
|
|
364
|
+
expectKeyword(ctx, error, KW.BE, `Use ${kwq(ctx, KW.BE)} in bindings: '${kw(ctx, KW.LET, 'Let')} x ${kw(ctx, KW.BE)} ...'.`);
|
|
363
365
|
// Special-case lambda block form to avoid trailing '.'
|
|
364
366
|
if ((ctx.isKeyword('a') && tokLowerAt(ctx, ctx.index + 1) === 'function') || ctx.isKeyword('function')) {
|
|
365
367
|
if (ctx.isKeyword('a'))
|
|
@@ -367,7 +369,7 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
367
369
|
const functionTok = ctx.nextWord(); // 'function'
|
|
368
370
|
const params = parseParamList(ctx, error);
|
|
369
371
|
expectCommaOr(ctx);
|
|
370
|
-
expectKeyword(ctx, error, KW.PRODUCE,
|
|
372
|
+
expectKeyword(ctx, error, KW.PRODUCE, `Expected ${kwq(ctx, KW.PRODUCE)} and return type`);
|
|
371
373
|
let retType;
|
|
372
374
|
if (ctx.at(TokenKind.COLON)) {
|
|
373
375
|
retType = Node.TypeName('Unknown');
|
|
@@ -403,7 +405,7 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
403
405
|
const setTok = ctx.peek();
|
|
404
406
|
ctx.nextWord();
|
|
405
407
|
const name = parseIdent(ctx, error);
|
|
406
|
-
expectKeyword(ctx, error, KW.TO_WORD,
|
|
408
|
+
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)} ...'.`);
|
|
407
409
|
const expr = parseExpr(ctx, error);
|
|
408
410
|
expectPeriodEnd(ctx, error);
|
|
409
411
|
const nd = Node.Set(name, expr);
|
|
@@ -521,9 +523,9 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
521
523
|
}
|
|
522
524
|
if (ctx.isKeyword(KW.WITHIN)) {
|
|
523
525
|
ctx.nextWord();
|
|
524
|
-
expectKeyword(ctx, error, KW.SCOPE,
|
|
526
|
+
expectKeyword(ctx, error, KW.SCOPE, `Expected ${kwq(ctx, KW.SCOPE)} after ${kwq(ctx, KW.WITHIN, 'Within')}`);
|
|
525
527
|
if (!ctx.at(TokenKind.COLON))
|
|
526
|
-
error(
|
|
528
|
+
error(`Expected ':' after ${kwq(ctx, KW.SCOPE)}`);
|
|
527
529
|
ctx.next();
|
|
528
530
|
expectNewline(ctx, error);
|
|
529
531
|
return parseBlock(ctx, error); // Lowering later
|
|
@@ -531,8 +533,8 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
531
533
|
if (ctx.isKeyword(KW.START)) {
|
|
532
534
|
const startTok = ctx.nextWord();
|
|
533
535
|
const name = parseIdent(ctx, error);
|
|
534
|
-
expectKeyword(ctx, error, KW.AS,
|
|
535
|
-
expectKeyword(ctx, error, KW.ASYNC,
|
|
536
|
+
expectKeyword(ctx, error, KW.AS, `Expected ${kwq(ctx, KW.AS)} after name`);
|
|
537
|
+
expectKeyword(ctx, error, KW.ASYNC, `Expected ${kwq(ctx, KW.ASYNC)}`);
|
|
536
538
|
const expr = parseExpr(ctx, error);
|
|
537
539
|
expectPeriodEnd(ctx, error);
|
|
538
540
|
const nd = Node.Start(name, expr);
|
|
@@ -565,9 +567,9 @@ export function parseStatement(ctx, error, reportOutcomeError = error) {
|
|
|
565
567
|
}
|
|
566
568
|
function parseWorkflow(ctx, error) {
|
|
567
569
|
const workflowTok = ctx.peek();
|
|
568
|
-
expectKeyword(ctx, error, KW.WORKFLOW,
|
|
570
|
+
expectKeyword(ctx, error, KW.WORKFLOW, `Expected ${kwq(ctx, KW.WORKFLOW)}`);
|
|
569
571
|
if (!ctx.at(TokenKind.COLON))
|
|
570
|
-
error(
|
|
572
|
+
error(`Expected ':' after ${kwq(ctx, KW.WORKFLOW)}`);
|
|
571
573
|
ctx.next();
|
|
572
574
|
expectNewline(ctx, error);
|
|
573
575
|
if (!ctx.at(TokenKind.INDENT))
|
|
@@ -603,14 +605,14 @@ function parseWorkflow(ctx, error) {
|
|
|
603
605
|
}
|
|
604
606
|
function parseStep(ctx, error) {
|
|
605
607
|
const stepTok = ctx.peek();
|
|
606
|
-
expectKeyword(ctx, error, KW.STEP,
|
|
608
|
+
expectKeyword(ctx, error, KW.STEP, `Expected ${kwq(ctx, KW.STEP)}`);
|
|
607
609
|
const name = parseIdent(ctx, error);
|
|
608
610
|
const dependencies = [];
|
|
609
611
|
if (ctx.isKeyword(KW.DEPENDS)) {
|
|
610
612
|
ctx.nextWord();
|
|
611
|
-
expectKeyword(ctx, error, KW.ON,
|
|
613
|
+
expectKeyword(ctx, error, KW.ON, `Expected ${kwq(ctx, KW.ON)} after ${kwq(ctx, KW.DEPENDS)}`);
|
|
612
614
|
if (!ctx.at(TokenKind.LBRACKET))
|
|
613
|
-
error(
|
|
615
|
+
error(`Expected '[' after '${kw(ctx, KW.DEPENDS)} ${kw(ctx, KW.ON)}'`);
|
|
614
616
|
ctx.next();
|
|
615
617
|
if (!ctx.at(TokenKind.RBRACKET)) {
|
|
616
618
|
while (true) {
|
|
@@ -642,7 +644,7 @@ function parseStep(ctx, error) {
|
|
|
642
644
|
if (ctx.isKeyword(KW.COMPENSATE)) {
|
|
643
645
|
ctx.nextWord();
|
|
644
646
|
if (!ctx.at(TokenKind.COLON))
|
|
645
|
-
error(
|
|
647
|
+
error(`Expected ':' after ${kwq(ctx, KW.COMPENSATE)}`);
|
|
646
648
|
ctx.next();
|
|
647
649
|
expectNewline(ctx, error);
|
|
648
650
|
compensate = parseBlock(ctx, error);
|
|
@@ -654,9 +656,9 @@ function parseStep(ctx, error) {
|
|
|
654
656
|
return step;
|
|
655
657
|
}
|
|
656
658
|
function parseRetryPolicy(ctx, error) {
|
|
657
|
-
expectKeyword(ctx, error, KW.RETRY,
|
|
659
|
+
expectKeyword(ctx, error, KW.RETRY, `Expected ${kwq(ctx, KW.RETRY)}`);
|
|
658
660
|
if (!ctx.at(TokenKind.COLON))
|
|
659
|
-
error(
|
|
661
|
+
error(`Expected ':' after ${kwq(ctx, KW.RETRY)}`);
|
|
660
662
|
ctx.next();
|
|
661
663
|
expectNewline(ctx, error);
|
|
662
664
|
if (!ctx.at(TokenKind.INDENT))
|
|
@@ -669,20 +671,20 @@ function parseRetryPolicy(ctx, error) {
|
|
|
669
671
|
if (ctx.isKeywordSeq(MAX_ATTEMPTS_PARTS)) {
|
|
670
672
|
ctx.nextWords(MAX_ATTEMPTS_PARTS);
|
|
671
673
|
if (!ctx.at(TokenKind.COLON))
|
|
672
|
-
error(
|
|
674
|
+
error(`Expected ':' after ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
|
|
673
675
|
ctx.next();
|
|
674
676
|
if (!ctx.at(TokenKind.INT))
|
|
675
|
-
error(
|
|
677
|
+
error(`Expected integer after ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
|
|
676
678
|
const attempts = ctx.next().value;
|
|
677
679
|
if (attempts <= 0)
|
|
678
|
-
error(
|
|
680
|
+
error(`${kwq(ctx, KW.MAX_ATTEMPTS)} must be greater than zero`);
|
|
679
681
|
maxAttempts = attempts;
|
|
680
682
|
expectPeriodEnd(ctx, error);
|
|
681
683
|
}
|
|
682
684
|
else if (ctx.isKeyword(KW.BACKOFF)) {
|
|
683
685
|
ctx.nextWord();
|
|
684
686
|
if (!ctx.at(TokenKind.COLON))
|
|
685
|
-
error(
|
|
687
|
+
error(`Expected ':' after ${kwq(ctx, KW.BACKOFF)}`);
|
|
686
688
|
ctx.next();
|
|
687
689
|
const modeTok = ctx.nextWord();
|
|
688
690
|
const mode = String(modeTok.value ?? '').toLowerCase();
|
|
@@ -699,15 +701,15 @@ function parseRetryPolicy(ctx, error) {
|
|
|
699
701
|
}
|
|
700
702
|
ctx.next();
|
|
701
703
|
if (maxAttempts === null)
|
|
702
|
-
error(
|
|
704
|
+
error(`Retry section must include ${kwq(ctx, KW.MAX_ATTEMPTS)}`);
|
|
703
705
|
if (backoff === null)
|
|
704
|
-
error(
|
|
706
|
+
error(`Retry section must include ${kwq(ctx, KW.BACKOFF)}`);
|
|
705
707
|
return { maxAttempts, backoff };
|
|
706
708
|
}
|
|
707
709
|
function parseTimeout(ctx, error) {
|
|
708
|
-
expectKeyword(ctx, error, KW.TIMEOUT,
|
|
710
|
+
expectKeyword(ctx, error, KW.TIMEOUT, `Expected ${kwq(ctx, KW.TIMEOUT)}`);
|
|
709
711
|
if (!ctx.at(TokenKind.COLON))
|
|
710
|
-
error(
|
|
712
|
+
error(`Expected ':' after ${kwq(ctx, KW.TIMEOUT)}`);
|
|
711
713
|
ctx.next();
|
|
712
714
|
if (!ctx.at(TokenKind.INT))
|
|
713
715
|
error('Expected integer timeout value');
|
|
@@ -716,6 +718,8 @@ function parseTimeout(ctx, error) {
|
|
|
716
718
|
if (seconds < 0)
|
|
717
719
|
error('Timeout value must be non-negative');
|
|
718
720
|
if (!(ctx.isKeyword('seconds') || ctx.isKeyword('second'))) {
|
|
721
|
+
// 'seconds' 不做本地化:所有 locale 的词表都没有时间单位的本地拼写,
|
|
722
|
+
// zh/de 作者同样只能写英文 seconds,报错里的 'seconds' 正是作者该写的词。
|
|
719
723
|
error("Timeout must specify time unit 'seconds'");
|
|
720
724
|
}
|
|
721
725
|
ctx.nextWord();
|
|
@@ -739,7 +743,7 @@ function parseCases(ctx, error) {
|
|
|
739
743
|
while (!ctx.at(TokenKind.DEDENT)) {
|
|
740
744
|
if (!ctx.isKeyword(KW.WHEN)) {
|
|
741
745
|
// 改进的错误消息,提示复合模式关系
|
|
742
|
-
error(
|
|
746
|
+
error(`Expected ${kwq(ctx, KW.WHEN, 'When')} (若...为 pattern requires '为' after '若')`);
|
|
743
747
|
}
|
|
744
748
|
const whenTok = ctx.nextWord();
|
|
745
749
|
const pat = parsePattern(ctx, error);
|
|
@@ -816,12 +820,13 @@ function parseIfExpr(ctx, error) {
|
|
|
816
820
|
const cond = parseOr(ctx, error);
|
|
817
821
|
skipInlineThenLayout(ctx);
|
|
818
822
|
if (!ctx.isKeyword(KW.THEN))
|
|
819
|
-
error(
|
|
823
|
+
error(`Expected ${kwq(ctx, KW.THEN)} in if-expression`);
|
|
820
824
|
ctx.nextWord(); // 消费 THEN
|
|
821
825
|
const thenE = parseExpr(ctx, error);
|
|
822
826
|
skipInlineElseLayout(ctx);
|
|
823
827
|
if (!ctx.isKeyword(KW.ELSE) && !ctx.isKeyword(KW.OTHERWISE)) {
|
|
824
|
-
error(
|
|
828
|
+
error(`Expected ${kwKindq(ctx, SemanticTokenKind.OTHERWISE, 'else')} in if-expression ` +
|
|
829
|
+
`(${kwKind(ctx, SemanticTokenKind.OTHERWISE, 'else')} is mandatory for expression-level if)`);
|
|
825
830
|
}
|
|
826
831
|
ctx.nextWord(); // 消费 ELSE / OTHERWISE
|
|
827
832
|
const elseE = parseExpr(ctx, error);
|
|
@@ -1293,7 +1298,7 @@ function parseApplyOrPrimary(ctx, error) {
|
|
|
1293
1298
|
if (ctx.isKeyword(KW.APPLY) && applyLooksLikeCall(ctx)) {
|
|
1294
1299
|
const applyTok = ctx.nextWord();
|
|
1295
1300
|
const fn = parseCallTargetName(ctx, error);
|
|
1296
|
-
expectKeyword(ctx, error, KW.TO_WORD,
|
|
1301
|
+
expectKeyword(ctx, error, KW.TO_WORD, `Use '${kw(ctx, KW.APPLY)} <fn> ${kw(ctx, KW.TO_WORD)} <arg>' (expected ${kwq(ctx, KW.TO_WORD)}).`);
|
|
1297
1302
|
const arg = parseExpr(ctx, error);
|
|
1298
1303
|
const call = Node.Call(fn, [arg]);
|
|
1299
1304
|
assignSpan(call, spanFromSources(applyTok, arg));
|
|
@@ -1332,7 +1337,7 @@ function applyLooksLikeCall(ctx) {
|
|
|
1332
1337
|
function parseCallTargetName(ctx, error) {
|
|
1333
1338
|
const startTok = ctx.peek();
|
|
1334
1339
|
if (!ctx.at(TokenKind.IDENT) && !ctx.at(TokenKind.TYPE_IDENT)) {
|
|
1335
|
-
error(
|
|
1340
|
+
error(`Expected a function name after ${kwq(ctx, KW.APPLY)}.`);
|
|
1336
1341
|
}
|
|
1337
1342
|
const consumed = [ctx.peek()];
|
|
1338
1343
|
let full = ctx.at(TokenKind.IDENT) ? parseIdent(ctx, error) : ctx.next().value;
|
|
@@ -1361,9 +1366,56 @@ const PREFIX_OPERATORS = new Map([
|
|
|
1361
1366
|
[TokenKind.STAR, '*'],
|
|
1362
1367
|
[TokenKind.SLASH, '/'],
|
|
1363
1368
|
]);
|
|
1364
|
-
|
|
1365
|
-
|
|
1369
|
+
// 运算符词 → 非英语词法表下 Java Canonicalizer(OPERATOR_SYMBOL_MAP)把它翻成的符号;
|
|
1370
|
+
// null 表示不翻译,词仍是 Java lexer 的硬 token,不能作前缀调用目标。
|
|
1371
|
+
const OPERATOR_WORD_SYMBOLS = new Map([
|
|
1372
|
+
[KW.AND, null],
|
|
1373
|
+
[KW.OR, null],
|
|
1374
|
+
[KW.MODULO, null],
|
|
1375
|
+
[KW.INTEGER_DIVIDED_BY, null],
|
|
1376
|
+
[KW.PLUS, '+'],
|
|
1377
|
+
[KW.MINUS, '-'],
|
|
1378
|
+
[KW.TIMES, '*'],
|
|
1379
|
+
[KW.DIVIDED_BY, '/'],
|
|
1380
|
+
[KW.LESS_THAN, '<'],
|
|
1381
|
+
[KW.GREATER_THAN, '>'],
|
|
1382
|
+
[KW.MORE_THAN, '>'],
|
|
1383
|
+
[KW.EQUALS_TO, '=='],
|
|
1384
|
+
[KW.AT_LEAST, '>='],
|
|
1385
|
+
[KW.AT_MOST, '<='],
|
|
1386
|
+
[KW.UNDER, '<'],
|
|
1387
|
+
[KW.OVER, '>'],
|
|
1388
|
+
]);
|
|
1389
|
+
// en-US 下 under/over 是软关键词(Java 当普通 IDENT),前缀位置就是普通函数调用
|
|
1390
|
+
const EN_SOFT_OPERATOR_WORDS = new Set([KW.UNDER, KW.OVER]);
|
|
1391
|
+
const PREFIX_SYMBOL_LIST = '< > <= >= != == = + - * /';
|
|
1392
|
+
/** 当前位置是否为「运算符词 + (」;按原文大小写精确匹配(Java lexer 区分大小写)。 */
|
|
1393
|
+
function operatorWordBeforeParen(ctx, english) {
|
|
1394
|
+
for (const word of OPERATOR_WORD_SYMBOLS.keys()) {
|
|
1395
|
+
const parts = kwParts(word);
|
|
1396
|
+
const matches = parts.every((part, k) => ctx.peek(k).kind === TokenKind.IDENT && ctx.peek(k).value === part);
|
|
1397
|
+
if (matches && ctx.peek(parts.length).kind === TokenKind.LPAREN) {
|
|
1398
|
+
return english && EN_SOFT_OPERATOR_WORDS.has(word) ? undefined : word;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return undefined;
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* 运算符词前缀调用,与 Java 一致:en-US 规范拼写不翻译,词是硬 token → 拒绝;
|
|
1405
|
+
* 非英语词法表下可映射为符号的词按符号前缀调用处理,其余拒绝。
|
|
1406
|
+
*/
|
|
1407
|
+
function parseOperatorWordCall(ctx, error, word, english) {
|
|
1408
|
+
const symbol = english ? null : OPERATOR_WORD_SYMBOLS.get(word);
|
|
1409
|
+
if (!symbol) {
|
|
1410
|
+
error(`运算符词 ${kwq(ctx, word)} 不能用作前缀调用;只有符号运算符 ${PREFIX_SYMBOL_LIST} 可以写成 op(a, b)`);
|
|
1411
|
+
}
|
|
1412
|
+
return parsePrefixOperatorCall(ctx, error, symbol, kwParts(word).length);
|
|
1413
|
+
}
|
|
1414
|
+
/** 前缀运算符调用:恰好 2 个实参,降为与中缀相同的 Call(Name(op), [a, b])。运算符可占多个 token。 */
|
|
1415
|
+
function parsePrefixOperatorCall(ctx, error, op, opTokenCount = 1) {
|
|
1366
1416
|
const opTok = ctx.next();
|
|
1417
|
+
for (let k = 1; k < opTokenCount; k++)
|
|
1418
|
+
ctx.next();
|
|
1367
1419
|
const args = parseArgList(ctx, error);
|
|
1368
1420
|
if (args.length !== 2)
|
|
1369
1421
|
error(`前缀操作符调用需要 2 个参数,但实际为 ${args.length}`);
|
|
@@ -1375,6 +1427,11 @@ function parsePrimary(ctx, error) {
|
|
|
1375
1427
|
if (prefixOp !== undefined && ctx.peek(1).kind === TokenKind.LPAREN) {
|
|
1376
1428
|
return parsePrefixOperatorCall(ctx, error, prefixOp);
|
|
1377
1429
|
}
|
|
1430
|
+
const english = ctx.lexicon === undefined || ctx.lexicon.id === 'en-US';
|
|
1431
|
+
const operatorWord = operatorWordBeforeParen(ctx, english);
|
|
1432
|
+
if (operatorWord !== undefined) {
|
|
1433
|
+
return parseOperatorWordCall(ctx, error, operatorWord, english);
|
|
1434
|
+
}
|
|
1378
1435
|
// Minimal: construction, literals, names, Ok/Err/Some/None, call with dotted names and parens args
|
|
1379
1436
|
// Lambda (block form): 'a function' (or 'function') ... 'produce' Type ':' \n Block
|
|
1380
1437
|
if ((ctx.isKeyword('a') && tokLowerAt(ctx, ctx.index + 1) === 'function') || ctx.isKeyword('function')) {
|
|
@@ -1382,7 +1439,7 @@ function parsePrimary(ctx, error) {
|
|
|
1382
1439
|
const functionTok = ctx.nextWord(); // 'function'
|
|
1383
1440
|
const params = parseParamList(ctx, error);
|
|
1384
1441
|
expectCommaOr(ctx);
|
|
1385
|
-
expectKeyword(ctx, error, KW.PRODUCE,
|
|
1442
|
+
expectKeyword(ctx, error, KW.PRODUCE, `Expected ${kwq(ctx, KW.PRODUCE)} and return type`);
|
|
1386
1443
|
let retType;
|
|
1387
1444
|
if (ctx.at(TokenKind.COLON)) {
|
|
1388
1445
|
retType = Node.TypeName('Unknown');
|
|
@@ -1606,11 +1663,11 @@ function parsePrimary(ctx, error) {
|
|
|
1606
1663
|
const nameTok = ctx.peek();
|
|
1607
1664
|
const name = parseIdent(ctx, error);
|
|
1608
1665
|
if (!ctx.isKeyword(KW.SET))
|
|
1609
|
-
error(
|
|
1666
|
+
error(`Expected '${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)}' in construction`);
|
|
1610
1667
|
const equalsTok = ctx.peek();
|
|
1611
1668
|
ctx.nextWord();
|
|
1612
1669
|
if (!ctx.isKeyword(KW.TO_WORD))
|
|
1613
|
-
error(
|
|
1670
|
+
error(`Expected ${kwq(ctx, KW.TO_WORD)} after ${kwq(ctx, KW.SET)} in construction`);
|
|
1614
1671
|
ctx.nextWord();
|
|
1615
1672
|
const e = parseExpr(ctx, error);
|
|
1616
1673
|
const fld = {
|
|
@@ -1666,7 +1723,7 @@ function parsePrimary(ctx, error) {
|
|
|
1666
1723
|
// 仅拦截裸类型名(无点链后缀),点链形态(如 SomeType.method(...))不受影响。
|
|
1667
1724
|
if (full === typeName && ctx.declaredRecordTypes.has(typeName)) {
|
|
1668
1725
|
error(`Cannot construct '${typeName}' with positional arguments. ` +
|
|
1669
|
-
`Use named fields instead: '${typeName}
|
|
1726
|
+
`Use named fields instead: '${typeName} ${kw(ctx, KW.WITH)} <field> ${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)} <value> ${kw(ctx, KW.AND)} ...'`);
|
|
1670
1727
|
}
|
|
1671
1728
|
const args = parseArgList(ctx, error);
|
|
1672
1729
|
const call = Node.Call(target, args);
|
|
@@ -1704,7 +1761,7 @@ function parsePrimary(ctx, error) {
|
|
|
1704
1761
|
// 同 TYPE_IDENT 分支:拦截对已声明类型的位置式构造(无大小写语言里类型名是 IDENT)。
|
|
1705
1762
|
if (full === name && ctx.declaredRecordTypes.has(name)) {
|
|
1706
1763
|
error(`Cannot construct '${name}' with positional arguments. ` +
|
|
1707
|
-
`Use named fields instead: '${name}
|
|
1764
|
+
`Use named fields instead: '${name} ${kw(ctx, KW.WITH)} <field> ${kw(ctx, KW.SET)} ${kw(ctx, KW.TO_WORD)} <value> ${kw(ctx, KW.AND)} ...'`);
|
|
1708
1765
|
}
|
|
1709
1766
|
const args = parseArgList(ctx, error);
|
|
1710
1767
|
const call = Node.Call(target, args);
|