vb6parse 1.2.4

vb6parse is a library for parsing and analyzing VB6 code, from projects, to controls, to modules, and forms.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
use super::Parser;
use crate::language::Token;
use crate::parsers::SyntaxKind;

// Extracted from: control_flow/resume.rs
impl Parser<'_> {
    /// Parse a Resume statement.
    ///
    /// VB6 Resume statement syntax:
    /// - `Resume`
    /// - `Resume Next`
    /// - `Resume Label`
    ///
    /// Resumes execution after an error-handling routine is finished.
    ///
    /// # Syntax
    ///
    /// The `Resume` statement has these forms:
    ///
    /// | Form | Description |
    /// |------|-------------|
    /// | `Resume` | If the error occurred in the same procedure as the error handler, execution resumes with the statement that caused the error. If the error occurred in a called procedure, execution resumes at the statement that last called out of the procedure containing the error-handling routine. |
    /// | `Resume Next` | If the error occurred in the same procedure as the error handler, execution resumes with the statement immediately following the statement that caused the error. If the error occurred in a called procedure, execution resumes with the statement immediately following the statement that last called out of the procedure containing the error-handling routine (or On Error Resume Next statement). |
    /// | `Resume Label` | Execution resumes at the line specified by the label argument. The label argument can be a line label or line number. |
    ///
    /// # Remarks
    ///
    /// - The `Resume` statement can be used only in an error-handling routine.
    /// - Using `Resume` without specifying a label causes execution to resume at the statement that caused the error.
    /// - `Resume Next` is useful when you want to continue execution despite an error.
    /// - `Resume Label` is useful when you want to continue execution at a specific location after handling an error.
    /// - If you use a `Resume` statement anywhere except in an error-handling routine, an error occurs.
    /// - `Resume` cannot be used in any procedure that contains an On Error `Resume Next` statement.
    ///
    /// # Examples
    ///
    /// ```vb
    /// Sub Test()
    ///     On Error GoTo ErrorHandler
    ///     ' Code that might cause error
    ///     x = 1 / 0
    ///     Exit Sub
    /// ErrorHandler:
    ///     MsgBox "Error occurred"
    ///     Resume Next
    /// End Sub
    /// ```
    ///
    /// ```vb
    /// Sub Test2()
    ///     On Error GoTo ErrorHandler
    ///     ' Code that might cause error
    ///     Exit Sub
    /// ErrorHandler:
    ///     If Err.Number = 11 Then
    ///         Resume
    ///     Else
    ///         Resume CleanUp
    ///     End If
    /// CleanUp:
    ///     ' Cleanup code
    /// End Sub
    /// ```
    ///
    /// # References
    ///
    /// [Microsoft VBA Language Reference - Resume Statement](https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/resume-statement)
    pub(crate) fn parse_resume_statement(&mut self) {
        // if we are now parsing a resume statement, we are no longer in the header.
        self.parsing_header = false;

        self.builder
            .start_node(SyntaxKind::ResumeStatement.to_raw());
        self.consume_whitespace();

        // Consume "Resume" keyword
        self.consume_token();

        // Consume everything until newline (Next keyword or label)
        self.consume_until_after(Token::Newline);

        self.builder.finish_node(); // ResumeStatement
    }
}

// Extracted from: control_flow/exit.rs
impl Parser<'_> {
    /// Parse an Exit statement.
    ///
    /// VB6 Exit statement syntax:
    /// - Exit Do
    /// - Exit For
    /// - Exit Function
    /// - Exit Property
    /// - Exit Sub
    ///
    /// [Reference](https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/exit-statement)
    pub(crate) fn parse_exit_statement(&mut self) {
        // if we are now parsing an exit statement, we are no longer in the header.
        self.parsing_header = false;

        self.builder.start_node(SyntaxKind::ExitStatement.to_raw());
        self.consume_whitespace();

        // Consume "Exit" keyword
        self.consume_token();

        // Consume whitespace after Exit
        self.consume_whitespace();

        // Consume the exit type (Do, For, Function, Property, Sub)
        if self.at_token(Token::DoKeyword)
            || self.at_token(Token::ForKeyword)
            || self.at_token(Token::FunctionKeyword)
            || self.at_token(Token::PropertyKeyword)
            || self.at_token(Token::SubKeyword)
        {
            self.consume_token();
        }

        self.builder.finish_node(); // ExitStatement
    }
}

