//! # OSUI Macros //! //! Procedural macros for OSUI that provide ergonomic syntax for defining components. //! //! ## Features //! //! - `#[component]` - Transforms a function into a reusable component with props //! - `rsx!` - Creates RSX (React-like Syntax) for component hierarchies use proc_macro::TokenStream; use quote::quote; use syn::{FnArg, ItemFn, Pat, ReturnType, Type, parse_macro_input}; mod emit; mod parse; /// RSX (React-like Syntax) macro for building component hierarchies /// /// # Example /// /// ```rust,ignore /// rsx! { /// Component { /// prop: value, /// } /// } /// ``` #[proc_macro] pub fn rsx(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as parse::RsxRoot); emit::emit_rsx(ast).into() } /// Component attribute macro for defining reusable components /// /// Transforms a function into a component with automatic prop handling. /// The first parameter must be `cx: &Arc`. /// Remaining parameters become component props. /// /// # Example /// /// ```rust,ignore /// #[component] /// pub fn Counter(cx: &Arc, initial: &i32) -> View { /// let count = use_state(*initial); /// /// Arc::new(move |ctx| { /// ctx.draw_text(Point { x: 0, y: 0 }, &format!("Count: {}", count.get_dl())); /// }) /// } /// ``` #[proc_macro_attribute] pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemFn); let name = &input.sig.ident; let vis = &input.vis; let body = &input.block; let return_ty = match &input.sig.output { ReturnType::Type(_, ty) => ty, _ => { return syn::Error::new_spanned(&input.sig, "component must return View") .to_compile_error() .into(); } }; let mut inputs = input.sig.inputs.iter(); // ---- First param must be cx ---- let cx = match inputs.next() { Some(FnArg::Typed(pat)) => pat, _ => { return syn::Error::new_spanned(&input.sig, "first argument must be cx: &Arc") .to_compile_error() .into(); } }; let cx_ident = match &*cx.pat { Pat::Ident(id) => &id.ident, _ => unreachable!(), }; let cx_ty = &cx.ty; // ---- Remaining params are props ---- let mut struct_fields = Vec::new(); let mut render_params = Vec::new(); let mut call_args = Vec::new(); for arg in inputs { let FnArg::Typed(pat) = arg else { continue }; let ident = match &*pat.pat { Pat::Ident(id) => &id.ident, _ => { return syn::Error::new_spanned(pat, "unsupported prop pattern") .to_compile_error() .into(); } }; // Strip leading & let owned_ty = match &*pat.ty { Type::Reference(r) => &r.elem, ty => ty, }; struct_fields.push(quote! { pub #ident: #owned_ty }); render_params.push(quote! { #ident: &#owned_ty }); call_args.push(quote! { &self.#ident }); } let expanded = quote! { #vis struct #name { #(#struct_fields,)* } impl #name { pub fn component( #cx_ident: #cx_ty, #(#render_params,)* ) -> #return_ty { #body } } impl ComponentImpl for #name { fn call(&self, cx: &std::sync::Arc) -> #return_ty { Self::component( cx, #(#call_args,)* ) } } }; expanded.into() }