VB6Parse / Documentation / Getting Started

Step-by-step tutorial for parsing Visual Basic 6 code

Introduction

This guide will walk you through using VB6Parse, from installation to advanced parsing scenarios. By the end, you'll understand how to parse VB6 projects, handle errors, navigate syntax trees, and build tools that process legacy Visual Basic 6 code.

Prerequisites

Installation

1

Create a new Rust project

cargo new vb6parse-demo
cd vb6parse-demo
2

Add VB6Parse to Cargo.toml

[dependencies]
vb6parse = "0.5.1"
3

Verify installation

cargo build
Compiling vb6parse v0.5.1
Compiling vb6parse-demo v0.1.0
Finished dev [unoptimized + debuginfo] target(s)

Your First Parse: Hello World

Let's start with the simplest possible example - parsing a VB6 module with a single subroutine.

1

Create a simple module parser

Replace the contents of src/main.rs:

use vb6parse::*;

                
fn main() {
    // VB6 code to parse
    let code = r#"Attribute VB_Name = "HelloWorld"
            
    Public Sub SayHello()
        MsgBox "Hello from VB6Parse!"
    End Sub
    "#;
            
    // Step 1: Create a SourceFile (handles encoding)
    let source = SourceFile::from_string("HelloWorld.bas", code);

    // Step 2: Parse as a module
    let result = ModuleFile::parse(&source);

    // Step 3: Unpack the result (separates output from errors)
    let (module_opt, failures) = result.unpack();

    // Step 4: Handle the result
    if let Some(module) = module_opt {
        println!("āœ“ Successfully parsed module: {}", module.name);
        println!("  Version: {}", module.name);
        println!("  Has code: {}", module.cst.debug_tree());
    }

    // Step 5: Display any parsing errors
    if !failures.is_empty() {
        println!("\n⚠ Encountered {} parsing issues:", failures.len());
        for failure in failures {
            failure.print();
        }
    }
}
2

Run the example

cargo run
āœ“ Successfully parsed module: HelloWorld
Version: 1.0
Has code: true
šŸ’” Key Concept: ParseResult
VB6Parse uses a ParseResult<T, E> type that contains both the parsed output (Option<T>) and any failures (Vec<ErrorDetails<E>>). This allows partial success - you can get a usable parse tree even if some parts failed.

Parsing VB6 Projects

VB6 projects (.vbp files) list all modules, forms, and references. Let's parse one:

use vb6parse::*;

fn main() {
    let project_content = r#"Type=Exe
Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\System32\stdole2.tlb#OLE Automation
Object={831FDD16-0C5C-11D2-A9FC-0000F8754DA1}#2.0#0; MSCOMCTL.OCX
Module=Utilities; Utilities.bas
Module=DataAccess; DataAccess.bas
Form=MainForm.frm
Class=DatabaseConnection; DatabaseConnection.cls
"#;
                
    let source = SourceFile::from_string("MyProject.vbp", project_content);
    let result = ProjectFile::parse(&source);
    let (project, _failures) = result.unpack();
                
    if let Some(proj) = project {
        println!("Project Type: {:?}", proj.project_type);
                
        // Iterate over modules
        println!("\nModules:");
        for module in proj.modules() {
            println!("  - {} ({})", module.name, module.path);
        }
                
        // Iterate over forms
        println!("\nForms:");
        for form_name in proj.forms() {
            println!("  - {form_name}");
        }
                
        // Iterate over classes
        println!("\nClasses:");
        for class in proj.classes() {
            println!("  - {} ({})", class.name, class.path);
        }
                
        // Check references
        println!("\nReferences: {}", proj.references().count());
    }
}
Project Type: Exe

Modules:
  - Utilities (Utilities.bas)
  - DataAccess (DataAccess.bas)

Forms:
  - MainForm.frm

Classes:
  - DatabaseConnection (DatabaseConnection.cls)

References: 1
āš ļø Important: ProjectFile only parses the .vbp file itself - it doesn't load the referenced files. You'll need to read and parse each module/form/class file separately.

Handling Parse Errors

VB6Parse is designed to handle malformed input gracefully. Even with syntax errors, you often get partial results:

use vb6parse::*;

fn main() {
    // Malformed VB6 code (missing End Sub, invalid syntax)
    let bad_code = r#"Attribute VB_Name = "BadModule"

Public Sub BrokenFunction()
    x = 5 + 
    ' Missing closing and the expression is incomplete

Public Sub AnotherFunction()
    MsgBox "This one is fine"
End Sub
"#;

    let source = SourceFile::from_string("BadModule.bas", bad_code);
    let result = ModuleFile::parse(&source);
    let (module, failures) = result.unpack();

    // We still might get a module!
    if let Some(module) = module {
        println!("āœ“ Parsed module: {}", module.name);
        println!("  (Despite {} errors)", failures.len());
    } else {
        println!("āœ— Parsing completely failed");
    }

    // Always check and handle failures
    if !failures.is_empty() {
        println!("\nParsing Issues:");
        for failure in failures {
            println!("  Line {}-{}: {:?}", 
                failure.line_start, 
                failure.line_end, 
                failure.kind
            );
            
            // Print the error with source context
            failure.print();
        }
    }
}
šŸ’” Best Practice: Always check both the result and failures. In tools that process many files, log failures but continue processing - don't stop on the first error.

Tokenization

For lower-level analysis, you can tokenize VB6 code without building a full parse tree:

