I'm making a simple macro for CLI (something much simpler than clap
). A simple version of it already works:
cli!(param1, param2, param3, "help text");
Parses 3 arguments into param1..3. If there are < 3 args, shows help text and exits.
I tried to add optional type annotation that will make it parse()
the args into the named type. Expected usage:
cli!(param1, param2:u32, param3:f64, "help text");
(param1
becomes String by default.)
This requires repetitions of tt
's, which can be parsed with TT Munchers.
Here's what I wrote. The compiler complains that in a nested pattern (@phase2 ...) => { __ here __ }
a var is not defined. Even though the fully expanded macro makes a seemingly valid code (the var is mentioned where it is already defined).
Down in this macro book, I read that I can make invalid token trees that will be validated only after the entire macro finished, but I don't see if it can fix the undefined var problem.
The code:
use std::{ffi::OsString, str::FromStr}; // importing for clarity of code in the example
macro_rules! cli {
// last bit -- the help text
(@phase2 $help_text:literal) => {
let help_text = $help_text;
};
// argument with type annotation
(@phase2 $var_name:ident: $var_type:ty, $($token2:tt)*) => {
let $var_name:$var_type = __parse_var(stringify!($var_name), &mut args, help_text).unwrap();
cli!(@phase2 $($token2)*)
};
// argument without type annotation, string by default
(@phase2 $var_name:ident, $($token2:tt)+) => {
let $var_name: String = __parse_var(stringify!($var_name), &mut args, help_text).unwrap();
cli!(@phase2 $($token2)+)
};
// ENTRY pattern
($($var_name:tt)*) => {
fn __exit(help_text: &str) {
println!("{}", help_text);
std::process::exit(64)
}
let mut args: Vec<OsString> = vec!["value1".into(), "value2".into()]; // std::env::args_os().collect();
args.reverse();
args.pop();
fn __parse_var<T: FromStr>(name: &str, args: &mut Vec<OsString>, help_text: &str) -> Option<T> {
let Some(tmp_var) = args.pop() else {
__exit(help_text);
};
let Ok(result) = tmp_var.to_str().unwrap().parse::<T>() else {
__exit(help_text);
};
Some(result)
}
cli!(@phase2 $($var_name)*)
};
}
fn main() {
cli!(param1, param2, "Help text");
}
Error:
error[E0425]: cannot find value `args` in this scope
--> src/main.rs:15:67
|
15 | let $var_name: String = __parse_var(stringify!($var_name), &mut args, help_text).unwrap();
| ^^^^ not found in this scope