Created
November 15, 2015 20:56
-
-
Save m4rw3r/db6d63301919fd392b94 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Macro emulating `do`-notation for the parser monad, automatically threading the linear type. | |
/// | |
/// ```ignore | |
/// parse!{input; | |
/// parser("parameter"); | |
/// let value = other_parser(); | |
/// | |
/// ret do_something(value); | |
/// } | |
/// // equivalent to: | |
/// parser(input, "parameter").bind(|i, _| | |
/// other_parser(i).bind(|i, value| | |
/// i.ret(do_something(value)))) | |
/// ``` | |
/// | |
/// # Example | |
/// | |
/// ``` | |
/// # #[macro_use] extern crate parsed; | |
/// # fn main() { | |
/// use parsed::{Input, Data, Error}; | |
/// use parsed::{take_while1, token}; | |
/// | |
/// let i = Input::new("martin wernstål\n".as_bytes()); | |
/// | |
/// #[derive(Debug, Eq, PartialEq)] | |
/// struct Name<'a> { | |
/// first: &'a [u8], | |
/// last: &'a [u8], | |
/// } | |
/// | |
/// let r = parse!{i; | |
/// let first = take_while1(|c| c != b' '); | |
/// token(b' '); | |
/// let last = take_while1(|c| c != b'\n'); | |
/// | |
/// ret @ _, Error<_>: Name{ | |
/// first: first, | |
/// last: last, | |
/// } | |
/// }; | |
/// | |
/// assert_eq!(r.unwrap(), Name{first: b"martin", last: "wernstål".as_bytes()}); | |
/// # } | |
/// ``` | |
/// | |
/// ## Grammar | |
/// | |
/// ```text | |
/// RET_TYPED = '@' $ty ',' $ty ':' $expr | |
/// RET_PLAIN = $expr | |
/// | |
/// ERR_TYPED = '@' $ty ',' $ty ':' $expr | |
/// ERR_PLAIN = $expr | |
/// | |
/// VAR = $ident ':' $ty | $pat | |
/// ACTION = INLINE_ACTION | NAMED_ACTION | |
/// INLINE_ACTION = $ident '->' $expr | |
/// NAMED_ACTION = $ident '(' ($expr ',')* ','? ')' | |
/// | |
/// BIND = 'let' VAR '=' ACTION | |
/// THEN = ACTION | |
/// | |
/// RET = 'ret' ( RET_TYPED | RET_PLAIN ) | |
/// ERR = 'err' ( ERR_TYPED | ERR_PLAIN ) | |
/// | |
/// EXPR = ( BIND ';' | THEN ';' )* (RET | ERR | THEN) | |
/// ``` | |
#[macro_export] | |
macro_rules! parse { | |
// RET_TYPED = '@' $ty ',' $ty ':' $expr | |
// special case for _, since it is not a $ty | |
( @RET($i:expr); @ $t_ty:ty , $e_ty:ty : $e:expr ) => { $i.ret::<$t_ty, $e_ty>($e) }; | |
// RET_PLAIN = $expr | |
( @RET($i:expr); $e:expr ) => { $i.ret($e) }; | |
// ERR_TYPED = '@' $ty ',' $ty ':' $expr | |
// special case for _, since it is not a $ty | |
( @ERR($i:expr); @ $t_ty:ty , $e_ty:ty : $e:expr ) => { $i.err::<$t_ty, $e_ty>($e) }; | |
// ERR_PLAIN = $expr | |
( @ERR($i:expr); $e:expr ) => { $i.err($e) }; | |
// VAR = $ident ':' $ty | $pat | |
// pattern must be before ident | |
( @BIND($i:expr); $v:pat = $($t:tt)* ) => { parse!{ @ACTION($i, $v ); $($t)* } }; | |
( @BIND($i:expr); $v:ident : $v_ty:ty = $($t:tt)* ) => { parse!{ @ACTION($i, $v:$v_ty); $($t)* } }; | |
// ACTION = INLINE_ACTION | NAMED_ACTION | |
// INLINE_ACTION = $ident '->' $expr | |
// version with expression following, nonterminal: | |
( @ACTION($i:expr, $($v:tt)*); $m:ident -> $e:expr ; $($t:tt)*) => { parse!{ @CONCAT({ let $m = $i; $e }, $($v)*); $($t)* } }; | |
// terminal: | |
( @ACTION($i:expr, $($v:tt)*); $m:ident -> $e:expr ) => { { let $m = $i; $e } }; | |
// NAMED_ACTION = $ident '(' ($expr ',')* ','? ')' | |
// version with expression following, nonterminal: | |
( @ACTION($i:expr, $($v:tt)*); $f:ident ( $($p:expr),* $(,)*) ; $($t:tt)* ) => { parse!{ @CONCAT($f($i, $($p),*), $($v)*); $($t)*} }; | |
// terminal: | |
( @ACTION($i:expr, $($v:tt)*); $f:ident ( $($p:expr),* $(,)*) ) => { $f($i, $($p),*) }; | |
// Ties an expression together with the next, using the bind operator | |
// invoked from @ACTION and @BIND (via @ACTION) | |
// three variants are needed to coerce the tt-list into a parameter token | |
// an additional three variants are needed to handle tailing semicolons, if there is nothing | |
// else to expand, do not use bind | |
( @CONCAT($i:expr, _ ); ) => { $i }; | |
( @CONCAT($i:expr, _ ); $($tail:tt)+ ) => { $i.bind(|i, _| parse!{ i; $($tail)* }) }; | |
( @CONCAT($i:expr, $v:pat ); ) => { $i }; | |
( @CONCAT($i:expr, $v:pat ); $($tail:tt)+ ) => { $i.bind(|i, $v| parse!{ i; $($tail)* }) }; | |
( @CONCAT($i:expr, $v:ident : $v_ty:ty); ) => { $i }; | |
( @CONCAT($i:expr, $v:ident : $v_ty:ty); $($tail:tt)+ ) => { $i.bind(|i, $v:$v_ty| parse!{ i; $($tail)* }) }; | |
// EXPR = ( BIND ';' | THEN ';' )* (RET | ERR | THEN) | |
// TODO: Any way to prevent BIND from being the last? | |
// BIND = 'let' VAR '=' ACTION | |
( $i:expr ; let $($tail:tt)* ) => { parse!{ @BIND($i); $($tail)+ } }; | |
// RET = 'ret' ( RET_TYPED | RET_PLAIN ) | |
( $i:expr ; ret $($tail:tt)+ ) => { parse!{ @RET($i); $($tail)+ } }; | |
// ERR = 'err' ( ERR_TYPED | ERR_PLAIN ) | |
( $i:expr ; err $($tail:tt)+ ) => { parse!{ @ERR($i); $($tail)+ } }; | |
// THEN = ACTION | |
// needs to be last since it is the most general | |
( $i:expr ; $($tail:tt)+ ) => { parse!{ @ACTION($i, _); $($tail)+ } }; | |
// Terminals: | |
( $i:expr ; ) => { $i }; | |
( $i:expr ) => { $i }; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment