151 lines
4.5 KiB
Rust
151 lines
4.5 KiB
Rust
//! 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 `<file>`
|
|
/// 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] <file> [<file> ...]");
|
|
|
|
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 <file>",
|
|
"-o".bold(),
|
|
"<file>".bold(),
|
|
);
|
|
|
|
println!();
|
|
println!("{}", "ARGS:".bold().yellow());
|
|
println!(
|
|
" {} One or more source files to compile",
|
|
"<file>".bold(),
|
|
);
|
|
}
|
|
|
|
/// Print the compiler version string (`buckyc <version>`) 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<PathBuf>,
|
|
/// `-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 <file>`: destination path for the final output. When `None` the
|
|
/// compiler chooses a default output name.
|
|
pub output: Option<PathBuf>,
|
|
}
|
|
|
|
/// 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 <file>` | Set [`output`](Opts::output) |
|
|
/// | `<file>` | 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,
|
|
}
|
|
}
|