Skip to content

Instantly share code, notes, and snippets.

@brendanzab
Last active January 2, 2016 16:59
Show Gist options
  • Save brendanzab/8333444 to your computer and use it in GitHub Desktop.
Save brendanzab/8333444 to your computer and use it in GitHub Desktop.

RFC: Proposal for enhancing macros

Currently we have a powerful macro system, but custom control structures stick out like a sore thumb due to the extra set of delimiters that are required.

Implementing a control structure with macro_rules!

Here is an example of a custom for-like construct with some added functionality:

#[feature(macro_rules)];

macro_rules! iter(
    ($x:pat <- .. $hi:expr $body:expr) => (
        iter!($x <- range(0, $hi) $body)
    );
    ($x:pat <- $lo:expr .. $hi:expr $body:expr) => (
        iter!($x <- range($lo, $hi) $body)
    );
    ($x:pat <- $it:expr $body:expr) => ({
        let mut it = $it;
        let mut curr = it.next();
        loop {
            match curr {
                Some($x) => $body,
                None => break,
            }
            curr = it.next();
        }
    });
    ($n:expr $body:expr) => (
        iter!(_ <- ..$n $body)
    );
)

iter!(10 {
    print!("hi ");
})

iter!(x <- ..10 {
    print!("{} ", x);
})

iter!(x <- 3..10 {
    print!("{} ", x);
})

let xs = ["bananas", "in", "pajamas"];
iter!(x <- xs.iter() {
    print!("{} ", *x);
})

Proposed alternative

I propose altering the macro parser to remove the need for the extra delimiters:

#[feature(macro)];

macro! iter {
    {$x:pat <- .. $hi:expr $body:block} => {
        iter! $x <- range(0, $hi) $body
    }
    {$x:pat <- $lo:expr .. $hi:expr $body:block} => {
        iter! $x <- range($lo, $hi) $body
    }
    {$x:pat <- $it:expr $body:block)} => {{
        let mut it = $it;
        let mut curr = it.next();
        loop {
            match curr {
                Some($x) => $body,
                None => break,
            }
            curr = it.next();
        }
    }}
    {$n:expr $body:expr} => {
        iter! _ <- ..$n $body
    };
}

iter! 10 {
    print!("hi ");
}

iter! x <- ..10 {
    print!("{} ", x);
}

iter! x <- 3..10 {
    print!("{} ", x);
}

let xs = ["bananas", "in", "pajamas"];
iter! x <- xs.iter() {
    print!("{} ", *x);
}

This would also have the side-affect of making macro_rules! <ident> { ... } look much less magical.

Other changes

  • macro_rules! -> macro!.
  • Delimiters surrounding macro! body changed from () to {}.
  • Delimiters surrounding rules should accept {} only. At the moment they can be either {} or ().
  • No semi-colons needed between rules.

Issues surrounding multiplicity

The parser would stop consuming tokens once a closing delimiter or the EOF was reached, throwing an error only if no rule could be completed.

macro! expr_muncher {
    {$($munched:expr)*} => {}
}

{
    println!("Hello!");
    expr_muncher!
    println!("oh noes!");           // dead code
    println!("we've been eaten!");  // dead code
}
println!("Hi!");

Example of assert! using the proposed syntax

Current implementation
macro_rules! assert(
    ($cond:expr) => {
        if !$cond { fail!("assertion failed: {:s}", stringify!($cond)) }
    };
    ($cond:expr, $msg:expr) => {
        if !$cond { fail!($msg) }
    };
    ($cond:expr, $( $arg:expr ),+) => {
        if !$cond { fail!( $($arg),+ ) }
    }
)
With the proposed implementation
macro! assert {
    {($cond:expr)} => {
        if !$cond { fail!("assertion failed: {:s}", stringify!($cond)) }
    }
    {($cond:expr, $msg:expr)} => {
        if !$cond { fail!($msg) }
    }
    {($cond:expr, $( $arg:expr ),+)} => {
        if !$cond { fail!( $($arg),+ ) }
    }
}

Notice the extra delimiters needed in the rule pattern.

Potential concerns

  • The expr_muncher! example is a little worrying.
  • Most macros written at the moment make sense looking like function-calls. The added delimiter for the rules could be seen as excessively ugly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment