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
- Rust: Version 1.70 or later (Install from rustup.rs)
- Basic Rust knowledge: Understanding of Result types, ownership, and cargo
- VB6 familiarity: Helpful but not required
Installation
Create a new Rust project
cargo new vb6parse-demo
cd vb6parse-demo
Add VB6Parse to Cargo.toml
[dependencies]
vb6parse = "0.5.1"
Verify installation
cargo build
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.
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();
}
}
}
Run the example
cargo run
Version: 1.0
Has code: true
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());
}
}
Modules:
- Utilities (Utilities.bas)
- DataAccess (DataAccess.bas)
Forms:
- MainForm.frm
Classes:
- DatabaseConnection (DatabaseConnection.cls)
References: 1
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();
}
}
}
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}'");
}
}
}
DimKeyword: 'Dim'
Whitespace: ' '
Identifier: 'x'
Whitespace: ' '
AsKeyword: 'As'
Whitespace: ' '
IntegerKeyword: 'Integer'
Whitespace: ' '
EndOfLineComment: '' Declare a variable'
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());
}
};
}
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:
š API Documentation
Complete API reference with all types, methods, and examples
š” Code Examples
More examples showing advanced parsing scenarios and techniques
š Technical Documentation
Deep dive into VB6 file formats and parser architecture
⢠Check the GitHub Issues for common problems
⢠Review the examples directory for more code samples
⢠Open a discussion or issue if you encounter problems