Skip to main content

moxy_template/
lib.rs

1//! # Moxy templates
2//!
3//! Procedural macros for runtime token templates and identifier pasting.
4//!
5//! ## Template syntax
6//!
7//! [`template!`] returns a `moxy::token::TokenStream`. `{{ expr }}` emits a
8//! value implementing `moxy::token::ToTokens`; `@for`, `@if`/`@else`, and
9//! `@match` evaluate ordinary Rust control flow while building the stream.
10//!
11//! ```ignore
12//! let fields = ["id", "name"];
13//! let tokens = moxy::template! {
14//!     struct User { @for (field in fields) { {{ field }}: String, } }
15//! };
16//! ```
17//!
18//! [`paste!`] instead joins tokens inside `{{ ... }}` into an identifier in
19//! declaration position, such as `fn {{ get_ value }}() {}`.
20
21mod ast;
22
23use ast::{Paste, Template};
24use moxy_ast::Parser;
25use moxy_token::{ToTokens, TokenStream};
26
27/// Build a `moxy::token::TokenStream` at runtime from a template, in the style
28/// of `quote!`.
29///
30/// The macro expands to a block that constructs and returns a `TokenStream`.
31/// Interpolations and control flow are evaluated against the surrounding scope:
32///
33/// - `{{ expr }}` splices the runtime value of `expr` (via `moxy::token::ToTokens`),
34///   preserving its source spans.
35/// - `@for (binding in iter) { … }`, `@if (cond) { … } @else { … }`, and
36///   `@match (expr) { pat => { … }, … }` run as real control flow, appending to
37///   the output as they execute.
38///
39/// # Example
40///
41/// ```ignore
42/// let items = vec!["a", "b", "c"];
43/// let tokens = template! {
44///     @for (item in items) {
45///         {{ item }}
46///     }
47/// };
48/// assert_eq!(tokens.to_string(), "a b c");
49/// ```
50///
51/// A malformed template produces a span-targeted compile error.
52#[proc_macro]
53pub fn template(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
54    let mut ts = TokenStream::new();
55    input.to_tokens(&mut ts);
56
57    let expanded = match Parser::from_tokens(&ts).parse::<Template>() {
58        Ok(tmpl) => tmpl.expand(),
59        Err(e) => e.to_compile_error(),
60    };
61
62    let mut out = proc_macro::TokenStream::new();
63    expanded.to_tokens(&mut out);
64    out
65}
66
67/// Concatenates the tokens inside each `{{ ... }}` marker into a single
68/// identifier at compile time, passing all other tokens through unchanged.
69///
70/// Use it to mint identifier *names* in declaration position — something the
71/// runtime `moxy::token::ident!` macro cannot do. Segments are taken by token
72/// text (not evaluated), then validated as a Rust identifier.
73///
74/// # Example
75///
76/// ```ignore
77/// paste! {
78///     fn {{ get_ value }}() -> u32 { 7 }   // expands to: fn get_value() -> u32 { 7 }
79/// }
80/// assert_eq!(get_value(), 7);
81/// ```
82///
83/// A `{{ ... }}` that does not concatenate to a valid identifier produces a
84/// span-targeted compile error.
85#[proc_macro]
86pub fn paste(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
87    let mut ts = TokenStream::new();
88    input.to_tokens(&mut ts);
89
90    let expanded = match Parser::from_tokens(&ts).parse::<Paste>() {
91        Ok(p) => p.expand(),
92        Err(e) => e.to_compile_error(),
93    };
94
95    let mut out = proc_macro::TokenStream::new();
96    expanded.to_tokens(&mut out);
97    out
98}