Skip to main content

osui_macros/
lib.rs

1//! # OSUI Macros
2//!
3//! Procedural macros for OSUI that provide ergonomic syntax for defining components.
4//!
5//! ## Features
6//!
7//! - `#[component]` - Transforms a function into a reusable component with props
8//! - `rsx!` - Creates RSX (React-like Syntax) for component hierarchies
9
10use proc_macro::TokenStream;
11use quote::quote;
12use syn::{FnArg, ItemFn, Pat, ReturnType, Type, parse_macro_input};
13
14mod emit;
15mod parse;
16
17/// RSX (React-like Syntax) macro for building component hierarchies
18///
19/// # Example
20///
21/// ```rust,ignore
22/// rsx! {
23///     Component {
24///         prop: value,
25///     }
26/// }
27/// ```
28#[proc_macro]
29pub fn rsx(input: TokenStream) -> TokenStream {
30    let ast = parse_macro_input!(input as parse::RsxRoot);
31    emit::emit_rsx(ast).into()
32}
33
34/// Component attribute macro for defining reusable components
35///
36/// Transforms a function into a component with automatic prop handling.
37/// The first parameter must be `cx: &Arc<Context>`.
38/// Remaining parameters become component props.
39///
40/// # Example
41///
42/// ```rust,ignore
43/// #[component]
44/// pub fn Counter(cx: &Arc<Context>, initial: &i32) -> View {
45///     let count = use_state(*initial);
46///     
47///     Arc::new(move |ctx| {
48///         ctx.draw_text(Point { x: 0, y: 0 }, &format!("Count: {}", count.get_dl()));
49///     })
50/// }
51/// ```
52#[proc_macro_attribute]
53pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
54    let input = parse_macro_input!(item as ItemFn);
55
56    let name = &input.sig.ident;
57    let vis = &input.vis;
58    let body = &input.block;
59
60    let return_ty = match &input.sig.output {
61        ReturnType::Type(_, ty) => ty,
62        _ => {
63            return syn::Error::new_spanned(&input.sig, "component must return View")
64                .to_compile_error()
65                .into();
66        }
67    };
68
69    let mut inputs = input.sig.inputs.iter();
70
71    // ---- First param must be cx ----
72    let cx = match inputs.next() {
73        Some(FnArg::Typed(pat)) => pat,
74        _ => {
75            return syn::Error::new_spanned(&input.sig, "first argument must be cx: &Arc<Context>")
76                .to_compile_error()
77                .into();
78        }
79    };
80
81    let cx_ident = match &*cx.pat {
82        Pat::Ident(id) => &id.ident,
83        _ => unreachable!(),
84    };
85
86    let cx_ty = &cx.ty;
87
88    // ---- Remaining params are props ----
89    let mut struct_fields = Vec::new();
90    let mut render_params = Vec::new();
91    let mut call_args = Vec::new();
92
93    for arg in inputs {
94        let FnArg::Typed(pat) = arg else { continue };
95
96        let ident = match &*pat.pat {
97            Pat::Ident(id) => &id.ident,
98            _ => {
99                return syn::Error::new_spanned(pat, "unsupported prop pattern")
100                    .to_compile_error()
101                    .into();
102            }
103        };
104
105        // Strip leading &
106        let owned_ty = match &*pat.ty {
107            Type::Reference(r) => &r.elem,
108            ty => ty,
109        };
110
111        struct_fields.push(quote! {
112            pub #ident: #owned_ty
113        });
114
115        render_params.push(quote! {
116            #ident: &#owned_ty
117        });
118
119        call_args.push(quote! {
120            &self.#ident
121        });
122    }
123
124    let expanded = quote! {
125        #vis struct #name {
126            #(#struct_fields,)*
127        }
128
129        impl #name {
130            pub fn component(
131                #cx_ident: #cx_ty,
132                #(#render_params,)*
133            ) -> #return_ty {
134                #body
135            }
136        }
137
138        impl ComponentImpl for #name {
139            fn call(&self, cx: &std::sync::Arc<Context>) -> #return_ty {
140                Self::component(
141                    cx,
142                    #(#call_args,)*
143                )
144            }
145        }
146    };
147
148    expanded.into()
149}