regex-cli 0.2.3

A command line tool for debugging, ad hoc benchmarking and generating regular expressions.
use std::io::{stdout, Write};

use crate::{
    args,
    util::{self, Table},
};

mod dfa;
mod literal;

pub fn run(p: &mut lexopt::Parser) -> anyhow::Result<()> {
    const USAGE: &'static str = "\
Prints the debug representation of various things from regex-automata and
regex-syntax.

This is useful for ad hoc interactions with objects on the command line. In
general, most objects support the full suite of configuration available in code
via the crate.

USAGE:
    regex-cli debug <command> ...

COMMANDS:
    ast        Print the debug representation of an AST.
    dense      Print the debug representation of a dense DFA.
    hir        Print the debug representation of an HIR.
    literal    Print the debug representation of extracted literals.
    onepass    Print the debug representation of a one-pass DFA.
    sparse     Print the debug representation of a sparse DFA.
    thompson   Print the debug representation of a Thompson NFA.
";

    let cmd = args::next_as_command(USAGE, p)?;
    match &*cmd {
        "ast" => run_ast(p),
        "dense" => dfa::run_dense(p),
        "hir" => run_hir(p),
        "literal" => literal::run(p),
        "onepass" => run_onepass(p),
        "sparse" => dfa::run_sparse(p),
        "thompson" => run_thompson(p),
        unk => anyhow::bail!("unrecognized command '{unk}'"),
    }
}

fn run_ast(p: &mut lexopt::Parser) -> anyhow::Result<()> {
    const USAGE: &'static str = "\
Prints the debug representation of an abstract syntax tree (AST).

USAGE:
    regex-cli debug ast <pattern>

TIP:
    use -h for short docs and --help for long docs

OPTIONS:
%options%
";

    let mut common = args::common::Config::default();
    let mut patterns = args::patterns::Config::positional();
    let mut syntax = args::syntax::Config::default();
    args::configure(p, USAGE, &mut [&mut common, &mut patterns, &mut syntax])?;

    let pats = patterns.get()?;
    anyhow::ensure!(
        pats.len() == 1,
        "only one pattern is allowed, but {} were given",
        pats.len(),
    );

    let mut table = Table::empty();
    let (asts, time) = util::timeitr(|| syntax.asts(&pats))?;
    table.add("parse time", time);
    if common.table() {
        table.print(stdout())?;
    }
    if !common.quiet {
        if common.table() {
            writeln!(stdout(), "")?;
        }
        writeln!(stdout(), "{:#?}", &asts[0])?;
    }
    Ok(())
}

fn run_hir(p: &mut lexopt::Parser) -> anyhow::Result<()> {
    const USAGE: &'static str = "\
Prints the debug representation of a high-level intermediate representation
(HIR).

USAGE:
    regex-cli debug hir <pattern>

TIP:
    use -h for short docs and --help for long docs

OPTIONS:
%options%
";

    let mut common = args::common::Config::default();
    let mut patterns = args::patterns::Config::positional();
    let mut syntax = args::syntax::Config::default();
    args::configure(p, USAGE, &mut [&mut common, &mut patterns, &mut syntax])?;

    let pats = patterns.get()?;
    anyhow::ensure!(
        pats.len() == 1,
        "only one pattern is allowed, but {} were given",
        pats.len(),
    );

    let mut table = Table::empty();
    let (asts, time) = util::timeitr(|| syntax.asts(&pats))?;
    table.add("parse time", time);
    let (hirs, time) = util::timeitr(|| syntax.hirs(&pats, &asts))?;
    table.add("translate time", time);
    if common.table() {
        table.print(stdout())?;
    }
    if !common.quiet {
        if common.table() {
            writeln!(stdout(), "")?;
        }
        writeln!(stdout(), "{:#?}", &hirs[0])?;
    }
    Ok(())
}

fn run_onepass(p: &mut lexopt::Parser) -> anyhow::Result<()> {
    const USAGE: &'static str = "\
Prints the debug representation of a one-pass DFA.

USAGE:
    regex-cli debug onepass [<pattern> ...]

TIP:
    use -h for short docs and --help for long docs

OPTIONS:
%options%
";

    let mut common = args::common::Config::default();
    let mut patterns = args::patterns::Config::positional();
    let mut syntax = args::syntax::Config::default();
    let mut thompson = args::thompson::Config::default();
    let mut onepass = args::onepass::Config::default();
    args::configure(
        p,
        USAGE,
        &mut [
            &mut common,
            &mut patterns,
            &mut syntax,
            &mut thompson,
            &mut onepass,
        ],
    )?;

    let pats = patterns.get()?;
    let mut table = Table::empty();
    let (asts, time) = util::timeitr(|| syntax.asts(&pats))?;
    table.add("parse time", time);
    let (hirs, time) = util::timeitr(|| syntax.hirs(&pats, &asts))?;
    table.add("translate time", time);
    let (nfa, time) = util::timeitr(|| thompson.from_hirs(&hirs))?;
    table.add("compile nfa time", time);
    let (dfa, time) = util::timeitr(|| onepass.from_nfa(&nfa))?;
    table.add("compile one-pass DFA time", time);
    table.add("memory", dfa.memory_usage());
    table.add("states", dfa.state_len());
    table.add("pattern len", dfa.pattern_len());
    table.add("alphabet len", dfa.alphabet_len());
    table.add("stride", dfa.stride());
    if common.table() {
        table.print(stdout())?;
    }
    if !common.quiet {
        if common.table() {
            writeln!(stdout(), "")?;
        }
        writeln!(stdout(), "{dfa:?}")?;
    }
    Ok(())
}

fn run_thompson(p: &mut lexopt::Parser) -> anyhow::Result<()> {
    const USAGE: &'static str = "\
Prints the debug representation of a Thompson NFA.

USAGE:
    regex-cli debug thompson [<pattern> ...]

TIP:
    use -h for short docs and --help for long docs

OPTIONS:
%options%
";

    let mut common = args::common::Config::default();
    let mut patterns = args::patterns::Config::positional();
    let mut syntax = args::syntax::Config::default();
    let mut thompson = args::thompson::Config::default();
    args::configure(
        p,
        USAGE,
        &mut [&mut common, &mut patterns, &mut syntax, &mut thompson],
    )?;

    let pats = patterns.get()?;
    let mut table = Table::empty();
    let (asts, time) = util::timeitr(|| syntax.asts(&pats))?;
    table.add("parse time", time);
    let (hirs, time) = util::timeitr(|| syntax.hirs(&pats, &asts))?;
    table.add("translate time", time);
    let (nfa, time) = util::timeitr(|| thompson.from_hirs(&hirs))?;
    table.add("compile nfa time", time);
    table.add("memory", nfa.memory_usage());
    table.add("states", nfa.states().len());
    table.add("pattern len", nfa.pattern_len());
    table.add("capture len", nfa.group_info().all_group_len());
    table.add("has empty?", nfa.has_empty());
    table.add("is utf8?", nfa.is_utf8());
    table.add("is reverse?", nfa.is_reverse());
    table.add(
        "line terminator",
        bstr::BString::from(&[nfa.look_matcher().get_line_terminator()][..]),
    );
    table.add("lookset any", nfa.look_set_any());
    table.add("lookset prefix any", nfa.look_set_prefix_any());
    if common.table() {
        table.print(stdout())?;
    }
    if !common.quiet {
        if common.table() {
            writeln!(stdout(), "")?;
        }
        writeln!(stdout(), "{nfa:?}")?;
    }
    Ok(())
}