//! Command-line interface: argument parsing, help/version output, and fatal //! error reporting. //! //! The primary entry point is [`parse_args`], which parses [`std::env::args`] //! and returns an [`Opts`] struct. If any argument is invalid or required //! arguments are missing, it calls [`fatal`] which prints an error to `stderr` //! and exits with code `1`. use std::path::PathBuf; use yansi::Paint; /// Print the help message to `stdout`. /// /// Describes the compiler's usage, all supported options, and the `` /// positional argument. pub fn print_help() { println!( "{} {} - the bucky language compiler", "buckyc".bold(), env!("CARGO_PKG_VERSION").dim() ); println!(); println!("{}", "USAGE:".bold().yellow()); println!(" fluxc [OPTIONS] [ ...]"); println!(); println!("{}", "OPTIONS:".bold().yellow()); println!( " {}, {} Print this help message", "-h".bold(), "--help".bold() ); println!( " {}, {} Print version information", "-V".bold(), "--version".bold() ); println!( " {} Emit IR and stop (implies `-c`)", "-S".bold() ); println!( " {} Compile to object file (no linking)", "-c".bold() ); println!( " {} {} Write output to ", "-o".bold(), "".bold(), ); println!(); println!("{}", "ARGS:".bold().yellow()); println!( " {} One or more source files to compile", "".bold(), ); } /// Print the compiler version string (`buckyc `) to `stdout`. pub fn print_version() { println!("buckyc {}", env!("CARGO_PKG_VERSION")); } /// Print a formatted error message to `stderr` and exit with code `1`. /// /// This function never returns (`-> !`). Use it for unrecoverable CLI errors /// such as missing arguments or unknown flags, discovered before compilation /// begins. pub fn fatal(message: impl ToString) -> ! { eprintln!("{}: {}", "error".bold().red(), message.to_string().bold()); std::process::exit(1); } /// Parsed command-line options returned by [`parse_args`]. #[derive(Debug)] pub struct Opts { /// One or more source files to compile, in the order they were supplied. pub files: Vec, /// `-S`: emit IR and stop (implies [`no_link`](Opts::no_link)). pub emit_ir: bool, /// `-c`: compile to an object file without invoking the linker. pub no_link: bool, /// `-o `: destination path for the final output. When `None` the /// compiler chooses a default output name. pub output: Option, } /// Parse [`std::env::args`] and return the resulting [`Opts`]. /// /// Recognised flags: /// /// | Flag | Effect | /// |------|--------| /// | `-h`, `--help` | Print help and exit `0` | /// | `-V`, `--version` | Print version and exit `0` | /// | `-S` | Set [`emit_ir`](Opts::emit_ir) and [`no_link`](Opts::no_link) | /// | `-c` | Set [`no_link`](Opts::no_link) | /// | `-o ` | Set [`output`](Opts::output) | /// | `` | Append to [`files`](Opts::files) | /// /// Calls [`fatal`] (and exits) if: /// - an unknown `-`-prefixed flag is encountered, or /// - `-o` is supplied without a following argument, or /// - no source files are provided. pub fn parse_args() -> Opts { let mut files = Vec::new(); let mut no_link = false; let mut emit_ir = false; let mut output = None; let mut args = std::env::args().skip(1).peekable(); while let Some(arg) = args.next() { match arg.as_str() { "-h" | "--help" => { print_help(); std::process::exit(0); } "-V" | "--version" => { print_version(); std::process::exit(0); } "-c" => no_link = true, "-S" => { emit_ir = true; no_link = true } "-o" => match args.next() { Some(path) => output = Some(path.into()), None => fatal("option `-o` requires an argument"), }, flag if flag.starts_with('-') => { fatal(format!("unknown option `{flag}`")); } path => files.push(path.into()), } } if files.is_empty() { fatal("no input files - at least one source file is required"); } Opts { files, emit_ir, no_link, output, } }