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}