Is it possible in Rust to define a macro that can parse custom literals, e.g. something along the lines of
vector!(3x + 15y)
To clarify, I would like to be able to get as close to the above syntax as one can (within the realm of what is possible of course).
The most widely used form of macros in Rust is the declarative macro. These are also sometimes referred to as “macros by example,” “ macro_rules! macros,” or just plain “macros.” At their core, declarative macros allow you to write something similar to a Rust match expression.
No, macros operate on syntax only. If there is a matcher that is ($arg:expr, &str) , the latter half of that expects a literal &str to be provided — it does not indicate that $arg is a string.
Another important difference between macros and functions is that you must define macros or bring them into scope before you call them in a file, as opposed to functions you can define anywhere and call anywhere. Save this answer.
I'm reading a book about Rust, and start playing with Rust macros. All metavariable types are explained there and have examples, except the last one – tt . According to the book, it is a “a single token tree”.
I'm going to assume that by "custom literal", you specifically mean "a regular Rust literal (excluding raw literals), immediately followed by a custom identifier". This includes:
"str"x
, a string literal "str"
with custom suffix x
123x
, a numeric literal 123
with custom suffix x
b"bytes"x
, a byte literal b"bytes"
with custom suffix x
If the above is a sufficient definition for you, then you're lucky, as the above are indeed all valid literal tokens in Rust, according to the Rust reference:
A suffix is a non-raw identifier immediately (without whitespace) following the primary part of a literal.
Any kind of literal (string, integer, etc) with any suffix is valid as a token, and can be passed to a macro without producing an error. The macro itself will decide how to interpret such a token and whether to produce an error or not.
However, suffixes on literal tokens parsed as Rust code are restricted. Any suffixes are rejected on non-numeric literal tokens, and numeric literal tokens are accepted only with suffixes from the list below.
So Rust explicitly allows macros to support custom string literals.
Now, how would you go about writing such a macro? You can't write a declarative macro with macro_rules!
, since it's not possible to detect and manipulate custom literal suffixes with its simple pattern matching. However, it is possible to write a procedural macro that does this.
I won't go into too much detail about how to write procedural macros, since that would be too much to write in a single StackOverflow answer. However, I'll give you this example of a procedural macro that does something along the lines of what you asked for, as a starting point. It takes any custom integer literals 123x
or 123y
in the given expression, and transforms them into the function calls x_literal(123)
and y_literal(123)
instead:
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{
parse_macro_input, parse_quote,
visit_mut::{self, VisitMut},
Expr, ExprLit, Lit, LitInt,
};
// actual procedural macro
#[proc_macro]
pub fn vector(input: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(input as Expr);
LiteralReplacer.visit_expr_mut(&mut input);
input.into_token_stream().into()
}
// "visitor" that visits every node in the syntax tree
// we add our own behavior to replace custom literals with proper Rust code
struct LiteralReplacer;
impl VisitMut for LiteralReplacer {
fn visit_expr_mut(&mut self, i: &mut Expr) {
if let Expr::Lit(ExprLit { lit, .. }) = i {
match lit {
Lit::Int(lit) => {
// get literal suffix
let suffix = lit.suffix();
// get literal without suffix
let lit_nosuffix = LitInt::new(lit.base10_digits(), lit.span());
match suffix {
// replace literal expression with new expression
"x" => *i = parse_quote! { x_literal(#lit_nosuffix) },
"y" => *i = parse_quote! { y_literal(#lit_nosuffix) },
_ => (), // other literal suffix we won't modify
}
}
_ => (), // other literal type we won't modify
}
} else {
// not a literal, use default visitor method
visit_mut::visit_expr_mut(self, i)
}
}
}
The macro would for example transform vector!(3x + 4y)
into x_literal(3) + y_literal(4)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With