Skip to content

Instantly share code, notes, and snippets.

@m4rw3r
Created November 15, 2015 20:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save m4rw3r/db6d63301919fd392b94 to your computer and use it in GitHub Desktop.
Save m4rw3r/db6d63301919fd392b94 to your computer and use it in GitHub Desktop.
/// 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