#[macro_export]
macro_rules! tree {
{
trace = [ $($trace:tt)* ]
rest = [[ < $name:ident $($rest:tt)* ]]
} => {
tagged_element! {
trace = [ $($trace)* { tagged_element } ]
name = $name
args=[]
rest=[[ $($rest)* ]]
}
};
{
trace = [ $($trace:tt)* ]
rest = [[ < $token:tt $($rest:tt)* ]]
} => {{
unexpected_token!(concat!("Didn't expect ", stringify!($token), "after `<`. A component must begin with an identifier"), trace = $trace, tokens = $token)
}};
{
trace = $trace:tt
rest = [[ < ]]
} => {{
unexpected_eof!("Unexpected end of block immediately following `<`", trace = $trace)
}};
{
trace = [ $($trace:tt)* ]
rest = [[ $token:tt $($rest:tt)* ]]
} => {{
let left = $crate::Render::into_fragment($token);
let right = tree! {
trace = [ $($trace)* { next token } ]
rest = [[ $($rest)* ]]
};
concat_trees!(left, right)
}};
{
trace = $trace:tt
rest = [[ ]]
} => {
$crate::Empty
};
{
trace = $trace:tt
rest = [[ $($rest:tt)* ]]
} => {
unexpected_token!("Unexpected token in tree!", trace = $trace, tokens = $($rest)*)
};
($($rest:tt)*) => {
tree! {
trace = [ { tree } ]
rest = [[ $($rest)* ]]
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! unexpected_token {
($message:expr,trace = $trace:tt,tokens = $token:tt $($tokens:tt)*) => {{
force_mismatch!($token);
macro_trace!($message, $trace);
}};
($message:expr,trace = $trace:tt,tokens =) => {{
unexpected_eof!($message, $trace);
}};
($($rest:tt)*) => {{
compile_error!("Invalid call to unexpected_token");
}};
}
#[doc(hidden)]
#[allow(unused_macros)]
#[macro_export]
macro_rules! macro_trace {
($message:expr, [ $({ $($trace:tt)* })* ]) => {{
compile_error!(concat!(
$message,
"\nMacro trace: ",
$(
$(
stringify!($trace),
" ",
)*
"-> ",
)*
))
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! force_mismatch {
() => {};
}
#[doc(hidden)]
#[macro_export]
macro_rules! unimplemented_branch {
($message:expr, trace = $trace:tt,tokens = $($tokens:tt)*) => {{
unexpected_token!(concat!("Unimplemented branch: ", $message), trace = $trace, tokens = $($tokens)*);
}};
($($rest:tt)*) => {{
compile_error("Invalid call to unimplemented_branch");
}}
}
#[doc(hidden)]
#[macro_export]
macro_rules! unexpected_eof {
{ $message:expr, trace = [ $($trace:tt)* ] } => {
compile_error!(concat!("Unexpected end of block: ", $message, "\nMacro trace: ", stringify!($($trace)*)))
};
($($rest:tt)*) => {{
compile_error("Invalid call to unexpected_eof");
}}
}
#[doc(hidden)]
#[macro_export]
macro_rules! concat_trees {
($left:tt,()) => {
$left
};
((), $right:tt) => {
$right
};
($left:tt, $right:tt) => {{
let mut document = $crate::Document::empty();
document = $crate::Render::render($left, document);
document = $crate::Render::render($right, document);
document
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! tagged_element {
{
trace = [ $($trace:tt)* ]
name = $name:tt
args = [ { args = $value:tt } ]
rest = [[ > $($rest:tt)*]]
} => {{
let left = $crate::Component($name, $value);
let rest = tree! {
trace = [ $($trace)* { rest tree } ]
rest = [[ $($rest)* ]]
};
concat_trees!(left, rest)
}};
{
trace = [ $($trace:tt)* ]
name = $name:tt
args = [ $({ $key:ident = $value:tt })* ]
rest = [[ > $($rest:tt)*]]
} => {{
let component = $name {
$(
$key: $value,
)*
};
let rest = tree! {
trace = [ $($trace)* { rest tree } ]
rest = [[ $($rest)* ]]
};
concat_trees!(component, rest)
}};
{
trace = $trace:tt
name = $name:tt
args = $args:tt
rest = [[ $maybe_block:tt $($rest:tt)* ]]
} => {{
tagged_element! {
trace = $trace
name = $name
args = $args
double = [[ @double << $maybe_block $maybe_block >> $($rest)* ]]
}
}};
{
trace = $trace:tt
name = $name:tt
args = $args:tt
double = [[ @double << $maybe_block:tt { $(maybe_block2:tt)* } >> $($rest:tt)* ]]
} => {{
unexpected_token!(
concat!(
"Pass a block to ",
stringify!($name),
" with the `as` keyword: `as` { ... } or pass args with args={ ... }"
),
trace = $trace,
tokens = $name
);
}};
{
trace = [ $($trace:tt)* ]
name = $name:tt
args = $args:tt
double = [[ @double << $as:tt as >> $($rest:tt)* ]]
} => {{
block_component!(
trace = [ $($trace)* { block_component } ]
name = $name
args = $args
rest = [[ $($rest)* ]]
)
}};
{
trace = [ $($trace:tt)* ]
name = $name:tt
args = $args:tt
double = [[ @double << $key:ident $key2:ident >> = $($rest:tt)* ]]
} => {{
tagged_element_value! {
trace = [ $($trace)* { tagged_element_values } ]
name = $name
args = $args
key = $key
rest = [[ $($rest)* ]]
}
}};
{
trace = $trace:tt
name = $name:tt
args = $args:tt
double = [[ @double << $token:tt $double:tt >> $($rest:tt)* ]]
} => {{
unexpected_token!(concat!("Unexpected tokens after <", stringify!($name), ". Expected `key=value`, `as {` or `as |`"), trace = $trace, tokens = $token);
}};
{
trace = $trace:tt
name = $name:tt
args = $args:tt
rest = [[ ]]
} => {{
unexpected_eof!(
concat!("Unexpected end of block after <", stringify!($name)),
trace = $trace
);
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! tagged_element_value {
{
trace = $trace:tt
name = $name:tt
args = [ $($args:tt)* ]
key = $key:ident
rest = [[ $value:ident $($rest:tt)* ]]
} => {
unexpected_token!(
concat!(
"Unexpected value ",
stringify!($value),
". The value must be enclosed in {...}. Did you mean `",
stringify!($key),
"={",
stringify!($value),
"}`?"
),
trace = $trace,
tokens = $value
);
};
{
trace = [ $($trace:tt)* ]
name = $name:tt
args = [ $($args:tt)* ]
key = $key:ident
rest = [[ $value:block $($rest:tt)* ]]
} => {
tagged_element! {
trace = [ $($trace)* { tagged_element } ]
name = $name
args = [ $($args)* { $key = $value } ]
rest = [[ $($rest)*]]
}
};
{
trace = [ $($trace:tt)* ]
name = $name:tt
args = [ $($args:tt)* ]
key = $key:ident
rest = [[ $value:tt $($rest:tt)* ]]
} => {
tagged_element! {
trace = [ $($trace)* { tagged_element } ]
name = $name
args = [ $($args)* { $key = $value } ]
rest = [[ $($rest)*]]
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! block_component {
{
trace = [ $($trace:tt)* ]
name = $name:tt
args = []
rest = [[ { $($block:tt)* }> $($rest:tt)* ]]
} => {{
let inner = tree! {
trace = [ $($trace)* { inner tree } ]
rest = [[ $($block)* ]]
};
let component = $name(inner);
let rest = tree! {
trace = [ $($trace)* { rest tree } ]
rest = [[ $($rest)* ]]
};
concat_trees!(component, rest)
}};
{
trace = [ $($trace:tt)* ]
name = $name:tt
args = [ $({ $key:ident = $value:tt })* ]
rest = [[ |$id:tt| { $($block:tt)* }> $($rest:tt)* ]]
} => {{
let component = $name {
$(
$key: $value
),*
};
let block = $name::with(
component, |$id, doc: $crate::Document| -> $crate::Document {
(tree! {
trace = [ $($trace)* { inner tree } ]
rest = [[ $($block)* ]]
}).render(doc)
}
);
let rest = tree! {
trace = [ $($trace)* { rest tree } ]
rest = [[ $($rest)* ]]
};
concat_trees!(block, rest)
}};
{
trace = [ $($trace:tt)* ]
name = $name:tt
args = [ $({ $key:ident = $value:tt })* ]
rest = [[ { $($block:tt)* }> $($rest:tt)* ]]
} => {{
let data = $name {
$(
$key: $value,
)*
};
let block = |document: Document| -> Document {
(tree! {
trace = [ $($trace)* { inner tree } ]
rest = [[ $($block)* ]]
}).render(document)
};
let component = $crate::BlockComponent::with(data, block);
let rest = tree! {
trace = [ $($trace)* { rest tree } ]
rest = [[ $($rest)* ]]
};
concat_trees!(component, rest)
}};
{
trace = $trace:tt
name = $name:tt
args = $args:tt
rest = [[ $($rest:tt)* ]]
} => {
unexpected_token!("Expected a block or closure parameters after `as`", trace = $trace, tokens=$($rest)*)
};
}
#[cfg(test)]
mod tests {
#[test]
fn basic_usage() -> ::std::io::Result<()> {
let hello = "hello";
let world = format!("world");
let answer = 42;
let document = tree! {
{hello} {" "} {world} {". The answer is "} {answer}
};
assert_eq!(document.to_string()?, "hello world. The answer is 42");
Ok(())
}
}