//! rust-langrpg — RPG IV compiler CLI //! //! Parses one or more RPG IV source files using the embedded BNF grammar //! and optionally writes the resulting parse tree to an output file. //! //! ## Usage //! //! ```text //! rust-langrpg [OPTIONS] ... //! //! Arguments: //! ... RPG IV source file(s) to parse //! //! Options: //! -o Write the parse tree to this file //! -h, --help Print help //! -V, --version Print version //! ``` //! //! ## Example //! //! ```text //! cargo run --release -- -o out.txt hello.rpg //! ``` use std::{ fs, io::{self, Write}, path::PathBuf, process, }; use clap::Parser; use rust_langrpg::{load_grammar, parse_as}; // ───────────────────────────────────────────────────────────────────────────── // CLI definition // ───────────────────────────────────────────────────────────────────────────── /// RPG IV free-format compiler — parses source files and emits parse trees. #[derive(Parser, Debug)] #[command(name = "rust-langrpg", version, about, long_about = None)] struct Cli { /// RPG IV source file(s) to parse. #[arg(required = true, value_name = "SOURCES")] sources: Vec, /// Write the parse tree(s) to this file. /// If omitted the tree is not printed. #[arg(short = 'o', value_name = "OUTPUT")] output: Option, } // ───────────────────────────────────────────────────────────────────────────── // Entry point // ───────────────────────────────────────────────────────────────────────────── fn main() { let cli = Cli::parse(); // ── Load grammar ───────────────────────────────────────────────────────── let grammar = match load_grammar() { Ok(g) => g, Err(e) => { eprintln!("error: failed to load RPG IV grammar: {e}"); process::exit(1); } }; // ── Build parser ───────────────────────────────────────────────────────── let parser = match grammar.build_parser() { Ok(p) => p, Err(e) => { eprintln!("error: failed to build parser: {e}"); process::exit(1); } }; // ── Open output sink ────────────────────────────────────────────────────── // `output` is Box so we can use either a file or a sink that // discards everything when -o was not supplied. let mut output: Box = match &cli.output { Some(path) => { let file = fs::File::create(path).unwrap_or_else(|e| { eprintln!("error: cannot open output file '{}': {e}", path.display()); process::exit(1); }); Box::new(io::BufWriter::new(file)) } None => Box::new(io::sink()), }; // ── Process each source file ────────────────────────────────────────────── let mut any_error = false; for path in &cli.sources { let source = match fs::read_to_string(path) { Ok(s) => s, Err(e) => { eprintln!("error: cannot read '{}': {e}", path.display()); any_error = true; continue; } }; // Try the top-level "program" rule first; fall back to "source-file" // so the binary is useful even if only one of those rule names exists // in the grammar. let tree = parse_as(&parser, source.trim(), "program") .or_else(|| parse_as(&parser, source.trim(), "source-file")); match tree { Some(t) => { eprintln!("ok: {}", path.display()); writeln!(output, "=== {} ===", path.display()) .and_then(|_| writeln!(output, "{t}")) .unwrap_or_else(|e| { eprintln!("error: write failed: {e}"); any_error = true; }); } None => { eprintln!("error: '{}' did not match the RPG IV grammar", path.display()); any_error = true; } } } if any_error { process::exit(1); } }