Construct ident from string/int in declarative macro - rust

I have a dozen crates all with two functions part1() and part2(). For each of those, I want to print the return value of both functions. To not repeat myself, I wrote a little macro, so my main crate's main() looks like this:
fn main() {
macro_rules! print_day {
($day:ident) => {
let day = stringify!($day).replace('d', "");
println!("Day {day} Part 1: {}", $day::part1());
println!("Day {day} Part 2: {}", $day::part2());
};
}
print_day!(d01);
print_day!(d02);
print_day!(d03);
// list goes on..
}
Is it possible to construct an ident from a string or int in a declarative macro, so I could instead do like below? All I've found is either for proc macros, using additional packages or the other way round.
fn main() {
macro_rules! print_day {
($day:expr) => {
let daystr = format!("{:02}", $day);
let dayident = ???;
println!("Day {daystr} Part 1: {}", $dayident::part1());
println!("Day {daystr} Part 2: {}", $dayident::part2());
};
}
for day in 0..=num_days {
print_day!(day);
}
}

Using concat_idents you can glue identifiers together, but unfortunately it does not (yet?) allow non-identifiers such as numbers.
#![feature(concat_idents)]
fn m_1() {
println!("Hi");
}
macro_rules! m {
($n:ident) => {
concat_idents!(m, $n)();
};
}
fn main() {
// underscore is needed to make it into an identifier
m!(_1);
}
So we need to list the crates manually, just like we list the functions manually.
I think something like below should work, but it does not.
macro_rules! call_all {
(#funs $crat:ident $($fun:ident)+) => {{
$($crat::$fun();)*
}};
($($crat:ident)+ # $($fun:ident)+) => {{
// error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
$(call_all!(#funs $crat $($fun)*);)*
// ^^^^^^
}};
}
fn main() {
call_all!(tokio rand # m_1 m_2)
}

You can use the seq-macro crate to simplify that:
fn main() {
seq_macro::seq!(day in 1..=25 {
println!("Day {} Part 1: {}", day, d~day::part1());
println!("Day {} Part 2: {}", day, d~day::part2());
});
}
Sadly, it does not support formatting with leading zeroes (01, 02, ...) or using a variable for the number of days. The first could be fixed with a custom proc macro, the second could not.

Related

Macro to match specific identifiers for inline lookups

Okay, writing my absolute first project in Rust. So, I have something like the following sort of setup:
use phf;
use std::str;
struct Values {
a: Option<char>,
b: Option<char>,
c: Option<char>
}
static MAPPING: phf::Map<&'static str, Values> = phf::phf_map! {
"some_key" => Values {
a: Some('a'),
b: Some('b'),
c: None
},
"some_other_key" => Values {
a: Some('1'),
b: None,
c: None
},
"some_third_key" => Values {
a: None,
b: Some('x'),
c: Some('y')
}
}
static NULL_VALUES: Values = Values {
a: None,
b: None,
c: None
}
// Should return a &str for any given key/val
#[macro_export]
macro_rules! get_value {
($key: ident, $val: ident) => {{
use crate::values::MAPPING;
use std::str;
let result = MAPPING.get("$key");
if let Some(r) = result {
if let Some(c) = r.$val {
if let Ok(s) = str::from_utf8(%[c as u8]) { s } else { "" }
} else { "" }
} else { "" }
}}
}
Which, it works, but it's just so much code and seeming like a whole lot of runtime overhead for no other reason than to organise some static values to avoid having to remember them all (in reality there are quite a lot and they're all raw codepoints). What I would love to be able to do is to just have a macro that takes a specific key/val and simply inlines either a known value or an empty value, but as far as I can tell there isn't any way to match a macro on a specific identifier, only any identifier... Is there any way that I can move all these lookups from runtime to compile time?
Macros can pattern match against specific identifiers — just don't use $.
macro_rules! get_value {
(some_key, a) => { Some('a') };
(some_key, b) => { Some('b') };
(some_key, c) => { None };
(some_other_key, a) => { Some(1) };
// ...
}
However, are you sure you don't want to just define a bunch of constants?
const SOME_KEY_A: Option<char> = Some('a');
const SOME_KEY_B: Option<char> = Some('b');
Or expose the Values struct you already designed, which would then be accessed like SOME_KEY.a:
const SOME_KEY: Values = Values {
a: Some('a'),
b: Some('b'),
c: None
};
That way, readers don't have to understand your macro to know that the data is just a constant. This will make your code easier to read and modify.

Why can't Rust find method for enum generated using proc_macro_attribute?