// Extracted from: control_flow/jump.rs
impl Parser<'_> {
    /// Parse a `GoSub` statement.
    ///
    /// VB6 `GoSub` statement syntax:
    /// - `GoSub` label
    ///
    /// Branches to and returns from a subroutine within a procedure.
    ///
    /// The `GoSub`...`Return` statement syntax has these parts:
    ///
    /// | Part   | Description |
    /// |--------|-------------|
    /// | label  | Required. A line label or line number. |
    ///
    /// Remarks:
    /// - You can use `GoSub` and `Return` anywhere in a procedure, but `GoSub` and the corresponding `Return` statement must be in the same procedure.
    /// - A subroutine can contain more than one `Return` statement, but the first one encountered causes the flow of execution to branch back to the statement immediately following the most recently executed `GoSub` statement.
    /// - You can't enter or exit `Sub` procedures with `GoSub`...`Return`.
    /// - Using `GoSub` and `Return` is considered obsolete. Modern VB6 code should use `Sub` or `Function` procedures instead.
    ///
    /// Examples:
    /// ```vb
    /// Sub Test()
    ///     GoSub ErrorHandler
    ///     Exit Sub
    /// ErrorHandler:
    ///     MsgBox "Error"
    ///     Return
    /// End Sub
    /// ```
    ///
    /// [Reference](https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/gosubreturn-statement)
    pub(crate) fn parse_gosub_statement(&mut self) {
        // if we are now parsing a gosub statement, we are no longer in the header.
        self.parsing_header = false;

        self.builder.start_node(SyntaxKind::GoSubStatement.to_raw());
        self.consume_whitespace();

        // Consume "GoSub" keyword
        self.consume_token();

        // Consume everything until newline (the label name)
        self.consume_until(Token::Newline);

        self.builder.finish_node(); // GoSubStatement
    }

    /// Parse a Return statement.
    ///
    /// VB6 Return statement syntax:
    /// - Return
    ///
    /// Returns from a subroutine within a procedure.
    ///
    /// Remarks:
    /// - `Return` must be used with `GoSub` to return to the statement following the `GoSub` call.
    /// - You can use `GoSub` and `Return` anywhere in a procedure, but `GoSub` and the corresponding `Return` statement must be in the same procedure.
    /// - A subroutine can contain more than one `Return` statement, but the first one encountered causes the flow of execution to branch back to the statement immediately following the most recently executed `GoSub` statement.
    /// - Using `GoSub` and `Return` is considered obsolete. Modern VB6 code should use `Sub` or `Function` procedures instead.
    ///
    /// Examples:
    /// ```vb
    /// Sub Test()
    ///     GoSub Cleanup
    ///     Exit Sub
    /// Cleanup:
    ///     Set obj = Nothing
    ///     Return
    /// End Sub
    /// ```
    ///
    /// [Reference](https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/gosubreturn-statement)
    pub(crate) fn parse_return_statement(&mut self) {
        // if we are now parsing a return statement, we are no longer in the header.
        self.parsing_header = false;

        self.builder
            .start_node(SyntaxKind::ReturnStatement.to_raw());
        self.consume_whitespace();

        // Consume "Return" keyword
        self.consume_token();

        self.builder.finish_node(); // ReturnStatement
    }

    /// Parse a `GoTo` statement.
    ///
    /// Syntax:
    ///   `GoTo` label
    ///
    /// [Reference](https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/goto-statement)
    pub(crate) fn parse_goto_statement(&mut self) {
        // if we are now parsing a `GoTo` statement, we are no longer in the header.
        self.parsing_header = false;

        self.builder.start_node(SyntaxKind::GotoStatement.to_raw());
        self.consume_whitespace();

        // Consume "`GoTo`" keyword
        self.consume_token();

        // Consume everything until newline (the label name)
        self.consume_until(Token::Newline);

        self.builder.finish_node(); // GotoStatement
    }

    /// Parse a label statement.
    ///
    /// VB6 label syntax:
    /// - `LabelName:`
    ///
    /// `Labels` are used as targets for `GoTo` and `GoSub` statements.
    ///
    /// [Reference](https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/goto-statement)
    pub(crate) fn parse_label_statement(&mut self) {
        // if we are now parsing a label statement, we are no longer in the header.
        self.parsing_header = false;

        self.builder.start_node(SyntaxKind::LabelStatement.to_raw());
        self.consume_whitespace();

        // Consume the label identifier
        self.consume_token();

        // Consume optional whitespace
        self.consume_whitespace();

        // Consume the colon
        if self.at_token(Token::ColonOperator) {
            self.consume_token();
        }

        // Consume the newline if present
        if self.at_token(Token::Newline) {
            self.consume_token();
        }

        self.builder.finish_node(); // LabelStatement
    }

    /// Check if the current position is at a label.
    /// A label is an identifier followed by a colon, or a numeric line label.
    pub(crate) fn is_at_label(&self) -> bool {
        let next_token_is_colon = matches!(self.peek_next_token(), Some(Token::ColonOperator));

        // If we are not parsing the header, then some keywords are valid identifiers (like "Begin")
        // TODO: Consider adding a list of keywords that can be used as labels.
        // TODO: Also consider modifying tokenizer to recognize when inside header to more easily identify Identifiers vs header only keywords.
        if next_token_is_colon
            && !self.parsing_header
            && matches!(self.current_token(), Some(Token::BeginKeyword))
        {
            return true;
        }

        (next_token_is_colon && (self.is_identifier() || self.is_number()))
            || self.is_at_numeric_line_label()
    }

    #[allow(clippy::needless_continue)]
    fn is_at_numeric_line_label(&self) -> bool {
        if !self.is_number() {
            return false;
        }

        if !matches!(
            self.peek_next_token(),
            Some(Token::Whitespace | Token::Newline)
        ) {
            return false;
        }

        let mut index = self.pos;
        while index > 0 {
            index -= 1;
            match self.tokens[index].1 {
                // The continue arm actually is needed, but couldn't convince clippy
                // so I just pushed it to ignore. Oh well.
                Token::Whitespace => continue,
                Token::Newline => return true,
                _ => return false,
            }
        }

        true
    }
}

