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
13struct 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 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 loop {
82 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; } else {
91 break;
92 }
93 }
94
95 if input.peek(Token![>]) || input.peek(Token![/]) || !input.peek(syn::Ident) {
97 break;
98 }
99
100 let attr_name: Ident = input.parse()?;
101
102 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; } else {
112 break;
113 }
114 }
115
116 if input.peek(Token![=]) {
118 input.parse::<Token![=]>()?;
119
120 while !input.is_empty() && !input.peek(syn::LitStr) && !input.peek(syn::token::Brace) {
122 if let Ok(_) = input.parse::<proc_macro2::TokenTree>() {
123 continue; } 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 let content;
135
136 let _brace_result = braced!(content in input);
138
139 let content_str = content.to_string();
141
142 eprintln!("🔍 Parsing attribute expression: {}", content_str);
144
145 let parsed_expr = if let Ok(expr) = content.parse::<Expr>() {
147 eprintln!("✅ Direct parsing succeeded");
149 expr
150 } else if let Ok(expr) = syn::parse_str::<Expr>(&format!("{{ {} }}", content_str)) {
151 eprintln!("✅ Block expression parsing succeeded");
153 expr
154 } else if let Ok(expr) =
155 syn::parse_str::<Expr>(&format!("(|| -> _ {{ {} }})()", content_str))
156 {
157 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 attrs.push((
177 attr_name,
178 RsxAttrValue::String(syn::LitStr::new("true", proc_macro2::Span::call_site())),
179 ));
180 }
181 }
182
183 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 let mut children = Vec::new();
198 while !input.is_empty() {
199 if input.peek(Token![<]) {
201 let fork = input.fork();
202 if fork.parse::<Token![<]>().is_ok() && fork.parse::<Token![/]>().is_ok() {
203 break; }
205 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 let content;
213 braced!(content in input);
214
215 match content.parse::<Expr>() {
217 Ok(expr) => children.push(RsxElement::Expression(expr)),
218 Err(_) => {
219 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 if input.parse::<proc_macro2::TokenTree>().is_err() {
236 break; }
238 }
239 }
240
241 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 let content;
261 braced!(content in input);
262
263 match content.parse::<Expr>() {
266 Ok(expr) => Ok(Some(expr)),
267 Err(e) => {
268 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 let input_str = input2.to_string();
284 let token_count = input2.clone().into_iter().count();
285
286 match syn::parse2::<RsxInput>(input2.clone()) {
288 Ok(jsx_input) => {
289 expand_jsx(jsx_input).into()
291 }
292 Err(jsx_err) => {
293 match syn::parse::<LitStr>(input2.clone().into()) {
295 Ok(s) => expand(&s.value()).into(),
296 Err(_string_err) => {
297 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 let is_component = tag_str.chars().next().map_or(false, |c| c.is_uppercase());
340
341 if is_component {
342 if !attrs.is_empty() {
345 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 return syn::Error::new(tag.span(), "Component children not yet supported. Use function calls for now.")
352 .to_compile_error();
353 }
354
355 quote! { #tag() }
357 } else {
358 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 match value {
383 RsxAttrValue::Expression(expr) => {
384 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..]) } 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#[proc_macro_attribute]
431pub fn component(_args: TokenStream, input: TokenStream) -> TokenStream {
432 input
435}
436
437#[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 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 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 let count = use_app_signal("counter", 0i64);
473 let mut renderer = rsx_web::WebRenderer::new();
474
475 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 rsx_core::Renderer;
507 let initial_vnode = #component_name();
508 let mut handle = renderer.mount("#root", &initial_vnode);
509
510 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, ¤t, &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(); 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(); 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(); 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 loop {
708 self.skip_whitespace();
709 match self.current() {
710 Some('/') if self.peek(1) == Some('>') => {
711 self.advance(); self.advance(); return Ok(Node::Element {
715 tag,
716 attributes,
717 children: Vec::new(),
718 });
719 }
720 Some('>') => {
721 self.advance(); break;
724 }
725 Some(ch) if ch.is_alphabetic() => {
726 let attr_name = self.read_identifier();
728 self.skip_whitespace();
729
730 let attr_value = if self.current() == Some('=') {
731 self.advance(); self.skip_whitespace();
733 match self.current() {
734 Some('"') => {
735 self.advance(); let value = self.read_until('"');
737 if self.current() == Some('"') {
738 self.advance(); 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 let mut children = Vec::new();
762 loop {
763 self.skip_whitespace();
764
765 if self.current() == Some('<') && self.peek(1) == Some('/') {
767 self.advance(); self.advance(); 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(); 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 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 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}