mxsh 0.2.0

Embeddable POSIX-style shell parser and runtime
Documentation
use std::hint::black_box;
use std::time::Instant;

use mxsh::parser::Parser;

fn repeated_assignments(lines: usize) -> String {
    let mut script = String::with_capacity(lines * 32);
    for i in 0..lines {
        script.push_str(&format!("VAR_{i}=$(({} + {}))\n", i, i + 1));
    }
    script
}

fn nested_loops(depth: usize, width: usize) -> String {
    let mut script = String::new();
    for level in 0..depth {
        script.push_str(&format!("for i{level} in"));
        for item in 0..width {
            script.push_str(&format!(" {item}"));
        }
        script.push_str("; do\n");
    }
    script.push_str("echo done\n");
    for _ in 0..depth {
        script.push_str("done\n");
    }
    script
}

fn heredoc_fanout(count: usize, lines_per_doc: usize) -> String {
    let mut script = String::new();
    for idx in 0..count {
        script.push_str(&format!("cat <<EOF_{idx}\n"));
        for line in 0..lines_per_doc {
            script.push_str(&format!("line_{idx}_{line} $HOME\n"));
        }
        script.push_str(&format!("EOF_{idx}\n"));
    }
    script
}

fn bench_case(name: &str, script: &str, iterations: usize) {
    let start = Instant::now();
    let mut parsed_bytes = 0usize;
    for _ in 0..iterations {
        let mut parser = Parser::from_string(script);
        let program = parser
            .parse_program()
            .unwrap_or_else(|err| panic!("{name} should parse: {err}"));
        parsed_bytes += black_box(program.body().len());
    }
    let elapsed = start.elapsed();
    let total_bytes = script.len() * iterations;
    println!(
        "{name}: iterations={iterations} bytes_per_script={} total_bytes={total_bytes} elapsed_ms={} commands={parsed_bytes}",
        script.len(),
        elapsed.as_millis(),
    );
}

fn main() {
    let cases = [
        ("assignments-10k", repeated_assignments(10_000), 20),
        ("nested-loops", nested_loops(24, 8), 200),
        ("heredoc-fanout", heredoc_fanout(128, 8), 40),
    ];

    for (name, script, iterations) in cases {
        bench_case(name, &script, iterations);
    }
}