// Extracted from: control_flow/on_statements.rs
impl Parser<'_> {
    /// Parse an `On Error` statement.
    ///
    /// VB6 `On Error` statement syntax:
    /// - `On Error GoTo label`
    /// - `On Error GoTo 0`
    /// - `On Error Resume Next`
    ///
    /// Enables an error-handling routine and specifies the location of the routine within a procedure.
    ///
    /// The `On Error` statement syntax has these forms:
    ///
    /// | Form | Description |
    /// |------|-------------|
    /// | `On Error GoTo line` | Enables the error-handling routine that starts at line. The line argument is any line label or line number. If a run-time error occurs, control branches to line, making the error handler active. |
    /// | `On Error Resume Next` | Specifies that when a run-time error occurs, control goes to the statement immediately following the statement where the error occurred, and execution continues from that point. |
    /// | `On Error GoTo 0` | Disables any enabled error handler in the current procedure. |
    ///
    /// Remarks:
    /// - If you don't use an `On Error` statement, any run-time error that occurs is fatal; that is, an error message is displayed and execution stops.
    /// - An "enabled" error handler is one that is turned on by an `On Error` statement. An "active" error handler is an enabled handler that is in the process of handling an error.
    /// - If an error occurs while an error handler is active (between the occurrence of the error and a `Resume`, `Exit Sub`, `Exit Function`, or `Exit Property` statement), the current procedure's error handler can't handle the error.
    /// - Control returns to the calling procedure. If the calling procedure has an enabled error handler, it is activated to handle the error.
    /// - If the calling procedure's error handler is also active, control passes back through previous calling procedures until an enabled, but inactive, error handler is found.
    /// - If no inactive, enabled error handler is found, the error is fatal at the point at which it actually occurred.
    /// - Each time the error handler passes control back to a calling procedure, that procedure becomes the current procedure. Once an error is handled in any procedure, execution resumes in the current procedure at the point designated by the `Resume` statement.
    ///
    /// Examples:
    /// ```vb
    /// Sub Test()
    ///     On Error GoTo ErrorHandler
    ///     ' Code that might cause an error
    ///     Exit Sub
    /// ErrorHandler:
    ///     MsgBox "An error occurred: " & Err.Description
    /// End Sub
    ///
    /// Sub Test2()
    ///     On Error Resume Next
    ///     ' Code continues even if errors occur
    ///     MkDir "C:\Temp"  ' Won't stop if directory exists
    /// End Sub
    ///
    /// Sub Test3()
    ///     On Error GoTo 0  ' Disable error handling
    ///     ' Normal error behavior
    /// End Sub
    /// ```
    ///
    /// [Reference](https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/on-error-statement)
    pub(crate) fn parse_on_error_statement(&mut self) {
        // if we are now parsing an on error statement, we are no longer in the header.
        self.parsing_header = false;

        self.builder
            .start_node(SyntaxKind::OnErrorStatement.to_raw());
        self.consume_whitespace();

        // Consume "On" keyword
        self.consume_token();

        // Consume "Error" keyword
        if self.at_token(Token::ErrorKeyword) {
            self.consume_token();
        }

        // Consume everything until newline (GoTo label, Resume Next, GoTo 0, etc.)
        self.consume_until_after(Token::Newline);

        self.builder.finish_node(); // OnErrorStatement
    }

    /// Parse an `On GoTo` statement.
    ///
    /// VB6 `On GoTo` statement syntax:
    /// - `On expression GoTo label1[, label2, ...]`
    ///
    /// Branches to one of several specified labels, depending on the value of an expression.
    ///
    /// The `On...GoTo` statement syntax has these parts:
    ///
    /// | Part | Description |
    /// |------|-------------|
    /// | expression | Required. Any numeric expression that evaluates to a whole number between 0 and 255, inclusive. If expression is any number other than a whole number, it is rounded before it is evaluated. |
    /// | labellist | Required. List of line labels or line numbers separated by commas. |
    ///
    /// Remarks:
    /// - The value of expression determines which line is branched to in the list of labels. If the value of expression is less than 1 or greater than the number of items in the list, one of the following results occurs:
    ///   - If expression equals 0, execution continues with the statement following `On...GoTo`.
    ///   - If expression is greater than the number of labels in the list, execution continues with the statement following `On...GoTo`.
    ///   - If expression is negative or greater than 255, an error occurs.
    /// - The `On...GoTo` statement is useful for branching to one of several different labels based on a value.
    /// - Using `On...GoTo` is considered obsolete. Modern VB6 code should use `Select Case` instead.
    ///
    /// Examples:
    /// ```vb
    /// Sub Test()
    ///     Dim choice As Integer
    ///     choice = 2
    ///     On choice GoTo Label1, Label2, Label3
    ///     Exit Sub
    /// Label1:
    ///     MsgBox "Choice 1"
    ///     Exit Sub
    /// Label2:
    ///     MsgBox "Choice 2"
    ///     Exit Sub
    /// Label3:
    ///     MsgBox "Choice 3"
    /// End Sub
    /// ```
    ///
    /// [Reference](https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/ongoto-and-ongosub-statements)
    pub(crate) fn parse_on_goto_statement(&mut self) {
        // if we are now parsing an on goto statement, we are no longer in the header.
        self.parsing_header = false;

        self.builder
            .start_node(SyntaxKind::OnGoToStatement.to_raw());
        self.consume_whitespace();

        // Consume "On" keyword
        self.consume_token();

        // Consume everything until newline (expression GoTo labels)
        self.consume_until_after(Token::Newline);

        self.builder.finish_node(); // OnGoToStatement
    }

    /// Parse an `On GoSub` statement.
    ///
    /// VB6 `On GoSub` statement syntax:
    /// - `On expression GoSub label1[, label2, ...]`
    ///
    /// Branches to one of several specified subroutines, depending on the value of an expression.
    ///
    /// The `On...GoSub` statement syntax has these parts:
    ///
    /// | Part | Description |
    /// |------|-------------|
    /// | expression | Required. Any numeric expression that evaluates to a whole number between 0 and 255, inclusive. If expression is any number other than a whole number, it is rounded before it is evaluated. |
    /// | labellist | Required. List of line labels or line numbers separated by commas. |
    ///
    /// Remarks:
    /// - The value of expression determines which subroutine is called in the list of labels. If the value of expression is less than 1 or greater than the number of items in the list, one of the following results occurs:
    ///   - If expression equals 0, execution continues with the statement following `On...GoSub`.
    ///   - If expression is greater than the number of labels in the list, execution continues with the statement following `On...GoSub`.
    ///   - If expression is negative or greater than 255, an error occurs.
    /// - The `On...GoSub` statement is useful for branching to one of several different subroutines based on a value.
    /// - Each subroutine must end with a Return statement to return to the statement following the `On...GoSub`.
    /// - Using `On...GoSub` is considered obsolete. Modern VB6 code should use `Select Case` with Sub procedure calls instead.
    ///
    /// Examples:
    /// ```vb
    /// Sub Test()
    ///     Dim menuChoice As Integer
    ///     menuChoice = 1
    ///     On menuChoice GoSub Menu1, Menu2, Menu3
    ///     Exit Sub
    /// Menu1:
    ///     MsgBox "Menu 1 selected"
    ///     Return
    /// Menu2:
    ///     MsgBox "Menu 2 selected"
    ///     Return
    /// Menu3:
    ///     MsgBox "Menu 3 selected"
    ///     Return
    /// End Sub
    /// ```
    ///
    /// [Reference](https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/ongoto-and-ongosub-statements)
    pub(crate) fn parse_on_gosub_statement(&mut self) {
        // if we are now parsing an on gosub statement, we are no longer in the header.
        self.parsing_header = false;

        self.builder
            .start_node(SyntaxKind::OnGoSubStatement.to_raw());
        self.consume_whitespace();

        // Consume "On" keyword
        self.consume_token();

        // Consume everything until newline (expression GoSub labels)
        self.consume_until_after(Token::Newline);

        self.builder.finish_node(); // OnGoSubStatement
    }
}

// Extracted from: control_flow/end.rs
impl Parser<'_> {
    /// Parse a standalone `End` statement.
    ///
    /// The `End` statement terminates program execution immediately.
    /// It closes all files opened using the `Open` statement and clears all variables.
    ///
    /// Syntax:
    ///   `End`
    ///
    /// Note: This is distinct from compound `End` keywords like `End If`, `End Sub`,
    /// `End Function`, etc., which are block terminators handled by their respective parsers.
    ///
    /// [Reference](https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/end-statement)
    pub(crate) fn parse_end_statement(&mut self) {
        self.parsing_header = false;

        self.builder.start_node(SyntaxKind::EndStatement.to_raw());
        self.consume_whitespace();

        // Consume "End" keyword
        self.consume_token();

        self.builder.finish_node(); // EndStatement
    }
}