Dynamically creating parameters in nested rust macros - rust

I've been tinkering around with Rust's macro system for a while now, and recently got interested in nesting two macros together, like this:
macro_rules! foo {
() => {
macro_rules! bar {
() => {}
}
}
}
Relating to the example, I wanted to dynamically make parameter names in bar! which were passed into foo!, to obtain a result like this:
foo!(bar, baz);
// The above call creates a macro, bar!, with the following definition:
macro_rules! bar {
( $bar:literal, $baz:literal ) => {
println!("{}", stringify!( $bar, $baz ));
}
}
To give a better idea of what I'm trying to do, here was my initial thought process on how this would work (this should parse exactly to the definition shown above):
macro_rules! foo {
( $( $attr:ident ), * ) => {
macro_rules! bar {
// the designator $$attr:literal consists of two parts - $attr,
// which should be replaced with the arguments passed into foo!,
// and $__:literal, which creates a literal designator for each of
// the arguments from foo! for bar!
( $( $$attr:literal ), * ) => {
// $( $$attr ), * follows the same logic as above
println!("{}", stringify!( $( $$attr ), * ));
}
}
}
}
This does look very weird, and sure enough, it didn't work, giving an error mentioning meta-variable expressions and this issue, both of which looked unrelated (full error can be seen on the playground).
Does anyone know if it is possible to dynamically create a macro with variables like this, and if so, how to do it?

Yes, however...
You cannot insert the $ sign, as it is reserved for metavariables.
You have two options to tackle that.
On stable, you need to pass $ to the macro. Then it can refer to it using the metavariable.
macro_rules! foo {
( $dollar:tt $( $attr:ident ), * ) => {
macro_rules! bar {
( $( $dollar $attr:literal ), * ) => {
println!("{}", stringify!( $( $dollar $attr ), * ));
}
}
}
}
foo!($ bar, baz);
Playground.
On nightly, you can escape the dollar sign: this is part of the feature macro_metavar_expr the compiler mentioned. You do it using $$:
#![feature(macro_metavar_expr)]
macro_rules! foo {
( $( $attr:ident ), * ) => {
macro_rules! bar {
( $( $$ $attr:literal ), * ) => {
println!("{}", stringify!( $( $$ $attr ), * ));
}
}
}
}
foo!(bar, baz);
Playground.

Related

How to match a struct instantiation with macro_rules

