Skip to main content

basecoat_macros/
lib.rs

1//! `basecoat-macros` — the `rsx!` proc-macro for basecoat-rs.
2//!
3//! # Usage
4//!
5//! ```rust,ignore
6//! use basecoat_macros::rsx;
7//!
8//! let markup = rsx! { <Button variant="primary">"Click me"</Button> };
9//! ```
10//!
11//! See `SYNTAX.md` in this crate for the full language specification.
12//!
13//! # Crate architecture
14//!
15//! - `parse` — rstml → internal `RsxNode` AST.
16//! - `emit`  — `RsxNode` AST → `proc_macro2::TokenStream`.
17//! - `props` — static tables: known typed setters per component, void element
18//!   list, PascalCase → snake_case conversion.
19//!
20//! The emitted code calls:
21//! - `::basecoat_core::*` for prop types.
22//! - `::basecoat_components::*` for component functions.
23//! - `::basecoat_macros_rt::escape_text` / `escape_attr` for runtime escaping.
24
25mod emit;
26mod parse;
27mod props;
28
29use proc_macro::TokenStream;
30use proc_macro2::TokenStream as TokenStream2;
31
32/// JSX-like template macro that emits pre-rendered HTML as
33/// `::basecoat_core::Markup`.
34///
35/// See `crates/basecoat-macros/SYNTAX.md` for the full syntax specification
36/// and worked examples.
37///
38/// # Quick reference
39///
40/// - **PascalCase tags** call `::basecoat_components::the_fn(Props::builder()..build())`.
41/// - **lowercase tags** emit raw HTML strings.
42/// - `<>...</>` is a fragment (children concatenated, no wrapper element).
43/// - `attr="literal"` — compile-time-escaped value.
44/// - `attr={expr}` — runtime-escaped via `::basecoat_macros_rt`.
45/// - No control-flow tags — use `{ if ... }` / `{ iter.map(...) }` inside `{}`.
46///
47/// # Example
48///
49/// ```rust,ignore
50/// let name = "World";
51/// let markup = rsx! {
52///     <div class="greeting">
53///         <span>"Hello, "</span>
54///         {name}
55///     </div>
56/// };
57/// assert!(markup.to_string().contains("Hello,"));
58/// ```
59#[proc_macro]
60pub fn rsx(input: TokenStream) -> TokenStream {
61    let input2: TokenStream2 = input.into();
62    match rsx_impl(input2) {
63        Ok(ts) => ts.into(),
64        Err(e) => e.to_compile_error().into(),
65    }
66}
67
68fn rsx_impl(input: TokenStream2) -> syn::Result<TokenStream2> {
69    // Parse via rstml.
70    let nodes = rstml::parse2(input)?;
71
72    // Translate to our AST.
73    let rsx_nodes = parse::translate_nodes(nodes)?;
74
75    // Emit the final TokenStream.
76    let output = emit::emit_root(rsx_nodes);
77    Ok(output)
78}