vba-runner 0.1.1-alpha.0 → 0.1.1-alpha.2
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/bin/vba-analyzer.cjs +473 -268
- package/dist/bin/vba-formatter.cjs +84 -47
- package/dist/bin/vba-parse-check.cjs +481 -292
- package/dist/bin/vba-run.cjs +768 -417
- package/dist/bin/vba-runner.cjs +12861 -0
- package/dist/lib.cjs +603 -279
- package/package.json +3 -2
|
@@ -41,6 +41,7 @@ __export(vba_analyzer_exports, {
|
|
|
41
41
|
formatWorkspaceOutlineForMcp: () => formatWorkspaceOutline,
|
|
42
42
|
formatWorkspaceReportForMcp: () => formatWorkspaceReportForMcp,
|
|
43
43
|
formatWorkspaceSummary: () => formatWorkspaceSummary,
|
|
44
|
+
main: () => main,
|
|
44
45
|
paramName: () => paramName,
|
|
45
46
|
vbaTypeToTs: () => vbaTypeToTs
|
|
46
47
|
});
|
|
@@ -113,7 +114,7 @@ var Lexer = class _Lexer {
|
|
|
113
114
|
const startLine = this.line;
|
|
114
115
|
const startColumn = this.column;
|
|
115
116
|
if (this.pos >= this.input.length) {
|
|
116
|
-
return { type:
|
|
117
|
+
return { type: 137 /* EOF */, value: "", line: startLine, column: startColumn };
|
|
117
118
|
}
|
|
118
119
|
const char = this.peek();
|
|
119
120
|
if (char === "#") {
|
|
@@ -142,12 +143,12 @@ var Lexer = class _Lexer {
|
|
|
142
143
|
}
|
|
143
144
|
if (this.peek() === "#") {
|
|
144
145
|
this.advance();
|
|
145
|
-
return { type:
|
|
146
|
+
return { type: 135 /* Date */, value: dateValue, line: startLine, column: startColumn };
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
this.advance();
|
|
150
|
-
return { type:
|
|
151
|
+
return { type: 112 /* OperatorHash */, value: "#", line: startLine, column: startColumn };
|
|
151
152
|
}
|
|
152
153
|
if (char === "_") {
|
|
153
154
|
const next = this.pos + 1 < this.input.length ? this.input[this.pos + 1] : "\0";
|
|
@@ -185,7 +186,7 @@ var Lexer = class _Lexer {
|
|
|
185
186
|
}
|
|
186
187
|
if (char === "\n") {
|
|
187
188
|
this.advance();
|
|
188
|
-
return { type:
|
|
189
|
+
return { type: 136 /* Newline */, value: "\n", line: startLine, column: startColumn };
|
|
189
190
|
}
|
|
190
191
|
if (char === '"') {
|
|
191
192
|
this.advance();
|
|
@@ -207,39 +208,39 @@ var Lexer = class _Lexer {
|
|
|
207
208
|
}
|
|
208
209
|
if (char === "=") {
|
|
209
210
|
this.advance();
|
|
210
|
-
return { type:
|
|
211
|
+
return { type: 121 /* OperatorEquals */, value: "=", line: startLine, column: startColumn };
|
|
211
212
|
}
|
|
212
213
|
if (char === "<") {
|
|
213
214
|
this.advance();
|
|
214
215
|
if (this.peek() === ">") {
|
|
215
216
|
this.advance();
|
|
216
|
-
return { type:
|
|
217
|
+
return { type: 122 /* OperatorNotEquals */, value: "<>", line: startLine, column: startColumn };
|
|
217
218
|
}
|
|
218
219
|
if (this.peek() === "=") {
|
|
219
220
|
this.advance();
|
|
220
|
-
return { type:
|
|
221
|
+
return { type: 125 /* OperatorLessThanOrEqual */, value: "<=", line: startLine, column: startColumn };
|
|
221
222
|
}
|
|
222
|
-
return { type:
|
|
223
|
+
return { type: 123 /* OperatorLessThan */, value: "<", line: startLine, column: startColumn };
|
|
223
224
|
}
|
|
224
225
|
if (char === ">") {
|
|
225
226
|
this.advance();
|
|
226
227
|
if (this.peek() === "=") {
|
|
227
228
|
this.advance();
|
|
228
|
-
return { type:
|
|
229
|
+
return { type: 126 /* OperatorGreaterThanOrEqual */, value: ">=", line: startLine, column: startColumn };
|
|
229
230
|
}
|
|
230
|
-
return { type:
|
|
231
|
+
return { type: 124 /* OperatorGreaterThan */, value: ">", line: startLine, column: startColumn };
|
|
231
232
|
}
|
|
232
233
|
if (char === "+") {
|
|
233
234
|
this.advance();
|
|
234
|
-
return { type:
|
|
235
|
+
return { type: 113 /* OperatorPlus */, value: "+", line: startLine, column: startColumn };
|
|
235
236
|
}
|
|
236
237
|
if (char === "-") {
|
|
237
238
|
this.advance();
|
|
238
|
-
return { type:
|
|
239
|
+
return { type: 114 /* OperatorMinus */, value: "-", line: startLine, column: startColumn };
|
|
239
240
|
}
|
|
240
241
|
if (char === "!") {
|
|
241
242
|
this.advance();
|
|
242
|
-
return { type:
|
|
243
|
+
return { type: 133 /* OperatorExclamation */, value: "!", line: startLine, column: startColumn };
|
|
243
244
|
}
|
|
244
245
|
if (char === "&") {
|
|
245
246
|
this.advance();
|
|
@@ -261,23 +262,23 @@ var Lexer = class _Lexer {
|
|
|
261
262
|
if (NUMERIC_TYPE_SUFFIXES.has(this.peek())) this.advance();
|
|
262
263
|
return { type: 1 /* Number */, value: "0o" + octStr, line: startLine, column: startColumn };
|
|
263
264
|
}
|
|
264
|
-
return { type:
|
|
265
|
+
return { type: 118 /* OperatorAmpersand */, value: "&", line: startLine, column: startColumn };
|
|
265
266
|
}
|
|
266
267
|
if (char === ",") {
|
|
267
268
|
this.advance();
|
|
268
|
-
return { type:
|
|
269
|
+
return { type: 127 /* OperatorComma */, value: ",", line: startLine, column: startColumn };
|
|
269
270
|
}
|
|
270
271
|
if (char === "#") {
|
|
271
272
|
this.advance();
|
|
272
|
-
return { type:
|
|
273
|
+
return { type: 112 /* OperatorHash */, value: "#", line: startLine, column: startColumn };
|
|
273
274
|
}
|
|
274
275
|
if (char === "(") {
|
|
275
276
|
this.advance();
|
|
276
|
-
return { type:
|
|
277
|
+
return { type: 128 /* OperatorLParen */, value: "(", line: startLine, column: startColumn };
|
|
277
278
|
}
|
|
278
279
|
if (char === ")") {
|
|
279
280
|
this.advance();
|
|
280
|
-
return { type:
|
|
281
|
+
return { type: 129 /* OperatorRParen */, value: ")", line: startLine, column: startColumn };
|
|
281
282
|
}
|
|
282
283
|
if (char === "[") {
|
|
283
284
|
this.advance();
|
|
@@ -286,23 +287,23 @@ var Lexer = class _Lexer {
|
|
|
286
287
|
foreignStr += this.advance();
|
|
287
288
|
}
|
|
288
289
|
if (this.peek() === "]") this.advance();
|
|
289
|
-
return { type:
|
|
290
|
+
return { type: 139 /* ForeignName */, value: foreignStr, line: startLine, column: startColumn };
|
|
290
291
|
}
|
|
291
292
|
if (char === ".") {
|
|
292
293
|
this.advance();
|
|
293
|
-
return { type:
|
|
294
|
+
return { type: 130 /* OperatorDot */, value: ".", line: startLine, column: startColumn };
|
|
294
295
|
}
|
|
295
296
|
if (char === ":") {
|
|
296
297
|
this.advance();
|
|
297
298
|
if (this.peek() === "=") {
|
|
298
299
|
this.advance();
|
|
299
|
-
return { type:
|
|
300
|
+
return { type: 132 /* OperatorColonEquals */, value: ":=", line: startLine, column: startColumn };
|
|
300
301
|
}
|
|
301
|
-
return { type:
|
|
302
|
+
return { type: 131 /* OperatorColon */, value: ":", line: startLine, column: startColumn };
|
|
302
303
|
}
|
|
303
304
|
if (char === ";") {
|
|
304
305
|
this.advance();
|
|
305
|
-
return { type:
|
|
306
|
+
return { type: 134 /* OperatorSemicolon */, value: ";", line: startLine, column: startColumn };
|
|
306
307
|
}
|
|
307
308
|
if (this.isDigit(char)) {
|
|
308
309
|
let numStr = "";
|
|
@@ -399,7 +400,7 @@ var Lexer = class _Lexer {
|
|
|
399
400
|
if (lowerBase === "exit") return { type: 45 /* KeywordExit */, value: idStr, line: startLine, column: startColumn };
|
|
400
401
|
if (lowerBase === "byref") return { type: 46 /* KeywordByRef */, value: idStr, line: startLine, column: startColumn };
|
|
401
402
|
if (lowerBase === "byval") return { type: 47 /* KeywordByVal */, value: idStr, line: startLine, column: startColumn };
|
|
402
|
-
if (lowerBase === "mod") return { type:
|
|
403
|
+
if (lowerBase === "mod") return { type: 119 /* KeywordMod */, value: idStr, line: startLine, column: startColumn };
|
|
403
404
|
if (lowerBase === "type") return { type: 48 /* KeywordType */, value: idStr, line: startLine, column: startColumn };
|
|
404
405
|
if (lowerBase === "nothing") return { type: 49 /* KeywordNothing */, value: idStr, line: startLine, column: startColumn };
|
|
405
406
|
if (lowerBase === "optional") return { type: 50 /* KeywordOptional */, value: idStr, line: startLine, column: startColumn };
|
|
@@ -460,26 +461,42 @@ var Lexer = class _Lexer {
|
|
|
460
461
|
if (lowerBase === "midb") return { type: 108 /* KeywordMid */, value: idStr, line: startLine, column: startColumn };
|
|
461
462
|
if (lowerBase === "width") return { type: 109 /* KeywordWidth */, value: idStr, line: startLine, column: startColumn };
|
|
462
463
|
if (lowerBase === "addressof") return { type: 110 /* KeywordAddressOf */, value: idStr, line: startLine, column: startColumn };
|
|
464
|
+
if ([
|
|
465
|
+
"defbool",
|
|
466
|
+
"defbyte",
|
|
467
|
+
"defint",
|
|
468
|
+
"deflng",
|
|
469
|
+
"deflnglng",
|
|
470
|
+
"deflngptr",
|
|
471
|
+
"defsng",
|
|
472
|
+
"defdbl",
|
|
473
|
+
"defcur",
|
|
474
|
+
"defdate",
|
|
475
|
+
"defstr",
|
|
476
|
+
"defobj",
|
|
477
|
+
"defvar"
|
|
478
|
+
].includes(lowerBase))
|
|
479
|
+
return { type: 111 /* KeywordDef */, value: idStr, line: startLine, column: startColumn };
|
|
463
480
|
return { type: 0 /* Identifier */, value: idStr, line: startLine, column: startColumn };
|
|
464
481
|
}
|
|
465
482
|
if (char === "*") {
|
|
466
483
|
this.advance();
|
|
467
|
-
return { type:
|
|
484
|
+
return { type: 115 /* OperatorMultiply */, value: "*", line: startLine, column: startColumn };
|
|
468
485
|
}
|
|
469
486
|
if (char === "/") {
|
|
470
487
|
this.advance();
|
|
471
|
-
return { type:
|
|
488
|
+
return { type: 116 /* OperatorDivide */, value: "/", line: startLine, column: startColumn };
|
|
472
489
|
}
|
|
473
490
|
if (char === "\\") {
|
|
474
491
|
this.advance();
|
|
475
|
-
return { type:
|
|
492
|
+
return { type: 117 /* OperatorIntDivide */, value: "\\", line: startLine, column: startColumn };
|
|
476
493
|
}
|
|
477
494
|
if (char === "^") {
|
|
478
495
|
this.advance();
|
|
479
|
-
return { type:
|
|
496
|
+
return { type: 120 /* OperatorPower */, value: "^", line: startLine, column: startColumn };
|
|
480
497
|
}
|
|
481
498
|
const unknownChar = this.advance();
|
|
482
|
-
return { type:
|
|
499
|
+
return { type: 138 /* Unknown */, value: unknownChar, line: startLine, column: startColumn };
|
|
483
500
|
}
|
|
484
501
|
}
|
|
485
502
|
tokenize() {
|
|
@@ -488,7 +505,7 @@ var Lexer = class _Lexer {
|
|
|
488
505
|
do {
|
|
489
506
|
token = this.getNextToken();
|
|
490
507
|
tokens.push(token);
|
|
491
|
-
} while (token.type !==
|
|
508
|
+
} while (token.type !== 137 /* EOF */);
|
|
492
509
|
return tokens;
|
|
493
510
|
}
|
|
494
511
|
};
|
|
@@ -591,31 +608,52 @@ var Parser = class _Parser {
|
|
|
591
608
|
..._Parser.CONTEXTUAL_KW_STMT_ABSENT,
|
|
592
609
|
..._Parser.CONTEXTUAL_KW_STRUCTURAL
|
|
593
610
|
]);
|
|
594
|
-
/** <statement-keyword> tokens
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
611
|
+
/** <statement-keyword> tokens that are <reserved-identifier> per §3.3.5.2 but whose
|
|
612
|
+
* enum values fall in [KeywordBase, KeywordAddressOf].
|
|
613
|
+
* These CANNOT be used as module-level procedure names (parseProcedureDeclaration rejects them).
|
|
614
|
+
* They ARE permitted as member names in expression context (obj.Print, ws.Get, obj.Open) via
|
|
615
|
+
* parsePrimary, which is why they appear in COMPAT_KW_EXPR. */
|
|
616
|
+
static STATEMENT_KW_RESERVED = /* @__PURE__ */ new Set([
|
|
617
|
+
81 /* KeywordOpen */,
|
|
618
|
+
82 /* KeywordClose */,
|
|
619
|
+
84 /* KeywordInput */,
|
|
620
|
+
85 /* KeywordPrint */,
|
|
621
|
+
86 /* KeywordPut */,
|
|
622
|
+
92 /* KeywordWrite */,
|
|
623
|
+
93 /* KeywordLock */,
|
|
624
|
+
98 /* KeywordSeek */,
|
|
625
|
+
100 /* KeywordUnlock */,
|
|
626
|
+
102 /* KeywordEvent */,
|
|
627
|
+
103 /* KeywordRaiseEvent */,
|
|
628
|
+
105 /* KeywordImplements */
|
|
629
|
+
]);
|
|
630
|
+
/** <statement-keyword> tokens permitted as member names in parsePrimary (obj.Print, ws.Get,
|
|
631
|
+
* obj.Open, etc.) per §3.3.5.2 "unrestricted-name" rule.
|
|
632
|
+
* In parseStatementInner these keywords are all dispatched unconditionally before the
|
|
633
|
+
* identifier branch, so the COMPAT_KW_EXPR entries there are never reached — they exist
|
|
634
|
+
* solely for parsePrimary dot-member-access context. */
|
|
598
635
|
static COMPAT_KW_EXPR = /* @__PURE__ */ new Set([
|
|
599
636
|
98 /* KeywordSeek */,
|
|
600
|
-
// <statement-keyword>
|
|
601
637
|
84 /* KeywordInput */,
|
|
602
|
-
// <statement-keyword> / <special-form>
|
|
603
638
|
85 /* KeywordPrint */,
|
|
604
|
-
// <statement-keyword>
|
|
605
639
|
86 /* KeywordPut */,
|
|
606
|
-
// <statement-keyword>
|
|
607
640
|
20 /* KeywordGet */,
|
|
608
|
-
// <
|
|
641
|
+
// enum < KeywordBase so also rejected by range check in parseProcedureDeclaration
|
|
609
642
|
93 /* KeywordLock */,
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
643
|
+
100 /* KeywordUnlock */,
|
|
644
|
+
81 /* KeywordOpen */,
|
|
645
|
+
82 /* KeywordClose */,
|
|
646
|
+
// Non-reserved statement keywords: can be user-defined proc names (e.g. Function Kill()).
|
|
647
|
+
// Included here so "Kill = v", "Reset = v", "Width = v" fall through to identifier branch.
|
|
648
|
+
97 /* KeywordKill */,
|
|
649
|
+
99 /* KeywordReset */,
|
|
650
|
+
109 /* KeywordWidth */
|
|
613
651
|
]);
|
|
614
652
|
errorRecovery;
|
|
615
653
|
/** Returns true if token is a valid IDENTIFIER per §3.3.5.2:
|
|
616
654
|
* either a plain lex-identifier or a contextual keyword that is not reserved. */
|
|
617
655
|
isIdentifier(token) {
|
|
618
|
-
return token.type === 0 /* Identifier */ || token.type ===
|
|
656
|
+
return token.type === 0 /* Identifier */ || token.type === 139 /* ForeignName */ || _Parser.CONTEXTUAL_KW.has(token.type);
|
|
619
657
|
}
|
|
620
658
|
constructor(tokens, options = {}) {
|
|
621
659
|
this.tokens = tokens;
|
|
@@ -624,15 +662,24 @@ var Parser = class _Parser {
|
|
|
624
662
|
}
|
|
625
663
|
// Keywords can appear as property/class names in VBA (e.g. obj.Property, New Collection)
|
|
626
664
|
isNameToken(token) {
|
|
627
|
-
return token.type === 0 /* Identifier */ || token.type ===
|
|
628
|
-
}
|
|
665
|
+
return token.type === 0 /* Identifier */ || token.type === 139 /* ForeignName */ || token.type >= 3 /* KeywordFor */ && token.type <= 110 /* KeywordAddressOf */;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Returns true if the current token stream contains a file-I/O Open statement pattern
|
|
669
|
+
* ("For <file-mode>") on the current logical line.
|
|
670
|
+
* Open "path" For Input|Output|Append|Random|Binary ... As #n → true
|
|
671
|
+
* Open() / Open = value / Open x (user-defined call) → false
|
|
672
|
+
*
|
|
673
|
+
* This syntactic check lets the parser disambiguate without a pre-scan:
|
|
674
|
+
* VBA §3.3.5.3 — user-defined procedures (priority 2) > built-in statement keywords (priority 3).
|
|
675
|
+
*/
|
|
629
676
|
recordError(message, token) {
|
|
630
677
|
const pos = { line: token.line, column: token.column };
|
|
631
678
|
this._diagnostics.push({ message, loc: { start: pos, end: pos }, severity: "error" });
|
|
632
679
|
}
|
|
633
680
|
throwMissingRParen() {
|
|
634
681
|
const peek = this.peek();
|
|
635
|
-
if (peek.type ===
|
|
682
|
+
if (peek.type === 136 /* Newline */) {
|
|
636
683
|
const prevToken = this.tokens[Math.max(0, this.pos - 1)];
|
|
637
684
|
if (prevToken && this.isContinuationEndToken(prevToken.type)) {
|
|
638
685
|
this.throwError(
|
|
@@ -644,24 +691,24 @@ var Parser = class _Parser {
|
|
|
644
691
|
this.throwError(`Parse error: Expected ')' at line ${peek.line} `);
|
|
645
692
|
}
|
|
646
693
|
isContinuationEndToken(type) {
|
|
647
|
-
return type ===
|
|
694
|
+
return type === 113 /* OperatorPlus */ || type === 114 /* OperatorMinus */ || type === 115 /* OperatorMultiply */ || type === 116 /* OperatorDivide */ || type === 117 /* OperatorIntDivide */ || type === 120 /* OperatorPower */ || type === 118 /* OperatorAmpersand */ || type === 121 /* OperatorEquals */ || type === 122 /* OperatorNotEquals */ || type === 123 /* OperatorLessThan */ || type === 124 /* OperatorGreaterThan */ || type === 125 /* OperatorLessThanOrEqual */ || type === 126 /* OperatorGreaterThanOrEqual */ || type === 127 /* OperatorComma */ || type === 128 /* OperatorLParen */ || type === 26 /* KeywordAnd */ || type === 27 /* KeywordOr */ || type === 64 /* KeywordXor */ || type === 65 /* KeywordEqv */ || type === 66 /* KeywordImp */ || type === 119 /* KeywordMod */ || type === 63 /* KeywordLike */ || type === 51 /* KeywordIs */ || type === 28 /* KeywordNot */;
|
|
648
695
|
}
|
|
649
696
|
tokenDisplay(value) {
|
|
650
697
|
return value.replace(/\n/g, "<\u6539\u884C>").replace(/\r/g, "<CR>").replace(/\t/g, "<\u30BF\u30D6>");
|
|
651
698
|
}
|
|
652
699
|
throwError(message, token) {
|
|
653
700
|
const peek = this.peek();
|
|
654
|
-
const t = token ?? (peek.type !==
|
|
701
|
+
const t = token ?? (peek.type !== 137 /* EOF */ ? peek : this.tokens[Math.max(0, this.pos - 1)]);
|
|
655
702
|
throw new ParseError(message, t.line, t.column);
|
|
656
703
|
}
|
|
657
704
|
syncToNextTopLevelStatement() {
|
|
658
|
-
while (this.peek().type !==
|
|
705
|
+
while (this.peek().type !== 137 /* EOF */ && this.peek().type !== 136 /* Newline */) {
|
|
659
706
|
this.advance();
|
|
660
707
|
}
|
|
661
708
|
while (this.isAtEndTerminator()) {
|
|
662
709
|
this.advance();
|
|
663
710
|
this.advance();
|
|
664
|
-
while (this.peek().type !==
|
|
711
|
+
while (this.peek().type !== 137 /* EOF */ && this.peek().type !== 136 /* Newline */) {
|
|
665
712
|
this.advance();
|
|
666
713
|
}
|
|
667
714
|
}
|
|
@@ -689,6 +736,18 @@ var Parser = class _Parser {
|
|
|
689
736
|
}
|
|
690
737
|
return { type: "OptionCompareStatement", mode };
|
|
691
738
|
}
|
|
739
|
+
validateParameterOrder(params) {
|
|
740
|
+
let seenOptional = false;
|
|
741
|
+
for (const p of params) {
|
|
742
|
+
if (p.isParamArray) break;
|
|
743
|
+
if (p.isOptional) {
|
|
744
|
+
seenOptional = true;
|
|
745
|
+
} else if (seenOptional) {
|
|
746
|
+
const line = p.loc?.start?.line ?? this.peek().line;
|
|
747
|
+
this.throwError(`Compile error at line ${line}: Non-optional parameter '${p.name}' cannot follow an Optional parameter`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
692
751
|
parseParameter() {
|
|
693
752
|
let isByVal = false;
|
|
694
753
|
let isOptional = false;
|
|
@@ -710,20 +769,20 @@ var Parser = class _Parser {
|
|
|
710
769
|
}
|
|
711
770
|
const nameToken = this.advance();
|
|
712
771
|
let isArray = false;
|
|
713
|
-
if (this.match(
|
|
714
|
-
this.consume(
|
|
772
|
+
if (this.match(128 /* OperatorLParen */)) {
|
|
773
|
+
this.consume(129 /* OperatorRParen */, "Expected ')' after parameter name");
|
|
715
774
|
isArray = true;
|
|
716
775
|
}
|
|
717
776
|
let paramType;
|
|
718
777
|
if (this.match(23 /* KeywordAs */)) {
|
|
719
778
|
paramType = this.advance().value;
|
|
720
|
-
if (this.peek().type ===
|
|
779
|
+
if (this.peek().type === 130 /* OperatorDot */) {
|
|
721
780
|
this.advance();
|
|
722
781
|
paramType += "." + this.advance().value;
|
|
723
782
|
}
|
|
724
783
|
}
|
|
725
784
|
let defaultValue;
|
|
726
|
-
if (this.match(
|
|
785
|
+
if (this.match(121 /* OperatorEquals */)) {
|
|
727
786
|
defaultValue = this.parseExpression();
|
|
728
787
|
}
|
|
729
788
|
return {
|
|
@@ -739,10 +798,43 @@ var Parser = class _Parser {
|
|
|
739
798
|
loc: { start: { line: nameToken.line, column: nameToken.column }, end: { line: nameToken.line, column: nameToken.column + nameToken.value.length } }
|
|
740
799
|
};
|
|
741
800
|
}
|
|
801
|
+
parseDefDirective() {
|
|
802
|
+
const defToken = this.advance();
|
|
803
|
+
const keyword = defToken.value.toLowerCase().replace(/[$%&#@!^]$/, "");
|
|
804
|
+
const typeMap = {
|
|
805
|
+
defbool: "Boolean",
|
|
806
|
+
defbyte: "Byte",
|
|
807
|
+
defint: "Integer",
|
|
808
|
+
deflng: "Long",
|
|
809
|
+
deflnglng: "LongLong",
|
|
810
|
+
deflngptr: "LongPtr",
|
|
811
|
+
defsng: "Single",
|
|
812
|
+
defdbl: "Double",
|
|
813
|
+
defcur: "Currency",
|
|
814
|
+
defdate: "Date",
|
|
815
|
+
defstr: "String",
|
|
816
|
+
defobj: "Object",
|
|
817
|
+
defvar: "Variant"
|
|
818
|
+
};
|
|
819
|
+
const vbaType = typeMap[keyword] ?? "Variant";
|
|
820
|
+
const ranges = [];
|
|
821
|
+
do {
|
|
822
|
+
const fromTok = this.advance();
|
|
823
|
+
const from = fromTok.value.charAt(0).toLowerCase();
|
|
824
|
+
let to = from;
|
|
825
|
+
if (this.peek().type === 114 /* OperatorMinus */) {
|
|
826
|
+
this.advance();
|
|
827
|
+
const toTok = this.advance();
|
|
828
|
+
to = toTok.value.charAt(0).toLowerCase();
|
|
829
|
+
}
|
|
830
|
+
ranges.push({ from, to });
|
|
831
|
+
} while (this.match(127 /* OperatorComma */));
|
|
832
|
+
return { type: "DefDirective", vbaType, ranges };
|
|
833
|
+
}
|
|
742
834
|
parseAttributeStatement() {
|
|
743
835
|
this.advance();
|
|
744
836
|
const name = this.advance().value;
|
|
745
|
-
this.consume(
|
|
837
|
+
this.consume(121 /* OperatorEquals */, "Expected '=' after Attribute name");
|
|
746
838
|
const value = this.parseExpression();
|
|
747
839
|
return { type: "AttributeStatement", name, value };
|
|
748
840
|
}
|
|
@@ -800,7 +892,7 @@ var Parser = class _Parser {
|
|
|
800
892
|
}
|
|
801
893
|
}
|
|
802
894
|
this.consume(23 /* KeywordAs */, "Expected 'As' in Open statement");
|
|
803
|
-
this.match(
|
|
895
|
+
this.match(112 /* OperatorHash */);
|
|
804
896
|
const fileNumber = this.parseExpression();
|
|
805
897
|
return { type: "OpenStatement", path: path2, mode, access, lock, fileNumber };
|
|
806
898
|
}
|
|
@@ -808,34 +900,34 @@ var Parser = class _Parser {
|
|
|
808
900
|
this.advance();
|
|
809
901
|
const fileNumbers = [];
|
|
810
902
|
while (!this.isAtTerminator()) {
|
|
811
|
-
this.match(
|
|
903
|
+
this.match(112 /* OperatorHash */);
|
|
812
904
|
fileNumbers.push(this.parseExpression());
|
|
813
|
-
if (!this.match(
|
|
905
|
+
if (!this.match(127 /* OperatorComma */)) break;
|
|
814
906
|
}
|
|
815
907
|
return { type: "CloseStatement", fileNumbers };
|
|
816
908
|
}
|
|
817
909
|
parsePrintStatement() {
|
|
818
910
|
this.advance();
|
|
819
|
-
this.consume(
|
|
911
|
+
this.consume(112 /* OperatorHash */, "Expected '#' in Print statement");
|
|
820
912
|
const fileNumber = this.parseExpression();
|
|
821
|
-
this.consume(
|
|
913
|
+
this.consume(127 /* OperatorComma */, "Expected ',' after file number in Print statement");
|
|
822
914
|
const expressions = [];
|
|
823
915
|
while (!this.isAtTerminator()) {
|
|
824
916
|
if (this.match(95 /* KeywordSpc */)) {
|
|
825
|
-
this.consume(
|
|
917
|
+
this.consume(128 /* OperatorLParen */, "Expected '(' after Spc");
|
|
826
918
|
expressions.push({ type: "Spc", val: this.parseExpression() });
|
|
827
|
-
this.consume(
|
|
919
|
+
this.consume(129 /* OperatorRParen */, "Expected ')' after Spc");
|
|
828
920
|
} else if (this.match(96 /* KeywordTab */)) {
|
|
829
|
-
if (this.match(
|
|
921
|
+
if (this.match(128 /* OperatorLParen */)) {
|
|
830
922
|
expressions.push({ type: "Tab", val: this.parseExpression() });
|
|
831
|
-
this.consume(
|
|
923
|
+
this.consume(129 /* OperatorRParen */, "Expected ')' after Tab");
|
|
832
924
|
} else {
|
|
833
925
|
expressions.push("Tab");
|
|
834
926
|
}
|
|
835
|
-
} else if (this.peek().type ===
|
|
927
|
+
} else if (this.peek().type === 127 /* OperatorComma */) {
|
|
836
928
|
this.advance();
|
|
837
929
|
expressions.push("Comma");
|
|
838
|
-
} else if (this.peek().type ===
|
|
930
|
+
} else if (this.peek().type === 134 /* OperatorSemicolon */) {
|
|
839
931
|
this.advance();
|
|
840
932
|
expressions.push("Semicolon");
|
|
841
933
|
} else {
|
|
@@ -848,9 +940,9 @@ var Parser = class _Parser {
|
|
|
848
940
|
parseLineInputStatement() {
|
|
849
941
|
this.advance();
|
|
850
942
|
this.consume(84 /* KeywordInput */, "Expected 'Input' after 'Line'");
|
|
851
|
-
this.consume(
|
|
943
|
+
this.consume(112 /* OperatorHash */, "Expected '#' in Line Input statement");
|
|
852
944
|
const fileNumber = this.parseExpression();
|
|
853
|
-
this.consume(
|
|
945
|
+
this.consume(127 /* OperatorComma */, "Expected ',' after file number");
|
|
854
946
|
const variable = this.parsePrimary();
|
|
855
947
|
if (variable.type !== "Identifier") {
|
|
856
948
|
this.throwError(`Parse error: Expected variable name in Line Input at line ${this.peek().line}`);
|
|
@@ -859,14 +951,14 @@ var Parser = class _Parser {
|
|
|
859
951
|
}
|
|
860
952
|
parsePutStatement() {
|
|
861
953
|
this.advance();
|
|
862
|
-
this.consume(
|
|
954
|
+
this.consume(112 /* OperatorHash */, "Expected '#' in Put statement");
|
|
863
955
|
const fileNumber = this.parseExpression();
|
|
864
|
-
this.consume(
|
|
956
|
+
this.consume(127 /* OperatorComma */, "Expected ',' after file number");
|
|
865
957
|
let recordNumber = void 0;
|
|
866
|
-
if (this.peek().type !==
|
|
958
|
+
if (this.peek().type !== 127 /* OperatorComma */) {
|
|
867
959
|
recordNumber = this.parseExpression();
|
|
868
960
|
}
|
|
869
|
-
this.consume(
|
|
961
|
+
this.consume(127 /* OperatorComma */, "Expected second ',' in Put statement");
|
|
870
962
|
const data = this.parseExpression();
|
|
871
963
|
return { type: "PutStatement", fileNumber, recordNumber, data };
|
|
872
964
|
}
|
|
@@ -877,47 +969,47 @@ var Parser = class _Parser {
|
|
|
877
969
|
}
|
|
878
970
|
parseWriteStatement() {
|
|
879
971
|
this.advance();
|
|
880
|
-
this.consume(
|
|
972
|
+
this.consume(112 /* OperatorHash */, "Expected '#' in Write statement");
|
|
881
973
|
const fileNumber = this.parseExpression();
|
|
882
974
|
const items = [];
|
|
883
|
-
if (this.match(
|
|
975
|
+
if (this.match(127 /* OperatorComma */)) {
|
|
884
976
|
while (!this.isAtTerminator()) {
|
|
885
977
|
items.push(this.parseExpression());
|
|
886
|
-
if (!this.match(
|
|
978
|
+
if (!this.match(127 /* OperatorComma */)) break;
|
|
887
979
|
}
|
|
888
980
|
}
|
|
889
981
|
return { type: "WriteStatement", fileNumber, items };
|
|
890
982
|
}
|
|
891
983
|
parseInputStatement() {
|
|
892
984
|
this.advance();
|
|
893
|
-
this.consume(
|
|
985
|
+
this.consume(112 /* OperatorHash */, "Expected '#' in Input statement");
|
|
894
986
|
const fileNumber = this.parseExpression();
|
|
895
|
-
this.consume(
|
|
987
|
+
this.consume(127 /* OperatorComma */, "Expected ',' in Input statement");
|
|
896
988
|
const variables = [];
|
|
897
989
|
while (!this.isAtTerminator()) {
|
|
898
990
|
variables.push(this.parseExpression());
|
|
899
|
-
if (!this.match(
|
|
991
|
+
if (!this.match(127 /* OperatorComma */)) break;
|
|
900
992
|
}
|
|
901
993
|
return { type: "InputStatement", fileNumber, variables };
|
|
902
994
|
}
|
|
903
995
|
parseGetStatement() {
|
|
904
996
|
this.advance();
|
|
905
|
-
this.consume(
|
|
997
|
+
this.consume(112 /* OperatorHash */, "Expected '#' in Get statement");
|
|
906
998
|
const fileNumber = this.parseExpression();
|
|
907
|
-
this.consume(
|
|
999
|
+
this.consume(127 /* OperatorComma */, "Expected ',' in Get statement");
|
|
908
1000
|
let recordNumber = void 0;
|
|
909
|
-
if (this.peek().type !==
|
|
1001
|
+
if (this.peek().type !== 127 /* OperatorComma */) {
|
|
910
1002
|
recordNumber = this.parseExpression();
|
|
911
1003
|
}
|
|
912
|
-
this.consume(
|
|
1004
|
+
this.consume(127 /* OperatorComma */, "Expected ',' in Get statement");
|
|
913
1005
|
const variable = this.parseExpression();
|
|
914
1006
|
return { type: "GetStatement", fileNumber, recordNumber, variable };
|
|
915
1007
|
}
|
|
916
1008
|
parseSeekStatement() {
|
|
917
1009
|
this.advance();
|
|
918
|
-
this.consume(
|
|
1010
|
+
this.consume(112 /* OperatorHash */, "Expected '#' in Seek statement");
|
|
919
1011
|
const fileNumber = this.parseExpression();
|
|
920
|
-
this.consume(
|
|
1012
|
+
this.consume(127 /* OperatorComma */, "Expected ',' in Seek statement");
|
|
921
1013
|
const position = this.parseExpression();
|
|
922
1014
|
return { type: "SeekStatement", fileNumber, position };
|
|
923
1015
|
}
|
|
@@ -954,15 +1046,16 @@ var Parser = class _Parser {
|
|
|
954
1046
|
aliasName = this.advance().value.replace(/^"|"$/g, "");
|
|
955
1047
|
}
|
|
956
1048
|
let parameters = [];
|
|
957
|
-
if (this.peek().type ===
|
|
1049
|
+
if (this.peek().type === 128 /* OperatorLParen */) {
|
|
958
1050
|
this.advance();
|
|
959
|
-
while (this.peek().type !==
|
|
1051
|
+
while (this.peek().type !== 129 /* OperatorRParen */ && this.peek().type !== 137 /* EOF */) {
|
|
960
1052
|
parameters.push(this.parseParameter());
|
|
961
|
-
if (this.peek().type ===
|
|
1053
|
+
if (this.peek().type === 127 /* OperatorComma */) {
|
|
962
1054
|
this.advance();
|
|
963
1055
|
}
|
|
964
1056
|
}
|
|
965
|
-
this.consume(
|
|
1057
|
+
this.consume(129 /* OperatorRParen */, "Expected ')' after declare parameters");
|
|
1058
|
+
this.validateParameterOrder(parameters);
|
|
966
1059
|
}
|
|
967
1060
|
let returnType;
|
|
968
1061
|
if (!isSub) {
|
|
@@ -999,7 +1092,7 @@ var Parser = class _Parser {
|
|
|
999
1092
|
// argument starters). When one of these follows a greedily-parsed CallExpression in a
|
|
1000
1093
|
// statement context, it signals that the `(args)` was actually part of the argument expression.
|
|
1001
1094
|
isBinaryOnlyOperator(type) {
|
|
1002
|
-
return type ===
|
|
1095
|
+
return type === 115 /* OperatorMultiply */ || type === 116 /* OperatorDivide */ || type === 117 /* OperatorIntDivide */ || type === 120 /* OperatorPower */ || type === 113 /* OperatorPlus */ || type === 114 /* OperatorMinus */ || type === 118 /* OperatorAmpersand */ || type === 122 /* OperatorNotEquals */ || type === 123 /* OperatorLessThan */ || type === 124 /* OperatorGreaterThan */ || type === 125 /* OperatorLessThanOrEqual */ || type === 126 /* OperatorGreaterThanOrEqual */ || type === 119 /* KeywordMod */;
|
|
1003
1096
|
}
|
|
1004
1097
|
// Returns true if there is whitespace between the previous token and the current token (same line).
|
|
1005
1098
|
// Used to distinguish `Foo(arg)` (no space = function-call postfix) from
|
|
@@ -1013,7 +1106,7 @@ var Parser = class _Parser {
|
|
|
1013
1106
|
}
|
|
1014
1107
|
isAtTerminator() {
|
|
1015
1108
|
const type = this.peek().type;
|
|
1016
|
-
return type ===
|
|
1109
|
+
return type === 136 /* Newline */ || type === 137 /* EOF */ || type === 131 /* OperatorColon */;
|
|
1017
1110
|
}
|
|
1018
1111
|
consume(expectedType, message) {
|
|
1019
1112
|
if (this.peek().type === expectedType) {
|
|
@@ -1022,7 +1115,7 @@ var Parser = class _Parser {
|
|
|
1022
1115
|
this.throwError(`Parse error at line ${this.peek().line}: ${message}`);
|
|
1023
1116
|
}
|
|
1024
1117
|
skipNewlines() {
|
|
1025
|
-
while (this.match(
|
|
1118
|
+
while (this.match(136 /* Newline */)) {
|
|
1026
1119
|
}
|
|
1027
1120
|
}
|
|
1028
1121
|
// Build a SourceLocation spanning from start token to end token (inclusive).
|
|
@@ -1060,7 +1153,7 @@ var Parser = class _Parser {
|
|
|
1060
1153
|
diagnostics: this._diagnostics
|
|
1061
1154
|
};
|
|
1062
1155
|
this.skipNewlines();
|
|
1063
|
-
while (this.peek().type !==
|
|
1156
|
+
while (this.peek().type !== 137 /* EOF */) {
|
|
1064
1157
|
const startPos = this.pos;
|
|
1065
1158
|
const startToken = this.peek();
|
|
1066
1159
|
try {
|
|
@@ -1079,7 +1172,7 @@ var Parser = class _Parser {
|
|
|
1079
1172
|
}
|
|
1080
1173
|
this.syncToNextTopLevelStatement();
|
|
1081
1174
|
}
|
|
1082
|
-
if (this.pos === startPos && this.peek().type !==
|
|
1175
|
+
if (this.pos === startPos && this.peek().type !== 137 /* EOF */) {
|
|
1083
1176
|
if (this.isAtEndTerminator()) {
|
|
1084
1177
|
this.advance();
|
|
1085
1178
|
this.advance();
|
|
@@ -1116,9 +1209,70 @@ var Parser = class _Parser {
|
|
|
1116
1209
|
}
|
|
1117
1210
|
return stmt;
|
|
1118
1211
|
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Parse an identifier, method call, or assignment statement.
|
|
1214
|
+
* Called from the identifier branch of parseStatementInner AND from the ARCH-1
|
|
1215
|
+
* user-proc override path (when a built-in keyword is user-defined as a procedure).
|
|
1216
|
+
*/
|
|
1217
|
+
parseIdentifierOrCallStatement() {
|
|
1218
|
+
const token = this.peek();
|
|
1219
|
+
if ((token.type === 0 /* Identifier */ || _Parser.CONTEXTUAL_KW.has(token.type)) && this.pos + 1 < this.tokens.length && this.tokens[this.pos + 1].type === 131 /* OperatorColon */) {
|
|
1220
|
+
const labelName = token.value;
|
|
1221
|
+
this.advance();
|
|
1222
|
+
this.advance();
|
|
1223
|
+
return { type: "LabelStatement", label: labelName };
|
|
1224
|
+
}
|
|
1225
|
+
if (token.type === 1 /* Number */) {
|
|
1226
|
+
const labelName = token.value;
|
|
1227
|
+
this.advance();
|
|
1228
|
+
this.match(131 /* OperatorColon */);
|
|
1229
|
+
return { type: "LabelStatement", label: labelName };
|
|
1230
|
+
}
|
|
1231
|
+
const savedPos = this.pos;
|
|
1232
|
+
const expr = this.parsePrimary();
|
|
1233
|
+
if (this.match(121 /* OperatorEquals */)) {
|
|
1234
|
+
return {
|
|
1235
|
+
type: "AssignmentStatement",
|
|
1236
|
+
left: expr,
|
|
1237
|
+
right: this.parseExpression()
|
|
1238
|
+
};
|
|
1239
|
+
} else {
|
|
1240
|
+
if (expr.type === "CallExpression" && this.isBinaryOnlyOperator(this.peek().type)) {
|
|
1241
|
+
this.pos = savedPos;
|
|
1242
|
+
const callee = this.parsePrimary(
|
|
1243
|
+
/* stopBeforeSpacedLParen= */
|
|
1244
|
+
true
|
|
1245
|
+
);
|
|
1246
|
+
const args2 = [this.parseCallArgument()];
|
|
1247
|
+
while (this.match(127 /* OperatorComma */)) {
|
|
1248
|
+
args2.push(this.parseCallArgument());
|
|
1249
|
+
}
|
|
1250
|
+
return { type: "CallStatement", expression: { type: "CallExpression", callee, args: args2 } };
|
|
1251
|
+
}
|
|
1252
|
+
const args = [];
|
|
1253
|
+
if (this.peek().type !== 136 /* Newline */ && this.peek().type !== 137 /* EOF */ && this.peek().type !== 9 /* KeywordElse */ && this.peek().type !== 8 /* KeywordElseIf */ && this.peek().type !== 10 /* KeywordEnd */ && this.peek().type !== 5 /* KeywordNext */ && this.peek().type !== 14 /* KeywordLoop */) {
|
|
1254
|
+
args.push(this.parseCallArgument());
|
|
1255
|
+
while (this.match(127 /* OperatorComma */)) {
|
|
1256
|
+
args.push(this.parseCallArgument());
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
if (args.length > 0) {
|
|
1260
|
+
return { type: "CallStatement", expression: { type: "CallExpression", callee: expr, args } };
|
|
1261
|
+
} else if (expr.type === "CallExpression") {
|
|
1262
|
+
const callExpr = expr;
|
|
1263
|
+
if (callExpr.args.length === 0 && callExpr.callee.type === "Identifier" && !callExpr.callee.foreign) {
|
|
1264
|
+
const line = callExpr.loc?.start.line ?? this.peek().line;
|
|
1265
|
+
this.throwError(`Parse error: syntax error at line ${line}`);
|
|
1266
|
+
}
|
|
1267
|
+
return { type: "CallStatement", expression: callExpr };
|
|
1268
|
+
} else {
|
|
1269
|
+
return { type: "CallStatement", expression: { type: "CallExpression", callee: expr, args: [] } };
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1119
1273
|
parseStatementInner() {
|
|
1120
1274
|
const token = this.peek();
|
|
1121
|
-
if (_Parser.CONTEXTUAL_KW.has(token.type) && this.pos + 1 < this.tokens.length && this.tokens[this.pos + 1].type ===
|
|
1275
|
+
if (_Parser.CONTEXTUAL_KW.has(token.type) && this.pos + 1 < this.tokens.length && this.tokens[this.pos + 1].type === 131 /* OperatorColon */) {
|
|
1122
1276
|
const labelName = token.value;
|
|
1123
1277
|
this.advance();
|
|
1124
1278
|
this.advance();
|
|
@@ -1150,6 +1304,9 @@ var Parser = class _Parser {
|
|
|
1150
1304
|
stmt2.scope = scope;
|
|
1151
1305
|
return stmt2;
|
|
1152
1306
|
}
|
|
1307
|
+
if (next.type === 102 /* KeywordEvent */) {
|
|
1308
|
+
return this.parseEventDeclaration(scope);
|
|
1309
|
+
}
|
|
1153
1310
|
const stmt = this.parseDimStatement(false, true);
|
|
1154
1311
|
if (stmt) {
|
|
1155
1312
|
stmt.scope = scope;
|
|
@@ -1164,7 +1321,7 @@ var Parser = class _Parser {
|
|
|
1164
1321
|
return this.parseDoWhileStatement();
|
|
1165
1322
|
} else if (token.type === 12 /* KeywordWhile */) {
|
|
1166
1323
|
return this.parseWhileStatement();
|
|
1167
|
-
} else if (token.type === 17 /* KeywordSub */ || token.type === 18 /* KeywordFunction */ || token.type === 19 /* KeywordProperty */ && this.peek(1).type !==
|
|
1324
|
+
} else if (token.type === 17 /* KeywordSub */ || token.type === 18 /* KeywordFunction */ || token.type === 19 /* KeywordProperty */ && this.peek(1).type !== 121 /* OperatorEquals */) {
|
|
1168
1325
|
return this.parseProcedureDeclaration();
|
|
1169
1326
|
} else if (token.type === 68 /* KeywordStatic */) {
|
|
1170
1327
|
this.advance();
|
|
@@ -1203,13 +1360,15 @@ var Parser = class _Parser {
|
|
|
1203
1360
|
} else {
|
|
1204
1361
|
return this.parseOnGoToSubStatement();
|
|
1205
1362
|
}
|
|
1206
|
-
} else if (token.type === 35 /* KeywordError */ && this.peek(1).type !==
|
|
1363
|
+
} else if (token.type === 35 /* KeywordError */ && this.peek(1).type !== 121 /* OperatorEquals */) {
|
|
1207
1364
|
return this.parseErrorStatement();
|
|
1208
1365
|
} else if (token.type === 37 /* KeywordGoSub */) {
|
|
1209
1366
|
return this.parseGoSubStatement();
|
|
1210
1367
|
} else if (token.type === 38 /* KeywordReturn */) {
|
|
1211
1368
|
this.advance();
|
|
1212
1369
|
return { type: "ReturnStatement" };
|
|
1370
|
+
} else if (token.type === 108 /* KeywordMid */ && this.hasMidAssignmentAhead()) {
|
|
1371
|
+
return this.parseMidStatement();
|
|
1213
1372
|
} else if (token.type === 39 /* KeywordLSet */) {
|
|
1214
1373
|
return this.parseLSetStatement();
|
|
1215
1374
|
} else if (token.type === 40 /* KeywordRSet */) {
|
|
@@ -1224,9 +1383,9 @@ var Parser = class _Parser {
|
|
|
1224
1383
|
return this.parseResumeStatement();
|
|
1225
1384
|
} else if (token.type === 105 /* KeywordImplements */) {
|
|
1226
1385
|
return this.parseImplementsDirective();
|
|
1227
|
-
} else if (token.type === 106 /* KeywordAppActivate */ && this.peek(1).type !==
|
|
1386
|
+
} else if (token.type === 106 /* KeywordAppActivate */ && this.peek(1).type !== 121 /* OperatorEquals */) {
|
|
1228
1387
|
return this.parseAppActivateStatement();
|
|
1229
|
-
} else if (token.type === 107 /* KeywordSendKeys */ && this.peek(1).type !==
|
|
1388
|
+
} else if (token.type === 107 /* KeywordSendKeys */ && this.peek(1).type !== 121 /* OperatorEquals */) {
|
|
1230
1389
|
return this.parseSendKeysStatement();
|
|
1231
1390
|
} else if (token.type === 29 /* KeywordOption */) {
|
|
1232
1391
|
this.advance();
|
|
@@ -1249,6 +1408,8 @@ var Parser = class _Parser {
|
|
|
1249
1408
|
return { type: "OptionPrivateModuleStatement" };
|
|
1250
1409
|
}
|
|
1251
1410
|
return null;
|
|
1411
|
+
} else if (token.type === 111 /* KeywordDef */) {
|
|
1412
|
+
return this.parseDefDirective();
|
|
1252
1413
|
} else if (token.type === 74 /* KeywordAttribute */) {
|
|
1253
1414
|
return this.parseAttributeStatement();
|
|
1254
1415
|
} else if (token.type === 75 /* KeywordDeclare */) {
|
|
@@ -1265,13 +1426,13 @@ var Parser = class _Parser {
|
|
|
1265
1426
|
return this.parseOpenStatement();
|
|
1266
1427
|
} else if (token.type === 82 /* KeywordClose */) {
|
|
1267
1428
|
return this.parseCloseStatement();
|
|
1268
|
-
} else if (token.type === 83 /* KeywordLine */ && this.peek(1).type !==
|
|
1429
|
+
} else if (token.type === 83 /* KeywordLine */ && this.peek(1).type !== 121 /* OperatorEquals */) {
|
|
1269
1430
|
return this.parseLineInputStatement();
|
|
1270
1431
|
} else if (token.type === 85 /* KeywordPrint */) {
|
|
1271
1432
|
return this.parsePrintStatement();
|
|
1272
1433
|
} else if (token.type === 86 /* KeywordPut */) {
|
|
1273
1434
|
return this.parsePutStatement();
|
|
1274
|
-
} else if (token.type === 20 /* KeywordGet */) {
|
|
1435
|
+
} else if (token.type === 20 /* KeywordGet */ && this.peek(1).type === 112 /* OperatorHash */) {
|
|
1275
1436
|
return this.parseGetStatement();
|
|
1276
1437
|
} else if (token.type === 84 /* KeywordInput */) {
|
|
1277
1438
|
return this.parseInputStatement();
|
|
@@ -1279,9 +1440,9 @@ var Parser = class _Parser {
|
|
|
1279
1440
|
return this.parseWriteStatement();
|
|
1280
1441
|
} else if (token.type === 98 /* KeywordSeek */) {
|
|
1281
1442
|
return this.parseSeekStatement();
|
|
1282
|
-
} else if (token.type === 99 /* KeywordReset */ && this.peek(1).type !==
|
|
1443
|
+
} else if (token.type === 99 /* KeywordReset */ && this.peek(1).type !== 121 /* OperatorEquals */) {
|
|
1283
1444
|
return this.parseResetStatement();
|
|
1284
|
-
} else if (token.type === 97 /* KeywordKill */ && this.peek(1).type !==
|
|
1445
|
+
} else if (token.type === 97 /* KeywordKill */ && this.peek(1).type !== 121 /* OperatorEquals */) {
|
|
1285
1446
|
return this.parseKillStatement();
|
|
1286
1447
|
} else if (token.type === 102 /* KeywordEvent */) {
|
|
1287
1448
|
return this.parseEventDeclaration();
|
|
@@ -1291,9 +1452,9 @@ var Parser = class _Parser {
|
|
|
1291
1452
|
return this.parseLockStatement();
|
|
1292
1453
|
} else if (token.type === 100 /* KeywordUnlock */) {
|
|
1293
1454
|
return this.parseUnlockStatement();
|
|
1294
|
-
} else if (token.type === 109 /* KeywordWidth */ && this.peek(1).type
|
|
1455
|
+
} else if (token.type === 109 /* KeywordWidth */ && this.peek(1).type === 112 /* OperatorHash */) {
|
|
1295
1456
|
return this.parseWidthStatement();
|
|
1296
|
-
} else if (token.type === 69 /* KeywordClass */ && this.peek(1).type !==
|
|
1457
|
+
} else if (token.type === 69 /* KeywordClass */ && this.peek(1).type !== 121 /* OperatorEquals */) {
|
|
1297
1458
|
return this.parseClassDeclaration();
|
|
1298
1459
|
} else if (token.type === 33 /* KeywordCall */) {
|
|
1299
1460
|
this.advance();
|
|
@@ -1304,67 +1465,16 @@ var Parser = class _Parser {
|
|
|
1304
1465
|
return { type: "CallStatement", expression: { type: "CallExpression", callee: expr, args: [] } };
|
|
1305
1466
|
}
|
|
1306
1467
|
this.throwError(`Parse error: Expected procedure call after 'Call'`);
|
|
1307
|
-
} else if (token.type === 0 /* Identifier */ || token.type ===
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
this.advance();
|
|
1311
|
-
this.advance();
|
|
1312
|
-
return { type: "LabelStatement", label: labelName };
|
|
1313
|
-
} else if (token.type === 1 /* Number */) {
|
|
1314
|
-
const labelName = token.value;
|
|
1315
|
-
this.advance();
|
|
1316
|
-
this.match(130 /* OperatorColon */);
|
|
1317
|
-
return { type: "LabelStatement", label: labelName };
|
|
1318
|
-
}
|
|
1319
|
-
const savedPos = this.pos;
|
|
1320
|
-
const expr = this.parsePrimary();
|
|
1321
|
-
if (this.match(120 /* OperatorEquals */)) {
|
|
1322
|
-
return {
|
|
1323
|
-
type: "AssignmentStatement",
|
|
1324
|
-
left: expr,
|
|
1325
|
-
right: this.parseExpression()
|
|
1326
|
-
};
|
|
1327
|
-
} else {
|
|
1328
|
-
if (expr.type === "CallExpression" && this.isBinaryOnlyOperator(this.peek().type)) {
|
|
1329
|
-
this.pos = savedPos;
|
|
1330
|
-
const callee = this.parsePrimary(
|
|
1331
|
-
/* stopBeforeSpacedLParen= */
|
|
1332
|
-
true
|
|
1333
|
-
);
|
|
1334
|
-
const args2 = [this.parseCallArgument()];
|
|
1335
|
-
while (this.match(126 /* OperatorComma */)) {
|
|
1336
|
-
args2.push(this.parseCallArgument());
|
|
1337
|
-
}
|
|
1338
|
-
return { type: "CallStatement", expression: { type: "CallExpression", callee, args: args2 } };
|
|
1339
|
-
}
|
|
1340
|
-
const args = [];
|
|
1341
|
-
if (this.peek().type !== 135 /* Newline */ && this.peek().type !== 136 /* EOF */ && this.peek().type !== 9 /* KeywordElse */ && this.peek().type !== 8 /* KeywordElseIf */ && this.peek().type !== 10 /* KeywordEnd */ && this.peek().type !== 5 /* KeywordNext */ && this.peek().type !== 14 /* KeywordLoop */) {
|
|
1342
|
-
args.push(this.parseCallArgument());
|
|
1343
|
-
while (this.match(126 /* OperatorComma */)) {
|
|
1344
|
-
args.push(this.parseCallArgument());
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
if (args.length > 0) {
|
|
1348
|
-
return { type: "CallStatement", expression: { type: "CallExpression", callee: expr, args } };
|
|
1349
|
-
} else if (expr.type === "CallExpression") {
|
|
1350
|
-
const callExpr = expr;
|
|
1351
|
-
if (callExpr.args.length === 0 && callExpr.callee.type === "Identifier" && !callExpr.callee.foreign) {
|
|
1352
|
-
const line = callExpr.loc?.start.line ?? this.peek().line;
|
|
1353
|
-
this.throwError(`Parse error: syntax error at line ${line}`);
|
|
1354
|
-
}
|
|
1355
|
-
return { type: "CallStatement", expression: callExpr };
|
|
1356
|
-
} else {
|
|
1357
|
-
return { type: "CallStatement", expression: { type: "CallExpression", callee: expr, args: [] } };
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
} else if (token.type === 137 /* Unknown */) {
|
|
1468
|
+
} else if (token.type === 0 /* Identifier */ || token.type === 139 /* ForeignName */ || token.type === 130 /* OperatorDot */ || token.type === 70 /* KeywordMe */ || token.type === 1 /* Number */ || _Parser.CONTEXTUAL_KW.has(token.type) || _Parser.COMPAT_KW_EXPR.has(token.type)) {
|
|
1469
|
+
return this.parseIdentifierOrCallStatement();
|
|
1470
|
+
} else if (token.type === 138 /* Unknown */) {
|
|
1361
1471
|
this.throwError(`Parse error: Unknown token '${this.tokenDisplay(token.value)}' at line ${token.line}`);
|
|
1362
1472
|
} else {
|
|
1363
1473
|
this.advance();
|
|
1364
1474
|
}
|
|
1365
1475
|
return null;
|
|
1366
1476
|
}
|
|
1367
|
-
parseProcedureDeclaration(scope, isStatic) {
|
|
1477
|
+
parseProcedureDeclaration(scope, isStatic, isClassMember = false) {
|
|
1368
1478
|
const isFunction = this.peek().type === 18 /* KeywordFunction */;
|
|
1369
1479
|
const isProperty = this.peek().type === 19 /* KeywordProperty */;
|
|
1370
1480
|
this.advance();
|
|
@@ -1382,23 +1492,27 @@ var Parser = class _Parser {
|
|
|
1382
1492
|
if (!this.isIdentifier(idToken) && (idToken.type < 79 /* KeywordBase */ || idToken.type > 110 /* KeywordAddressOf */)) {
|
|
1383
1493
|
this.throwError(`Parse error at line ${idToken.line}: Expected procedure name (Found ${this.tokenDisplay(idToken.value)})`);
|
|
1384
1494
|
}
|
|
1495
|
+
if (!isClassMember && _Parser.STATEMENT_KW_RESERVED.has(idToken.type)) {
|
|
1496
|
+
this.throwError(`Compile error at line ${idToken.line}: '${idToken.value}' is a reserved word and cannot be used as a procedure name`);
|
|
1497
|
+
}
|
|
1385
1498
|
const name = this.makeIdentifier(idToken);
|
|
1386
1499
|
const parameters = [];
|
|
1387
1500
|
let paramsEndColumn;
|
|
1388
|
-
if (this.match(
|
|
1389
|
-
if (this.peek().type !==
|
|
1501
|
+
if (this.match(128 /* OperatorLParen */)) {
|
|
1502
|
+
if (this.peek().type !== 129 /* OperatorRParen */) {
|
|
1390
1503
|
parameters.push(this.parseParameter());
|
|
1391
|
-
while (this.match(
|
|
1504
|
+
while (this.match(127 /* OperatorComma */)) {
|
|
1392
1505
|
parameters.push(this.parseParameter());
|
|
1393
1506
|
}
|
|
1394
1507
|
}
|
|
1395
|
-
const rParen = this.consume(
|
|
1508
|
+
const rParen = this.consume(129 /* OperatorRParen */, "Expected ')' after procedure parameters");
|
|
1396
1509
|
paramsEndColumn = rParen.column + 1;
|
|
1510
|
+
this.validateParameterOrder(parameters);
|
|
1397
1511
|
}
|
|
1398
1512
|
let returnType;
|
|
1399
1513
|
if (this.match(23 /* KeywordAs */)) {
|
|
1400
1514
|
returnType = this.advance().value;
|
|
1401
|
-
if (this.peek().type ===
|
|
1515
|
+
if (this.peek().type === 130 /* OperatorDot */) {
|
|
1402
1516
|
this.advance();
|
|
1403
1517
|
returnType += "." + this.advance().value;
|
|
1404
1518
|
}
|
|
@@ -1410,7 +1524,7 @@ var Parser = class _Parser {
|
|
|
1410
1524
|
this.skipNewlines();
|
|
1411
1525
|
const body = [];
|
|
1412
1526
|
const expectedEndStr = isFunction ? "Function" : isProperty ? "Property" : "Sub";
|
|
1413
|
-
while (!this.isAtEndTerminator() && this.peek().type !==
|
|
1527
|
+
while (!this.isAtEndTerminator() && this.peek().type !== 137 /* EOF */) {
|
|
1414
1528
|
const stmt = this.parseStatement();
|
|
1415
1529
|
if (stmt) body.push(stmt);
|
|
1416
1530
|
this.skipNewlines();
|
|
@@ -1421,7 +1535,7 @@ var Parser = class _Parser {
|
|
|
1421
1535
|
if (endToken.value.toLowerCase() !== expectedEndStr.toLowerCase()) {
|
|
1422
1536
|
this.throwError(`Parse error: Expected '${expectedEndStr}' after 'End' at line ${endToken.line}`);
|
|
1423
1537
|
}
|
|
1424
|
-
} else if (this.peek().type ===
|
|
1538
|
+
} else if (this.peek().type === 137 /* EOF */) {
|
|
1425
1539
|
this.throwError(`Parse error: Expected 'End ${expectedEndStr}'`);
|
|
1426
1540
|
}
|
|
1427
1541
|
return { type: "ProcedureDeclaration", isFunction, isProperty, propertyType, name, parameters, returnType, body, scope: scope || "public", isStatic, paramsEndColumn };
|
|
@@ -1444,9 +1558,9 @@ var Parser = class _Parser {
|
|
|
1444
1558
|
let isNew = false;
|
|
1445
1559
|
let objectType;
|
|
1446
1560
|
let arrayEndColumn;
|
|
1447
|
-
if (this.match(
|
|
1561
|
+
if (this.match(128 /* OperatorLParen */)) {
|
|
1448
1562
|
isArray = true;
|
|
1449
|
-
if (this.peek().type !==
|
|
1563
|
+
if (this.peek().type !== 129 /* OperatorRParen */) {
|
|
1450
1564
|
arrayBounds = [];
|
|
1451
1565
|
while (true) {
|
|
1452
1566
|
let lower;
|
|
@@ -1456,13 +1570,13 @@ var Parser = class _Parser {
|
|
|
1456
1570
|
upper = this.parseExpression();
|
|
1457
1571
|
}
|
|
1458
1572
|
arrayBounds.push({ lower, upper });
|
|
1459
|
-
if (!this.match(
|
|
1573
|
+
if (!this.match(127 /* OperatorComma */)) {
|
|
1460
1574
|
break;
|
|
1461
1575
|
}
|
|
1462
1576
|
}
|
|
1463
1577
|
}
|
|
1464
1578
|
const rParenTok = this.peek();
|
|
1465
|
-
if (this.match(
|
|
1579
|
+
if (this.match(129 /* OperatorRParen */)) {
|
|
1466
1580
|
arrayEndColumn = rParenTok.column + 1;
|
|
1467
1581
|
}
|
|
1468
1582
|
}
|
|
@@ -1473,14 +1587,14 @@ var Parser = class _Parser {
|
|
|
1473
1587
|
const typeToken = this.peek();
|
|
1474
1588
|
if (this.isNameToken(typeToken)) {
|
|
1475
1589
|
objectType = this.advance().value;
|
|
1476
|
-
if (this.peek().type ===
|
|
1590
|
+
if (this.peek().type === 130 /* OperatorDot */) {
|
|
1477
1591
|
this.advance();
|
|
1478
1592
|
objectType += "." + this.advance().value;
|
|
1479
1593
|
}
|
|
1480
1594
|
}
|
|
1481
1595
|
}
|
|
1482
1596
|
declarations.push({ name, isArray, arrayBounds, isNew, isWithEvents, objectType, arrayEndColumn });
|
|
1483
|
-
if (this.match(
|
|
1597
|
+
if (this.match(127 /* OperatorComma */)) {
|
|
1484
1598
|
continue;
|
|
1485
1599
|
} else {
|
|
1486
1600
|
break;
|
|
@@ -1496,14 +1610,14 @@ var Parser = class _Parser {
|
|
|
1496
1610
|
if (this.match(23 /* KeywordAs */)) {
|
|
1497
1611
|
this.advance();
|
|
1498
1612
|
}
|
|
1499
|
-
if (!this.match(
|
|
1613
|
+
if (!this.match(121 /* OperatorEquals */)) this.throwError(`Parse error: Expected '=' in Const at line ${this.peek().line}`);
|
|
1500
1614
|
const value = this.parseExpression();
|
|
1501
1615
|
return { type: "ConstDeclaration", name, value };
|
|
1502
1616
|
}
|
|
1503
1617
|
parseSetStatement() {
|
|
1504
1618
|
this.advance();
|
|
1505
1619
|
const left = this.parsePrimary();
|
|
1506
|
-
if (!this.match(
|
|
1620
|
+
if (!this.match(121 /* OperatorEquals */)) this.throwError(`Parse error: Expected '=' in Set statement at line ${this.peek().line}`);
|
|
1507
1621
|
const right = this.parseExpression();
|
|
1508
1622
|
return { type: "SetStatement", left, right };
|
|
1509
1623
|
}
|
|
@@ -1515,7 +1629,7 @@ var Parser = class _Parser {
|
|
|
1515
1629
|
const labelToken = this.advance();
|
|
1516
1630
|
label = labelToken.value;
|
|
1517
1631
|
} else {
|
|
1518
|
-
while (this.peek().type !==
|
|
1632
|
+
while (this.peek().type !== 136 /* Newline */ && this.peek().type !== 137 /* EOF */) {
|
|
1519
1633
|
label += this.advance().value + " ";
|
|
1520
1634
|
}
|
|
1521
1635
|
label = label.trim();
|
|
@@ -1544,7 +1658,7 @@ var Parser = class _Parser {
|
|
|
1544
1658
|
parseGoToStatement() {
|
|
1545
1659
|
this.advance();
|
|
1546
1660
|
const labelToken = this.advance();
|
|
1547
|
-
if (labelToken
|
|
1661
|
+
if (!this.isIdentifier(labelToken) && labelToken.type !== 1 /* Number */) {
|
|
1548
1662
|
this.throwError(`Parse error: Expected identifier or number after 'GoTo' at line ${labelToken.line}`);
|
|
1549
1663
|
}
|
|
1550
1664
|
return { type: "GoToStatement", label: labelToken.value };
|
|
@@ -1552,7 +1666,7 @@ var Parser = class _Parser {
|
|
|
1552
1666
|
parseResumeStatement() {
|
|
1553
1667
|
this.advance();
|
|
1554
1668
|
let target = "";
|
|
1555
|
-
while (this.peek().type !==
|
|
1669
|
+
while (this.peek().type !== 136 /* Newline */ && this.peek().type !== 137 /* EOF */) {
|
|
1556
1670
|
target += this.advance().value;
|
|
1557
1671
|
}
|
|
1558
1672
|
return { type: "ResumeStatement", target: target.trim() };
|
|
@@ -1573,8 +1687,8 @@ var Parser = class _Parser {
|
|
|
1573
1687
|
const idToken = this.advance();
|
|
1574
1688
|
const name = this.makeIdentifier(idToken);
|
|
1575
1689
|
const bounds = [];
|
|
1576
|
-
if (this.match(
|
|
1577
|
-
if (this.peek().type !==
|
|
1690
|
+
if (this.match(128 /* OperatorLParen */)) {
|
|
1691
|
+
if (this.peek().type !== 129 /* OperatorRParen */) {
|
|
1578
1692
|
while (true) {
|
|
1579
1693
|
let lower;
|
|
1580
1694
|
let upper = this.parseExpression();
|
|
@@ -1583,12 +1697,12 @@ var Parser = class _Parser {
|
|
|
1583
1697
|
upper = this.parseExpression();
|
|
1584
1698
|
}
|
|
1585
1699
|
bounds.push({ lower, upper });
|
|
1586
|
-
if (!this.match(
|
|
1700
|
+
if (!this.match(127 /* OperatorComma */)) {
|
|
1587
1701
|
break;
|
|
1588
1702
|
}
|
|
1589
1703
|
}
|
|
1590
1704
|
}
|
|
1591
|
-
this.match(
|
|
1705
|
+
this.match(129 /* OperatorRParen */);
|
|
1592
1706
|
}
|
|
1593
1707
|
let objectType;
|
|
1594
1708
|
if (this.match(23 /* KeywordAs */)) {
|
|
@@ -1605,18 +1719,18 @@ var Parser = class _Parser {
|
|
|
1605
1719
|
const typeName = nameToken.value;
|
|
1606
1720
|
const members = [];
|
|
1607
1721
|
this.skipNewlines();
|
|
1608
|
-
while (this.peek().type !== 10 /* KeywordEnd */ && this.peek().type !==
|
|
1722
|
+
while (this.peek().type !== 10 /* KeywordEnd */ && this.peek().type !== 137 /* EOF */) {
|
|
1609
1723
|
const memberNameToken = this.advance();
|
|
1610
1724
|
if (!this.isWordToken(memberNameToken)) {
|
|
1611
1725
|
this.throwError(`Parse error: Expected member name in Type at line ${memberNameToken.line}`);
|
|
1612
1726
|
}
|
|
1613
|
-
if (this.peek().type ===
|
|
1727
|
+
if (this.peek().type === 128 /* OperatorLParen */) {
|
|
1614
1728
|
this.advance();
|
|
1615
1729
|
let depth = 1;
|
|
1616
|
-
while (depth > 0 && this.peek().type !==
|
|
1730
|
+
while (depth > 0 && this.peek().type !== 137 /* EOF */ && this.peek().type !== 136 /* Newline */) {
|
|
1617
1731
|
const t = this.advance();
|
|
1618
|
-
if (t.type ===
|
|
1619
|
-
else if (t.type ===
|
|
1732
|
+
if (t.type === 128 /* OperatorLParen */) depth++;
|
|
1733
|
+
else if (t.type === 129 /* OperatorRParen */) depth--;
|
|
1620
1734
|
}
|
|
1621
1735
|
}
|
|
1622
1736
|
if (!this.match(23 /* KeywordAs */)) {
|
|
@@ -1643,14 +1757,14 @@ var Parser = class _Parser {
|
|
|
1643
1757
|
const name = this.makeIdentifier(nameToken);
|
|
1644
1758
|
const members = [];
|
|
1645
1759
|
this.skipNewlines();
|
|
1646
|
-
while (this.peek().type !== 10 /* KeywordEnd */ && this.peek().type !==
|
|
1760
|
+
while (this.peek().type !== 10 /* KeywordEnd */ && this.peek().type !== 137 /* EOF */) {
|
|
1647
1761
|
const memberNameToken = this.advance();
|
|
1648
1762
|
if (!this.isWordToken(memberNameToken)) {
|
|
1649
1763
|
this.throwError(`Parse error: Expected member name in Enum at line ${memberNameToken.line}`);
|
|
1650
1764
|
}
|
|
1651
1765
|
const memberName = this.makeIdentifier(memberNameToken);
|
|
1652
1766
|
let value;
|
|
1653
|
-
if (this.match(
|
|
1767
|
+
if (this.match(121 /* OperatorEquals */)) {
|
|
1654
1768
|
value = this.parseExpression();
|
|
1655
1769
|
}
|
|
1656
1770
|
members.push({ name: memberName, value });
|
|
@@ -1667,7 +1781,7 @@ var Parser = class _Parser {
|
|
|
1667
1781
|
parseClassDeclaration() {
|
|
1668
1782
|
this.advance();
|
|
1669
1783
|
const nameToken = this.advance();
|
|
1670
|
-
if (nameToken
|
|
1784
|
+
if (!this.isIdentifier(nameToken)) {
|
|
1671
1785
|
this.throwError(`Parse error: Expected class name after 'Class' at line ${nameToken.line}`);
|
|
1672
1786
|
}
|
|
1673
1787
|
return this.parseClassBody(nameToken.value, true);
|
|
@@ -1677,12 +1791,12 @@ var Parser = class _Parser {
|
|
|
1677
1791
|
const procedures = [];
|
|
1678
1792
|
const body = [];
|
|
1679
1793
|
this.skipNewlines();
|
|
1680
|
-
while (this.peek().type !==
|
|
1794
|
+
while (this.peek().type !== 137 /* EOF */) {
|
|
1681
1795
|
if (untilEndClass && this.peek().type === 10 /* KeywordEnd */ && this.peek(1).type === 69 /* KeywordClass */) {
|
|
1682
1796
|
break;
|
|
1683
1797
|
}
|
|
1684
1798
|
const tok = this.peek();
|
|
1685
|
-
if (tok.type ===
|
|
1799
|
+
if (tok.type === 136 /* Newline */) {
|
|
1686
1800
|
this.skipNewlines();
|
|
1687
1801
|
continue;
|
|
1688
1802
|
}
|
|
@@ -1693,7 +1807,7 @@ var Parser = class _Parser {
|
|
|
1693
1807
|
}
|
|
1694
1808
|
const inner = this.peek();
|
|
1695
1809
|
if (inner.type === 17 /* KeywordSub */ || inner.type === 18 /* KeywordFunction */ || inner.type === 19 /* KeywordProperty */) {
|
|
1696
|
-
const proc = this.parseProcedureDeclaration(scope);
|
|
1810
|
+
const proc = this.parseProcedureDeclaration(scope, void 0, true);
|
|
1697
1811
|
proc.moduleName = className;
|
|
1698
1812
|
procedures.push(proc);
|
|
1699
1813
|
body.push(proc);
|
|
@@ -1709,13 +1823,16 @@ var Parser = class _Parser {
|
|
|
1709
1823
|
} else if (inner.type === 102 /* KeywordEvent */) {
|
|
1710
1824
|
const event = this.parseEventDeclaration(scope);
|
|
1711
1825
|
body.push(event);
|
|
1826
|
+
} else if (inner.type === 31 /* KeywordConst */) {
|
|
1827
|
+
const constDecl = this.parseConstDeclaration();
|
|
1828
|
+
body.push(constDecl);
|
|
1712
1829
|
} else if (inner.type === 68 /* KeywordStatic */) {
|
|
1713
1830
|
this.advance();
|
|
1714
1831
|
const field = this.parseDimStatement(true, true);
|
|
1715
1832
|
field.scope = scope ?? "public";
|
|
1716
1833
|
fields.push(field);
|
|
1717
1834
|
body.push(field);
|
|
1718
|
-
} else if (scope !== void 0 && inner.type ===
|
|
1835
|
+
} else if (scope !== void 0 && (this.isIdentifier(inner) || inner.type === 104 /* KeywordWithEvents */)) {
|
|
1719
1836
|
const field = this.parseDimStatement(false, true);
|
|
1720
1837
|
field.scope = scope;
|
|
1721
1838
|
fields.push(field);
|
|
@@ -1743,7 +1860,7 @@ var Parser = class _Parser {
|
|
|
1743
1860
|
this.throwError(`Parse error: Expected identifier after 'For' at line ${idToken.line} `);
|
|
1744
1861
|
}
|
|
1745
1862
|
const identifier = this.makeIdentifier(idToken);
|
|
1746
|
-
if (!this.match(
|
|
1863
|
+
if (!this.match(121 /* OperatorEquals */)) {
|
|
1747
1864
|
this.throwError(`Parse error: Expected '=' in For statement at line ${this.peek().line} `);
|
|
1748
1865
|
}
|
|
1749
1866
|
const startExpr = this.parseExpression();
|
|
@@ -1757,7 +1874,7 @@ var Parser = class _Parser {
|
|
|
1757
1874
|
}
|
|
1758
1875
|
this.skipNewlines();
|
|
1759
1876
|
const body = [];
|
|
1760
|
-
while (this.peek().type !== 5 /* KeywordNext */ && this.peek().type !==
|
|
1877
|
+
while (this.peek().type !== 5 /* KeywordNext */ && this.peek().type !== 137 /* EOF */ && !this.isAtEndTerminator()) {
|
|
1761
1878
|
const stmt = this.parseStatement();
|
|
1762
1879
|
if (stmt) body.push(stmt);
|
|
1763
1880
|
this.skipNewlines();
|
|
@@ -1799,7 +1916,7 @@ var Parser = class _Parser {
|
|
|
1799
1916
|
const collection = this.parseExpression();
|
|
1800
1917
|
this.skipNewlines();
|
|
1801
1918
|
const body = [];
|
|
1802
|
-
while (this.peek().type !== 5 /* KeywordNext */ && this.peek().type !==
|
|
1919
|
+
while (this.peek().type !== 5 /* KeywordNext */ && this.peek().type !== 137 /* EOF */ && !this.isAtEndTerminator()) {
|
|
1803
1920
|
const stmt = this.parseStatement();
|
|
1804
1921
|
if (stmt) body.push(stmt);
|
|
1805
1922
|
this.skipNewlines();
|
|
@@ -1832,18 +1949,18 @@ var Parser = class _Parser {
|
|
|
1832
1949
|
if (!this.match(7 /* KeywordThen */)) {
|
|
1833
1950
|
this.throwError(`Parse error: Expected 'Then' after condition at line ${this.peek().line}`);
|
|
1834
1951
|
}
|
|
1835
|
-
const isMultiLine = this.peek().type ===
|
|
1952
|
+
const isMultiLine = this.peek().type === 136 /* Newline */;
|
|
1836
1953
|
const consequent = [];
|
|
1837
1954
|
let alternate = null;
|
|
1838
1955
|
if (!isMultiLine) {
|
|
1839
|
-
while (this.peek().type !==
|
|
1956
|
+
while (this.peek().type !== 136 /* Newline */ && this.peek().type !== 137 /* EOF */ && this.peek().type !== 9 /* KeywordElse */) {
|
|
1840
1957
|
const stmt = this.parseStatement(false);
|
|
1841
1958
|
if (stmt) consequent.push(stmt);
|
|
1842
1959
|
}
|
|
1843
1960
|
if (this.peek().type === 9 /* KeywordElse */) {
|
|
1844
1961
|
this.advance();
|
|
1845
1962
|
alternate = [];
|
|
1846
|
-
while (this.peek().type !==
|
|
1963
|
+
while (this.peek().type !== 136 /* Newline */ && this.peek().type !== 137 /* EOF */) {
|
|
1847
1964
|
const stmt = this.parseStatement(false);
|
|
1848
1965
|
if (stmt) alternate.push(stmt);
|
|
1849
1966
|
}
|
|
@@ -1856,7 +1973,7 @@ var Parser = class _Parser {
|
|
|
1856
1973
|
};
|
|
1857
1974
|
}
|
|
1858
1975
|
this.skipNewlines();
|
|
1859
|
-
while (!this.isAtEndTerminator() && this.peek().type !== 9 /* KeywordElse */ && this.peek().type !== 8 /* KeywordElseIf */ && this.peek().type !==
|
|
1976
|
+
while (!this.isAtEndTerminator() && this.peek().type !== 9 /* KeywordElse */ && this.peek().type !== 8 /* KeywordElseIf */ && this.peek().type !== 137 /* EOF */) {
|
|
1860
1977
|
const stmt = this.parseStatement();
|
|
1861
1978
|
if (stmt) consequent.push(stmt);
|
|
1862
1979
|
this.skipNewlines();
|
|
@@ -1866,7 +1983,7 @@ var Parser = class _Parser {
|
|
|
1866
1983
|
} else if (this.match(9 /* KeywordElse */)) {
|
|
1867
1984
|
this.skipNewlines();
|
|
1868
1985
|
alternate = [];
|
|
1869
|
-
while (!this.isAtEndTerminator() && this.peek().type !==
|
|
1986
|
+
while (!this.isAtEndTerminator() && this.peek().type !== 137 /* EOF */) {
|
|
1870
1987
|
const stmt = this.parseStatement();
|
|
1871
1988
|
if (stmt) alternate.push(stmt);
|
|
1872
1989
|
this.skipNewlines();
|
|
@@ -1903,7 +2020,7 @@ var Parser = class _Parser {
|
|
|
1903
2020
|
}
|
|
1904
2021
|
this.skipNewlines();
|
|
1905
2022
|
const body = [];
|
|
1906
|
-
while (this.peek().type !== 14 /* KeywordLoop */ && this.peek().type !==
|
|
2023
|
+
while (this.peek().type !== 14 /* KeywordLoop */ && this.peek().type !== 137 /* EOF */ && !this.isAtEndTerminator()) {
|
|
1907
2024
|
const stmt = this.parseStatement();
|
|
1908
2025
|
if (stmt) body.push(stmt);
|
|
1909
2026
|
this.skipNewlines();
|
|
@@ -1935,7 +2052,7 @@ var Parser = class _Parser {
|
|
|
1935
2052
|
const condition = this.parseExpression();
|
|
1936
2053
|
this.skipNewlines();
|
|
1937
2054
|
const body = [];
|
|
1938
|
-
while (this.peek().type !== 13 /* KeywordWend */ && this.peek().type !==
|
|
2055
|
+
while (this.peek().type !== 13 /* KeywordWend */ && this.peek().type !== 137 /* EOF */ && !this.isAtEndTerminator()) {
|
|
1939
2056
|
const stmt = this.parseStatement();
|
|
1940
2057
|
if (stmt) body.push(stmt);
|
|
1941
2058
|
this.skipNewlines();
|
|
@@ -1954,7 +2071,7 @@ var Parser = class _Parser {
|
|
|
1954
2071
|
this.skipNewlines();
|
|
1955
2072
|
const cases = [];
|
|
1956
2073
|
let elseBody = null;
|
|
1957
|
-
while (!this.isAtEndTerminator() && this.peek().type !==
|
|
2074
|
+
while (!this.isAtEndTerminator() && this.peek().type !== 137 /* EOF */) {
|
|
1958
2075
|
if (this.peek().type !== 54 /* KeywordCase */) {
|
|
1959
2076
|
this.throwError(`Parse error: Expected 'Case' in Select Case at line ${this.peek().line}`);
|
|
1960
2077
|
}
|
|
@@ -1963,7 +2080,7 @@ var Parser = class _Parser {
|
|
|
1963
2080
|
this.advance();
|
|
1964
2081
|
this.skipNewlines();
|
|
1965
2082
|
elseBody = [];
|
|
1966
|
-
while (!this.isAtEndTerminator() && this.peek().type !== 54 /* KeywordCase */ && this.peek().type !==
|
|
2083
|
+
while (!this.isAtEndTerminator() && this.peek().type !== 54 /* KeywordCase */ && this.peek().type !== 137 /* EOF */) {
|
|
1967
2084
|
const stmt = this.parseStatement();
|
|
1968
2085
|
if (stmt) elseBody.push(stmt);
|
|
1969
2086
|
this.skipNewlines();
|
|
@@ -1972,12 +2089,12 @@ var Parser = class _Parser {
|
|
|
1972
2089
|
}
|
|
1973
2090
|
const ranges = [];
|
|
1974
2091
|
ranges.push(this.parseRangeClause());
|
|
1975
|
-
while (this.match(
|
|
2092
|
+
while (this.match(127 /* OperatorComma */)) {
|
|
1976
2093
|
ranges.push(this.parseRangeClause());
|
|
1977
2094
|
}
|
|
1978
2095
|
this.skipNewlines();
|
|
1979
2096
|
const body = [];
|
|
1980
|
-
while (!this.isAtEndTerminator() && this.peek().type !== 54 /* KeywordCase */ && this.peek().type !==
|
|
2097
|
+
while (!this.isAtEndTerminator() && this.peek().type !== 54 /* KeywordCase */ && this.peek().type !== 137 /* EOF */) {
|
|
1981
2098
|
const stmt = this.parseStatement();
|
|
1982
2099
|
if (stmt) body.push(stmt);
|
|
1983
2100
|
this.skipNewlines();
|
|
@@ -1997,7 +2114,7 @@ var Parser = class _Parser {
|
|
|
1997
2114
|
const expression = this.parseExpression();
|
|
1998
2115
|
this.skipNewlines();
|
|
1999
2116
|
const body = [];
|
|
2000
|
-
while (!this.isAtEndTerminator() && this.peek().type !==
|
|
2117
|
+
while (!this.isAtEndTerminator() && this.peek().type !== 137 /* EOF */) {
|
|
2001
2118
|
const stmt = this.parseStatement();
|
|
2002
2119
|
if (stmt) body.push(stmt);
|
|
2003
2120
|
this.skipNewlines();
|
|
@@ -2014,7 +2131,7 @@ var Parser = class _Parser {
|
|
|
2014
2131
|
const isKeyword = this.peek().type === 51 /* KeywordIs */;
|
|
2015
2132
|
if (isKeyword) this.advance();
|
|
2016
2133
|
const compOp = this.peek();
|
|
2017
|
-
if (compOp.type ===
|
|
2134
|
+
if (compOp.type === 121 /* OperatorEquals */ || compOp.type === 122 /* OperatorNotEquals */ || compOp.type === 123 /* OperatorLessThan */ || compOp.type === 124 /* OperatorGreaterThan */ || compOp.type === 125 /* OperatorLessThanOrEqual */ || compOp.type === 126 /* OperatorGreaterThanOrEqual */) {
|
|
2018
2135
|
this.advance();
|
|
2019
2136
|
const value = this.parseExpression();
|
|
2020
2137
|
return { kind: "comparison", operator: compOp.value, value };
|
|
@@ -2094,7 +2211,7 @@ var Parser = class _Parser {
|
|
|
2094
2211
|
}
|
|
2095
2212
|
parseEquality() {
|
|
2096
2213
|
let left = this.parseRelational();
|
|
2097
|
-
while (this.peek().type ===
|
|
2214
|
+
while (this.peek().type === 121 /* OperatorEquals */ || this.peek().type === 122 /* OperatorNotEquals */ || this.peek().type === 51 /* KeywordIs */ || this.peek().type === 63 /* KeywordLike */) {
|
|
2098
2215
|
const operator = this.advance().value;
|
|
2099
2216
|
const right = this.parseRelational();
|
|
2100
2217
|
left = { type: "BinaryExpression", operator, left, right, loc: this.makeBinaryLoc(left, right) };
|
|
@@ -2103,7 +2220,7 @@ var Parser = class _Parser {
|
|
|
2103
2220
|
}
|
|
2104
2221
|
parseRelational() {
|
|
2105
2222
|
let left = this.parseConcatenation();
|
|
2106
|
-
while (this.peek().type ===
|
|
2223
|
+
while (this.peek().type === 123 /* OperatorLessThan */ || this.peek().type === 124 /* OperatorGreaterThan */ || this.peek().type === 125 /* OperatorLessThanOrEqual */ || this.peek().type === 126 /* OperatorGreaterThanOrEqual */) {
|
|
2107
2224
|
const operator = this.advance().value;
|
|
2108
2225
|
const right = this.parseConcatenation();
|
|
2109
2226
|
left = { type: "BinaryExpression", operator, left, right, loc: this.makeBinaryLoc(left, right) };
|
|
@@ -2112,7 +2229,7 @@ var Parser = class _Parser {
|
|
|
2112
2229
|
}
|
|
2113
2230
|
parseConcatenation() {
|
|
2114
2231
|
let left = this.parseAdditive();
|
|
2115
|
-
while (this.peek().type ===
|
|
2232
|
+
while (this.peek().type === 118 /* OperatorAmpersand */) {
|
|
2116
2233
|
const operator = this.advance().value;
|
|
2117
2234
|
const right = this.parseAdditive();
|
|
2118
2235
|
left = { type: "BinaryExpression", operator, left, right, loc: this.makeBinaryLoc(left, right) };
|
|
@@ -2121,7 +2238,7 @@ var Parser = class _Parser {
|
|
|
2121
2238
|
}
|
|
2122
2239
|
parseAdditive() {
|
|
2123
2240
|
let left = this.parseModulo();
|
|
2124
|
-
while (this.peek().type ===
|
|
2241
|
+
while (this.peek().type === 113 /* OperatorPlus */ || this.peek().type === 114 /* OperatorMinus */) {
|
|
2125
2242
|
const operator = this.advance().value;
|
|
2126
2243
|
const right = this.parseModulo();
|
|
2127
2244
|
left = { type: "BinaryExpression", operator, left, right, loc: this.makeBinaryLoc(left, right) };
|
|
@@ -2130,7 +2247,7 @@ var Parser = class _Parser {
|
|
|
2130
2247
|
}
|
|
2131
2248
|
parseModulo() {
|
|
2132
2249
|
let left = this.parseIntDivision();
|
|
2133
|
-
while (this.peek().type ===
|
|
2250
|
+
while (this.peek().type === 119 /* KeywordMod */) {
|
|
2134
2251
|
const operator = this.advance().value;
|
|
2135
2252
|
const right = this.parseIntDivision();
|
|
2136
2253
|
left = { type: "BinaryExpression", operator, left, right, loc: this.makeBinaryLoc(left, right) };
|
|
@@ -2139,7 +2256,7 @@ var Parser = class _Parser {
|
|
|
2139
2256
|
}
|
|
2140
2257
|
parseIntDivision() {
|
|
2141
2258
|
let left = this.parseMultiplicative();
|
|
2142
|
-
while (this.peek().type ===
|
|
2259
|
+
while (this.peek().type === 117 /* OperatorIntDivide */) {
|
|
2143
2260
|
const operator = this.advance().value;
|
|
2144
2261
|
const right = this.parseMultiplicative();
|
|
2145
2262
|
left = { type: "BinaryExpression", operator, left, right, loc: this.makeBinaryLoc(left, right) };
|
|
@@ -2148,7 +2265,7 @@ var Parser = class _Parser {
|
|
|
2148
2265
|
}
|
|
2149
2266
|
parseMultiplicative() {
|
|
2150
2267
|
let left = this.parseUnary();
|
|
2151
|
-
while (this.peek().type ===
|
|
2268
|
+
while (this.peek().type === 115 /* OperatorMultiply */ || this.peek().type === 116 /* OperatorDivide */) {
|
|
2152
2269
|
const operator = this.advance().value;
|
|
2153
2270
|
const right = this.parseUnary();
|
|
2154
2271
|
left = { type: "BinaryExpression", operator, left, right, loc: this.makeBinaryLoc(left, right) };
|
|
@@ -2156,7 +2273,7 @@ var Parser = class _Parser {
|
|
|
2156
2273
|
return left;
|
|
2157
2274
|
}
|
|
2158
2275
|
parseUnary() {
|
|
2159
|
-
if (this.peek().type ===
|
|
2276
|
+
if (this.peek().type === 114 /* OperatorMinus */ || this.peek().type === 113 /* OperatorPlus */) {
|
|
2160
2277
|
const opTok = this.tokens[this.pos];
|
|
2161
2278
|
const operator = this.advance().value;
|
|
2162
2279
|
const argument = this.parseUnary();
|
|
@@ -2168,7 +2285,7 @@ var Parser = class _Parser {
|
|
|
2168
2285
|
}
|
|
2169
2286
|
parseExponentiation() {
|
|
2170
2287
|
let left = this.parsePrimary();
|
|
2171
|
-
while (this.peek().type ===
|
|
2288
|
+
while (this.peek().type === 120 /* OperatorPower */) {
|
|
2172
2289
|
const operator = this.advance().value;
|
|
2173
2290
|
const right = this.parsePrimary();
|
|
2174
2291
|
left = { type: "BinaryExpression", operator, left, right, loc: this.makeBinaryLoc(left, right) };
|
|
@@ -2187,15 +2304,24 @@ var Parser = class _Parser {
|
|
|
2187
2304
|
expr = { type: "NumberLiteral", value: Number(cleanVal), typeSuffix, isFloat };
|
|
2188
2305
|
} else if (token.type === 2 /* String */) {
|
|
2189
2306
|
expr = { type: "StringLiteral", value: token.value };
|
|
2190
|
-
} else if (token.type ===
|
|
2307
|
+
} else if (token.type === 135 /* Date */) {
|
|
2191
2308
|
expr = { type: "DateLiteral", value: token.value };
|
|
2192
|
-
} else if (token.type ===
|
|
2309
|
+
} else if (token.type === 139 /* ForeignName */) {
|
|
2193
2310
|
expr = { type: "Identifier", name: token.value, foreign: true };
|
|
2194
2311
|
} else if (token.type === 0 /* Identifier */ || _Parser.CONTEXTUAL_KW.has(token.type) || _Parser.COMPAT_KW_EXPR.has(token.type)) {
|
|
2195
2312
|
expr = { type: "Identifier", name: token.value };
|
|
2196
2313
|
} else if (token.type === 110 /* KeywordAddressOf */) {
|
|
2197
|
-
const
|
|
2198
|
-
|
|
2314
|
+
const firstTok = this.advance();
|
|
2315
|
+
if (!this.isIdentifier(firstTok)) this.throwError(`Parse error at line ${firstTok.line}: Expected procedure name after 'AddressOf'`);
|
|
2316
|
+
let moduleName;
|
|
2317
|
+
let procTok = firstTok;
|
|
2318
|
+
if (this.peek().type === 130 /* OperatorDot */) {
|
|
2319
|
+
this.advance();
|
|
2320
|
+
moduleName = firstTok.value;
|
|
2321
|
+
procTok = this.advance();
|
|
2322
|
+
if (!this.isIdentifier(procTok)) this.throwError(`Parse error at line ${procTok.line}: Expected procedure name after 'AddressOf ${moduleName}.'`);
|
|
2323
|
+
}
|
|
2324
|
+
expr = { type: "AddressOfExpression", procedureName: { type: "Identifier", name: procTok.value }, moduleName };
|
|
2199
2325
|
} else if (token.type === 44 /* KeywordEmpty */) {
|
|
2200
2326
|
expr = { type: "Identifier", name: token.value };
|
|
2201
2327
|
} else if (token.type === 49 /* KeywordNothing */) {
|
|
@@ -2212,14 +2338,14 @@ var Parser = class _Parser {
|
|
|
2212
2338
|
this.throwError(`Parse error: Expected class name after 'New' at line ${classNameToken.line}`);
|
|
2213
2339
|
}
|
|
2214
2340
|
let className = classNameToken.value;
|
|
2215
|
-
if (this.peek().type ===
|
|
2341
|
+
if (this.peek().type === 130 /* OperatorDot */) {
|
|
2216
2342
|
this.advance();
|
|
2217
2343
|
className += "." + this.advance().value;
|
|
2218
2344
|
}
|
|
2219
2345
|
expr = { type: "NewExpression", className };
|
|
2220
|
-
} else if (token.type ===
|
|
2346
|
+
} else if (token.type === 128 /* OperatorLParen */) {
|
|
2221
2347
|
const innerExpr = this.parseExpression();
|
|
2222
|
-
if (!this.match(
|
|
2348
|
+
if (!this.match(129 /* OperatorRParen */)) {
|
|
2223
2349
|
this.throwMissingRParen();
|
|
2224
2350
|
}
|
|
2225
2351
|
expr = { type: "ParenthesizedExpression", expression: innerExpr };
|
|
@@ -2229,18 +2355,18 @@ var Parser = class _Parser {
|
|
|
2229
2355
|
this.throwError(`Parse error: Expected 'Is' after 'TypeOf' at line ${this.peek().line}`);
|
|
2230
2356
|
}
|
|
2231
2357
|
const typeToken = this.advance();
|
|
2232
|
-
if (typeToken
|
|
2358
|
+
if (!this.isIdentifier(typeToken) && typeToken.type !== 25 /* KeywordCollection */) {
|
|
2233
2359
|
this.throwError(`Parse error: Expected type name after 'Is' at line ${typeToken.line}`);
|
|
2234
2360
|
}
|
|
2235
2361
|
expr = { type: "TypeOfIsExpression", expression: expr, typeName: typeToken.value };
|
|
2236
|
-
} else if (token.type ===
|
|
2362
|
+
} else if (token.type === 130 /* OperatorDot */) {
|
|
2237
2363
|
const propToken = this.advance();
|
|
2238
2364
|
if (!this.isNameToken(propToken)) {
|
|
2239
2365
|
this.throwError(`Parse error: Expected identifier after '.' at line ${propToken.line}`);
|
|
2240
2366
|
}
|
|
2241
2367
|
const property = { type: "Identifier", name: propToken.value };
|
|
2242
2368
|
expr = { type: "ImplicitWithObjectExpression", property };
|
|
2243
|
-
} else if (token.type ===
|
|
2369
|
+
} else if (token.type === 136 /* Newline */) {
|
|
2244
2370
|
const prevToken = this.tokens[Math.max(0, this.pos - 2)];
|
|
2245
2371
|
if (prevToken && this.isContinuationEndToken(prevToken.type)) {
|
|
2246
2372
|
this.throwError(
|
|
@@ -2255,31 +2381,31 @@ var Parser = class _Parser {
|
|
|
2255
2381
|
}
|
|
2256
2382
|
expr.loc = this.exprLoc(startTok, this.tokens[this.pos - 1]);
|
|
2257
2383
|
while (true) {
|
|
2258
|
-
if (this.match(
|
|
2259
|
-
if (this.peek().type ===
|
|
2384
|
+
if (this.match(130 /* OperatorDot */)) {
|
|
2385
|
+
if (this.peek().type === 137 /* EOF */) this.throwError("Expected property name after '.'");
|
|
2260
2386
|
const propToken = this.advance();
|
|
2261
2387
|
const property = { type: "Identifier", name: propToken.value };
|
|
2262
2388
|
expr = { type: "MemberExpression", object: expr, property };
|
|
2263
2389
|
expr.loc = this.exprLoc(startTok, this.tokens[this.pos - 1]);
|
|
2264
|
-
} else if (this.match(
|
|
2265
|
-
if (this.peek().type ===
|
|
2390
|
+
} else if (this.match(133 /* OperatorExclamation */)) {
|
|
2391
|
+
if (this.peek().type === 137 /* EOF */) this.throwError("Expected identifier after '!'");
|
|
2266
2392
|
const propToken = this.advance();
|
|
2267
2393
|
const property = { type: "Identifier", name: propToken.value };
|
|
2268
2394
|
expr = { type: "DictionaryAccessExpression", object: expr, property };
|
|
2269
2395
|
expr.loc = this.exprLoc(startTok, this.tokens[this.pos - 1]);
|
|
2270
|
-
} else if (this.peek().type ===
|
|
2396
|
+
} else if (this.peek().type === 128 /* OperatorLParen */) {
|
|
2271
2397
|
if (stopBeforeSpacedLParen && this.hasSpaceBeforeCurrentToken()) {
|
|
2272
2398
|
break;
|
|
2273
2399
|
}
|
|
2274
2400
|
this.advance();
|
|
2275
2401
|
const args = [];
|
|
2276
|
-
if (this.peek().type !==
|
|
2402
|
+
if (this.peek().type !== 129 /* OperatorRParen */) {
|
|
2277
2403
|
args.push(this.parseCallArgument());
|
|
2278
|
-
while (this.match(
|
|
2404
|
+
while (this.match(127 /* OperatorComma */)) {
|
|
2279
2405
|
args.push(this.parseCallArgument());
|
|
2280
2406
|
}
|
|
2281
2407
|
}
|
|
2282
|
-
if (!this.match(
|
|
2408
|
+
if (!this.match(129 /* OperatorRParen */)) {
|
|
2283
2409
|
this.throwMissingRParen();
|
|
2284
2410
|
}
|
|
2285
2411
|
expr = { type: "CallExpression", callee: expr, args };
|
|
@@ -2293,10 +2419,10 @@ var Parser = class _Parser {
|
|
|
2293
2419
|
// Parse a call argument, handling named arguments (e.g., shift:=xlUp)
|
|
2294
2420
|
parseCallArgument() {
|
|
2295
2421
|
const next = this.peek().type;
|
|
2296
|
-
if (next ===
|
|
2422
|
+
if (next === 127 /* OperatorComma */ || next === 129 /* OperatorRParen */ || this.isAtTerminator()) {
|
|
2297
2423
|
return { type: "MissingArgument" };
|
|
2298
2424
|
}
|
|
2299
|
-
if (this.pos + 1 < this.tokens.length && this.tokens[this.pos + 1].type ===
|
|
2425
|
+
if (this.pos + 1 < this.tokens.length && this.tokens[this.pos + 1].type === 132 /* OperatorColonEquals */ && typeof this.peek().value === "string" && /^[A-Za-z_]\w*$/.test(this.peek().value)) {
|
|
2300
2426
|
const nameToken = this.advance();
|
|
2301
2427
|
this.advance();
|
|
2302
2428
|
const value = this.parseExpression();
|
|
@@ -2318,11 +2444,11 @@ var Parser = class _Parser {
|
|
|
2318
2444
|
const labels = [];
|
|
2319
2445
|
while (true) {
|
|
2320
2446
|
const labelToken = this.advance();
|
|
2321
|
-
if (labelToken
|
|
2447
|
+
if (!this.isIdentifier(labelToken) && labelToken.type !== 1 /* Number */) {
|
|
2322
2448
|
this.throwError(`Parse error: Expected label (identifier or number) in On...GoTo/GoSub at line ${labelToken.line}`);
|
|
2323
2449
|
}
|
|
2324
2450
|
labels.push(labelToken.value);
|
|
2325
|
-
if (!this.match(
|
|
2451
|
+
if (!this.match(127 /* OperatorComma */)) {
|
|
2326
2452
|
break;
|
|
2327
2453
|
}
|
|
2328
2454
|
}
|
|
@@ -2331,7 +2457,7 @@ var Parser = class _Parser {
|
|
|
2331
2457
|
parseGoSubStatement() {
|
|
2332
2458
|
this.advance();
|
|
2333
2459
|
const labelToken = this.advance();
|
|
2334
|
-
if (labelToken
|
|
2460
|
+
if (!this.isIdentifier(labelToken) && labelToken.type !== 1 /* Number */) {
|
|
2335
2461
|
this.throwError(`Parse error: Expected label after GoSub at line ${labelToken.line}`);
|
|
2336
2462
|
}
|
|
2337
2463
|
return { type: "GoSubStatement", label: labelToken.value };
|
|
@@ -2339,7 +2465,7 @@ var Parser = class _Parser {
|
|
|
2339
2465
|
parseLSetStatement() {
|
|
2340
2466
|
this.advance();
|
|
2341
2467
|
const left = this.parsePrimary();
|
|
2342
|
-
if (!this.match(
|
|
2468
|
+
if (!this.match(121 /* OperatorEquals */)) {
|
|
2343
2469
|
this.throwError(`Parse error: Expected '=' in LSet statement at line ${this.peek().line}`);
|
|
2344
2470
|
}
|
|
2345
2471
|
const right = this.parseExpression();
|
|
@@ -2348,12 +2474,45 @@ var Parser = class _Parser {
|
|
|
2348
2474
|
parseRSetStatement() {
|
|
2349
2475
|
this.advance();
|
|
2350
2476
|
const left = this.parsePrimary();
|
|
2351
|
-
if (!this.match(
|
|
2477
|
+
if (!this.match(121 /* OperatorEquals */)) {
|
|
2352
2478
|
this.throwError(`Parse error: Expected '=' in RSet statement at line ${this.peek().line}`);
|
|
2353
2479
|
}
|
|
2354
2480
|
const right = this.parseExpression();
|
|
2355
2481
|
return { type: "RSetStatement", left, right };
|
|
2356
2482
|
}
|
|
2483
|
+
hasMidAssignmentAhead() {
|
|
2484
|
+
if (this.peek(1).type !== 128 /* OperatorLParen */) return false;
|
|
2485
|
+
let depth = 0;
|
|
2486
|
+
let i = 1;
|
|
2487
|
+
while (this.pos + i < this.tokens.length) {
|
|
2488
|
+
const t = this.tokens[this.pos + i];
|
|
2489
|
+
if (t.type === 128 /* OperatorLParen */) depth++;
|
|
2490
|
+
else if (t.type === 129 /* OperatorRParen */) {
|
|
2491
|
+
depth--;
|
|
2492
|
+
if (depth === 0) {
|
|
2493
|
+
return this.tokens[this.pos + i + 1]?.type === 121 /* OperatorEquals */;
|
|
2494
|
+
}
|
|
2495
|
+
} else if (t.type === 136 /* Newline */ || t.type === 137 /* EOF */) break;
|
|
2496
|
+
i++;
|
|
2497
|
+
}
|
|
2498
|
+
return false;
|
|
2499
|
+
}
|
|
2500
|
+
parseMidStatement() {
|
|
2501
|
+
const midToken = this.advance();
|
|
2502
|
+
const isByte = midToken.value.toLowerCase().startsWith("midb");
|
|
2503
|
+
this.consume(128 /* OperatorLParen */, "Expected '(' after Mid");
|
|
2504
|
+
const target = this.parseExpression();
|
|
2505
|
+
this.consume(127 /* OperatorComma */, "Expected ',' after Mid target");
|
|
2506
|
+
const start = this.parseExpression();
|
|
2507
|
+
let length = null;
|
|
2508
|
+
if (this.match(127 /* OperatorComma */)) {
|
|
2509
|
+
length = this.parseExpression();
|
|
2510
|
+
}
|
|
2511
|
+
this.consume(129 /* OperatorRParen */, "Expected ')' after Mid arguments");
|
|
2512
|
+
this.consume(121 /* OperatorEquals */, "Expected '=' in Mid statement");
|
|
2513
|
+
const value = this.parseExpression();
|
|
2514
|
+
return { type: "MidStatement", target, start, length, value, isByte };
|
|
2515
|
+
}
|
|
2357
2516
|
parseErrorStatement() {
|
|
2358
2517
|
this.advance();
|
|
2359
2518
|
const errorNumber = this.parseExpression();
|
|
@@ -2361,46 +2520,50 @@ var Parser = class _Parser {
|
|
|
2361
2520
|
}
|
|
2362
2521
|
parseEventDeclaration(scope) {
|
|
2363
2522
|
this.advance();
|
|
2364
|
-
const idToken = this.
|
|
2523
|
+
const idToken = this.advance();
|
|
2524
|
+
if (!this.isIdentifier(idToken) && !this.isNameToken(idToken)) this.throwError(`Expected identifier after 'Event' at line ${idToken.line}`);
|
|
2365
2525
|
const name = { type: "Identifier", name: idToken.value };
|
|
2366
2526
|
const parameters = [];
|
|
2367
|
-
if (this.match(
|
|
2368
|
-
if (this.peek().type !==
|
|
2527
|
+
if (this.match(128 /* OperatorLParen */)) {
|
|
2528
|
+
if (this.peek().type !== 129 /* OperatorRParen */) {
|
|
2369
2529
|
parameters.push(this.parseParameter());
|
|
2370
|
-
while (this.match(
|
|
2530
|
+
while (this.match(127 /* OperatorComma */)) {
|
|
2371
2531
|
parameters.push(this.parseParameter());
|
|
2372
2532
|
}
|
|
2373
2533
|
}
|
|
2374
|
-
this.consume(
|
|
2534
|
+
this.consume(129 /* OperatorRParen */, "Expected ')' after event parameters");
|
|
2535
|
+
this.validateParameterOrder(parameters);
|
|
2375
2536
|
}
|
|
2376
2537
|
return { type: "EventDeclaration", name, parameters, scope };
|
|
2377
2538
|
}
|
|
2378
2539
|
parseRaiseEventStatement() {
|
|
2379
2540
|
this.advance();
|
|
2380
|
-
const idToken = this.
|
|
2541
|
+
const idToken = this.advance();
|
|
2542
|
+
if (!this.isIdentifier(idToken) && !this.isNameToken(idToken)) this.throwError(`Expected identifier after 'RaiseEvent' at line ${idToken.line}`);
|
|
2381
2543
|
const eventName = { type: "Identifier", name: idToken.value };
|
|
2382
2544
|
const args = [];
|
|
2383
|
-
if (this.match(
|
|
2384
|
-
if (this.peek().type !==
|
|
2545
|
+
if (this.match(128 /* OperatorLParen */)) {
|
|
2546
|
+
if (this.peek().type !== 129 /* OperatorRParen */) {
|
|
2385
2547
|
args.push(this.parseExpression());
|
|
2386
|
-
while (this.match(
|
|
2548
|
+
while (this.match(127 /* OperatorComma */)) {
|
|
2387
2549
|
args.push(this.parseExpression());
|
|
2388
2550
|
}
|
|
2389
2551
|
}
|
|
2390
|
-
this.consume(
|
|
2552
|
+
this.consume(129 /* OperatorRParen */, "Expected ')' after RaiseEvent arguments");
|
|
2391
2553
|
}
|
|
2392
2554
|
return { type: "RaiseEventStatement", eventName, args };
|
|
2393
2555
|
}
|
|
2394
2556
|
parseImplementsDirective() {
|
|
2395
2557
|
this.advance();
|
|
2396
|
-
const idToken = this.
|
|
2558
|
+
const idToken = this.advance();
|
|
2559
|
+
if (!this.isIdentifier(idToken)) this.throwError(`Parse error at line ${idToken.line}: Expected interface name after 'Implements'`);
|
|
2397
2560
|
return { type: "ImplementsDirective", interfaceName: idToken.value };
|
|
2398
2561
|
}
|
|
2399
2562
|
parseAppActivateStatement() {
|
|
2400
2563
|
this.advance();
|
|
2401
2564
|
const title = this.parseExpression();
|
|
2402
2565
|
let wait;
|
|
2403
|
-
if (this.match(
|
|
2566
|
+
if (this.match(127 /* OperatorComma */)) {
|
|
2404
2567
|
wait = this.parseExpression();
|
|
2405
2568
|
}
|
|
2406
2569
|
return { type: "AppActivateStatement", title, wait };
|
|
@@ -2409,17 +2572,17 @@ var Parser = class _Parser {
|
|
|
2409
2572
|
this.advance();
|
|
2410
2573
|
const keys = this.parseExpression();
|
|
2411
2574
|
let wait;
|
|
2412
|
-
if (this.match(
|
|
2575
|
+
if (this.match(127 /* OperatorComma */)) {
|
|
2413
2576
|
wait = this.parseExpression();
|
|
2414
2577
|
}
|
|
2415
2578
|
return { type: "SendKeysStatement", keys, wait };
|
|
2416
2579
|
}
|
|
2417
2580
|
parseLockStatement() {
|
|
2418
2581
|
this.advance();
|
|
2419
|
-
this.match(
|
|
2582
|
+
this.match(112 /* OperatorHash */);
|
|
2420
2583
|
const fileNumber = this.parseExpression();
|
|
2421
2584
|
let recordRange;
|
|
2422
|
-
if (this.match(
|
|
2585
|
+
if (this.match(127 /* OperatorComma */)) {
|
|
2423
2586
|
const start = this.parseExpression();
|
|
2424
2587
|
let end;
|
|
2425
2588
|
if (this.match(4 /* KeywordTo */)) {
|
|
@@ -2431,10 +2594,10 @@ var Parser = class _Parser {
|
|
|
2431
2594
|
}
|
|
2432
2595
|
parseUnlockStatement() {
|
|
2433
2596
|
this.advance();
|
|
2434
|
-
this.match(
|
|
2597
|
+
this.match(112 /* OperatorHash */);
|
|
2435
2598
|
const fileNumber = this.parseExpression();
|
|
2436
2599
|
let recordRange;
|
|
2437
|
-
if (this.match(
|
|
2600
|
+
if (this.match(127 /* OperatorComma */)) {
|
|
2438
2601
|
const start = this.parseExpression();
|
|
2439
2602
|
let end;
|
|
2440
2603
|
if (this.match(4 /* KeywordTo */)) {
|
|
@@ -2446,9 +2609,9 @@ var Parser = class _Parser {
|
|
|
2446
2609
|
}
|
|
2447
2610
|
parseWidthStatement() {
|
|
2448
2611
|
this.advance();
|
|
2449
|
-
this.consume(
|
|
2612
|
+
this.consume(112 /* OperatorHash */, "Expected '#' after Width");
|
|
2450
2613
|
const fileNumber = this.parseExpression();
|
|
2451
|
-
this.consume(
|
|
2614
|
+
this.consume(127 /* OperatorComma */, "Expected ',' after file number");
|
|
2452
2615
|
const width = this.parseExpression();
|
|
2453
2616
|
return { type: "WidthStatement", fileNumber, width };
|
|
2454
2617
|
}
|
|
@@ -2705,6 +2868,11 @@ function evaluateCCExpr(expr, resolve) {
|
|
|
2705
2868
|
// ../../test-libs/vba-analyzer.ts
|
|
2706
2869
|
var fs = __toESM(require("fs"), 1);
|
|
2707
2870
|
var path = __toESM(require("path"), 1);
|
|
2871
|
+
|
|
2872
|
+
// ../../test-libs/version.ts
|
|
2873
|
+
var VERSION = "0.1.1-alpha.1";
|
|
2874
|
+
|
|
2875
|
+
// ../../test-libs/vba-analyzer.ts
|
|
2708
2876
|
function isStatementArray(v) {
|
|
2709
2877
|
return Array.isArray(v);
|
|
2710
2878
|
}
|
|
@@ -3728,6 +3896,18 @@ function collectVarRefsInBody(stmts, writes, reads, skipVars) {
|
|
|
3728
3896
|
collectReadsInExpr(stmt.right, reads);
|
|
3729
3897
|
break;
|
|
3730
3898
|
}
|
|
3899
|
+
case "MidStatement": {
|
|
3900
|
+
const w = extractWriteName(stmt.target);
|
|
3901
|
+
if (w && !skipVars.has(w)) {
|
|
3902
|
+
if (!writes.has(w)) writes.set(w, []);
|
|
3903
|
+
writes.get(w).push(line);
|
|
3904
|
+
}
|
|
3905
|
+
collectReadsInExpr(stmt.target, reads);
|
|
3906
|
+
collectReadsInExpr(stmt.start, reads);
|
|
3907
|
+
if (stmt.length) collectReadsInExpr(stmt.length, reads);
|
|
3908
|
+
collectReadsInExpr(stmt.value, reads);
|
|
3909
|
+
break;
|
|
3910
|
+
}
|
|
3731
3911
|
case "ReDimStatement": {
|
|
3732
3912
|
const w = stmt.name?.name?.toLowerCase() ?? null;
|
|
3733
3913
|
if (w && !skipVars.has(w)) {
|
|
@@ -3900,6 +4080,12 @@ function collectWriteAttrs(stmts, targets, selfRefs, boolOnly, arrayWrites) {
|
|
|
3900
4080
|
if (stmt.left?.type === "CallExpression") arrayWrites.add(w);
|
|
3901
4081
|
}
|
|
3902
4082
|
}
|
|
4083
|
+
if (stmt.type === "MidStatement") {
|
|
4084
|
+
const w = extractWriteName(stmt.target);
|
|
4085
|
+
if (w && targets.has(w)) {
|
|
4086
|
+
selfRefs.add(w);
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
3903
4089
|
const sub = (s) => collectWriteAttrs(Array.isArray(s) ? s : [s], targets, selfRefs, boolOnly, arrayWrites);
|
|
3904
4090
|
if (stmt.body) sub(stmt.body);
|
|
3905
4091
|
if (stmt.consequent) sub(stmt.consequent);
|
|
@@ -4208,7 +4394,8 @@ function analyzeFile(filePath) {
|
|
|
4208
4394
|
definedTypes: /* @__PURE__ */ new Set(),
|
|
4209
4395
|
callsByProc: /* @__PURE__ */ new Map(),
|
|
4210
4396
|
xlVbConstantRefs: /* @__PURE__ */ new Set(),
|
|
4211
|
-
commentedCodeBlocks: detectCommentedCode(src, filePath)
|
|
4397
|
+
commentedCodeBlocks: detectCommentedCode(src, filePath),
|
|
4398
|
+
withEventsVars: /* @__PURE__ */ new Set()
|
|
4212
4399
|
};
|
|
4213
4400
|
}
|
|
4214
4401
|
if (ast.diagnostics && ast.diagnostics.length > 0) {
|
|
@@ -4287,13 +4474,22 @@ function analyzeFile(filePath) {
|
|
|
4287
4474
|
const xlVbConstantRefs = collectExcelConstantRefs(ast);
|
|
4288
4475
|
const gotoGraphs = procs.map((proc) => analyzeGotoInProc(proc));
|
|
4289
4476
|
const loopAnalyses = procs.map((proc, i) => analyzeLoopsInProc(proc, gotoGraphs[i]));
|
|
4477
|
+
const withEventsVars = /* @__PURE__ */ new Set();
|
|
4478
|
+
for (const s of ast.body) {
|
|
4479
|
+
if (s.type === "VariableDeclaration") {
|
|
4480
|
+
for (const decl of s.declarations ?? []) {
|
|
4481
|
+
if (decl.isWithEvents) withEventsVars.add((decl.name?.name ?? "").toLowerCase());
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4290
4485
|
return {
|
|
4291
4486
|
report: { filePath, totalLines: lines.length, procedureCount: procedures.length, procedures, prefixClusters, warnings, gotoGraphs, loopAnalyses },
|
|
4292
4487
|
definedProcs,
|
|
4293
4488
|
definedTypes,
|
|
4294
4489
|
callsByProc,
|
|
4295
4490
|
xlVbConstantRefs,
|
|
4296
|
-
commentedCodeBlocks: detectCommentedCode(src, filePath)
|
|
4491
|
+
commentedCodeBlocks: detectCommentedCode(src, filePath),
|
|
4492
|
+
withEventsVars
|
|
4297
4493
|
};
|
|
4298
4494
|
}
|
|
4299
4495
|
function buildWorkspaceReport(analyses) {
|
|
@@ -4325,6 +4521,10 @@ function buildWorkspaceReport(analyses) {
|
|
|
4325
4521
|
for (const a of analyses) {
|
|
4326
4522
|
for (const p of a.report.procedures) {
|
|
4327
4523
|
if (p.referenceCount === 0) {
|
|
4524
|
+
const nameLower = p.name.toLowerCase();
|
|
4525
|
+
const sep = nameLower.indexOf("_");
|
|
4526
|
+
const isEventHandler = nameLower === "class_initialize" || nameLower === "class_terminate" || sep > 0 && a.withEventsVars.has(nameLower.slice(0, sep));
|
|
4527
|
+
if (isEventHandler) continue;
|
|
4328
4528
|
if (p.scope === "private") {
|
|
4329
4529
|
deadCodeCandidates.push({
|
|
4330
4530
|
file: a.report.filePath,
|
|
@@ -4989,10 +5189,13 @@ function buildDiffJson(baseline, current) {
|
|
|
4989
5189
|
}
|
|
4990
5190
|
};
|
|
4991
5191
|
}
|
|
4992
|
-
function main() {
|
|
4993
|
-
|
|
5192
|
+
function main(args) {
|
|
5193
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
5194
|
+
console.log(VERSION);
|
|
5195
|
+
process.exit(0);
|
|
5196
|
+
}
|
|
4994
5197
|
if (args.length === 0 || args.includes("--help")) {
|
|
4995
|
-
console.log("Usage:
|
|
5198
|
+
console.log("Usage: vba-runner analyze <file-or-dir> [options]");
|
|
4996
5199
|
console.log("");
|
|
4997
5200
|
console.log(" --json JSON \u5F62\u5F0F\u3067\u51FA\u529B\uFF08\u30D7\u30ED\u30B0\u30E9\u30E0\u9023\u643A\u7528\uFF09");
|
|
4998
5201
|
console.log(" --diff <baseline> baseline JSON \u3068\u306E\u5DEE\u5206\u3092\u8868\u793A\uFF08--json \u3068\u4F75\u7528\u53EF\uFF09");
|
|
@@ -5002,6 +5205,7 @@ function main() {
|
|
|
5002
5205
|
console.log(" --goto-graph GoTo \u6587\u306E\u5236\u5FA1\u30D5\u30ED\u30FC\u30B0\u30E9\u30D5\u3068\u30EA\u30D5\u30A1\u30AF\u30BF\u30EA\u30F3\u30B0\u63D0\u6848\u3092\u8868\u793A");
|
|
5003
5206
|
console.log(" --cross-iter \u30AF\u30ED\u30B9\u30A4\u30C6\u30EC\u30FC\u30B7\u30E7\u30F3\u5909\u6570\uFF08ByRef \u5FC5\u8981\u5909\u6570\uFF09\u306E\u4E00\u89A7\u3092\u8868\u793A");
|
|
5004
5207
|
console.log(" --gen-test-dir <dir> \u30C6\u30B9\u30C8\u7528\u30BD\u30FC\u30B9\u3092\u6307\u5B9A\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u751F\u6210\uFF08const.ts \u7B49\uFF09");
|
|
5208
|
+
console.log(" --version \u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u8868\u793A");
|
|
5005
5209
|
process.exit(args.length === 0 ? 1 : 0);
|
|
5006
5210
|
}
|
|
5007
5211
|
const wantJson = args.includes("--json");
|
|
@@ -5076,7 +5280,7 @@ function main() {
|
|
|
5076
5280
|
);
|
|
5077
5281
|
if (hasParseErrors) process.exit(1);
|
|
5078
5282
|
}
|
|
5079
|
-
if (process.argv[1]?.includes("vba-analyzer")) main();
|
|
5283
|
+
if (process.argv[1]?.includes("vba-analyzer")) main(process.argv.slice(2));
|
|
5080
5284
|
function analyzeWorkspaceForMcpFromFiles(files) {
|
|
5081
5285
|
const analyses = files.map((f) => analyzeFile(f));
|
|
5082
5286
|
return buildWorkspaceReport(analyses);
|
|
@@ -5100,6 +5304,7 @@ function formatWorkspaceReportForMcp(w) {
|
|
|
5100
5304
|
formatWorkspaceOutlineForMcp,
|
|
5101
5305
|
formatWorkspaceReportForMcp,
|
|
5102
5306
|
formatWorkspaceSummary,
|
|
5307
|
+
main,
|
|
5103
5308
|
paramName,
|
|
5104
5309
|
vbaTypeToTs
|
|
5105
5310
|
});
|