A powerful, lightweight regex-based Ada parser written in Rust. Extract packages, procedures, types, control flow, and more from Ada source code into a traversable Abstract Syntax Tree (AST) for analysis, linting, and coding standards enforcement.
- Smart Code Cleaning: Strips comments and string literals while preserving code structure and line numbers
- Tab Normalization: Converts tabs to spaces for consistent parsing
- Packages: Specs and bodies, including nested packages
- Subprograms: Procedures and functions (specs, bodies, and generic instantiations)
- Tasks & Entries: Concurrent programming constructs with guard conditions
- Types: Records, arrays, derived types, subtypes, enumerations, and representation clauses
- Control Flow:
if/elsif/else,case,loop,while,for,exit when,declareblocks - Variables: Complete declaration parsing with types and default values
- Tree-Based AST: Uses
indextreefor efficient parent-child relationships - Structured Parameters: Parses procedure/function arguments into typed
ArgumentDatastructs - Expression Trees: Converts conditions into hierarchical
ConditionExprwith support for:- Binary operators:
and,or,and then,or else,xor - Comparison:
<,>,<=,>=,=,/= - Membership:
in,not in - Unary:
not - Proper precedence and parenthesis handling
- Binary operators:
- Automatic End Association: Matches
endstatements to their corresponding blocks
- Precise Location Tracking: Line numbers, character indices, and column positions
- Metadata Capture: Distinguishes specs from bodies, captures type kinds, loop directions, etc.
- Post-Processing: Populate
casealternatives andexit whenconditions after initial parse
Add to your Cargo.toml:
[dependencies]
ADA_Standards = "1.2.2"Or use cargo:
cargo add ADA_Standardsuse ADA_Standards::{AST, ASTError};
use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Read Ada source file
let code_text = fs::read_to_string("my_ada_file.adb")?;
// 2. Clean the code (removes comments, preserves structure)
let cleaned_code = AST::clean_code(&code_text);
// 3. Extract all nodes
let nodes = AST::extract_all_nodes(&cleaned_code)?;
// 4. Build the AST
let mut ast = AST::new(nodes);
ast.build(&cleaned_code)?;
// 5. Run post-processing
ast.populate_cases(&cleaned_code)?;
ast.populate_simple_loop_conditions(&cleaned_code)?;
// 6. Analyze the tree
ast.print_tree();
// Find a specific procedure
if let Some(proc_id) = ast.find_node_by_name_and_type("My_Procedure", "ProcedureNode") {
println!("\nAnalyzing 'My_Procedure':");
// Access node data
let proc_node = ast.arena().get(proc_id).unwrap().get();
println!(" Lines: {} to {}",
proc_node.start_line.unwrap(),
proc_node.end_line.unwrap()
);
// Check if it's a body or spec
if proc_node.is_body == Some(true) {
println!(" This is a procedure body");
}
// Inspect parameters
if let Some(args) = &proc_node.arguments {
println!(" Parameters:");
for arg in args {
println!(" - {}: {} (mode: {})",
arg.name, arg.data_type, arg.mode
);
}
}
// Traverse children
println!("\n Children:");
for child_id in proc_id.children(ast.arena()) {
let child = ast.arena().get(child_id).unwrap().get();
println!(" - {} ({})", child.name, child.node_type);
}
}
Ok(())
}// Extract only packages
let packages = AST::extract_packages(&cleaned_code)?;
for pkg in &packages {
println!("Package: {}", pkg.name);
if pkg.is_body == Some(true) {
println!(" (body)");
}
}
// Extract procedures and functions
let subprograms = AST::extract_procedures_functions(&cleaned_code)?;
// Extract control flow
let loops = AST::extract_simple_loops(&cleaned_code)?;
let while_loops = AST::extract_while_loops(&cleaned_code)?;
let for_loops = AST::extract_for_loops(&cleaned_code)?;// Parse a condition expression
let condition = AST::parse_condition_expression("X > 10 and Y < 20");
// Access the expression tree
if let Some(root) = &condition.albero {
AST::leggitree(root, 0, "Root: "); // Print tree structure
}
// Access the flat list
for expr in &condition.list {
match expr {
Expression::Binary(bin) => {
println!("Binary op: {:?}", bin.op);
}
Expression::Literal(lit) => {
println!("Literal: {}", lit);
}
_ => {}
}
}use indextree::NodeId;
// Example: Find procedures without documentation comments
fn check_undocumented_procedures(ast: &AST, code: &str) {
for node_id in ast.root_id().descendants(ast.arena()) {
let node = ast.arena().get(node_id).unwrap().get();
if node.node_type == "ProcedureNode" && node.is_body == Some(true) {
if let Some(start_line) = node.start_line {
// Check if previous line is a comment
let lines: Vec<&str> = code.lines().collect();
if start_line > 1 {
let prev_line = lines[start_line - 2].trim();
if !prev_line.starts_with("--") {
println!("Warning: Procedure '{}' at line {} lacks documentation",
node.name, start_line);
}
}
}
}
}
}// Visit all nodes in tree order
for node_id in ast.root_id().descendants(ast.arena()) {
let node = ast.arena().get(node_id).unwrap().get();
let depth = node_id.ancestors(ast.arena()).count() - 1;
println!("{}{} - {}",
" ".repeat(depth),
node.node_type,
node.name
);
}
// Find all children of a node
if let Some(pkg_id) = ast.find_node_by_name_and_type("MyPackage", "PackageNode") {
for child_id in pkg_id.children(ast.arena()) {
let child = ast.arena().get(child_id).unwrap().get();
println!("Child: {} ({})", child.name, child.node_type);
}
}
// Get parent of a node
if let Some(parent_id) = some_node_id.ancestors(ast.arena()).nth(1) {
let parent = ast.arena().get(parent_id).unwrap().get();
println!("Parent: {}", parent.name);
}Represents a single Ada construct with fields like:
name: Identifier (e.g., "MyProcedure")node_type: Type of construct (e.g., "ProcedureNode", "IfStatement")start_line,end_line: Location in sourceis_body: Whether it's a body or specarguments: Parsed parametersconditions: Parsed expressionscases: Case alternatives (forcasestatements)
The main tree structure with methods:
new(): Create from node listbuild(): Construct parent-child relationshipspopulate_cases(): Extractwhenclausespopulate_simple_loop_conditions(): Extractexit whenconditionsfind_node_by_name_and_type(): Search helper
Parsed condition with:
list: Flat list of all sub-expressionsalbero: Root of expression tree
| Construct | Node Type | Notes |
|---|---|---|
| Package spec/body | PackageNode |
Nested packages supported |
| Procedure spec/body | ProcedureNode |
Generic instantiations |
| Function spec/body | FunctionNode |
Return types parsed |
| Task spec/body | TaskNode |
|
| Entry spec/body | EntryNode |
Guard conditions supported |
| Type declaration | TypeDeclaration |
Records, arrays, derived, enums |
| Subtype | TypeDeclaration |
Category: "subtype" |
| Representation clause | TypeDeclaration |
for...use record, for...use at |
| Variable | VariableDeclaration |
Multiple per line, with defaults |
| If/elsif/else | IfStatement, ElsifStatement, ElseStatement |
|
| Case | CaseStatement |
when clauses extracted |
| Simple loop | SimpleLoop |
exit when parsed |
| While loop | WhileLoop |
Condition parsed |
| For loop | ForLoop |
Range/reverse/discrete types |
| Declare block | DeclareNode |
The project includes comprehensive tests:
# Run all tests
cargo test
# Run with output
cargo test -- --nocapture
# Run specific test
cargo test test_full_integration_on_blob_adaThe test suite validates:
- Individual extractors (packages, procedures, loops, etc.)
- Expression parser with complex precedence
- Code cleaning (comments, strings, tabs)
- Full end-to-end parsing of a large Ada file (
blop.ada) - Tree structure verification
- Linting: Check coding standards (naming conventions, documentation, complexity)
- Metrics: Calculate cyclomatic complexity, lines of code, nesting depth
- Refactoring: Identify code smells, unused declarations
- Documentation: Auto-generate interface docs from specs
- Migration: Analyze legacy code for modernization
- Education: Teach Ada syntax and structure
- Regex Extraction: Pattern matching identifies Ada constructs
- Node Creation: Each match becomes a
NodeDatawith metadata - Sorting: Nodes sorted by start position
- Tree Building: Stack-based algorithm establishes parent-child relationships
- End Association: Matches
endstatements to opening blocks - Post-Processing: Populates derived data (cases, conditions)
Contributions are welcome! To contribute:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes with tests
- Run the test suite (
cargo test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please ensure:
- All tests pass
- New features have corresponding tests
- Code follows Rust conventions
- Documentation is updated
Licensed under the MIT License. See LICENSE-MIT for details.
Francesco Abate
- Computer Engineer specializing in software, embedded programming, cybersecurity, and AI
- Currently mastering Rust and seeking opportunities in the field
- Website: https://frontinus.github.io/
- Email: [email protected]
- LinkedIn: Connect with me
Built with:
- indextree - Tree data structure
- regex - Pattern matching
- fancy-regex - Advanced regex features
- lazy_static - Static initialization
Note: This is a parser for analysis purposes, not a full Ada compiler. It's designed to be fast and flexible for tooling, but may not handle all edge cases of the Ada language specification.