rsx_macro/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::quote;
4use std::fs;
5use syn::{braced, parse::Parse, parse_macro_input, Expr, Ident, LitStr, Token};
6
7#[cfg(test)]
8mod jsx_tests;
9
10#[cfg(test)]
11mod complex_closure_tests;
12
13// New JSX-like syntax parser
14struct RsxInput {
15    elements: Vec<RsxElement>,
16}
17
18#[derive(Clone)]
19enum RsxElement {
20    Element {
21        tag: Ident,
22        attrs: Vec<(Ident, RsxAttrValue)>,
23        children: Vec<RsxElement>,
24    },
25    Text(LitStr),
26    Expression(Expr),
27}
28
29#[derive(Clone)]
30enum RsxAttrValue {
31    String(LitStr),
32    Expression(Expr),
33}
34
35impl Parse for RsxInput {
36    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
37        let mut elements = Vec::new();
38
39        while !input.is_empty() {
40            if input.peek(Token![<]) {
41                match parse_element_manual(input) {
42                    Ok(element) => elements.push(element),
43                    Err(e) => {
44                        let remaining = input.to_string();
45                        return Err(input.error(&format!(
46                            "Element parsing failed: {}. Remaining tokens: {}",
47                            e, remaining
48                        )));
49                    }
50                }
51            } else {
52                // Skip whitespace and other tokens
53                match input.parse::<proc_macro2::TokenTree>() {
54                    Ok(_) => continue,
55                    Err(e) => {
56                        let remaining = input.to_string();
57                        return Err(input.error(&format!(
58                            "Token parsing failed: {}. Remaining tokens: {}",
59                            e, remaining
60                        )));
61                    }
62                }
63            }
64        }
65
66        if elements.is_empty() {
67            return Err(input.error("Empty JSX input"));
68        }
69
70        Ok(RsxInput { elements })
71    }
72}
73
74fn parse_element_manual(input: syn::parse::ParseStream) -> syn::Result<RsxElement> {
75    input.parse::<Token![<]>()?;
76    let tag: Ident = input.parse()?;
77
78    let mut attrs = Vec::new();
79
80    // Parse attributes (handling both boolean and value attributes)
81    loop {
82        // Skip whitespace before checking for attributes
83        while !input.is_empty()
84            && !input.peek(Token![>])
85            && !input.peek(Token![/])
86            && !input.peek(syn::Ident)
87        {
88            if let Ok(_) = input.parse::<proc_macro2::TokenTree>() {
89                continue; // Skip whitespace tokens
90            } else {
91                break;
92            }
93        }
94
95        // Check if we should exit the attribute parsing loop
96        if input.peek(Token![>]) || input.peek(Token![/]) || !input.peek(syn::Ident) {
97            break;
98        }
99
100        let attr_name: Ident = input.parse()?;
101
102        // Skip whitespace after attribute name
103        while !input.is_empty()
104            && !input.peek(Token![=])
105            && !input.peek(Token![>])
106            && !input.peek(Token![/])
107            && !input.peek(syn::Ident)
108        {
109            if let Ok(_) = input.parse::<proc_macro2::TokenTree>() {
110                continue; // Skip whitespace tokens
111            } else {
112                break;
113            }
114        }
115
116        // Check if attribute has a value
117        if input.peek(Token![=]) {
118            input.parse::<Token![=]>()?;
119
120            // Skip whitespace after =
121            while !input.is_empty() && !input.peek(syn::LitStr) && !input.peek(syn::token::Brace) {
122                if let Ok(_) = input.parse::<proc_macro2::TokenTree>() {
123                    continue; // Skip whitespace tokens
124                } else {
125                    break;
126                }
127            }
128
129            let attr_value = if input.peek(syn::LitStr) {
130                let s: LitStr = input.parse()?;
131                RsxAttrValue::String(s)
132            } else if input.peek(syn::token::Brace) {
133                // Handle complex braced expressions with better brace matching
134                let content;
135
136                // Use syn's robust brace handling
137                let _brace_result = braced!(content in input);
138
139                // Convert the entire content to an expression, handling complex patterns
140                let content_str = content.to_string();
141
142                // Debug: show what we're trying to parse
143                eprintln!("🔍 Parsing attribute expression: {}", content_str);
144
145                // Try multiple parsing strategies for robustness
146                let parsed_expr = if let Ok(expr) = content.parse::<Expr>() {
147                    // Direct parsing worked (simple expressions)
148                    eprintln!("✅ Direct parsing succeeded");
149                    expr
150                } else if let Ok(expr) = syn::parse_str::<Expr>(&format!("{{ {} }}", content_str)) {
151                    // Block expression parsing worked (complex statements)
152                    eprintln!("✅ Block expression parsing succeeded");
153                    expr
154                } else if let Ok(expr) =
155                    syn::parse_str::<Expr>(&format!("(|| -> _ {{ {} }})()", content_str))
156                {
157                    // Closure wrapper parsing (fallback for complex closures)
158                    eprintln!("✅ Closure wrapper parsing succeeded");
159                    expr
160                } else {
161                    eprintln!("❌ All parsing strategies failed for: {}", content_str);
162                    return Err(input.error(&format!(
163                        "Cannot parse attribute expression: {}",
164                        content_str
165                    )));
166                };
167
168                RsxAttrValue::Expression(parsed_expr)
169            } else {
170                return Err(input.error("Expected string or expression for attribute value"));
171            };
172
173            attrs.push((attr_name, attr_value));
174        } else {
175            // Boolean attribute (no value)
176            attrs.push((
177                attr_name,
178                RsxAttrValue::String(syn::LitStr::new("true", proc_macro2::Span::call_site())),
179            ));
180        }
181    }
182
183    // Check for self-closing
184    if input.peek(Token![/]) {
185        input.parse::<Token![/]>()?;
186        input.parse::<Token![>]>()?;
187        return Ok(RsxElement::Element {
188            tag,
189            attrs,
190            children: Vec::new(),
191        });
192    }
193
194    input.parse::<Token![>]>()?;
195
196    // Parse children using manual token parsing
197    let mut children = Vec::new();
198    while !input.is_empty() {
199        // Check for closing tag
200        if input.peek(Token![<]) {
201            let fork = input.fork();
202            if fork.parse::<Token![<]>().is_ok() && fork.parse::<Token![/]>().is_ok() {
203                break; // This is a closing tag
204            }
205            // This is another element
206            children.push(parse_element_manual(input)?);
207        } else if input.peek(syn::LitStr) {
208            let text: LitStr = input.parse()?;
209            children.push(RsxElement::Text(text));
210        } else if input.peek(syn::token::Brace) {
211            // Parse braced expression directly
212            let content;
213            braced!(content in input);
214
215            // Try to parse as expression directly first
216            match content.parse::<Expr>() {
217                Ok(expr) => children.push(RsxElement::Expression(expr)),
218                Err(_) => {
219                    // If direct parsing fails, try as block expression
220                    let expr_str = content.to_string();
221                    let block_expr = format!("{{ {} }}", expr_str);
222
223                    match syn::parse_str::<Expr>(&block_expr) {
224                        Ok(expr) => children.push(RsxElement::Expression(expr)),
225                        Err(e) => {
226                            return Err(
227                                input.error(&format!("Failed to parse child expression: {}", e))
228                            );
229                        }
230                    }
231                }
232            }
233        } else {
234            // Skip other tokens (whitespace, etc.)
235            if input.parse::<proc_macro2::TokenTree>().is_err() {
236                break; // End of input
237            }
238        }
239    }
240
241    // Parse closing tag
242    input.parse::<Token![<]>()?;
243    input.parse::<Token![/]>()?;
244    let _closing_tag: Ident = input.parse()?;
245    input.parse::<Token![>]>()?;
246
247    Ok(RsxElement::Element {
248        tag,
249        attrs,
250        children,
251    })
252}
253
254fn try_parse_braced_expression(input: syn::parse::ParseStream) -> syn::Result<Option<Expr>> {
255    if !input.peek(syn::token::Brace) {
256        return Ok(None);
257    }
258
259    // Use syn's built-in brace parsing to extract the TokenStream inside braces
260    let content;
261    braced!(content in input);
262
263    // Try to parse the content directly as an expression
264    // This preserves the original tokens instead of converting to string
265    match content.parse::<Expr>() {
266        Ok(expr) => Ok(Some(expr)),
267        Err(e) => {
268            // If direct parsing fails, provide a helpful error
269            let content_str = content.to_string();
270            Err(input.error(&format!(
271                "Failed to parse braced expression '{{{}}}': {}",
272                content_str, e
273            )))
274        }
275    }
276}
277
278#[proc_macro]
279pub fn rsx(input: TokenStream) -> TokenStream {
280    let input2: proc_macro2::TokenStream = input.into();
281
282    // DEBUG: Show exactly what tokens we receive
283    let input_str = input2.to_string();
284    let token_count = input2.clone().into_iter().count();
285
286    // Try JSX syntax first (our proven working parser)
287    match syn::parse2::<RsxInput>(input2.clone()) {
288        Ok(jsx_input) => {
289            // Success! Use the JSX parser
290            expand_jsx(jsx_input).into()
291        }
292        Err(jsx_err) => {
293            // Fallback to string syntax
294            match syn::parse::<LitStr>(input2.clone().into()) {
295                Ok(s) => expand(&s.value()).into(),
296                Err(_string_err) => {
297                    // Enhanced error with token debugging
298                    let debug_msg = format!(
299                        "JSX parsing failed!\n\
300                        Error: {}\n\
301                        Token count: {}\n\
302                        Tokens: {}\n\
303                        Expected: rsx! {{ <div>content</div> }}\n\
304                        or: rsx!(r#\"<div>content</div>\"#)",
305                        jsx_err, token_count, input_str
306                    );
307                    syn::Error::new(proc_macro2::Span::call_site(), debug_msg)
308                        .to_compile_error()
309                        .into()
310                }
311            }
312        }
313    }
314}
315
316fn expand_jsx(input: RsxInput) -> TokenStream2 {
317    let elements: Vec<_> = input.elements.iter().map(emit_jsx_element).collect();
318
319    quote! {{
320        let nodes: ::std::vec::Vec<rsx_core::VNode> = vec![#(#elements),*];
321        if nodes.len() == 1 {
322            nodes.into_iter().next().unwrap()
323        } else {
324            rsx_core::fragment(nodes)
325        }
326    }}
327}
328
329fn emit_jsx_element(element: &RsxElement) -> TokenStream2 {
330    match element {
331        RsxElement::Element {
332            tag,
333            attrs,
334            children,
335        } => {
336            let tag_str = tag.to_string();
337
338            // Check if this is a React component (starts with uppercase)
339            let is_component = tag_str.chars().next().map_or(false, |c| c.is_uppercase());
340
341            if is_component {
342                // React component: <Counter /> -> Counter()
343                // For now, ignore props and children - components manage their own state
344                if !attrs.is_empty() {
345                    // TODO: Support component props in future
346                    return syn::Error::new(tag.span(), "Component props not yet supported. Use function calls for now.")
347                        .to_compile_error();
348                }
349                if !children.is_empty() {
350                    // TODO: Support component children in future
351                    return syn::Error::new(tag.span(), "Component children not yet supported. Use function calls for now.")
352                        .to_compile_error();
353                }
354
355                // Generate component function call
356                quote! { #tag() }
357            } else {
358                // HTML element: <div> -> rsx_core::h("div", props, children)
359                let props = emit_jsx_props(attrs);
360                let children_tokens: Vec<_> = children.iter().map(emit_jsx_element).collect();
361
362                quote! {
363                    rsx_core::h(#tag_str, #props, vec![#(#children_tokens),*])
364                }
365            }
366        }
367        RsxElement::Text(text) => {
368            let text_value = text.value();
369            quote! { rsx_core::text(#text_value) }
370        }
371        RsxElement::Expression(expr) => {
372            quote! { rsx_core::VNode::from(#expr) }
373        }
374    }
375}
376
377fn emit_jsx_props(attrs: &[(Ident, RsxAttrValue)]) -> TokenStream2 {
378    let prop_entries: Vec<_> = attrs.iter().map(|(name, value)| {
379        let name_str = name.to_string();
380        let prop_value = if name_str.starts_with("on") {
381            // Event handler like onclick, onchange, etc.
382            match value {
383                RsxAttrValue::Expression(expr) => {
384                    // Handle closures properly by wrapping in EventHandler
385                    quote! { rsx_core::PropValue::EventHandler(std::rc::Rc::new(std::cell::RefCell::new(#expr))) }
386                }
387                RsxAttrValue::String(s) => {
388                    let s_val = s.value();
389                    quote! { rsx_core::PropValue::Callback(#s_val.to_string()) }
390                }
391            }
392        } else {
393            match value {
394                RsxAttrValue::String(s) => {
395                    let s_val = s.value();
396                    quote! { rsx_core::PropValue::Str(#s_val.to_string()) }
397                }
398                RsxAttrValue::Expression(expr) => {
399                    quote! { rsx_core::PropValue::Str(format!("{}", #expr)) }
400                }
401            }
402        };
403
404        let event_name = if name_str.starts_with("on") {
405            format!("on:{}", &name_str[2..])  // onclick -> on:click
406        } else {
407            name_str
408        };
409
410        quote! { (#event_name.to_string(), #prop_value) }
411    }).collect();
412
413    quote! {
414        {
415            let mut map = indexmap::IndexMap::new();
416            #(map.insert #prop_entries;)*
417            map
418        }
419    }
420}
421
422#[proc_macro]
423pub fn include_rsx(input: TokenStream) -> TokenStream {
424    let path = parse_macro_input!(input as LitStr).value();
425    let content = fs::read_to_string(&path).expect("failed to read RSX file");
426    expand(&content).into()
427}
428
429/// Component attribute macro for automatic setup
430#[proc_macro_attribute]
431pub fn component(_args: TokenStream, input: TokenStream) -> TokenStream {
432    // Just return the function as-is for now
433    // The component system will be handled by rsx_main!
434    input
435}
436
437/// React-style app initialization - like createRoot() but better!
438#[proc_macro]
439pub fn rsx_main(input: TokenStream) -> TokenStream {
440    let component_name = parse_macro_input!(input as syn::Ident);
441
442    let output = quote! {
443        // Global state storage - safe and automatic
444        thread_local! {
445            static APP_SIGNALS: std::cell::RefCell<std::collections::HashMap<String, Box<dyn std::any::Any>>> =
446                std::cell::RefCell::new(std::collections::HashMap::new());
447        }
448
449        // Safe signal creation without unsafe code
450        fn use_app_signal<T: Clone + 'static>(key: &str, initial: T) -> rsx_core::Signal<T> {
451            APP_SIGNALS.with(|signals| {
452                let mut signals = signals.borrow_mut();
453
454                if let Some(existing) = signals.get(key) {
455                    if let Some(signal) = existing.downcast_ref::<rsx_core::Signal<T>>() {
456                        return signal.clone();
457                    }
458                }
459
460                let signal = rsx_core::Signal::new(initial);
461                signals.insert(key.to_string(), Box::new(signal.clone()));
462                signal
463            })
464        }
465
466        #[wasm_bindgen::prelude::wasm_bindgen(start)]
467        pub fn start() {
468            console_error_panic_hook::set_once();
469            rsx_web::init();
470
471            // Get the counter signal
472            let count = use_app_signal("counter", 0i64);
473            let mut renderer = rsx_web::WebRenderer::new();
474
475            // Wire up standard counter callbacks
476            let inc_closure = wasm_bindgen::closure::Closure::wrap(Box::new({
477                let count = count.clone();
478                move |_: web_sys::Event| {
479                    count.update(|c| *c += 1);
480                }
481            }) as Box<dyn FnMut(_)>);
482
483            let dec_closure = wasm_bindgen::closure::Closure::wrap(Box::new({
484                let count = count.clone();
485                move |_: web_sys::Event| {
486                    count.update(|c| *c -= 1);
487                }
488            }) as Box<dyn FnMut(_)>);
489
490            let reset_closure = wasm_bindgen::closure::Closure::wrap(Box::new({
491                let count = count.clone();
492                move |_: web_sys::Event| {
493                    count.set(0);
494                }
495            }) as Box<dyn FnMut(_)>);
496
497            renderer.on("increment", inc_closure.as_ref().unchecked_ref());
498            renderer.on("decrement", dec_closure.as_ref().unchecked_ref());
499            renderer.on("reset", reset_closure.as_ref().unchecked_ref());
500
501            inc_closure.forget();
502            dec_closure.forget();
503            reset_closure.forget();
504
505            // Use our configured renderer directly (it has the callbacks!)
506            use rsx_core::Renderer;
507            let initial_vnode = #component_name();
508            let mut handle = renderer.mount("#root", &initial_vnode);
509
510            // Setup reactivity with a static reference
511            thread_local! {
512                static RENDERER: std::cell::RefCell<Option<rsx_web::WebRenderer>> = std::cell::RefCell::new(None);
513                static HANDLE: std::cell::RefCell<Option<rsx_web::MountHandle>> = std::cell::RefCell::new(None);
514            }
515
516            RENDERER.with(|r| *r.borrow_mut() = Some(renderer));
517            HANDLE.with(|h| *h.borrow_mut() = Some(handle));
518
519            rsx_core::effect(move || {
520                RENDERER.with(|r| {
521                    HANDLE.with(|h| {
522                        if let (Some(ref mut renderer), Some(ref mut handle)) =
523                            (&mut *r.borrow_mut(), &mut *h.borrow_mut()) {
524                            let current = handle.current.clone();
525                            let next = #component_name();
526                            renderer.patch(handle, &current, &next);
527                        }
528                    });
529                });
530            });
531        }
532    };
533
534    output.into()
535}
536
537fn expand(s: &str) -> TokenStream2 {
538    match parse_rsx(s) {
539        Ok(nodes) => {
540            let tokens = emit_nodes(&nodes);
541            quote! {{
542                let nodes: ::std::vec::Vec<rsx_core::VNode> = { #tokens };
543                if nodes.len() == 1 {
544                    nodes.into_iter().next().unwrap()
545                } else {
546                    rsx_core::fragment(nodes)
547                }
548            }}
549        }
550        Err(e) => syn::Error::new(proc_macro2::Span::call_site(), e).to_compile_error(),
551    }
552}
553
554#[derive(Debug, Clone)]
555enum Node {
556    Element {
557        tag: String,
558        attributes: Vec<(String, AttributeValue)>,
559        children: Vec<Node>,
560    },
561    Text(String),
562    Expression(String),
563}
564
565#[derive(Debug, Clone)]
566enum AttributeValue {
567    String(String),
568    Boolean,
569    Expression(String),
570}
571
572struct Parser {
573    input: Vec<char>,
574    pos: usize,
575}
576
577impl Parser {
578    fn new(input: &str) -> Self {
579        Self {
580            input: input.chars().collect(),
581            pos: 0,
582        }
583    }
584
585    fn current(&self) -> Option<char> {
586        self.input.get(self.pos).copied()
587    }
588
589    fn advance(&mut self) -> Option<char> {
590        let ch = self.current();
591        self.pos += 1;
592        ch
593    }
594
595    fn peek(&self, offset: usize) -> Option<char> {
596        self.input.get(self.pos + offset).copied()
597    }
598
599    fn skip_whitespace(&mut self) {
600        while let Some(ch) = self.current() {
601            if ch.is_whitespace() {
602                self.advance();
603            } else {
604                break;
605            }
606        }
607    }
608
609    fn read_until(&mut self, delimiter: char) -> String {
610        let mut result = String::new();
611        while let Some(ch) = self.current() {
612            if ch == delimiter {
613                break;
614            }
615            result.push(ch);
616            self.advance();
617        }
618        result
619    }
620
621    fn read_identifier(&mut self) -> String {
622        let mut result = String::new();
623        while let Some(ch) = self.current() {
624            if ch.is_alphanumeric() || ch == '_' || ch == '-' || ch == ':' {
625                result.push(ch);
626                self.advance();
627            } else {
628                break;
629            }
630        }
631        result
632    }
633
634    fn read_expression(&mut self) -> Result<String, String> {
635        if self.current() != Some('{') {
636            return Err("Expected '{'".to_string());
637        }
638        self.advance(); // consume '{'
639
640        let mut result = String::new();
641        let mut brace_count = 1;
642
643        while let Some(ch) = self.current() {
644            if ch == '{' {
645                brace_count += 1;
646            } else if ch == '}' {
647                brace_count -= 1;
648                if brace_count == 0 {
649                    self.advance(); // consume '}'
650                    return Ok(result);
651                }
652            }
653            result.push(ch);
654            self.advance();
655        }
656
657        Err("Unclosed expression brace".to_string())
658    }
659
660    fn parse(&mut self) -> Result<Vec<Node>, String> {
661        let mut nodes = Vec::new();
662
663        while self.pos < self.input.len() {
664            self.skip_whitespace();
665            if self.pos >= self.input.len() {
666                break;
667            }
668            nodes.push(self.parse_node()?);
669        }
670
671        Ok(nodes)
672    }
673
674    fn parse_node(&mut self) -> Result<Node, String> {
675        self.skip_whitespace();
676
677        match self.current() {
678            Some('<') => self.parse_element(),
679            Some('{') => {
680                let expr = self.read_expression()?;
681                Ok(Node::Expression(expr))
682            }
683            Some(_) => self.parse_text(),
684            None => Err("Unexpected end of input".to_string()),
685        }
686    }
687
688    fn parse_element(&mut self) -> Result<Node, String> {
689        if self.current() != Some('<') {
690            return Err("Expected '<'".to_string());
691        }
692        self.advance(); // consume '<'
693
694        // Check for closing tag
695        if self.current() == Some('/') {
696            return Err("Unexpected closing tag".to_string());
697        }
698
699        let tag = self.read_identifier();
700        if tag.is_empty() {
701            return Err("Empty tag name".to_string());
702        }
703
704        let mut attributes = Vec::new();
705
706        // Parse attributes
707        loop {
708            self.skip_whitespace();
709            match self.current() {
710                Some('/') if self.peek(1) == Some('>') => {
711                    // Self-closing tag
712                    self.advance(); // consume '/'
713                    self.advance(); // consume '>'
714                    return Ok(Node::Element {
715                        tag,
716                        attributes,
717                        children: Vec::new(),
718                    });
719                }
720                Some('>') => {
721                    // Start of children
722                    self.advance(); // consume '>'
723                    break;
724                }
725                Some(ch) if ch.is_alphabetic() => {
726                    // Attribute
727                    let attr_name = self.read_identifier();
728                    self.skip_whitespace();
729
730                    let attr_value = if self.current() == Some('=') {
731                        self.advance(); // consume '='
732                        self.skip_whitespace();
733                        match self.current() {
734                            Some('"') => {
735                                self.advance(); // consume '"'
736                                let value = self.read_until('"');
737                                if self.current() == Some('"') {
738                                    self.advance(); // consume closing '"'
739                                    AttributeValue::String(value)
740                                } else {
741                                    return Err("Unclosed string attribute".to_string());
742                                }
743                            }
744                            Some('{') => {
745                                let expr = self.read_expression()?;
746                                AttributeValue::Expression(expr)
747                            }
748                            _ => return Err("Expected attribute value".to_string()),
749                        }
750                    } else {
751                        AttributeValue::Boolean
752                    };
753
754                    attributes.push((attr_name, attr_value));
755                }
756                _ => return Err("Unexpected character in tag".to_string()),
757            }
758        }
759
760        // Parse children
761        let mut children = Vec::new();
762        loop {
763            self.skip_whitespace();
764
765            // Check for closing tag
766            if self.current() == Some('<') && self.peek(1) == Some('/') {
767                self.advance(); // consume '<'
768                self.advance(); // consume '/'
769                let close_tag = self.read_identifier();
770                if close_tag != tag {
771                    return Err(format!(
772                        "Mismatched closing tag: expected {}, found {}",
773                        tag, close_tag
774                    ));
775                }
776                self.skip_whitespace();
777                if self.current() == Some('>') {
778                    self.advance(); // consume '>'
779                    break;
780                } else {
781                    return Err("Expected '>' after closing tag".to_string());
782                }
783            }
784
785            if self.pos >= self.input.len() {
786                return Err(format!("Unclosed tag: {}", tag));
787            }
788
789            children.push(self.parse_node()?);
790        }
791
792        Ok(Node::Element {
793            tag,
794            attributes,
795            children,
796        })
797    }
798
799    fn parse_text(&mut self) -> Result<Node, String> {
800        let mut text = String::new();
801
802        while let Some(ch) = self.current() {
803            if ch == '<' || ch == '{' {
804                break;
805            }
806            text.push(ch);
807            self.advance();
808        }
809
810        if text.is_empty() {
811            Err("Empty text node".to_string())
812        } else {
813            Ok(Node::Text(text))
814        }
815    }
816}
817
818fn parse_rsx(input: &str) -> Result<Vec<Node>, String> {
819    let mut parser = Parser::new(input);
820    parser.parse()
821}
822
823fn emit_nodes(nodes: &[Node]) -> TokenStream2 {
824    let node_tokens: Vec<_> = nodes.iter().map(emit_node).collect();
825    quote! { vec![#(#node_tokens),*] }
826}
827
828fn emit_node(node: &Node) -> TokenStream2 {
829    match node {
830        Node::Element {
831            tag,
832            attributes,
833            children,
834        } => {
835            let props = emit_props(attributes);
836            let children_tokens = emit_nodes(children);
837            quote! {
838                rsx_core::h(#tag, #props, #children_tokens)
839            }
840        }
841        Node::Text(text) => {
842            quote! { rsx_core::text(#text) }
843        }
844        Node::Expression(expr) => {
845            let expr: TokenStream2 = expr.parse().unwrap_or_else(|_| {
846                quote! { rsx_core::text("Invalid expression") }
847            });
848            quote! { rsx_core::VNode::from(#expr) }
849        }
850    }
851}
852
853fn emit_props(attributes: &[(String, AttributeValue)]) -> TokenStream2 {
854    let prop_entries: Vec<_> = attributes
855        .iter()
856        .map(|(name, value)| {
857            let prop_value = if name.starts_with("on:") {
858                // Event handler - should always be a callback
859                match value {
860                    AttributeValue::String(s) => {
861                        quote! { rsx_core::PropValue::Callback(#s.to_string()) }
862                    }
863                    _ => quote! { rsx_core::PropValue::Str("invalid_event_handler".to_string()) },
864                }
865            } else {
866                // Regular attribute
867                match value {
868                    AttributeValue::String(s) => {
869                        quote! { rsx_core::PropValue::Str(#s.to_string()) }
870                    }
871                    AttributeValue::Boolean => quote! { rsx_core::PropValue::Bool(true) },
872                    AttributeValue::Expression(expr) => {
873                        let expr: TokenStream2 =
874                            expr.parse().unwrap_or_else(|_| quote! { "error" });
875                        quote! { rsx_core::PropValue::Str(format!("{}", #expr)) }
876                    }
877                }
878            };
879            quote! { (#name.to_string(), #prop_value) }
880        })
881        .collect();
882
883    quote! {
884        {
885            let mut map = indexmap::IndexMap::new();
886            #(map.insert #prop_entries;)*
887            map
888        }
889    }
890}