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.
@@ -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: 136 /* EOF */, value: "", line: startLine, column: startColumn };
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: 134 /* Date */, value: dateValue, line: startLine, column: startColumn };
146
+ return { type: 135 /* Date */, value: dateValue, line: startLine, column: startColumn };
146
147
  }
147
148
  }
148
149
  }
149
150
  this.advance();
150
- return { type: 111 /* OperatorHash */, value: "#", line: startLine, column: startColumn };
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: 135 /* Newline */, value: "\n", line: startLine, column: startColumn };
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: 120 /* OperatorEquals */, value: "=", line: startLine, column: startColumn };
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: 121 /* OperatorNotEquals */, value: "<>", line: startLine, column: startColumn };
217
+ return { type: 122 /* OperatorNotEquals */, value: "<>", line: startLine, column: startColumn };
217
218
  }
218
219
  if (this.peek() === "=") {
219
220
  this.advance();
220
- return { type: 124 /* OperatorLessThanOrEqual */, value: "<=", line: startLine, column: startColumn };
221
+ return { type: 125 /* OperatorLessThanOrEqual */, value: "<=", line: startLine, column: startColumn };
221
222
  }
222
- return { type: 122 /* OperatorLessThan */, value: "<", line: startLine, column: startColumn };
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: 125 /* OperatorGreaterThanOrEqual */, value: ">=", line: startLine, column: startColumn };
229
+ return { type: 126 /* OperatorGreaterThanOrEqual */, value: ">=", line: startLine, column: startColumn };
229
230
  }
230
- return { type: 123 /* OperatorGreaterThan */, value: ">", line: startLine, column: startColumn };
231
+ return { type: 124 /* OperatorGreaterThan */, value: ">", line: startLine, column: startColumn };
231
232
  }
232
233
  if (char === "+") {
233
234
  this.advance();
234
- return { type: 112 /* OperatorPlus */, value: "+", line: startLine, column: startColumn };
235
+ return { type: 113 /* OperatorPlus */, value: "+", line: startLine, column: startColumn };
235
236
  }
236
237
  if (char === "-") {
237
238
  this.advance();
238
- return { type: 113 /* OperatorMinus */, value: "-", line: startLine, column: startColumn };
239
+ return { type: 114 /* OperatorMinus */, value: "-", line: startLine, column: startColumn };
239
240
  }
240
241
  if (char === "!") {
241
242
  this.advance();
242
- return { type: 132 /* OperatorExclamation */, value: "!", line: startLine, column: startColumn };
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: 117 /* OperatorAmpersand */, value: "&", line: startLine, column: startColumn };
265
+ return { type: 118 /* OperatorAmpersand */, value: "&", line: startLine, column: startColumn };
265
266
  }
266
267
  if (char === ",") {
267
268
  this.advance();
268
- return { type: 126 /* OperatorComma */, value: ",", line: startLine, column: startColumn };
269
+ return { type: 127 /* OperatorComma */, value: ",", line: startLine, column: startColumn };
269
270
  }
270
271
  if (char === "#") {
271
272
  this.advance();
272
- return { type: 111 /* OperatorHash */, value: "#", line: startLine, column: startColumn };
273
+ return { type: 112 /* OperatorHash */, value: "#", line: startLine, column: startColumn };
273
274
  }
274
275
  if (char === "(") {
275
276
  this.advance();
276
- return { type: 127 /* OperatorLParen */, value: "(", line: startLine, column: startColumn };
277
+ return { type: 128 /* OperatorLParen */, value: "(", line: startLine, column: startColumn };
277
278
  }
278
279
  if (char === ")") {
279
280
  this.advance();
280
- return { type: 128 /* OperatorRParen */, value: ")", line: startLine, column: startColumn };
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: 138 /* ForeignName */, value: foreignStr, line: startLine, column: startColumn };
290
+ return { type: 139 /* ForeignName */, value: foreignStr, line: startLine, column: startColumn };
290
291
  }
291
292
  if (char === ".") {
292
293
  this.advance();
293
- return { type: 129 /* OperatorDot */, value: ".", line: startLine, column: startColumn };
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: 131 /* OperatorColonEquals */, value: ":=", line: startLine, column: startColumn };
300
+ return { type: 132 /* OperatorColonEquals */, value: ":=", line: startLine, column: startColumn };
300
301
  }
