rusty-alto 0.1.0

Weighted tree automata and interpreted regular tree grammars with Alto-compatible I/O
Documentation
use rusty_alto::{Irtg, StringAlgebra, parse_irtg};
use std::{
    env,
    error::Error,
    fs::File,
    io::{self, BufRead, IsTerminal, Write},
    time::{Duration, Instant},
};

fn main() {
    if let Err(err) = run() {
        eprintln!("error: {err}");
        std::process::exit(1);
    }
}

fn run() -> Result<(), Box<dyn Error>> {
    let Some(path) = env::args().nth(1) else {
        eprintln!("usage: rusty-alto <grammar.irtg>");
        std::process::exit(2);
    };

    let load_start = Instant::now();
    let file = File::open(&path)?;
    let irtg = parse_irtg(file)?;
    let load_time = load_start.elapsed();

    let interpretation_name = choose_string_interpretation(&irtg)
        .ok_or("IRTG does not contain a supported StringAlgebra interpretation")?;
    let interpretation = irtg.interpretation::<StringAlgebra>(&interpretation_name)?;

    eprintln!("loaded {path} in {}", format_duration(load_time));
    eprintln!("using interpretation {interpretation_name:?}");

    let stdin = io::stdin();
    let interactive = stdin.is_terminal();
    let mut stdin = stdin.lock();
    let mut line = String::new();
    let mut sentence_number = 0usize;

    if interactive {
        eprintln!("enter sentences; Ctrl-D exits");
    }

    loop {
        if interactive {
            eprint!("> ");
            io::stderr().flush()?;
        }

        line.clear();
        if stdin.read_line(&mut line)? == 0 {
            if interactive {
                eprintln!();
            }
            break;
        }

        let sentence = line.trim();
        if sentence.is_empty() {
            continue;
        }
        sentence_number += 1;

        let object_start = Instant::now();
        let value = match interpretation.parse_object(sentence) {
            Ok(value) => value,
            Err(err) => {
                println!("{sentence_number:05} [{sentence}] input-error={err}");
                continue;
            }
        };
        let object_time = object_start.elapsed();

        let parse_start = Instant::now();
        let chart = match irtg.parse([interpretation.input(value)]) {
            Ok(chart) => chart,
            Err(err) => {
                println!("{sentence_number:05} [{sentence}] parse-error={err}");
                continue;
            }
        };
        let parse_time = parse_start.elapsed();

        let top_start = Instant::now();
        let top = chart.automaton.viterbi();
        if let Some(top) = &top {
            let _ = irtg
                .grammar_signature()
                .resolve_tree(top.arena(), top.root());
        }
        let top_time = top_start.elapsed();
        let total_time = object_time + parse_time + top_time;

        println!(
            "{sentence_number:05} [{sentence}] {} parse={} top={} input={}",
            format_duration_compact(total_time),
            format_duration_compact(parse_time),
            format_duration_compact(top_time),
            format_duration_compact(object_time),
        );
    }

    Ok(())
}

fn choose_string_interpretation(irtg: &Irtg) -> Option<String> {
    let names = irtg.string_interpretation_names();
    if names.is_empty() {
        return None;
    }
    names
        .iter()
        .copied()
        .find(|&name| name == "english")
        .or_else(|| names.iter().copied().find(|&name| name == "i"))
        .or_else(|| names.first().copied())
        .map(str::to_owned)
}

fn format_duration_compact(duration: Duration) -> String {
    let nanos = duration.as_nanos();
    if nanos < 1_000 {
        format!("{nanos}ns")
    } else if nanos < 1_000_000 {
        format!("{:.2}us", nanos as f64 / 1_000.0)
    } else if nanos < 1_000_000_000 {
        format!("{:.2}ms", nanos as f64 / 1_000_000.0)
    } else {
        format!("{:.3}s", duration.as_secs_f64())
    }
}

fn format_duration(duration: Duration) -> String {
    let nanos = duration.as_nanos();
    if nanos < 1_000 {
        format!("{nanos} ns")
    } else if nanos < 1_000_000 {
        format!("{:.2} us", nanos as f64 / 1_000.0)
    } else if nanos < 1_000_000_000 {
        format!("{:.2} ms", nanos as f64 / 1_000_000.0)
    } else {
        format!("{:.3} s", duration.as_secs_f64())
    }
}