I am trying to write procedural macros that will accept a Rust enum like
#[repr(u8)]
enum Ty {
A,
B
}
and generate a method for the enum that will let me convert an u8 into an allowed variant like this
fn from_byte(byte: u8) -> Ty {
match {
0 => Ty::A,
1 => Ty::B,
_ => unreachable!()
}
}
This is what I have implemented using proc_macro lib. (no external lib)
#![feature(proc_macro_diagnostic)]
#![feature(proc_macro_quote)]
extern crate proc_macro;
use proc_macro::{TokenStream, Diagnostic, Level, TokenTree, Ident, Group, Literal};
use proc_macro::quote;
fn report_error(tt: TokenTree, msg: &str) {
Diagnostic::spanned(tt.span(), Level::Error, msg).emit();
}
fn variants_from_group(group: Group) -> Vec<Ident> {
let mut iter = group.stream().into_iter();
let mut res = vec![];
while let Some(TokenTree::Ident(id)) = iter.next() {
match iter.next() {
Some(TokenTree::Punct(_)) | None => res.push(id),
Some(tt) => {
report_error(tt, "unexpected variant. Only unit variants accepted.");
return res
}
}
}
res
}
#[proc_macro_attribute]
pub fn procmac(args: TokenStream, input: TokenStream) -> TokenStream {
let _ = args;
let mut res = TokenStream::new();
res.extend(input.clone());
let mut iter = input.into_iter()
.skip_while(|tt| if let TokenTree::Punct(_) | TokenTree::Group(_) = tt {true} else {false})
.skip_while(|tt| tt.to_string() == "pub");
match iter.next() {
Some(tt # TokenTree::Ident(_)) if tt.to_string() == "enum" => (),
Some(tt) => {
report_error(tt, "unexpected token. this should be only used with enums");
return res
},
None => return res
}
match iter.next() {
Some(tt) => {
let variants = match iter.next() {
Some(TokenTree::Group(g)) => {
variants_from_group(g)
}
_ => return res
};
let mut match_arms = TokenStream::new();
for (i, v) in variants.into_iter().enumerate() {
let lhs = TokenTree::Literal(Literal::u8_suffixed(i as u8));
if i >= u8::MAX as usize {
report_error(lhs, "enum can have only u8::MAX variants");
return res
}
let rhs = TokenTree::Ident(v);
match_arms.extend(quote! {
$lhs => $tt::$rhs,
})
}
res.extend(quote!(impl $tt {
pub fn from_byte(byte: u8) -> $tt {
match byte {
$match_arms
_ => unreachable!()
}
}
}))
}
_ => ()
}
res
}
And this is how I am using it.
use helper_macros::procmac;
#[procmac]
#[derive(Debug)]
#[repr(u8)]
enum Ty {
A,
B
}
fn main() {
println!("TEST - {:?}", Ty::from_byte(0))
}
The problem is this causing an error from the compiler. The exact error being
error[E0599]: no variant or associated item named `from_byte` found for enum `Ty` in the current scope
--> main/src/main.rs:91:32
|
85 | enum Ty {
| ------- variant or associated item `from_byte` not found here
...
91 | println!("TEST - {:?}", Ty::from_byte(0))
| ^^^^^^^^^ variant or associated item not found in `Ty`
Running cargo expand though generate the proper code. And running that code directly works as expected. And so I am stumped. It could be I am missing something about how proc_macros should be used since this is the first time I am playing with them and I don't see anything that would cause this error. I am following the sorted portion of the proc_macro_workshop0. Only change is, I am using TokenStream directly instead of using syn and quote crates. Also, if I mistype the method name, the rust compiler does suggest that a method with similar name exists.
Here is a Playground repro: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=02c1ee77bcd80c68967834a53c011e41
So, indeed what you mention is true: the expanded code could be copy-pasted and it would work. When this happens (having behavior from macro expansion and "manual copy-pasted expansion" differ), there are two possibilities:
macro_rules! metavariables
When emitting code using macro_rules! special captures, some of these captures are wrapped with special invisible parenthesis that already tell the parser how the thing inside should be parsed, which make it illegal to use in other places (for instance, one may capture a $Trait:ty, and then doing impl $Trait for ... will fail (it will parse $Trait as a type, thus leading to it being interpreted as a trait object (old syntax)); see also https://github.com/danielhenrymantilla/rust-defile for other examples.
This is not your case, but it's good to keep in mind (e.g. my initial hunch was that when doing $tt::$rhs if $tt was a :path-like capture, then that could fail).
macro hygiene/transparency and Spans
Consider, for instance:
macro_rules! let_x_42 {() => (
let x = 42;
)}
let_x_42!();
let y = x;
This expands to code that, if copy-pasted, does not fail to compile.
Basically the name x that the macro uses is "tainted" to be different from any x used outside the macro body, precisely to avoid misinteractions when the macro needs to define helper stuff such as variables.
And it turns out that this is the same thing that has happened with your from_byte identifier: your code was emitting a from_byte with private hygiene / a def_site() span, which is something that normally never happens for method names when using classic macros, or classic proc-macros (i.e., when not using the unstable ::proc_macro::quote! macro). See this comment: https://github.com/rust-lang/rust/issues/54722#issuecomment-696510769
And so the from_byte identifier is being "tainted" in a way that allows Rust to make it invisible to code not belonging to that same macro expansion, such as the code in your fn main.
The solution, at this point, is easy: forge a from_bytes Identifier with an explicit non-def_site() Span (e.g., Span::call_site(), or even better: Span::mixed_site() to mimic the rules of macro_rules! macros) so as to prevent it from getting that default def_site() Span that ::proc_macro::quote! uses:
use ::proc_macro::Span;
// ...
let from_byte = TokenTree::from(Ident::new("from_byte", Span::mixed_site()));
res.extend(quote!(impl $tt {
// use an interpolated ident rather than a "hardcoded one"
// vvvvvvvvvv
pub fn $from_byte(byte: u8) -> $tt {
match byte {
$match_arms
_ => unreachable!()
}
}
}))
Playground

Simplifying a `match` using a Rust macro

There are many question functions (hundreds), and each may have a different type. For each question I want to run a run_question function, which shows how long that function took, and print it's output.
I'm trying to shorten the following match expression with a Rust macro (writing run_question 100s of times does make the code rather long):
fn run_question<T: std::fmt::Display>(question_func: fn() -> T) {
let begin = Instant::now();
let output: T = question_func();
let elapsed_secs = begin.elapsed().as_micros() as f32 / 1e6;
println!("{}", output);
println!("{:.6}s taken", elapsed_secs);
}
fn q1() -> u8 { /* ... */ }
fn q2() -> u32 { /* ... */ }
fn q3() -> u64 { /* ... */ }
fn q4() -> String { /* ... */ }
fn main() {
// ...
match question_num {
1 => run_question(q1), 2 => run_question(q2), 3 => run_question(q3), 4 => run_question(q4),
_ => {
println!("Question doesn't exist.");
},
}
}
I have no experience in writing macros, and attempted the following which doesn't exactly work. It gives the error:
error: variable 'question_num' is still repeating at this depth
I'm rather stumped too how I can print the Question doesn't exist. as a default case.
#[macro_export]
macro_rules! run_questions {
( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
{
if $chosen_question == $question_num {
run_question($question_mod::solve);
}
}
};
}
The way I'd like to use it, is (or anything just as short is fine as well):
run_questions!(question_num, 1, q1, 2, q2, 3, q3, 4, q4);
I read a bit of the Rust book, but there aren't exactly that many examples of macros.
How would I go about doing this?
Rather than many if statements, I just reproduced the match statement
with a repetition $( ... )* for all the available branches.
It seems to behave like the extensive match expression.
macro_rules! run_questions {
( $chosen_question: expr, $( $question_num: expr, $question_mod: expr ), * ) => {
match $chosen_question {
$($question_num => run_question($question_mod),)*
_ => {
println!("Question doesn't exist.");
}
}
};
}
The error message explained:
macro_rules! run_questions {
($chosen_question: expr, $($question_num: expr, $question_mod: expr),*) => {{
In the above pattern you have a repetition with the * operator that involves variables $question_num and $question_mod
if $chosen_question == $question_num {
run_question($question_mod::solve);
}
In the corresponding code, you can't use $question_num and $question_mod directly: since they are repeated they potentially have more than one value and which one should the compiler use here? Instead, you need to tell the compiler to repeat the block of code that uses these variables. This is done by surrounding the repeated code block with $() and adding the * operator:
$(if $chosen_question == $question_num {
run_question($question_mod::solve);
})*
Although as pointed out by #prog-fh's answer, better to use a match in the macro, same as in the straight code:
match $chosen_question {
$($question_num => run_question ($question_mod::solve),)*
_ => println!("Question doesn't exist.")
};

Consolidating multiple copies of a character at start of string into one in Rust

I'm working on a parser for a mini language, and I have the need to differentiate between plain strings ("hello") and strings that are meant to be operators/commands, and start with a specific sigil character (e.g. "$add").
I also want to add a way for the user to escape the sigil, in which a double-sigil gets consolidated into one, and then is treated like a plain string.
As an example:
"hello" becomes Str("hello")
"$add" becomes Operator(Op::Add)
"$$add" becomes Str("$add")
What would be the best way to do this check and manipulation? I was looking for a method that counts how many times a character appears at the start of a string, to no avail.
Can't you just use starts_with?
fn main() {
let line_list= [ "hello", "$add", "$$add" ];
let mut result;
for line in line_list.iter() {
if line.starts_with("$$") {
result = line[1..].to_string();
}
else if line.starts_with("$") {
result = format!("operator:{}", &line[1..]);
}
else {
result = line.to_string();
}
println!("result = {}", result);
}
}
Output
result = hello
result = operator:add
result = $add
According to the comments, your problem seems to be related to the access to the first chars.
The proper and efficient way is to get a char iterator:
#[derive(Debug)]
enum Token {
Str(String),
Operator(String),
}
impl From<&str> for Token {
fn from(s: &str) -> Self {
let mut chars = s.chars();
let first_char = chars.next();
let second_char = chars.next();
match (first_char, second_char) {
(Some('$'), Some('$')) => {
Token::Str(format!("${}", chars.as_str()))
}
(Some('$'), Some(c)) => {
// your real handling here is probably different
Token::Operator(format!("{}{}", c, chars.as_str()))
}
_ => {
Token::Str(s.to_string())
}
}
}
}
fn main() {
println!("{:?}", Token::from("π"));
println!("{:?}", Token::from("hello"));
println!("{:?}", Token::from("$add"));
println!("{:?}", Token::from("$$add"));
}
Result:
Str("π")
Str("hello")
Operator("add")
Str("$add")
playground

How to combine different algorithms in one executable

To learn Rust, I have started to implement some of the Project Euler problems. Now I want to take the next step and create a console based user interface, which has the ability for running all or only specific problems. Another requirement is that the user should be able to pass optional parameters only to a specific problem.
My current solution is to have a Trait ProjectEulerProblem that declares for example run(). With that I can do something like this:
fn main() {
let args: Args = Args::docopt().decode().unwrap_or_else(|e| e.exit());
let problems: Vec<Box<problems::ProjectEulerProblem>> = vec![
box problems::Problem1,
box problems::Problem2
];
match args.flag_problem {
Some(x) => println!("Result of problem: {} is {}", x, problems[x-1].run()),
None => println!("No problem number given.")
}
}
My question is, is there a way to get rid of the explicit problems vector initialization, maybe by using macros? Alternative ideas for implementing my application like described above are also welcome.
You can use a macro with repetition to generate your list without having to type out the full path and name every time.
macro_rules! problem_vec(
($( $prob:tt ),*) => ({
&[
$(
&concat_idents!(Proble, $prob),
)*
]
});
);
const PROBLEMS: &'static[&'static ProjectEulerProblem] = problem_vec!(m1, m2);
Note, you cannot simply use indices, because the concat_idents macro requires an identifier and numbers are not identifiers. concat_idents is also only available on nightly. On stable you need to give the entire struct name:
macro_rules! problem_vec(
($( $prob:ident ),*) => ({
&[
$(
&problems::$prob,
)*
]
});
);
const PROBLEMS: &'static [&'static problems::ProjectEulerProblem] = problem_vec!(
Problem1, Problem2
);
PlayPen
My mashup crate lets you define a concise way to build the problems array as problems![1, 2]. This approach works with any Rust version >= 1.15.0.
#[macro_use]
extern crate mashup;
mod problems {
pub trait ProjectEulerProblem {
fn run(&self);
}
pub struct Problem1;
impl ProjectEulerProblem for Problem1 {
fn run(&self) {
println!("running Project Euler problem 1");
}
}
pub struct Problem2;
impl ProjectEulerProblem for Problem2 {
fn run(&self) {
println!("running Project Euler problem 2");
}
}
}
macro_rules! problems {
($($number:tt),*) => {{
// Use mashup to define a substitution macro `m!` that replaces every
// occurrence of the tokens `"Problem" $number` in its input with the
// concatenated identifier `Problem $number`.
mashup! {
$(
m["Problem" $number] = Problem $number;
)*
}
// Invoke the substitution macro to build a slice of problems. This
// expands to:
//
// &[
// &problems::Problem1 as &problems::ProjectEulerProblem,
// &problems::Problem2 as &problems::ProjectEulerProblem,
// ]
m! {
&[
$(
&problems::"Problem" $number as &problems::ProjectEulerProblem,
)*
]
}
}}
}
fn main() {
for p in problems![1, 2] {
p.run();
}
}

Resources