301
- return { type: 130 /* OperatorColon */, value: ":", line: startLine, column: startColumn };
302
+ return { type: 131 /* OperatorColon */, value: ":", line: startLine, column: startColumn };
302
303
  }
303
304
  if (char === ";") {
304
305
  this.advance();
305
- return { type: 133 /* OperatorSemicolon */, value: ";", line: startLine, column: startColumn };
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: 118 /* KeywordMod */, value: idStr, line: startLine, column: startColumn };
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: 114 /* OperatorMultiply */, value: "*", line: startLine, column: startColumn };
484
+ return { type: 115 /* OperatorMultiply */, value: "*", line: startLine, column: startColumn };
468
485
  }
469
486
  if (char === "/") {
470
487
  this.advance();
471
- return { type: 115 /* OperatorDivide */, value: "/", line: startLine, column: startColumn };
488
+ return { type: 116 /* OperatorDivide */, value: "/", line: startLine, column: startColumn };
472
489
  }
473
490
  if (char === "\\") {
474
491
  this.advance();
475
- return { type: 116 /* OperatorIntDivide */, value: "\\", line: startLine, column: startColumn };
492
+ return { type: 117 /* OperatorIntDivide */, value: "\\", line: startLine, column: startColumn };
476
493
  }
477
494
  if (char === "^") {
478
495
  this.advance();
479
- return { type: 119 /* OperatorPower */, value: "^", line: startLine, column: startColumn };
496
+ return { type: 120 /* OperatorPower */, value: "^", line: startLine, column: startColumn };
480
497
  }
481
498
  const unknownChar = this.advance();
482
- return { type: 137 /* Unknown */, value: unknownChar, line: startLine, column: startColumn };
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 !== 136 /* EOF */);
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 additionally permitted as IDENTIFIERs in
595
- * expression context (parsePrimary) for practical VBA compatibility —
596
- * e.g. as method names (obj.Print, ws.Get) or file I/O targets.
597
- * These remain <reserved-identifier> per §3.3.5.2. */
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
- // <statement-keyword>
641
+ // enum < KeywordBase so also rejected by range check in parseProcedureDeclaration
609
642
  93 /* KeywordLock */,
