From e85257a10e684bf5e5709640f5c76ede3b6c66d6 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Fri, 30 Jan 2026 19:29:52 +0100 Subject: [PATCH] component generator --- examples/benchmark.rs | 18 +++--- examples/hello_world.rs | 15 ++--- examples/simple_benchmark.rs | 15 ++--- macros/src/emit.rs | 4 +- macros/src/lib.rs | 103 ++++++++++++++++++++++++++++++++++- src/lib.rs | 2 +- 6 files changed, 125 insertions(+), 32 deletions(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 110860d..6b2e8a2 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -17,7 +17,7 @@ pub fn main() { let handle = thread::spawn(move || { let res = engine - .run(BenchmarkApp(items)) + .run(BenchmarkApp { items }) .expect("Failed to run engine"); tx.send((items, res)).expect("Failed to send result"); @@ -41,16 +41,14 @@ pub fn main() { } } -struct BenchmarkApp(pub usize); +#[component] +fn BenchmarkApp(cx: &Arc, items: &usize) -> View { + let items = items.clone(); -impl ComponentImpl for BenchmarkApp { - fn call(&self, cx: &Arc) -> View { - let items = self.0.clone(); - rsx! { - for _ in (0..items) { - "Hello, world!" - } + rsx! { + for _ in (0..items) { + "Hello, world!" } - .view(cx.clone()) } + .view(cx.clone()) } diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 9807544..7bb27da 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -4,16 +4,13 @@ use osui::prelude::*; pub fn main() { let engine = Console::new(); - engine.run(App).expect("Failed to run engine"); + engine.run(App {}).expect("Failed to run engine"); } -struct App; - -impl ComponentImpl for App { - fn call(&self, cx: &Arc) -> View { - rsx! { - "Hello, world!" - } - .view(cx.clone()) +#[component] +fn App(cx: &Arc) -> View { + rsx! { + "Hello World" } + .view(cx.clone()) } diff --git a/examples/simple_benchmark.rs b/examples/simple_benchmark.rs index ece76c0..539eeaf 100644 --- a/examples/simple_benchmark.rs +++ b/examples/simple_benchmark.rs @@ -4,7 +4,7 @@ use osui::prelude::*; pub fn main() { let engine = Benchmark::new(Console::new()); - let benchmark_result = engine.run(App).expect("Failed to run engine"); + let benchmark_result = engine.run(App {}).expect("Failed to run engine"); println!("Avg: {} μs", benchmark_result.average); println!("Min: {} μs", benchmark_result.min); println!("Max: {} μs", benchmark_result.max); @@ -12,13 +12,10 @@ pub fn main() { println!("Tot Render: {} μs", benchmark_result.total_render); } -struct App; - -impl ComponentImpl for App { - fn call(&self, cx: &Arc) -> View { - rsx! { - "Hello, world!" - } - .view(cx.clone()) +#[component] +fn App(cx: &Arc) -> View { + rsx! { + "Hello, world!" } + .view(cx.clone()) } diff --git a/macros/src/emit.rs b/macros/src/emit.rs index f41e3bc..e47f393 100644 --- a/macros/src/emit.rs +++ b/macros/src/emit.rs @@ -51,14 +51,14 @@ fn emit_node_scope(node: &RsxNode) -> TokenStream { RsxNode::Text(_) => { let emit = emit_node(node); quote! { - r.static_scope(|scope| {#emit}); + r.static_scope(move |scope| {#emit}); } } RsxNode::Component(_) => { let emit = emit_node(node); quote! { - r.static_scope(|scope| {#emit}); + r.static_scope(move |scope| {#emit}); } } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index bb4e672..f13a496 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,10 +1,111 @@ use proc_macro::TokenStream; +use quote::quote; +use syn::{FnArg, ItemFn, Pat, ReturnType, Type, parse_macro_input}; mod emit; mod parse; #[proc_macro] pub fn rsx(input: TokenStream) -> TokenStream { - let ast = syn::parse_macro_input!(input as parse::RsxRoot); + let ast = parse_macro_input!(input as parse::RsxRoot); emit::emit_rsx(ast).into() } + +#[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() +} diff --git a/src/lib.rs b/src/lib.rs index 7923bda..b282e53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ pub mod prelude { pub use crate::render::*; pub use crate::state::*; pub use crate::View; - pub use osui_macros::rsx; + pub use osui_macros::{component, rsx}; } pub type View = Arc;