Files
langrpg/src/main.rs

133 lines
5.0 KiB
Rust
Raw Normal View History

2026-03-12 21:09:49 -07:00
//! rust-langrpg — RPG IV compiler CLI
2026-03-12 20:55:01 -07:00
//!
2026-03-12 21:09:49 -07:00
//! 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] <SOURCES>...
//!
//! Arguments:
//! <SOURCES>... RPG IV source file(s) to parse
//!
//! Options:
//! -o <OUTPUT> 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
//! ```
2026-03-12 20:55:01 -07:00
2026-03-12 21:09:49 -07:00
use std::{
fs,
io::{self, Write},
path::PathBuf,
process,
};
2026-03-12 20:55:01 -07:00
2026-03-12 21:09:49 -07:00
use clap::Parser;
use rust_langrpg::{load_grammar, parse_as};
2026-03-12 20:55:01 -07:00
// ─────────────────────────────────────────────────────────────────────────────
2026-03-12 21:09:49 -07:00
// CLI definition
2026-03-12 20:55:01 -07:00
// ─────────────────────────────────────────────────────────────────────────────
2026-03-12 21:09:49 -07:00
/// 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<PathBuf>,
/// Write the parse tree(s) to this file.
/// If omitted the tree is not printed.
#[arg(short = 'o', value_name = "OUTPUT")]
output: Option<PathBuf>,
2026-03-12 20:55:01 -07:00
}
// ─────────────────────────────────────────────────────────────────────────────
2026-03-12 21:09:49 -07:00
// Entry point
2026-03-12 20:55:01 -07:00
// ─────────────────────────────────────────────────────────────────────────────
2026-03-05 22:28:14 -08:00
fn main() {
2026-03-12 21:09:49 -07:00
let cli = Cli::parse();
2026-03-12 20:55:01 -07:00
2026-03-12 21:09:49 -07:00
// ── Load grammar ─────────────────────────────────────────────────────────
2026-03-12 20:55:01 -07:00
let grammar = match load_grammar() {
2026-03-12 21:09:49 -07:00
Ok(g) => g,
2026-03-12 20:55:01 -07:00
Err(e) => {
2026-03-12 21:09:49 -07:00
eprintln!("error: failed to load RPG IV grammar: {e}");
process::exit(1);
2026-03-12 20:55:01 -07:00
}
};
2026-03-12 21:09:49 -07:00
// ── Build parser ─────────────────────────────────────────────────────────
2026-03-12 20:55:01 -07:00
let parser = match grammar.build_parser() {
2026-03-12 21:09:49 -07:00
Ok(p) => p,
2026-03-12 20:55:01 -07:00
Err(e) => {
2026-03-12 21:09:49 -07:00
eprintln!("error: failed to build parser: {e}");
process::exit(1);
2026-03-12 20:55:01 -07:00
}
};
2026-03-12 21:09:49 -07:00
// ── Open output sink ──────────────────────────────────────────────────────
// `output` is Box<dyn Write> so we can use either a file or a sink that
// discards everything when -o was not supplied.
let mut output: Box<dyn Write> = 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))
2026-03-12 20:55:01 -07:00
}
2026-03-12 21:09:49 -07:00
None => Box::new(io::sink()),
};
2026-03-12 20:55:01 -07:00
2026-03-12 21:09:49 -07:00
// ── 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;
}
2026-03-12 20:55:01 -07:00
}
}
2026-03-12 21:09:49 -07:00
if any_error {
process::exit(1);
2026-03-12 20:55:01 -07:00
}
2026-03-05 22:28:14 -08:00
}