Skip to content

A lightweight, regex-based Ada parser in Rust. Extracts packages, procedures, loops, types, and more into an AST for analysis, linting, and standards-checking.

Notifications You must be signed in to change notification settings

frontinus/ADA_Standards_Lib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

21 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ADA_Standards

Rust Latest version Documentation License

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.

✨ Features

Code Preprocessing

  • Smart Code Cleaning: Strips comments and string literals while preserving code structure and line numbers
  • Tab Normalization: Converts tabs to spaces for consistent parsing

Comprehensive Ada Construct Extraction

  • 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, declare blocks
  • Variables: Complete declaration parsing with types and default values

Intelligent Parsing

  • Tree-Based AST: Uses indextree for efficient parent-child relationships
  • Structured Parameters: Parses procedure/function arguments into typed ArgumentData structs
  • Expression Trees: Converts conditions into hierarchical ConditionExpr with support for:
    • Binary operators: and, or, and then, or else, xor
    • Comparison: <, >, <=, >=, =, /=
    • Membership: in, not in
    • Unary: not
    • Proper precedence and parenthesis handling
  • Automatic End Association: Matches end statements to their corresponding blocks

Analysis-Ready Output

  • 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 case alternatives and exit when conditions after initial parse

πŸ“¦ Installation

Add to your Cargo.toml:

[dependencies]
ADA_Standards = "1.2.2"

Or use cargo:

cargo add ADA_Standards

πŸš€ Quick Start

use 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(())
}

πŸ“– Usage Examples

Extract Specific Constructs

// 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)?;

Analyze Conditions

// 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);
        }
        _ => {}
    }
}

Check Coding Standards

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);
                    }
                }
            }
        }
    }
}

Traverse the AST

// 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);
}

πŸ” Key Data Structures

NodeData

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 source
  • is_body: Whether it's a body or spec
  • arguments: Parsed parameters
  • conditions: Parsed expressions
  • cases: Case alternatives (for case statements)

AST

The main tree structure with methods:

  • new(): Create from node list
  • build(): Construct parent-child relationships
  • populate_cases(): Extract when clauses
  • populate_simple_loop_conditions(): Extract exit when conditions
  • find_node_by_name_and_type(): Search helper

ConditionExpr

Parsed condition with:

  • list: Flat list of all sub-expressions
  • albero: Root of expression tree

πŸ“Š Supported Constructs

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

πŸ§ͺ Testing

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_ada

The 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

🎯 Use Cases

  • 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

βš™οΈ Architecture

  1. Regex Extraction: Pattern matching identifies Ada constructs
  2. Node Creation: Each match becomes a NodeData with metadata
  3. Sorting: Nodes sorted by start position
  4. Tree Building: Stack-based algorithm establishes parent-child relationships
  5. End Association: Matches end statements to opening blocks
  6. Post-Processing: Populates derived data (cases, conditions)

🀝 Contributing

Contributions are welcome! To contribute:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes with tests
  4. Run the test suite (cargo test)
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

Please ensure:

  • All tests pass
  • New features have corresponding tests
  • Code follows Rust conventions
  • Documentation is updated

πŸ“ License

Licensed under the MIT License. See LICENSE-MIT for details.

πŸ‘€ Author

Francesco Abate

πŸ™ Acknowledgments

Built with:

πŸ“š Further Reading


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.


About

A lightweight, regex-based Ada parser in Rust. Extracts packages, procedures, loops, types, and more into an AST for analysis, linting, and standards-checking.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published