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//! Directive headers may use Rust patterns and omit their outer parentheses.
11//!
12//! ```ignore
13//! let fields = ["id", "name"];
14//! let tokens = moxy::template! {
15//! struct User { @for field in fields { {{ field }}: String, } }
16//! };
17//! ```
18//!
19//! [`paste!`] instead joins tokens inside `{{ ... }}` into an identifier in
20//! declaration position, such as `fn {{ get_ value }}() {}`.
21
22mod ast;
23
24use ast::{Paste, Template};
25use moxy_ast::{Parse, Parser};
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. Multiple Rust statements with a tail expression
35/// are evaluated as a block: `{{ let value = source(); value }}`.
36/// - `@for pattern in iter { … }`, `@if cond { … } @else { … }`, and
37/// `@match (expr) { pat => { … }, … }` run as real control flow, appending to
38/// the output as they execute. Parentheses around `@for` and `@if` headers
39/// remain supported, and conditions may use `if let` bindings.
40///
41/// # Example
42///
43/// ```ignore
44/// let items = vec!["a", "b", "c"];
45/// let tokens = template! {
46/// @for item in items {
47/// {{ item }}
48/// }
49/// };
50/// assert_eq!(tokens.to_string(), "a b c");
51/// ```
52///
53/// A malformed template produces a span-targeted compile error.
54#[proc_macro]
55pub fn template(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
56 let tokens = tokens.into();
57 let parser = Parser::from_tokens(&tokens);
58 let expanded = match Template::parse(&parser) {
59 Ok(tmpl) => tmpl.expand(),
60 Err(e) => e.to_compile_error(),
61 };
62
63 expanded.into()
64}
65
66/// Concatenates the tokens inside each `{{ ... }}` marker into a single
67/// identifier at compile time, passing all other tokens through unchanged.
68///
69/// Use it to mint identifier *names* in declaration position — something the
70/// runtime `moxy::token::ident!` macro cannot do. Segments are taken by token
71/// text (not evaluated), then validated as a Rust identifier.
72///
73/// # Example
74///
75/// ```ignore
76/// paste! {
77/// fn {{ get_ value }}() -> u32 { 7 } // expands to: fn get_value() -> u32 { 7 }
78/// }
79/// assert_eq!(get_value(), 7);
80/// ```
81///
82/// A `{{ ... }}` that does not concatenate to a valid identifier produces a
83/// span-targeted compile error.
84#[proc_macro]
85pub fn paste(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
86 let tokens = tokens.into();
87 let parser = Parser::from_tokens(&tokens);
88 let expanded = match Paste::parse(&parser) {
89 Ok(p) => p.expand(),
90 Err(e) => e.to_compile_error(),
91 };
92
93 expanded.into()
94}