610
- // <statement-keyword>
611
- 100 /* KeywordUnlock */
612
- // <statement-keyword>
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 === 138 /* ForeignName */ || _Parser.CONTEXTUAL_KW.has(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 === 138 /* ForeignName */ || token.type >= 3 /* KeywordFor */ && token.type <= 110 /* KeywordAddressOf */;
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 === 135 /* Newline */) {
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 === 112 /* OperatorPlus */ || type === 113 /* OperatorMinus */ || type === 114 /* OperatorMultiply */ || type === 115 /* OperatorDivide */ || type === 116 /* OperatorIntDivide */ || type === 119 /* OperatorPower */ || type === 117 /* OperatorAmpersand */ || type === 120 /* OperatorEquals */ || type === 121 /* OperatorNotEquals */ || type === 122 /* OperatorLessThan */ || type === 123 /* OperatorGreaterThan */ || type === 124 /* OperatorLessThanOrEqual */ || type === 125 /* OperatorGreaterThanOrEqual */ || type === 126 /* OperatorComma */ || type === 127 /* OperatorLParen */ || type === 26 /* KeywordAnd */ || type === 27 /* KeywordOr */ || type === 64 /* KeywordXor */ || type === 65 /* KeywordEqv */ || type === 66 /* KeywordImp */ || type === 118 /* KeywordMod */ || type === 63 /* KeywordLike */ || type === 51 /* KeywordIs */ || type === 28 /* KeywordNot */;
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 !== 136 /* EOF */ ? peek : this.tokens[Math.max(0, this.pos - 1)]);
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 !== 136 /* EOF */ && this.peek().type !== 135 /* Newline */) {
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 !== 136 /* EOF */ && this.peek().type !== 135 /* Newline */) {
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(127 /* OperatorLParen */)) {
714
- this.consume(128 /* OperatorRParen */, "Expected ')' after parameter name");
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 === 129 /* OperatorDot */) {
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(120 /* OperatorEquals */)) {
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(120 /* OperatorEquals */, "Expected '=' after Attribute name");
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(111 /* OperatorHash */);
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(111 /* OperatorHash */);
903
+ this.match(112 /* OperatorHash */);
812
904
  fileNumbers.push(this.parseExpression());
813
- if (!this.match(126 /* OperatorComma */)) break;
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(111 /* OperatorHash */, "Expected '#' in Print statement");
911
+ this.consume(112 /* OperatorHash */, "Expected '#' in Print statement");
820
912
  const fileNumber = this.parseExpression();
821
- this.consume(126 /* OperatorComma */, "Expected ',' after file number in Print statement");
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(127 /* OperatorLParen */, "Expected '(' after Spc");
917
+ this.consume(128 /* OperatorLParen */, "Expected '(' after Spc");
826
918
  expressions.push({ type: "Spc", val: this.parseExpression() });
827
- this.consume(128 /* OperatorRParen */, "Expected ')' after Spc");
919
+ this.consume(129 /* OperatorRParen */, "Expected ')' after Spc");
828
920
  } else if (this.match(96 /* KeywordTab */)) {
829
- if (this.match(127 /* OperatorLParen */)) {
921
+ if (this.match(128 /* OperatorLParen */)) {
830
922
  expressions.push({ type: "Tab", val: this.parseExpression() });
831
- this.consume(128 /* OperatorRParen */, "Expected ')' after Tab");
923
+ this.consume(129 /* OperatorRParen */, "Expected ')' after Tab");
832
924
  } else {
833
925
  expressions.push("Tab");
834
926
  }
835
- } else if (this.peek().type === 126 /* OperatorComma */) {
927
+ } else if (this.peek().type === 127 /* OperatorComma */) {
836
928
  this.advance();
837
929
  expressions.push("Comma");
838
- } else if (this.peek().type === 133 /* OperatorSemicolon */) {
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(111 /* OperatorHash */, "Expected '#' in Line Input statement");
943
+ this.consume(112 /* OperatorHash */, "Expected '#' in Line Input statement");
852
944
  const fileNumber = this.parseExpression();
853
- this.consume(126 /* OperatorComma */, "Expected ',' after file number");
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(111 /* OperatorHash */, "Expected '#' in Put statement");
954
+ this.consume(112 /* OperatorHash */, "Expected '#' in Put statement");
863
955
  const fileNumber = this.parseExpression();
864
- this.consume(126 /* OperatorComma */, "Expected ',' after file number");
956
+ this.consume(127 /* OperatorComma */, "Expected ',' after file number");
865
957
  let recordNumber = void 0;
866
- if (this.peek().type !== 126 /* OperatorComma */) {
958
+ if (this.peek().type !== 127 /* OperatorComma */) {
867
959
  recordNumber = this.parseExpression();
868
960
  }
869
- this.consume(126 /* OperatorComma */, "Expected second ',' in Put statement");
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(111 /* OperatorHash */, "Expected '#' in Write statement");
972
+ this.consume(112 /* OperatorHash */, "Expected '#' in Write statement");
881
973
  const fileNumber = this.parseExpression();
882
974
  const items = [];
883
- if (this.match(126 /* OperatorComma */)) {
975
+ if (this.match(127 /* OperatorComma */)) {
884
976
  while (!this.isAtTerminator()) {
885
977
  items.push(this.parseExpression());
886
- if (!this.match(126 /* OperatorComma */)) break;
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(111 /* OperatorHash */, "Expected '#' in Input statement");
985
+ this.consume(112 /* OperatorHash */, "Expected '#' in Input statement");
894
986
  const fileNumber = this.parseExpression();
895
- this.consume(126 /* OperatorComma */, "Expected ',' in Input statement");
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(126 /* OperatorComma */)) break;
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(111 /* OperatorHash */, "Expected '#' in Get statement");
997
+ this.consume(112 /* OperatorHash */, "Expected '#' in Get statement");
906
998
  const fileNumber = this.parseExpression();
907
- this.consume(126 /* OperatorComma */, "Expected ',' in Get statement");
999
+ this.consume(127 /* OperatorComma */, "Expected ',' in Get statement");
908
1000
  let recordNumber = void 0;
909
- if (this.peek().type !== 126 /* OperatorComma */) {
1001
+ if (this.peek().type !== 127 /* OperatorComma */) {
910
1002
  recordNumber = this.parseExpression();
911
1003
  }
912
- this.consume(126 /* OperatorComma */, "Expected ',' in Get statement");
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(111 /* OperatorHash */, "Expected '#' in Seek statement");
1010
+ this.consume(112 /* OperatorHash */, "Expected '#' in Seek statement");
919
1011
  const fileNumber = this.parseExpression();
920
- this.consume(126 /* OperatorComma */, "Expected ',' in Seek statement");
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 === 127 /* OperatorLParen */) {
1049
+ if (this.peek().type === 128 /* OperatorLParen */) {
958
1050
  this.advance();
959
- while (this.peek().type !== 128 /* OperatorRParen */ && this.peek().type !== 136 /* EOF */) {
1051
+ while (this.peek().type !== 129 /* OperatorRParen */ && this.peek().type !== 137 /* EOF */) {
960
1052
  parameters.push(this.parseParameter());
961
- if (this.peek().type === 126 /* OperatorComma */) {
1053
+ if (this.peek().type === 127 /* OperatorComma */) {
962
1054
  this.advance();
963
1055
  }
964
1056
  }
965
- this.consume(128 /* OperatorRParen */, "Expected ')' after declare parameters");
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 === 114 /* OperatorMultiply */ || type === 115 /* OperatorDivide */ || type === 116 /* OperatorIntDivide */ || type === 119 /* OperatorPower */ || type === 112 /* OperatorPlus */ || type === 113 /* OperatorMinus */ || type === 117 /* OperatorAmpersand */ || type === 121 /* OperatorNotEquals */ || type === 122 /* OperatorLessThan */ || type === 123 /* OperatorGreaterThan */ || type === 124 /* OperatorLessThanOrEqual */ || type === 125 /* OperatorGreaterThanOrEqual */ || type === 118 /* KeywordMod */;
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 === 135 /* Newline */ || type === 136 /* EOF */ || type === 130 /* OperatorColon */;
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(135 /* Newline */)) {
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 !== 136 /* EOF */) {
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 !== 136 /* EOF */) {
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 === 130 /* OperatorColon */) {
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 !== 120 /* OperatorEquals */) {
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 !== 120 /* OperatorEquals */) {
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 !== 120 /* OperatorEquals */) {
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 !== 120 /* OperatorEquals */) {
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 !== 120 /* OperatorEquals */) {
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 !== 120 /* OperatorEquals */) {
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 !== 120 /* OperatorEquals */) {
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 !== 120 /* OperatorEquals */) {
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 !== 120 /* OperatorEquals */) {
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 === 138 /* ForeignName */ || token.type === 129 /* OperatorDot */ || token.type === 1 /* Number */ || _Parser.CONTEXTUAL_KW.has(token.type)) {
1308
- if ((token.type === 0 /* Identifier */ || _Parser.CONTEXTUAL_KW.has(token.type)) && this.pos + 1 < this.tokens.length && this.tokens[this.pos + 1].type === 130 /* OperatorColon */) {
1309
- const labelName = token.value;
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(127 /* OperatorLParen */)) {
1389
- if (this.peek().type !== 128 /* OperatorRParen */) {
1501
+ if (this.match(128 /* OperatorLParen */)) {
1502
+ if (this.peek().type !== 129 /* OperatorRParen */) {
1390
1503
  parameters.push(this.parseParameter());
1391
- while (this.match(126 /* OperatorComma */)) {
1504
+ while (this.match(127 /* OperatorComma */)) {
1392
1505
  parameters.push(this.parseParameter());
1393
1506
  }
1394
1507
  }
1395
- const rParen = this.consume(128 /* OperatorRParen */, "Expected ')' after procedure parameters");
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 === 129 /* OperatorDot */) {
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 !== 136 /* EOF */) {
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 === 136 /* EOF */) {
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(127 /* OperatorLParen */)) {
1561
+ if (this.match(128 /* OperatorLParen */)) {
1448
1562
  isArray = true;
1449
- if (this.peek().type !== 128 /* OperatorRParen */) {
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(126 /* OperatorComma */)) {
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(128 /* OperatorRParen */)) {
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 === 129 /* OperatorDot */) {
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(126 /* OperatorComma */)) {
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(120 /* OperatorEquals */)) this.throwError(`Parse error: Expected '=' in Const at line ${this.peek().line}`);
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(120 /* OperatorEquals */)) this.throwError(`Parse error: Expected '=' in Set statement at line ${this.peek().line}`);
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 !== 135 /* Newline */ && this.peek().type !== 136 /* EOF */) {
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.type !== 0 /* Identifier */ && labelToken.type !== 1 /* Number */) {
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 !== 135 /* Newline */ && this.peek().type !== 136 /* EOF */) {
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(127 /* OperatorLParen */)) {
1577
- if (this.peek().type !== 128 /* OperatorRParen */) {
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(126 /* OperatorComma */)) {
1700
+ if (!this.match(127 /* OperatorComma */)) {
1587
1701
  break;
1588
1702
  }
1589
1703
  }
1590
1704
  }
1591
- this.match(128 /* OperatorRParen */);
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 !== 136 /* EOF */) {
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 === 127 /* OperatorLParen */) {
1727
+ if (this.peek().type === 128 /* OperatorLParen */) {
1614
1728
  this.advance();
1615
1729
  let depth = 1;
1616
- while (depth > 0 && this.peek().type !== 136 /* EOF */ && this.peek().type !== 135 /* Newline */) {
1730
+ while (depth > 0 && this.peek().type !== 137 /* EOF */ && this.peek().type !== 136 /* Newline */) {
1617
1731
  const t = this.advance();
1618
- if (t.type === 127 /* OperatorLParen */) depth++;
1619
- else if (t.type === 128 /* OperatorRParen */) depth--;
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 !== 136 /* EOF */) {
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(120 /* OperatorEquals */)) {
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.type !== 0 /* Identifier */) {
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 !== 136 /* EOF */) {
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 === 135 /* Newline */) {
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 === 0 /* Identifier */) {
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(120 /* OperatorEquals */)) {
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 !== 136 /* EOF */ && !this.isAtEndTerminator()) {
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 !== 136 /* EOF */ && !this.isAtEndTerminator()) {
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 === 135 /* Newline */;
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 !== 135 /* Newline */ && this.peek().type !== 136 /* EOF */ && this.peek().type !== 9 /* KeywordElse */) {
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 !== 135 /* Newline */ && this.peek().type !== 136 /* EOF */) {
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 !== 136 /* EOF */) {
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 !== 136 /* EOF */) {
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 !== 136 /* EOF */ && !this.isAtEndTerminator()) {
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 !== 136 /* EOF */ && !this.isAtEndTerminator()) {
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 !== 136 /* EOF */) {
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 !== 136 /* EOF */) {
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(126 /* OperatorComma */)) {
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 !== 136 /* EOF */) {
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 !== 136 /* EOF */) {
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 === 120 /* OperatorEquals */ || compOp.type === 121 /* OperatorNotEquals */ || compOp.type === 122 /* OperatorLessThan */ || compOp.type === 123 /* OperatorGreaterThan */ || compOp.type === 124 /* OperatorLessThanOrEqual */ || compOp.type === 125 /* OperatorGreaterThanOrEqual */) {
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 === 120 /* OperatorEquals */ || this.peek().type === 121 /* OperatorNotEquals */ || this.peek().type === 51 /* KeywordIs */ || this.peek().type === 63 /* KeywordLike */) {
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 === 122 /* OperatorLessThan */ || this.peek().type === 123 /* OperatorGreaterThan */ || this.peek().type === 124 /* OperatorLessThanOrEqual */ || this.peek().type === 125 /* OperatorGreaterThanOrEqual */) {
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 === 117 /* OperatorAmpersand */) {
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 === 112 /* OperatorPlus */ || this.peek().type === 113 /* OperatorMinus */) {
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 === 118 /* KeywordMod */) {
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 === 116 /* OperatorIntDivide */) {
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 === 114 /* OperatorMultiply */ || this.peek().type === 115 /* OperatorDivide */) {
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 === 113 /* OperatorMinus */ || this.peek().type === 112 /* OperatorPlus */) {
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 === 119 /* OperatorPower */) {
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 === 134 /* Date */) {
2307
+ } else if (token.type === 135 /* Date */) {
2191
2308
  expr = { type: "DateLiteral", value: token.value };
2192
- } else if (token.type === 138 /* ForeignName */) {
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 procName = this.consume(0 /* Identifier */, "Expected procedure name after 'AddressOf'");
2198
- expr = { type: "AddressOfExpression", procedureName: { type: "Identifier", name: procName.value } };
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 === 129 /* OperatorDot */) {
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 === 127 /* OperatorLParen */) {
2346
+ } else if (token.type === 128 /* OperatorLParen */) {
2221
2347
  const innerExpr = this.parseExpression();
2222
- if (!this.match(128 /* OperatorRParen */)) {
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.type !== 0 /* Identifier */ && typeToken.type !== 25 /* KeywordCollection */) {
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 === 129 /* OperatorDot */) {
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 === 135 /* Newline */) {
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(129 /* OperatorDot */)) {
2259
- if (this.peek().type === 136 /* EOF */) this.throwError("Expected property name after '.'");
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(132 /* OperatorExclamation */)) {
2265
- if (this.peek().type === 136 /* EOF */) this.throwError("Expected identifier after '!'");
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 === 127 /* OperatorLParen */) {
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 !== 128 /* OperatorRParen */) {
2402
+ if (this.peek().type !== 129 /* OperatorRParen */) {
2277
2403
  args.push(this.parseCallArgument());
2278
- while (this.match(126 /* OperatorComma */)) {
2404
+ while (this.match(127 /* OperatorComma */)) {
2279
2405
  args.push(this.parseCallArgument());
2280
2406
  }
2281
2407
  }
2282
- if (!this.match(128 /* OperatorRParen */)) {
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 === 126 /* OperatorComma */ || next === 128 /* OperatorRParen */ || this.isAtTerminator()) {
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 === 131 /* OperatorColonEquals */ && typeof this.peek().value === "string" && /^[A-Za-z_]\w*$/.test(this.peek().value)) {
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.type !== 0 /* Identifier */ && labelToken.type !== 1 /* Number */) {
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(126 /* OperatorComma */)) {
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.type !== 0 /* Identifier */ && labelToken.type !== 1 /* Number */) {
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(120 /* OperatorEquals */)) {
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(120 /* OperatorEquals */)) {
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.consume(0 /* Identifier */, "Expected identifier after 'Event'");
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(127 /* OperatorLParen */)) {
2368
- if (this.peek().type !== 128 /* OperatorRParen */) {
2527
+ if (this.match(128 /* OperatorLParen */)) {
2528
+ if (this.peek().type !== 129 /* OperatorRParen */) {
2369
2529
  parameters.push(this.parseParameter());
2370
- while (this.match(126 /* OperatorComma */)) {
2530
+ while (this.match(127 /* OperatorComma */)) {
2371
2531
  parameters.push(this.parseParameter());
2372
2532
  }
2373
2533
  }
2374
- this.consume(128 /* OperatorRParen */, "Expected ')' after event parameters");
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.consume(0 /* Identifier */, "Expected identifier after 'RaiseEvent'");
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(127 /* OperatorLParen */)) {
2384
- if (this.peek().type !== 128 /* OperatorRParen */) {
2545
+ if (this.match(128 /* OperatorLParen */)) {
2546
+ if (this.peek().type !== 129 /* OperatorRParen */) {
2385
2547
  args.push(this.parseExpression());
2386
- while (this.match(126 /* OperatorComma */)) {
2548
+ while (this.match(127 /* OperatorComma */)) {
2387
2549
  args.push(this.parseExpression());
2388
2550
  }
2389
2551
  }
2390
- this.consume(128 /* OperatorRParen */, "Expected ')' after RaiseEvent arguments");
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.consume(0 /* Identifier */, "Expected interface name after 'Implements'");
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(126 /* OperatorComma */)) {
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(126 /* OperatorComma */)) {
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(111 /* OperatorHash */);
2582
+ this.match(112 /* OperatorHash */);
2420
2583
  const fileNumber = this.parseExpression();
2421
2584
  let recordRange;
2422
- if (this.match(126 /* OperatorComma */)) {
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(111 /* OperatorHash */);
2597
+ this.match(112 /* OperatorHash */);
2435
2598
  const fileNumber = this.parseExpression();
2436
2599
  let recordRange;
2437
- if (this.match(126 /* OperatorComma */)) {
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(111 /* OperatorHash */, "Expected '#' after Width");
2612
+ this.consume(112 /* OperatorHash */, "Expected '#' after Width");
2450
2613
  const fileNumber = this.parseExpression();
2451
- this.consume(126 /* OperatorComma */, "Expected ',' after file number");
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
- const args = process.argv.slice(2);
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: npx tsx test-libs/vba-analyzer.ts <file-or-dir> [options]");
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
  });