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::{Parse, 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 parser = Parser::from_tokens(&ts);
58    let expanded = match Template::parse(&parser) {
59        Ok(tmpl) => tmpl.expand(),
60        Err(e) => e.to_compile_error(),
61    };
62
63    let mut out = proc_macro::TokenStream::new();
64    expanded.to_tokens(&mut out);
65    out
66}
67
68/// Concatenates the tokens inside each `{{ ... }}` marker into a single
69/// identifier at compile time, passing all other tokens through unchanged.
70///
71/// Use it to mint identifier *names* in declaration position — something the
72/// runtime `moxy::token::ident!` macro cannot do. Segments are taken by token
73/// text (not evaluated), then validated as a Rust identifier.
74///
75/// # Example
76///
77/// ```ignore
78/// paste! {
79///     fn {{ get_ value }}() -> u32 { 7 }   // expands to: fn get_value() -> u32 { 7 }
80/// }
81/// assert_eq!(get_value(), 7);
82/// ```
83///
84/// A `{{ ... }}` that does not concatenate to a valid identifier produces a
85/// span-targeted compile error.
86#[proc_macro]
87pub fn paste(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
88    let mut ts = TokenStream::new();
89    input.to_tokens(&mut ts);
90
91    let parser = Parser::from_tokens(&ts);
92    let expanded = match Paste::parse(&parser) {
93        Ok(p) => p.expand(),
94        Err(e) => e.to_compile_error(),
95    };
96
97    let mut out = proc_macro::TokenStream::new();
98    expanded.to_tokens(&mut out);
99    out
100}