Since this took me a while to figure out, I may as well share how I fixed it.
I was trying to wrap every item on a struct with some function, in my case Arc::new(Mutex::new(item)) with macro_rules
My initial attempt was many variations on this:
macro_rules! decl_sr {
(
$name:ident {
$( $it:ident : $value:expr) ,*
}
) => {
$name {
$( $it: Arc::new(Mutex::new( $value )) ),*
}
};
}
And the idea was to use it like this:
let mut value = decl_sr!{
StructName {
field_1: Value1::from_function_call(parameter1, parameter2),
// -- snip
field_n: ValueN::from_function_call(parameter1, parameter2),
}
}
So it actually resulted in this:
let mut value = decl_sr!{
StructName {
field_1: Arc::new(Mutex::new(Value1::from_function_call(parameter1, parameter2))),
// -- snip
field_n: Arc::new(Mutex::new(ValueN::from_function_call(parameter1, parameter2))),
}
}
The correct answer was this:
macro_rules! decl_sr {
(
$name:ident {
$( $it:ident : $value:expr, )*
}
) => {
$name {
$( $it: Arc::new(Mutex::new( $value )) ),*
}
};
With the comma (',') inside the repetition pattern.
Otherwise it would throw the classic macro error no rules expected token '}'. -Z macro-backtrace and trace_macros!(true); didn't help at all.
The only thing that tipped me off was this other question, but it wasn't exactly about this.
I may have picked too loose/bad fragment specifiers, feel free to correct me/suggest better options in the comments. Most of the time trying to make this works was thinking they were the problem, not the comma so I was glad it worked at all.

Question on invoking another macro_rules in macro_rules definition

I'm implementing writing TLV packet to somewhat impl std::io::Write.
First I implement WriteBE<T> trait, whose write_be(&mut self, data: T) method can write data with type T to Self. (implementation details omitted)
And I'm trying to use macro_rules! to implement calculation of total packet length in compile time (because most packets have fixed length in my case). macros are as follows:
macro_rules! len_in_expr {
(
self.write_be( $data: expr $(,)? ) $(?)? $(;)*
) => {
std::mem::size_of_val(&$data)
};
(
write_be(self, $data: expr $(,)? ) $(?)? $(;)*
) => {
std::mem::size_of_val(&$data)
};
(
$other: expr
) => {
0
};
}
/// calculate total write size in block
macro_rules! tlv_len_in_block {
({
$( $e: expr );* $(;)?
}) => {
0 $(
+ ( len_in_expr!($e) )
)*
};
}
But when I calculating total length like this:
fn main() {
let y = tlv_len_in_block!({
write_be(self, 0u32,)?;
});
println!("y={}", y);
}
I get a result 0.
If I comment the $other: expr match arm, I get a compile error:
6 | macro_rules! len_in_expr {
| ------------------------ when calling this macro
...
30 | + ( len_in_expr!($e) )
| ^^ no rules expected this token in macro call
...
39 | let y = tlv_len_in_block!({
| _____________-
40 | | write_be(self, 0u32,)?;
41 | | });
| |______- in this macro invocation
What's the problem with my code? And how can I fix it?
Once metavariables inside macro_rules! are captured into some fragment specifier (e.g. expr), they cannot be decomposed anymore. Quoting the reference:
When forwarding a matched fragment to another macro-by-example, matchers in the second macro will see an opaque AST of the fragment type. The second macro can't use literal tokens to match the fragments in the matcher, only a fragment specifier of the same type. The ident, lifetime, and tt fragment types are an exception, and can be matched by literal tokens. The following illustrates this restriction:
macro_rules! foo {
($l:expr) => { bar!($l); }
// ERROR: ^^ no rules expected this token in macro call
}
macro_rules! bar {
(3) => {}
}
foo!(3);
The following illustrates how tokens can be directly matched after matching a tt fragment:
// compiles OK
macro_rules! foo {
($l:tt) => { bar!($l); }
}
macro_rules! bar {
(3) => {}
}
foo!(3);
Once tlv_len_in_block!() captured write_be(self, 0u32,)? inside $e, it cannot be decomposed into write_be(self, $data:expr $(,)? ) and thus, cannot be matched by the second case of the len_in_expr!()` macro, as it should have been.
There are generally two solutions to this problem:
The first is, if possible, decomposing them from the beginning. The problem is that this is not always possible. In this case, for example, I don't see a way for that to work.
The second way is much more complicated and it is using the Push-down Accumulation technique together with tt Munching.
The idea is as follows: instead of parsing the input as whole, we parse each piece one at a time. Then, recursively, we forward the parsed bits and the yet-to-parse bit to ourselves. We also should have a stop condition on an empty input.
Here is how it will look like in your example:
macro_rules! tlv_len_in_block_impl {
// Stop condition - no input left to parse.
(
parsed = [ $($parsed:tt)* ]
rest = [ ]
) => {
$($parsed)*
};
(
parsed = [ $($parsed:tt)* ]
rest = [
self.write_be( $data:expr $(,)? ) $(?)? ;
$($rest:tt)*
]
) => {
tlv_len_in_block_impl!(
parsed = [
$($parsed)*
+ std::mem::size_of_val(&$data)
]
rest = [ $($rest)* ]
)
};
(
parsed = [ $($parsed:tt)* ]
rest = [
write_be(self, $data:expr $(,)? ) $(?)? ;
$($rest:tt)*
]
) => {
tlv_len_in_block_impl!(
parsed = [
$($parsed)*
+ std::mem::size_of_val(&$data)
]
rest = [ $($rest)* ]
)
};
}
/// calculate total write size in block
macro_rules! tlv_len_in_block {
({
$($input:tt)*
}) => {
tlv_len_in_block_impl!(
parsed = [ 0 ]
rest = [ $($input)* ]
)
};
}
(Note that this is not exactly the same as your macro - mine requires a trailing semicolon, while in yours it's optional. It's possible to make it optional here, too, but it will be much more complicated.
Playground.

Can I 'enumerate' with Rust's variadic macros?

Essentially I have a macro that looks like:
macro_rules! my_macro {
( $expr:expr; $( $pat:pat ),* ) => {
match $expr {
$(
$pat => $(some-macro-magic-here),
)*
}
}
}
Is there anything that can go into $(some-macro-magic-here), so that
my_macro!(foo; A, B, C)
will expand to
match foo {
A => 2,
B => 4,
C => 6,
}
?
Is there some other way I might be able to get a similar feature that effectively lets me "enumerate" over the sequence of inputs for the macro?
I think I could probably write a recursive macro to get a similar effect, but I'm wondering if there's a more elegant/idiomatic way about it than what I'm thinking of
Because macros aren't allowed to store or manipulate "variables" in any form, this problem becomes very difficult. You could, however, use an iterator to do something to the same effect, by creating an iterator that "enumerates" over the input the way you want it (using std::iter::successors, for example), and simply calling iterator.next().unwrap() in $(some-macro-magic-here).
You cannot create such match statement as Rust do not allow creating match branches via macro, aka this will not work right now:
match val {
my_macro! (A, B, C)
}
However in this case we can "hack it around" by using nested if let blocks and using recursive macro:
macro_rules! my_macro {
($expr:expr; $($pat:pat),*) => {
my_macro!($expr; 2, 2; $($pat),*)
};
($expr:expr; $curr:expr, $step:literal; $pat:pat) => {
if let $pat = $expr {
$curr
} else {
unreachable!()
}
};
($expr:expr; $curr:expr, $step:literal; $pat:pat, $($rest:pat),*) => {
if let $pat = $expr {
$curr
} else {
my_macro! ($expr; $curr+$step, $step; $($rest),*)
}
}
}
Playground
It will generate the nested entries with enough 2 added to create the expected constants. Alternatively you could replace that with multiplication, but it should be optimised out by the compiler anyway.

How to replace one identifier in an expression with another one via Rust macro?

I'm trying to build a macro that does some code transformation, and should be able to parse its own syntax.
Here is the simplest example I can think of:
replace!(x, y, x * 100 + z) ~> y * 100 + z
This macro should be able to replace the first identifier with the second in the expression provided as third parameter. The macro should have some understanding of the language of the third parameter (which in my particular case, as opposed to the example, wouldn't parse in Rust) and apply recursively over it.
What's the most effective way to build such a macro in Rust? I'm aware of the proc_macro approach and the macro_rules! one. However I am not sure whether macro_rules! is powerful enough to handle this and I couldn't find much documentation in how to build my own transformations using proc_macro. Can anyone point me in the right direction?
Solution with macro_rules! macro
To implement this with declarative macros (macro_rules!) is a bit tricky but possible. However, it's necessary to use a few tricks.
But first, here is the code (Playground):
macro_rules! replace {
// This is the "public interface". The only thing we do here is to delegate
// to the actual implementation. The implementation is more complicated to
// call, because it has an "out" parameter which accumulates the token we
// will generate.
($x:ident, $y:ident, $($e:tt)*) => {
replace!(#impl $x, $y, [], $($e)*)
};
// Recursion stop: if there are no tokens to check anymore, we just emit
// what we accumulated in the out parameter so far.
(#impl $x:ident, $y:ident, [$($out:tt)*], ) => {
$($out)*
};
// This is the arm that's used when the first token in the stream is an
// identifier. We potentially replace the identifier and push it to the
// out tokens.
(#impl $x:ident, $y:ident, [$($out:tt)*], $head:ident $($tail:tt)*) => {{
replace!(
#impl $x, $y,
[$($out)* replace!(#replace $x $y $head)],
$($tail)*
)
}};
// These arms are here to recurse into "groups" (tokens inside of a
// (), [] or {} pair)
(#impl $x:ident, $y:ident, [$($out:tt)*], ( $($head:tt)* ) $($tail:tt)*) => {{
replace!(
#impl $x, $y,
[$($out)* ( replace!($x, $y, $($head)*) ) ],
$($tail)*
)
}};
(#impl $x:ident, $y:ident, [$($out:tt)*], [ $($head:tt)* ] $($tail:tt)*) => {{
replace!(
#impl $x, $y,
[$($out)* [ replace!($x, $y, $($head)*) ] ],
$($tail)*
)
}};
(#impl $x:ident, $y:ident, [$($out:tt)*], { $($head:tt)* } $($tail:tt)*) => {{
replace!(
#impl $x, $y,
[$($out)* { replace!($x, $y, $($head)*) } ],
$($tail)*
)
}};
// This is the standard recusion case: we have a non-identifier token as
// head, so we just put it into the out parameter.
(#impl $x:ident, $y:ident, [$($out:tt)*], $head:tt $($tail:tt)*) => {{
replace!(#impl $x, $y, [$($out)* $head], $($tail)*)
}};
// Helper to replace the identifier if its the needle.
(#replace $needle:ident $replacement:ident $i:ident) => {{
// This is a trick to check two identifiers for equality. Note that
// the patterns in this macro don't contain any meta variables (the
// out meta variables $needle and $i are interpolated).
macro_rules! __inner_helper {
// Identifiers equal, emit $replacement
($needle $needle) => { $replacement };
// Identifiers not equal, emit original
($needle $i) => { $i };
}
__inner_helper!($needle $i)
}}
}
fn main() {
let foo = 3;
let bar = 7;
let z = 5;
dbg!(replace!(abc, foo, bar * 100 + z)); // no replacement
dbg!(replace!(bar, foo, bar * 100 + z)); // replace `bar` with `foo`
}
It outputs:
[src/main.rs:56] replace!(abc , foo , bar * 100 + z) = 705
[src/main.rs:57] replace!(bar , foo , bar * 100 + z) = 305
How does this work?
There are two main tricks one need to understand before understanding this macro: push down accumulation and how to check two identifiers for equality.
Furthermore, just to be sure: the #foobar things at the start of the macro pattern are not a special feature, but simply a convention to mark internal helper macros (also see: "The little book of Macros", StackOverflow question).
Push down accumulation is well described in this chapter of "The little book of Rust macros". The important part is:
All macros in Rust must result in a complete, supported syntax element (such as an expression, item, etc.). This means that it is impossible to have a macro expand to a partial construct.
But often it is necessary to have partial results, for example when dealing token for token with some input. To solve this, one basically has an "out" parameter which is just a list of tokens that grows with each recursive macro call. This works, because macro input can be arbitrary tokens and don't have to be a valid Rust construct.
This pattern only makes sense for macros that work as "incremental TT munchers", which my solution does. There is also a chapter about this pattern in TLBORM.
The second key point is to check two identifiers for equality. This is done with an interesting trick: the macro defines a new macro which is then immediately used. Let's take a look at the code:
(#replace $needle:ident $replacement:ident $i:ident) => {{
macro_rules! __inner_helper {
($needle $needle) => { $replacement };
($needle $i) => { $i };
}
__inner_helper!($needle $i)
}}
Let's go through two different invocations:
replace!(#replace foo bar baz): this expands to:
macro_rules! __inner_helper {
(foo foo) => { bar };
(foo baz) => { baz };
}
__inner_helper!(foo baz)
And the inner_helper! invocation now clearly takes the second pattern, resulting in baz.
replace!(#replace foo bar foo) on the other hand expands to:
macro_rules! __inner_helper {
(foo foo) => { bar };
(foo foo) => { foo };
}
__inner_helper!(foo foo)
This time, the inner_helper! invocation takes the first pattern, resulting in bar.
I learned this trick from a crate that offers basically only exactly that: a macro checking two identifiers for equality. But unfortunately, I cannot find this crate anymore. Let me know if you know the name of that crate!
This implementation has a few limitations, however:
As an incremental TT muncher, it recurses for each token in the input. So it's easy to reach the recursion limit (which can be increased, but it's not optimal). It could be possible to write a non-recursive version of this macro, but so far I haven't found a way to do that.
macro_rules! macros are a bit strange when it comes to identifiers. The solution presented above might behave strange with self as identifier. See this chapter for more information on that topic.
Solution with proc-macro
Of course this can also be done via a proc-macro. It also involves less strange tricks. My solution looks like this:
extern crate proc_macro;
use proc_macro::{
Ident, TokenStream, TokenTree,
token_stream,
};
#[proc_macro]
pub fn replace(input: TokenStream) -> TokenStream {
let mut it = input.into_iter();
// Get first parameters
let needle = get_ident(&mut it);
let _comma = it.next().unwrap();
let replacement = get_ident(&mut it);
let _comma = it.next().unwrap();
// Return the remaining tokens, but replace identifiers.
it.map(|tt| {
match tt {
// Comparing `Ident`s can only be done via string comparison right
// now. Note that this ignores syntax contexts which can be a
// problem in some situation.
TokenTree::Ident(ref i) if i.to_string() == needle.to_string() => {
TokenTree::Ident(replacement.clone())
}
// All other tokens are just forwarded
other => other,
}
}).collect()
}
/// Extract an identifier from the iterator.
fn get_ident(it: &mut token_stream::IntoIter) -> Ident {
match it.next() {
Some(TokenTree::Ident(i)) => i,
_ => panic!("oh noes!"),
}
}
Using this proc macro with the main() example from above works exactly the same.
Note: error handling was ignored here to keep the example short. Please see this question on how to do error reporting in proc macros.
Apart from this, that code doesn't need as much explanations, I think. This proc macro version also doesn't suffer from the recursion limit problem as the macro_rules! macro.

Optional field with strict format

I am trying to build nom parser to examine URLs with ID as UUID
rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912
I created the following:
extern crate uuid;
use uuid::Uuid;
named!(room_uuid<&str, Option<Uuid>>,
do_parse!(
tag_s!("rooms") >>
id: opt!(complete!(preceded!(
tag_s!("/"),
map_res!(take_s!(36), FromStr::from_str)
))) >>
(id)
)
);
It handles almost all cases well:
assert_eq!(room_uuid("rooms"), Done("", None));
assert_eq!(room_uuid("rooms/"), Done("/", None));
assert_eq!(room_uuid("rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912"), Done("", Some(Uuid::parse_str("e19c94cf-53eb-4048-9c94-7ae74ff6d912").unwrap())));
Except cases where ID is not a valid UUID:
assert!(room_uuid("rooms/123").is_err()); # it fails
# room_uuid("rooms/123").to_result() => Ok(None)
As far as I understand it happens because opt! converts inner Err into None.
I would like to have ID as optional section but if it is present it should be a valid UUID.
Unfortunately, I don't understand how to combine both those things: optionality and strict format.
I've only started working with nom myself in the last couple of weeks but I found one way of solving this. It doesn't fit exclusively within a macro but it does give the correct behavior with one modification. I swallow the / rather than leave it dangling after when a UUID is not given.
#[macro_use]
extern crate nom;
extern crate uuid;
use std::str::FromStr;
use nom::IResult;
use uuid::Uuid;
fn room_uuid(input: &str) -> IResult<&str, Option<Uuid>> {
// Check that it starts with "rooms"
let res = tag_s!(input, "rooms");
let remaining = match res {
IResult::Incomplete(i) => return IResult::Incomplete(i),
IResult::Error(e) => return IResult::Error(e),
IResult::Done(i, _) => i
};
// If a slash is not present, return early
let optional_slash = opt!(remaining, tag_s!("/"));
let remaining = match optional_slash {
IResult::Error(_) |
IResult::Incomplete(_) => return IResult::Done(remaining, None),
IResult::Done(i, _) => i
};
// If something follows a slash, make sure
// it's a valid UUID
if remaining.len() > 0 {
let res = complete!(remaining, map_res!(take_s!(36), FromStr::from_str));
match res {
IResult::Done(i, o) => IResult::Done(i, Some(o)),
IResult::Error(e) => IResult::Error(e),
IResult::Incomplete(n) => IResult::Incomplete(n)
}
} else {
// This branch allows for "rooms/"
IResult::Done(remaining, None)
}
}
#[test]
fn match_room_plus_uuid() {
use nom::IResult::*;
assert_eq!(room_uuid("rooms"), Done("", None));
assert_eq!(room_uuid("rooms/"), Done("", None));
assert_eq!(room_uuid("rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912"), Done("", Some(Uuid::parse_str("e19c94cf-53eb-4048-9c94-7ae74ff6d912").unwrap())));
assert!(room_uuid("rooms/123").is_err());
}
Given parsing URLs doesn't need a streaming interface you could use synom instead. It was maintained as part of the syn crate, but is sadly not maintained anymore (it was merged into syn and changed to only process rust tokens).
Sadly synom doesn't provide take_s! and eof! (the latter one is going to forbid the trailing "unparsed" 123), but it's easy enough to implement those.
Using eof! also means you can't return an unparsed "/" (although I consider that a good thing); and the nested option! needs some unwrapping at the end (you could return Option<Option<Uuid>> instead to detect the trailing "/").
Playground
#[macro_use]
extern crate synom;
extern crate uuid;
use uuid::Uuid;
macro_rules! take_s {
($i:expr, $length:expr) => {{
let length: usize = $length;
if 0 == length {
synom::IResult::Done($i, "")
} else {
let mut ci = $i.char_indices().skip(length - 1);
match ci.next() {
None => synom::IResult::Error,
Some(_) => {
match ci.next() {
None => synom::IResult::Done("", $i),
Some((pos, _)) => {
let (value, rem) = $i.split_at(pos);
synom::IResult::Done(rem, value)
},
}
}
}
}
}};
}
macro_rules! eof {
($i:expr,) => {{
if $i.is_empty() {
synom::IResult::Done($i, ())
} else {
synom::IResult::Error
}
}};
}
named!(room_uuid -> Option<Uuid>,
do_parse!(
tag!("rooms") >>
id: option!(preceded!(
tag!("/"),
option!(
switch!(map!(take_s!(36), str::parse),
Ok(v) => value!(v)
)
)
)) >>
eof!() >>
(id.unwrap_or(None))
)
);
fn main() {
use synom::IResult::*;
assert_eq!(room_uuid("rooms"), Done("", None));
assert_eq!(room_uuid("rooms/"), Done("", None));
assert_eq!(
room_uuid("rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912"),
Done(
"",
Some(Uuid::parse_str("e19c94cf-53eb-4048-9c94-7ae74ff6d912").unwrap())
)
);
assert_eq!(room_uuid("rooms/123"), Error);
}
Ok, so I got it working with nom and the extended URL format api/v1/rooms/UUID/tracks/UUID.
The basics are the same as before: you want to check for eof, ignore trailing "/" and never wait for incomplete results (alt_complete! is doing a good job here).
Regarding your ErrorKind::Verify wish: I don't think the error kind is actually important, just ignore it, or map it to whatever you want manually.
Be careful with the alt_complete! branches: in case of overlaps the preferred option (usually the "longer one") should come first.
I like my with! helper, but you could also inline it.
Playground doesn't support nom, so no link this time.
#[macro_use]
extern crate nom;
extern crate uuid;
use uuid::Uuid;
named!(uuid<&str, Uuid>, preceded!(
tag_s!("/"),
map_res!(take_s!(36), str::parse)
));
#[derive(Clone, PartialEq, Eq, Debug)]
enum ApiRequest {
Rooms,
Room { room: Uuid },
Tracks { room: Uuid },
Track { room: Uuid, track: Uuid },
}
/// shortcut for: `do_parse!(name: expr >> r: otherexpr >> (r))`
///
/// `otherexpr` should use `name`, otherwise you could just use `preceded!`.
macro_rules! with {
($i:expr, $var:ident: $submac:ident!( $($args:tt)* ) >> $($rest:tt)*) => {
do_parse!($i, $var: $submac!($($args)*) >> r: $($rest)* >> (r));
};
($i:expr, $var:ident: $submac:ident >> $($rest:tt)*) => {
do_parse!($i, $var: $submac >> r: $($rest)* >> (r));
};
}
// /api/v1/rooms/UUID/tracks/UUID
named!(apiv1<&str, ApiRequest>, preceded!(tag_s!("/api/v1"),
alt_complete!(
preceded!(tag_s!("/rooms"), alt_complete!(
with!(room: uuid >> alt_complete!(
preceded!(tag_s!("/tracks"), alt_complete!(
with!(track: uuid >> alt_complete!(
// ... sub track requests?
value!(ApiRequest::Track{room, track})
))
|
value!(ApiRequest::Tracks{room})
))
// other room requests
|
value!(ApiRequest::Room{room})
))
|
value!(ApiRequest::Rooms)
))
// | ... other requests
)
));
named!(api<&str, ApiRequest>, terminated!(
alt_complete!(
apiv1
// | ... other versions
// also could wrap in new enum like:
// apiv1 => { ApiRequest::V1 }
// |
// apiv2 => { ApiRequest::V2 }
),
tuple!(
alt_complete!(tag_s!("/") | value!("")), // ignore trailing "/"
eof!() // make sure full URL was parsed
)
));
fn main() {
use nom::IResult::*;
use nom::ErrorKind;
let room = Uuid::parse_str("e19c94cf-53eb-4048-9c94-7ae74ff6d912").unwrap();
let track = Uuid::parse_str("83d235e8-03cd-420d-a8c6-6e42440a5573").unwrap();
assert_eq!(api("/api/v1/rooms"), Done("", ApiRequest::Rooms));
assert_eq!(api("/api/v1/rooms/"), Done("", ApiRequest::Rooms));
assert_eq!(
api("/api/v1/rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912"),
Done("", ApiRequest::Room { room })
);
assert_eq!(
api("/api/v1/rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912/"),
Done("", ApiRequest::Room { room })
);
assert_eq!(
api("/api/v1/rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912/tracks"),
Done("", ApiRequest::Tracks { room })
);
assert_eq!(
api("/api/v1/rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912/tracks/"),
Done("", ApiRequest::Tracks { room })
);
assert_eq!(
api("/api/v1/rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912/tracks/83d235e8-03cd-420d-a8c6-6e42440a5573"),
Done("", ApiRequest::Track{room, track})
);
assert_eq!(
api("/api/v1/rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912/tracks/83d235e8-03cd-420d-a8c6-6e42440a5573/"),
Done("", ApiRequest::Track{room, track})
);
assert_eq!(api("/api/v1"), Error(ErrorKind::Alt));
assert_eq!(api("/api/v1/foo"), Error(ErrorKind::Alt));
assert_eq!(api("/api/v1/rooms/123"), Error(ErrorKind::Eof));
assert_eq!(
api("/api/v1/rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912/bar"),
Error(ErrorKind::Eof)
);
assert_eq!(
api("/api/v1/rooms/e19c94cf-53eb-4048-9c94-7ae74ff6d912/tracks/83d235e8-03cd-420d-a8c6-6e42440a5573/123"),
Error(ErrorKind::Eof)
);
assert_eq!(api("/api/v2"), Error(ErrorKind::Alt));
}
You could also use a more strict alt_full_opt_slash! branch method, which would ensure a branch only matches if it fully parsed the input.
You could then use a more "flat" way (although nested branches should still be working) to parse the alternatives (although this means you might end up parsing some UUIDs more than once; also now all errors are of kind Alt):
/// Similar to alt_complete, but also requires the branch parses until
/// the end of the input (but ignores a trailing "/").
macro_rules! alt_full_opt_slash {
(__impl_push2 ($i:expr,) ($($new:tt)*), $($rest:tt)*) => {
alt_full_opt_slash!(__impl ($i, $($new)*), $($rest)*)
};
(__impl_push2 ($i:expr, $($result:tt)+) ($($new:tt)*), $($rest:tt)*) => {
alt_full_opt_slash!(__impl ($i, $($result)+ | $($new)*), $($rest)*)
};
(__impl_push ($($result:tt)*) ($($new:tt)*), $($rest:tt)*) => {
// modify branch:
alt_full_opt_slash!(__impl_push2 ($($result)*) (
terminated!(
$($new)*,
tuple!(
alt_complete!(tag_s!("/") | value!("")), // ignore trailing "/"
eof!() // make sure full URL was parsed
)
)
), $($rest)*)
};
(__impl ($($result:tt)*), $e:ident | $($rest:tt)*) => {
alt_full_opt_slash!(__impl_push ($($result)*) ( $e ), $($rest)*)
};
(__impl ($($result:tt)*), $subrule:ident!( $($args:tt)*) | $($rest:tt)*) => {
alt_full_opt_slash!(__impl_push ($($result)*) ( $subrule!($($args)*) ), $($rest)*)
};
(__impl ($($result:tt)*), $subrule:ident!( $($args:tt)* ) => { $gen:expr } | $($rest:tt)*) => {
alt_full_opt_slash!(__impl_push ($($result)*) ( $subrule!($($args)*) => { $gen } ), $($rest)*)
};
(__impl ($($result:tt)*), $e:ident => { $gen:expr } | $($rest:tt)*) => {
alt_full_opt_slash!(__impl_push ($($result)*) ( $e => { $gen } ), $($rest)*)
};
(__impl ($i:expr, $($result:tt)*), __end) => {
alt_complete!($i, $($result)*)
};
($i:expr, $($rest:tt)*) => {{
alt_full_opt_slash!(__impl ($i, ), $($rest)* | __end)
}};
}
// /api/v1/rooms/UUID/tracks/UUID
named!(apiv1<&str, ApiRequest>, preceded!(tag_s!("/api/v1"),
alt_full_opt_slash!(
do_parse!(
tag_s!("/rooms") >>
(ApiRequest::Rooms)
)
|
do_parse!(
tag_s!("/rooms") >>
room: uuid >>
(ApiRequest::Room{room})
)
|
do_parse!(
tag_s!("/rooms") >>
room: uuid >>
tag_s!("/tracks") >>
(ApiRequest::Tracks{room})
)
|
do_parse!(
tag_s!("/rooms") >>
room: uuid >>
tag_s!("/tracks") >>
track: uuid >>
(ApiRequest::Track{room, track})
)
)
));
named!(api<&str, ApiRequest>, alt_complete!(
apiv1
// | ... other versions
// also could wrap in new enum like:
// apiv1 => { ApiRequest::V1 }
// |
// apiv2 => { ApiRequest::V2 }
));

Resources