I'm trying to write a macro that will rewrite certain Rust control flow, but I'm having difficulty matching an if expression. The problem is that the predicate is an expression, but an expr is not permitted to be followed by a block or {.
The best I've got is to use tt:
macro_rules! branch {
(
if $pred:tt
$r1:block
else
$r2:block
) => {
if $pred {
$r1
} else {
$r2
}
};
}
Which works fine with single-token or grouped predicates:
branch! {
if (foo == bar) {
1
} else {
2
}
}
But fails if the predicate was not grouped:
branch! {
if foo == bar {
1
} else {
2
}
}
error: no rules expected the token `==`
I also tried to use a repeating pattern of tt in the predicate:
macro_rules! branch {
(
if $($pred:tt)+
$r1:block
else
$r2:block
) => {
if $($pred)+ {
$r1
} else {
$r2
}
};
}
But this produces an error because it's now ambiguous whether subsequent block should match the tt too:
error: local ambiguity: multiple parsing options: built-in NTs tt ('pred') or block ('r1').
Is there a way to do this, or am I stuck with inventing special syntax to use in the macro?
You could use a TT muncher to parse the predicate:
macro_rules! branch {
{
if $($rest:tt)*
} => {
branch_parser! {
predicate = ()
rest = ($($rest)*)
}
};
}
macro_rules! branch_parser {
{
predicate = ($($predicate:tt)*)
rest = ({ $($then:tt)* } else { $($else:tt)* })
} => {
println!("predicate: {}", stringify!($($predicate)*));
println!("then: {}", stringify!($($then)*));
println!("else: {}", stringify!($($else)*));
};
{
predicate = ($($predicate:tt)*)
rest = ($next:tt $($rest:tt)*)
} => {
branch_parser! {
predicate = ($($predicate)* $next)
rest = ($($rest)*)
}
};
}
fn main() {
branch! {
if foo == bar {
1
} else {
2
}
}
}
Output:
predicate: foo == bar
then: 1
else: 2
Related
I'm developing Angular2 with Meteor.
When I make a little component with a recursive function, it has some weird error.
Here is my part of codes.
Not recursive - return a result
ngOnInit() {
//this.current_canvas return the right results
this.current_canvas = this.get_canvase(1);
}
get_canvase(which_canvas): Canvas[] {
if (!isNaN(which_canvas)) {
this.current_canvas_id = which_canvas;
return CanvasContents.find().map((messages: Canvas[]) => { return messages; })[0].content;
return '';
} else if(which_canvas == 'most-recent') {
this.get_canvase(1);
}
}
Recursive - Don't return a result
ngOnInit() {
//this.current_canvas Goes to NUll
this.current_canvas = this.get_canvase('most-recent');
}
get_canvase(which_canvas): Canvas[] {
if (!isNaN(which_canvas)) {
this.current_canvas_id = which_canvas;
console.log('this.current_canvas_id : ' + this.current_canvas_id);
return CanvasContents.find().map((messages: Canvas[]) => { return messages; })[0].content;
return '';
} else if(which_canvas == 'most-recent') {
this.get_canvase(1);
}
}
Have I used a wrong syntax? or is it on wrong Angular2 state to get right result?
I'd like to use a macro variable in the macro-generated documentation:
macro_rules! impl_foo {
($name:ident) => {
/// Returns a new `$name`.
fn myfoo() -> $name {
}
};
}
However, the variable won't be substituted. I also tried using the #[doc] attribute:
macro_rules! impl_foo {
($name:ident) => {
#[doc = concat!("Returns a new `", $name, "`.")]
fn myfoo() -> $name {
}
};
}
This one even fails to parse: unexpected token: 'concat'
This can be done using a recursive macro:
macro_rules! impl_foo {
($name:ident, $sname:expr) => {
#[doc = "Returns a new `"]
#[doc = $sname]
#[doc = "`."]
pub fn myfoo() -> $name {
42
}
};
($name:tt) => {
impl_foo!($name, stringify!($name));
};
}
impl_foo!(u32);
fn main() {
println!("Hello, world!");
}
Which renders as:
While the answer #mcarton gave does work perfectly fine for simple examples, it breaks a bit for more complicated ones. Rustdoc seems to insert spaces between the different doc attributes. The markdown processor strips them out most of the time, but sometimes, it transform them to spaces instead. Consider this example:
macro_rules! impl_foo {
($name:ident, $sname:expr) => {
#[doc = "You can call this as `myfoo("]
#[doc = $sname]
#[doc = ")`."]
pub fn myfoo(_: $name) -> $name {
42
}
};
($name:tt) => {
impl_foo!($name, stringify!($name));
};
}
impl_foo!(i32);
fn main() {
println!("Hello, world!");
}
This should generate the documentation "You can call this as myfoo(i32).", but in reality, it results in "You can call this as myfoo( i32 )." (note the additional spaces):
I'm not too sure whether my solution would have worked with the 2017 rustc back when the question was asked, but in modern Rust, this can be done by combining stringify! with concat!:
macro_rules! impl_foo {
($name:tt) => {
#[doc = concat!("You can call this as `myfoo(", stringify!($name), ")`.")]
pub fn myfoo(_: $name) -> $name {
42
}
};
}
impl_foo!(i32);
fn main() {
println!("Hello, world!");
}
This results in the documentation you want (so, without superfluous spaces):
In some cases you may want to pass in an argument to a macro which is either some text, or nothing (blank space, as if nothing was written).
Given this starting point:
macro_rules! testme {
($var:ident, $code:block) => {
for i in 0..10 {
let $var = i;
{ $code }
if $var > 5 {
println!("over 5");
}
}
}
}
fn main() {
testme!(myvar, {
println!("{}", myvar);
});
}
We may want var to optionally be mutable, assuming the macro body is larger then in the example above, its best not to duplicate the entire macro.
macro_rules! testme {
(private $var:ident, $code:block, $var_qual:tt) => {
for i in 0..10 {
// imagine this is a lot more code :)
let $var_qual $var = i;
{ $code }
if $var > 5 {
println!("over 5");
}
}
};
(mut $var:ident, $code:block) => {
testme!(private $var, $code, mut)
};
/*
($var:ident, $code:block) => {
testme!(private $var, $code, )
// ^ how to pass in a blank argument?
};
*/
}
fn main() {
testme!(mut myvar_mut, {
myvar_mut += 10;
println!("{}", myvar_mut);
});
/*
testme!(myvar_immutable, {
println!("{}", myvar_immutable);
});
*/
}
As far as I can tell there is no way to pass in a an empty argument, uncomment the /**/ comments to see the error.
Is it possible to pass in an empty argument to a macro to make an example like this work?
As far as I know its not possible to pass in blank / empty arguments.
It is possible however to pass in a locally defined macro which optionally adds a prefix.
Working example:
macro_rules! testme {
(private $var:ident, $code:block, $var_qual_macro:ident) => {
for i in 0..10 {
// imagine this is a lot more code :)
let $var_qual_macro!($var) = i;
{ $code }
if $var > 5 {
println!("over 5");
}
}
};
(mut $var:ident, $code:block) => {
macro_rules! var_qualifier { ($v:ident) => { mut $v } }
testme!(private $var, $code, var_qualifier)
};
($var:ident, $code:block) => {
macro_rules! var_qualifier { ($v:ident) => { $v } }
testme!(private $var, $code, var_qualifier)
};
}
fn main() {
testme!(mut myvar_mut, {
myvar_mut += 10;
println!("{}", myvar_mut);
});
testme!(myvar_immutable, {
println!("{}", myvar_immutable);
});
}
Take care when nesting macros like this that the name of the macro (var_qualifier in this case) is isn't the same name used inside a different macro since the name will be silently shadowed.
I have different versions of the same macro and I want to be able to choose one of them at compile time.
Here is the code I have:
macro_rules! macro_a {
($identifier:ident) => {
println!("A: {}", stringify!($identifier));
}
}
macro_rules! macro_b {
($identifier:ident) => {
println!("B: {}", stringify!($identifier));
}
}
macro_rules! macro_c {
($identifier:ident) => {
println!("C: {}", stringify!($identifier));
}
}
macro_rules! choose_macro {
(a) => {
const CHOSEN_MACRO: u32 = 1;
};
(b) => {
const CHOSEN_MACRO: u32 = 2;
};
(c) => {
const CHOSEN_MACRO: u32 = 3;
};
}
choose_macro!(c);
macro_rules! use_macro {
($identifier:ident) => {
match CHOSEN_MACRO {
1 => macro_a!($identifier),
2 => macro_b!($identifier),
3 => macro_c!($identifier),
_ => unreachable!(),
}
}
}
fn main() {
use_macro!(test);
}
This will print, as expected:
C: test
I wonder if there is a better way to doing this (with macro or attribute or anything else).
It is not clear if the macro is chosen at compile time here. Will Rust remove the match because it is on a constant?
Update: I prefer to choose the macro in the code, not using compiler flags. Also, I do not want to hide the macros that are not chosen: I want to be able to use them using their real name.
I would recommend using conditional compilation flags for something like this. See https://doc.rust-lang.org/book/conditional-compilation.html
In your case, it might look something like this:
#[cfg(feature = "feature_a")]
macro_rules! use_macro {
($identifier:ident) => {
println!("A: {}", stringify!($identifier));
}
}
#[cfg(feature = "feature_b")]
macro_rules! use_macro {
($identifier:ident) => {
println!("B: {}", stringify!($identifier));
}
}
#[cfg(feature = "feature_c")]
macro_rules! use_macro {
($identifier:ident) => {
println!("C: {}", stringify!($identifier));
}
}
fn main() {
use_macro!(test);
}
Then add the following to your Cargo.toml file:
[features]
feature_a = []
feature_b = []
feature_c = []
If you want it to print out "C: test" for example, then run the following:
cargo run --features feature_c
I've tried using the following code to accomplish it, but the unnest function returns an error:
foreach($params as $key=>$param) {
if(strpos($param, ',') !== false) {
$where = new \Zend\Db\Sql\Where();
$where->nest();
$param_arr = explode(',', $param);
$entered_once = 0;
foreach($param_arr as $p) {
$where->equalTo($key, $p);
if($entered_once < count($param_arr)) {
$where->or;
}
$entered_once++;
}
$where->unnest();
$select->where($where);
}
else {
$select->where(array($key => $param));
}
}
I've figured out what I'm doing wrong: I didn't realize that the nest() function returned a brand new PredicateSet and that's what I needed to call everything on. The solution is as follows:
foreach($params as $key=>$param) {
if(strpos($param, ',') !== false) {
$where = new \Zend\Db\Sql\Where();
$predicate_set = $where->nest();
$param_arr = explode(',', $param);
$entered_once = 0;
foreach($param_arr as $p) {
$predicate_set->equalTo($key, $p);
if($entered_once < count($param_arr)) {
$predicate_set->or;
}
$entered_once++;
}
$predicate_set->unnest();
$select->where($where);
}
else {
$select->where(array($key => $param));
}
}