use crate::terminal_colors::{END, RED};
use std::{collections::HashMap, env::Args};
pub struct Cmd {
pub options: HashMap<CmdOptions, Option<String>>,
pub free_args: Vec<String>,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CmdOptions {
Help,
OutputPath,
TopLevel,
Compile,
Optimize,
Exec,
Verbose,
}
#[derive(Debug)]
pub enum CmdError {
OptionExpectedArgument(String),
UnknownOption(String),
SingleDashLongOption(String),
}
pub fn parse_arguments(args: Args) -> Result<Cmd, CmdError> {
let mut free_args = vec![];
let mut options = HashMap::new();
let mut args = args.into_iter();
while let Some(arg) = args.next() {
match extract_option(&arg)? {
None => free_args.push(arg),
Some(option) if !option.accepts_arg() => _ = options.insert(option, None),
Some(option) => {
if let Some(arg) = args.next()
&& !is_option(&arg)
{
options.insert(option, Some(arg));
} else {
return Err(CmdError::OptionExpectedArgument(arg));
}
}
}
}
Ok(Cmd { options, free_args })
}
pub fn print_help() -> &'static str {
r#"OCaml to LLVM IR compiler
Usage: oonta [OPTIONS] <file>
Options:
-h, --help Display this information.
-o <file>, --output <file> Write output to file.
-t, --top-level Output main function instead of caml_main.
-O, --opt Optimize LLVM IR output.
-c, --compile Compile LLVM IR to object file using LLVM.
-e, --exec Compile LLVM IR to executable. Turning this
option on implicitly turns on both '-t' and
'-c' options.
-v, --verbose Print steps.
"#
}
fn extract_option(arg: &str) -> Result<Option<CmdOptions>, CmdError> {
if !is_option(arg) {
return Ok(None);
}
if is_long_option(arg) {
match CmdOptions::try_from(&arg[2..]) {
Ok(opt) => Ok(Some(opt)),
Err(e) => Err(e),
}
} else if arg.chars().count() == 2 {
let ch = arg.chars().nth(1).unwrap();
match CmdOptions::try_from(ch) {
Ok(opt) => Ok(Some(opt)),
Err(e) => Err(e),
}
} else {
Err(CmdError::SingleDashLongOption(arg.to_string()))
}
}
fn is_option(arg: &str) -> bool {
arg.starts_with("-")
}
fn is_long_option(arg: &str) -> bool {
arg.starts_with("--")
}
impl TryFrom<&str> for CmdOptions {
type Error = CmdError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"help" => Ok(Self::Help),
"output" => Ok(Self::OutputPath),
"top-level" => Ok(Self::TopLevel),
"opt" => Ok(Self::Optimize),
"compile" => Ok(Self::Compile),
"exec" => Ok(Self::Exec),
"verbose" => Ok(Self::Verbose),
_ => Err(CmdError::UnknownOption(format!("--{value}"))),
}
}
}
impl TryFrom<char> for CmdOptions {
type Error = CmdError;
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'h' => Ok(Self::Help),
'o' => Ok(Self::OutputPath),
't' => Ok(Self::TopLevel),
'O' => Ok(Self::Optimize),
'c' => Ok(Self::Compile),
'e' => Ok(Self::Exec),
'v' => Ok(Self::Verbose),
_ => Err(CmdError::UnknownOption(format!("-{value}"))),
}
}
}
impl CmdOptions {
fn accepts_arg(&self) -> bool {
match self {
CmdOptions::OutputPath => true,
CmdOptions::Help => false,
CmdOptions::TopLevel => false,
CmdOptions::Compile => false,
CmdOptions::Exec => false,
CmdOptions::Verbose => false,
CmdOptions::Optimize => false,
}
}
}
impl std::error::Error for CmdError {}
impl std::fmt::Display for CmdError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CmdError::OptionExpectedArgument(arg) => {
write!(f, "{RED}Error{END}: option \"{arg}\" expected an argument")
}
CmdError::UnknownOption(arg) => write!(f, "Error: unknown option \"{arg}\""),
CmdError::SingleDashLongOption(arg) => {
write!(
f,
"{RED}Error{END}: singe dash options (\"{arg}\") only accepts a single character"
)
}
}
}
}