use vb6parse::*;

fn main() {
    let code = "Dim x As Integer ' Declare a variable";
    let mut source = SourceStream::new("test.bas", code);
    let result = tokenize(&mut source);
    let (token_stream, _) = result.unpack();
                
    if let Some(tokens) = token_stream {
        println!("Tokens found: {}", tokens.len());
                
        for (text, token) in tokens.tokens().iter() {
            println!("  {token:?}: '{text}'");
        }
    }
}
            
Tokens found: 9
DimKeyword: 'Dim'
Whitespace: ' '
Identifier: 'x'
Whitespace: ' '
AsKeyword: 'As'
Whitespace: ' '
IntegerKeyword: 'Integer'
Whitespace: ' '
EndOfLineComment: '' Declare a variable'

Navigating the Concrete Syntax Tree

The CST preserves all source information including whitespace and comments, making it perfect for code analysis tools:

use vb6parse::*;
use vb6parse::parsers::SyntaxKind;

fn main() {
    let code = r#"Sub Calculate()
    Dim result As Double
    result = 10 * 5 + 3
    MsgBox result
End Sub"#;

    let cst = ConcreteSyntaxTree::from_text("test.bas", code).unwrap();
    let root = cst.to_serializable().root;

    println!("Total nodes in tree: {}", root.descendants().count());
    
    // Find all Dim statements
    let dims = root.find_all(SyntaxKind::DimStatement);
    println!("Found {} Dim statements", dims.len());
    
    // Find all identifiers
    let identifiers = root.find_all_if(|n| {
        n.kind() == SyntaxKind::Identifier
    });
    println!("Found {} identifiers", identifiers.len());
    for id in identifiers {
        println!("  - {}", id.text());
    }
    
    // Navigate to specific nodes
    if let Some(sub_stmt) = root.find(SyntaxKind::SubStatement) {
        println!("\nSubroutine found:");
        println!("  Text:\n'{}'", sub_stmt.text());
        println!("  Children: {}", sub_stmt.child_count());
    }
}
Total nodes in tree: 51
Found 1 Dim statements
Found 5 identifiers
  - Calculate
  - result
  - result
  - MsgBox
  - result

Subroutine found:
  Text:
'Sub Calculate()
   Dim result As Double
   result = 10 * 5 + 3
   MsgBox result
End Sub'
  Children: 9

Parsing Forms and Controls

Forms are the most complex VB6 file type, containing both UI controls and code:

use vb6parse::{language::ControlKind, *};

fn main() {
    let form_content = r#"VERSION 5.00
Begin VB.Form MainForm 
   Caption         =   "My Application"
   ClientHeight    =   3090
   ClientWidth     =   4560
   Begin VB.CommandButton btnSubmit 
      Caption         =   "Submit"
      Height          =   375
      Left            =   1680
      TabIndex        =   0
      Top             =   1200
      Width           =   1215
   End
End
Attribute VB_Name = "MainForm"

Private Sub btnSubmit_Click()
    MsgBox "Button clicked!"
End Sub
"#;

    let source = SourceFile::from_string("MainForm.frm", form_content);
    let result = FormFile::parse(&source);
    let (form_opt, _failures) = result.unpack();

    let Some(form_file) = form_opt else {
        println!("Failed to parse the form file.");
        return;
    };

    // Access the root form control
    match form_file.form.kind() {
        ControlKind::Form {
            controls: _,
            menus: _,
            properties,
        } => {
            println!("Form: {}", form_file.attributes.name);
            println!("  Caption: {}", properties.caption);
            println!(
                "  Size: {}x{}",
                properties.client_width, properties.client_height
            );
        }
        ControlKind::MDIForm {
            properties,
            controls: _,
            menus: _,
        } => {
            println!("MDI Form: {}", form_file.attributes.name);
            println!("  Caption: {}", properties.caption);
            println!("  Size: {}x{}", properties.width, properties.height);
        }
        _ => {
            println!("Only Form and MDIForm are valid top level controls in a form file.");
            return;
        }
    }

    // Iterate child controls
    println!("\n  Controls:");
    if let Some(children) = form_file.form.children() {
        for child in children {
            println!("      - {} ({})", child.name(), child.kind());
        }
    };
}
Form: MainForm
  Caption: My Application
  Size: 4560x3090

 Controls:
   - btnSubmit (CommandButton)

Common Use Cases

Here are some practical applications you can build with VB6Parse:

Code Migration Tools

Parse VB6 projects and convert them to modern languages (C#, VB.NET, Python). Extract forms, business logic, and database connections for automated conversion.

Static Analysis

Build linters and code quality tools for legacy VB6 codebases. Find deprecated API usage, detect code smells, enforce coding standards.

Documentation Generation

Automatically generate API documentation from VB6 source code. Extract function signatures, comments, and module relationships.

Code Metrics

Calculate lines of code, cyclomatic complexity, dependency graphs, and other metrics for project planning and technical debt assessment.

Form Extraction

Extract UI layouts and control hierarchies from .frm files for migration to modern UI frameworks or for visual documentation.

Code Search Tools

Build semantic search tools that understand VB6 syntax. Find all usages of a function, locate API calls, track variable usage across projects.

Next Steps

Now that you understand the basics, explore these resources:

šŸ’” Need Help?
• Check the GitHub Issues for common problems
• Review the examples directory for more code samples
• Open a discussion or issue if you encounter problems