render_tree/
macros.rs

1/// This macro builds a [`Document`] using nested syntax.
2///
3/// # Inline values using `{...}` syntax
4///
5/// You can insert any [`Render`] value into a document using `{...}` syntax.
6///
7/// ```
8/// # #[macro_use]
9/// # extern crate render_tree;
10/// # fn main() -> ::std::io::Result<()> {
11/// use render_tree::prelude::*;
12///
13/// let hello = "hello";
14/// let world = format!("world");
15/// let title = ". The answer is ";
16/// let answer = 42;
17///
18/// let document = tree! {
19///     {hello} {" "} {world} {". The answer is "} {answer}
20/// };
21///
22/// assert_eq!(document.to_string()?, "hello world. The answer is 42");
23/// #
24/// # Ok(())
25/// # }
26/// ```
27///
28/// Built-in types that implement render include:
29///
30/// - Anything that implements `Display` (including String, &str, the number types, etc.).
31///   The text value is inserted into the document.
32/// - Other [`Document`]s, which are concatenated onto the document.
33/// - A [`SomeValue`] adapter that takes an `Option<impl Renderable>` and inserts its inner
34///   value if present.
35/// - An [`Empty`] value that adds nothing to the document.
36///
37/// # Inline Components
38///
39/// You can create components to encapsulate some logic:
40///
41/// ```
42/// # #[macro_use]
43/// # extern crate render_tree;
44/// use render_tree::prelude::*;
45///
46/// struct Header {
47///     code: usize,
48///     message: &'static str,
49/// }
50///
51/// impl Render for Header {
52///     fn render(self, document: Document) -> Document {
53///         document.add(tree! {
54///             {self.code} {": "} {self.message}
55///         })
56///     }
57/// }
58///
59/// # fn main() -> ::std::io::Result<()> {
60/// let code = 1;
61/// let message = "Something went wrong";
62///
63/// let document = tree! {
64///     <Header code={code} message={message}>
65/// };
66///
67/// assert_eq!(document.to_string()?, "1: Something went wrong");
68/// #
69/// # Ok(())
70/// # }
71/// ```
72///
73/// # Block Components
74///
75/// You can also build components that take a block that runs exactly
76/// once (an [`FnOnce`]).
77///
78/// ```
79/// #[macro_use]
80/// extern crate render_tree;
81/// use render_tree::prelude::*;
82///
83/// struct Message {
84///     code: usize,
85///     message: &'static str,
86///     trailing: &'static str,
87/// }
88///
89/// impl BlockComponent for Message {
90///     fn append(
91///         self,
92///         block: impl FnOnce(Document) -> Document,
93///         mut document: Document,
94///     ) -> Document {
95///         document = document.add(tree! {
96///             {self.code} {": "} {self.message} {" "}
97///         });
98///
99///         document = block(document);
100///
101///         document = document.add(tree! {
102///             {self.trailing}
103///         });
104///
105///         document
106///     }
107/// }
108///
109/// # fn main() -> ::std::io::Result<()> {
110/// let code = 1;
111/// let message = "Something went wrong";
112///
113/// let document = tree! {
114///     <Message code={code} message={message} trailing={" -- yikes!"} as {
115///         {"!!! It's really quite bad !!!"}
116///     }>
117/// };
118///
119/// assert_eq!(document.to_string()?, "1: Something went wrong !!! It's really quite bad !!! -- yikes!");
120/// #
121/// # Ok(())
122/// # }
123/// ```
124///
125/// # Iterators
126///
127/// Finally, you can create components that take a block and call the block
128/// multiple times (an iterator).
129///
130/// ```
131/// # #[macro_use]
132/// # extern crate render_tree;
133/// use render_tree::prelude::*;
134/// use std::io;
135///
136/// pub struct UpcaseAll<Iterator: IntoIterator<Item = String>> {
137///     pub items: Iterator,
138/// }
139///
140/// impl<Iterator: IntoIterator<Item = String>> IterBlockComponent for UpcaseAll<Iterator> {
141///     type Item = String;
142///
143///     fn append(
144///         self,
145///         mut block: impl FnMut(String, Document) -> Document,
146///         mut document: Document,
147///     ) -> Document {
148///         for item in self.items {
149///             document = block(item.to_uppercase(), document);
150///         }
151///
152///         document
153///     }
154/// }
155///
156/// # fn main() -> io::Result<()> {
157/// let list = vec![format!("Hello"), format!("World")];
158///
159/// let document = tree! {
160///     <UpcaseAll items={list} as |item| {
161///         {"upcase:"} {item}
162///     }>
163/// };
164///
165/// assert_eq!(document.to_string()?, "upcase:HELLOupcase:WORLD");
166/// # Ok(())
167/// # }
168/// ```
169#[macro_export]
170macro_rules! tree {
171    // We're effectively handling patterns of matched delimiters that aren't intrinsically
172    // supported by Rust here.
173    //
174    // If the first character we're processing is a `<`, that means we're looking at a
175    // component of some kind. This macro matches a list of individual tokens, and
176    // delegates the stuff between matching `< ... >`.
177    {
178        trace = [ $($trace:tt)* ]
179        rest = [[ < $name:ident $($rest:tt)* ]]
180    } => {
181        tagged_element! {
182            trace = [ $($trace)* { tagged_element } ]
183            name = $name
184            args=[]
185            rest=[[ $($rest)* ]]
186        }
187    };
188
189    // Anything other than an identifier immediately following a `<` is an error.
190    {
191        trace = [ $($trace:tt)* ]
192        rest = [[ < $token:tt $($rest:tt)* ]]
193    } => {{
194        unexpected_token!(concat!("Didn't expect ", stringify!($token), "after `<`. A component must begin with an identifier"), trace = $trace, tokens = $token)
195    }};
196
197    // An empty stream after `<` is an unexpected EOF
198    {
199        trace = $trace:tt
200        rest = [[ < ]]
201    } => {{
202        unexpected_eof!("Unexpected end of block immediately following `<`", trace = $trace)
203    }};
204
205    // If we didn't see a component, we're matching a single token, which must
206    // correspond to an expression that produces an impl Render.
207    {
208        trace = [ $($trace:tt)* ]
209        rest = [[ $token:tt $($rest:tt)* ]]
210    } => {{
211        let left = $crate::Render::into_fragment($token);
212
213        let right = tree! {
214            trace = [ $($trace)* { next token } ]
215            rest = [[ $($rest)* ]]
216        };
217
218        concat_trees!(left, right)
219    }};
220
221    // If there's no tokens left, produce Empty, which can be concatenated to
222    // the end of any other produced `Render`s.
223    {
224        trace = $trace:tt
225        rest = [[  ]]
226    } => {
227        $crate::Empty
228    };
229
230    // Anything else is an unexpected token, but since a previous rule matches
231    // any `$token:tt`, it's not obvious what could match here.
232    {
233        trace = $trace:tt
234        rest = [[ $($rest:tt)* ]]
235    } => {
236        unexpected_token!("Unexpected token in tree!", trace = $trace, tokens = $($rest)*)
237    };
238
239    // The entry point of the entire macro from the user.
240    ($($rest:tt)*) => {
241        tree! {
242            trace = [ { tree } ]
243            rest = [[ $($rest)* ]]
244        }
245    };
246}
247
248#[doc(hidden)]
249#[macro_export]
250macro_rules! unexpected_token {
251    ($message:expr,trace = $trace:tt,tokens = $token:tt $($tokens:tt)*) => {{
252        force_mismatch!($token);
253        macro_trace!($message, $trace);
254    }};
255
256    ($message:expr,trace = $trace:tt,tokens =) => {{
257        unexpected_eof!($message, $trace);
258    }};
259
260    ($($rest:tt)*) => {{
261        compile_error!("Invalid call to unexpected_token");
262    }};
263}
264
265#[doc(hidden)]
266#[allow(unused_macros)]
267#[macro_export]
268macro_rules! macro_trace {
269    ($message:expr, [ $({ $($trace:tt)* })* ]) => {{
270        compile_error!(concat!(
271            $message,
272            "\nMacro trace: ",
273
274            $(
275                $(
276                    stringify!($trace),
277                    " ",
278                )*
279                "-> ",
280            )*
281        ))
282    }};
283}
284
285#[doc(hidden)]
286#[macro_export]
287macro_rules! force_mismatch {
288    () => {};
289}
290
291#[doc(hidden)]
292#[macro_export]
293macro_rules! unimplemented_branch {
294    ($message:expr, trace = $trace:tt,tokens = $($tokens:tt)*) => {{
295        unexpected_token!(concat!("Unimplemented branch: ", $message), trace = $trace, tokens = $($tokens)*);
296    }};
297
298    ($($rest:tt)*) => {{
299        compile_error("Invalid call to unimplemented_branch");
300    }}
301}
302
303#[doc(hidden)]
304#[macro_export]
305macro_rules! unexpected_eof {
306     { $message:expr, trace = [ $($trace:tt)* ] } => {
307        compile_error!(concat!("Unexpected end of block: ", $message, "\nMacro trace: ", stringify!($($trace)*)))
308    };
309
310    ($($rest:tt)*) => {{
311        compile_error("Invalid call to unexpected_eof");
312    }}
313}
314
315#[doc(hidden)]
316#[macro_export]
317macro_rules! concat_trees {
318    ($left:tt,()) => {
319        $left
320    };
321
322    ((), $right:tt) => {
323        $right
324    };
325
326    ($left:tt, $right:tt) => {{
327        let mut document = $crate::Document::empty();
328        document = $crate::Render::render($left, document);
329        document = $crate::Render::render($right, document);
330
331        document
332    }};
333}
334
335#[doc(hidden)]
336#[macro_export]
337macro_rules! tagged_element {
338    {
339        trace = [ $($trace:tt)* ]
340        name = $name:tt
341        args = [ { args = $value:tt } ]
342        rest = [[ > $($rest:tt)*]]
343    } => {{
344        let left = $crate::Component($name, $value);
345
346        let rest =  tree! {
347            trace = [ $($trace)* { rest tree } ]
348            rest = [[ $($rest)* ]]
349        };
350
351        concat_trees!(left, rest)
352    }};
353
354    // The `key={value}` syntax is only compatible with block-based components,
355    // so if we see a `>` at this point, it's an error.
356    {
357        trace = [ $($trace:tt)* ]
358        name = $name:tt
359        args = [ $({ $key:ident = $value:tt })* ]
360        rest = [[ > $($rest:tt)*]]
361    } => {{
362        let component = $name {
363            $(
364                $key: $value,
365            )*
366        };
367
368        let rest = tree! {
369            trace = [ $($trace)* { rest tree } ]
370            rest = [[ $($rest)* ]]
371        };
372
373        concat_trees!(component, rest)
374    }};
375
376    // Triage the next token into a "double token" because it may indicate an
377    // error. If it turns out to be an error, we wil have the token as a
378    // variable that we can get span reporting for.
379    {
380        trace = $trace:tt
381        name = $name:tt
382        args = $args:tt
383        rest = [[ $maybe_block:tt $($rest:tt)* ]]
384    } => {{
385        tagged_element! {
386            trace = $trace
387            name = $name
388            args = $args
389            double = [[ @double << $maybe_block $maybe_block >> $($rest)*  ]]
390        }
391    }};
392
393    // If we see a block, it's a mistake. Either the user forgot the name of
394    // the key for an argument or they forgot the `as` prefix to a block.
395    {
396        trace = $trace:tt
397        name = $name:tt
398        args = $args:tt
399        double = [[ @double << $maybe_block:tt { $(maybe_block2:tt)* } >> $($rest:tt)*  ]]
400    } => {{
401        unexpected_token!(
402            concat!(
403                "Pass a block to ",
404                stringify!($name),
405                " with the `as` keyword: `as` { ... } or pass args with args={ ... }"
406            ),
407            trace = $trace,
408            tokens = $name
409        );
410    }};
411
412    // If we see an `as`, we're looking at a block component.
413    {
414        trace = [ $($trace:tt)* ]
415        name = $name:tt
416        args = $args:tt
417        double = [[ @double << $as:tt as >> $($rest:tt)*  ]]
418    } => {{
419        block_component!(
420            trace = [ $($trace)* { block_component } ]
421            name = $name
422            args = $args
423            rest = [[ $($rest)* ]]
424        )
425    }};
426
427    // // Otherwise, if we see `args=`, it's the special singleton `args` case.
428    // {
429    //     trace = [ $($trace:tt)* ]
430    //     name = $name:tt
431    //     args = $args:tt
432    //     double = [[ @double << args args >> = $($rest:tt)*  ]]
433    // } => {{
434    //     component_with_args! {
435    //         trace = [ $($trace)* { component_with_args } ]
436    //         name = $name
437    //         rest = [[ $($rest)* ]]
438    //     }
439    // }};
440
441    // Otherwise, if we see an `ident=`, we're looking at a key of an
442    // argument. TODO: Combine this case with the previous one.
443    {
444        trace = [ $($trace:tt)* ]
445        name = $name:tt
446        args = $args:tt
447        double = [[ @double << $key:ident $key2:ident >> = $($rest:tt)*  ]]
448    } => {{
449        tagged_element_value! {
450            trace = [ $($trace)* { tagged_element_values } ]
451            name = $name
452            args = $args
453            key = $key
454            rest = [[ $($rest)* ]]
455        }
456    }};
457
458    // Anything else is an error.
459    {
460        trace = $trace:tt
461        name = $name:tt
462        args = $args:tt
463        double = [[ @double << $token:tt $double:tt >> $($rest:tt)* ]]
464    } => {{
465        unexpected_token!(concat!("Unexpected tokens after <", stringify!($name), ". Expected `key=value`, `as {` or `as |`"), trace = $trace, tokens = $token);
466    }};
467
468    // No more tokens is an error
469    {
470        trace = $trace:tt
471        name = $name:tt
472        args = $args:tt
473        rest = [[ ]]
474    } => {{
475        unexpected_eof!(
476            concat!("Unexpected end of block after <", stringify!($name)),
477            trace = $trace
478        );
479    }};
480}
481
482#[doc(hidden)]
483#[macro_export]
484macro_rules! tagged_element_value {
485    // We saw a `ident=` and are now looking for a value.
486    {
487        trace = $trace:tt
488        name = $name:tt
489        args = [ $($args:tt)* ]
490        key = $key:ident
491        rest = [[ $value:ident $($rest:tt)* ]]
492    } => {
493        unexpected_token!(
494            concat!(
495                "Unexpected value ",
496                stringify!($value),
497                ". The value must be enclosed in {...}. Did you mean `",
498                stringify!($key),
499                "={",
500                stringify!($value),
501                "}`?"
502            ),
503            trace = $trace,
504            tokens = $value
505        );
506    };
507
508    // We saw a `ident=` and found a block. Accumulate the key/value pair and
509    // continue parsing the tag.
510    {
511        trace = [ $($trace:tt)* ]
512        name = $name:tt
513        args = [ $($args:tt)* ]
514        key = $key:ident
515        rest = [[ $value:block $($rest:tt)* ]]
516    } => {
517        tagged_element! {
518            trace = [ $($trace)* { tagged_element } ]
519            name = $name
520            args = [ $($args)* { $key = $value } ]
521            rest = [[ $($rest)*]]
522        }
523    };
524
525    // Anything else is an error.
526    {
527        trace = [ $($trace:tt)* ]
528        name = $name:tt
529        args = [ $($args:tt)* ]
530        key = $key:ident
531        rest = [[ $value:tt $($rest:tt)* ]]
532    } => {
533        tagged_element! {
534            trace = [ $($trace)* { tagged_element } ]
535            name = $name
536            args = [ $($args)* { $key = $value } ]
537            rest = [[ $($rest)*]]
538        }
539    };
540}
541
542// We got to the end of the tag opening and now we found a block. Parse
543// the contents of the block as a new tree, and then continue processing
544// the tokens.
545#[doc(hidden)]
546#[macro_export]
547macro_rules! block_component {
548    // If there were no arguments, call the function with the inner block.
549    {
550        trace = [ $($trace:tt)* ]
551        name = $name:tt
552        args = []
553        rest = [[ { $($block:tt)* }> $($rest:tt)* ]]
554    } => {{
555        let inner = tree! {
556            trace = [ $($trace)* { inner tree } ]
557            rest = [[ $($block)* ]]
558        };
559
560        let component = $name(inner);
561
562        let rest = tree! {
563            trace = [ $($trace)* { rest tree } ]
564            rest = [[ $($rest)* ]]
565        };
566
567        concat_trees!(component, rest)
568    }};
569
570    // Otherwise, if there were arguments and closure parameters, construct
571    // the argument object with the component's name and supplied arguments.
572    // Then, call the component function with the constructed object and a
573    // closure that takes a component-supplied callback parameter.
574    {
575        trace = [ $($trace:tt)* ]
576        name = $name:tt
577        args = [ $({ $key:ident = $value:tt })* ]
578        rest = [[ |$id:tt| { $($block:tt)* }> $($rest:tt)* ]]
579    } => {{
580        let component = $name {
581            $(
582                $key: $value
583            ),*
584        };
585
586        let block = $name::with(
587            component, |$id, doc: $crate::Document| -> $crate::Document {
588                (tree! {
589                    trace = [ $($trace)* { inner tree } ]
590                    rest = [[ $($block)* ]]
591                }).render(doc)
592            }
593        );
594
595        let rest = tree! {
596            trace = [ $($trace)* { rest tree } ]
597            rest = [[ $($rest)* ]]
598        };
599
600        concat_trees!(block, rest)
601    }};
602
603    // Otherwise, if there were arguments, construct the argument object
604    // with the component's name and supplied arguments, and call the
605    // function with a closure that doesn't take a user-supplied parameter.
606    {
607        trace = [ $($trace:tt)* ]
608        name = $name:tt
609        args = [ $({ $key:ident = $value:tt })* ]
610        rest = [[ { $($block:tt)* }> $($rest:tt)* ]]
611    } => {{
612        let data = $name {
613            $(
614                $key: $value,
615            )*
616        };
617
618        let block = |document: Document| -> Document {
619            (tree! {
620                trace = [ $($trace)* { inner tree } ]
621                rest = [[ $($block)* ]]
622            }).render(document)
623        };
624
625        let component = $crate::BlockComponent::with(data, block);
626
627
628        let rest = tree! {
629            trace = [ $($trace)* { rest tree } ]
630            rest = [[ $($rest)* ]]
631        };
632
633        concat_trees!(component, rest)
634    }};
635
636    {
637        trace = $trace:tt
638        name = $name:tt
639        args = $args:tt
640        rest = [[ $($rest:tt)* ]]
641    } => {
642        unexpected_token!("Expected a block or closure parameters after `as`", trace = $trace, tokens=$($rest)*)
643    };
644}
645
646#[cfg(test)]
647mod tests {
648    #[test]
649    fn basic_usage() -> ::std::io::Result<()> {
650        let hello = "hello";
651        let world = format!("world");
652        let answer = 42;
653
654        let document = tree! {
655            {hello} {" "} {world} {". The answer is "} {answer}
656        };
657
658        assert_eq!(document.to_string()?, "hello world. The answer is 42");
659
660        Ok(())
661    }
662}