1use proc_macro::TokenStream;
10use proc_macro2::TokenStream as TokenStream2;
11use quote::{format_ident, quote};
12use std::sync::atomic::{AtomicU64, Ordering};
13use syn::{
14 braced,
15 parse::{Parse, ParseStream},
16 parse_macro_input,
17 punctuated::Punctuated,
18 Expr, Ident, LitStr, Result, Token,
19};
20
21#[proc_macro]
23pub fn view(input: TokenStream) -> TokenStream {
24 let node = parse_macro_input!(input as ViewNode);
25 let expanded = node.to_tokens();
26 TokenStream::from(expanded)
27}
28
29struct WithInput {
31 idents: Vec<Ident>,
32 expr: Expr,
33}
34
35impl Parse for WithInput {
36 fn parse(input: ParseStream) -> Result<Self> {
37 let idents: Punctuated<Ident, Token![,]> = Punctuated::parse_separated_nonempty(input)?;
39 let idents: Vec<Ident> = idents.into_iter().collect();
40
41 input.parse::<Token![=>]>()?;
43
44 let expr: Expr = input.parse()?;
46
47 Ok(WithInput { idents, expr })
48 }
49}
50
51impl WithInput {
52 fn to_tokens(&self) -> TokenStream2 {
53 let clone_statements: Vec<TokenStream2> = self
54 .idents
55 .iter()
56 .map(|ident| quote! { let #ident = #ident.clone(); })
57 .collect();
58
59 let expr = &self.expr;
60
61 quote! {
62 {
63 #(#clone_statements)*
64 #expr
65 }
66 }
67 }
68}
69
70static STATE_COUNTER: AtomicU64 = AtomicU64::new(0);
72
73struct StateInput {
75 scope: Expr,
76 init: Expr,
77}
78
79impl Parse for StateInput {
80 fn parse(input: ParseStream) -> Result<Self> {
81 let scope: Expr = input.parse()?;
83
84 input.parse::<Token![,]>()?;
86
87 let init: Expr = input.parse()?;
89
90 Ok(StateInput { scope, init })
91 }
92}
93
94impl StateInput {
95 fn to_tokens(&self) -> TokenStream2 {
96 let scope = &self.scope;
97 let init = &self.init;
98
99 let counter = STATE_COUNTER.fetch_add(1, Ordering::SeqCst);
102 let key_type = format_ident!("__State_{}", counter);
103
104 quote! {
105 {
106 struct #key_type;
107 #scope.use_state_keyed::<#key_type, _>(#init)
108 }
109 }
110 }
111}
112
113#[proc_macro]
143pub fn state(input: TokenStream) -> TokenStream {
144 let input = parse_macro_input!(input as StateInput);
145 let expanded = input.to_tokens();
146 TokenStream::from(expanded)
147}
148
149static EFFECT_COUNTER: AtomicU64 = AtomicU64::new(0);
151
152struct EffectInput {
154 scope: Expr,
155 deps: Expr,
156 effect_fn: Expr,
157}
158
159impl Parse for EffectInput {
160 fn parse(input: ParseStream) -> Result<Self> {
161 let scope: Expr = input.parse()?;
163 input.parse::<Token![,]>()?;
164
165 let deps: Expr = input.parse()?;
167 input.parse::<Token![,]>()?;
168
169 let effect_fn: Expr = input.parse()?;
171
172 Ok(EffectInput {
173 scope,
174 deps,
175 effect_fn,
176 })
177 }
178}
179
180impl EffectInput {
181 fn to_tokens(&self) -> TokenStream2 {
182 let scope = &self.scope;
183 let deps = &self.deps;
184 let effect_fn = &self.effect_fn;
185
186 let counter = EFFECT_COUNTER.fetch_add(1, Ordering::SeqCst);
187 let key_type = format_ident!("__Effect_{}", counter);
188
189 quote! {
190 {
191 struct #key_type;
192 #scope.use_effect_keyed::<#key_type, _, _, _>(#deps, #effect_fn)
193 }
194 }
195 }
196}
197
198#[proc_macro]
235pub fn effect(input: TokenStream) -> TokenStream {
236 let input = parse_macro_input!(input as EffectInput);
237 let expanded = input.to_tokens();
238 TokenStream::from(expanded)
239}
240
241struct EffectOnceInput {
243 scope: Expr,
244 effect_fn: Expr,
245}
246
247impl Parse for EffectOnceInput {
248 fn parse(input: ParseStream) -> Result<Self> {
249 let scope: Expr = input.parse()?;
251 input.parse::<Token![,]>()?;
252
253 let effect_fn: Expr = input.parse()?;
255
256 Ok(EffectOnceInput { scope, effect_fn })
257 }
258}
259
260impl EffectOnceInput {
261 fn to_tokens(&self) -> TokenStream2 {
262 let scope = &self.scope;
263 let effect_fn = &self.effect_fn;
264
265 let counter = EFFECT_COUNTER.fetch_add(1, Ordering::SeqCst);
266 let key_type = format_ident!("__Effect_{}", counter);
267
268 quote! {
269 {
270 struct #key_type;
271 #scope.use_effect_once_keyed::<#key_type, _, _>(#effect_fn)
272 }
273 }
274 }
275}
276
277#[proc_macro]
308pub fn effect_once(input: TokenStream) -> TokenStream {
309 let input = parse_macro_input!(input as EffectOnceInput);
310 let expanded = input.to_tokens();
311 TokenStream::from(expanded)
312}
313
314#[proc_macro]
351pub fn with(input: TokenStream) -> TokenStream {
352 let input = parse_macro_input!(input as WithInput);
353 let expanded = input.to_tokens();
354 TokenStream::from(expanded)
355}
356
357enum ViewNode {
359 Element(ElementNode),
361 Text(String),
363 Expr(Expr),
365}
366
367struct Prop {
369 name: Ident,
370 value: Expr,
371}
372
373struct ElementNode {
374 tag: String,
375 props: Vec<Prop>,
376 children: Vec<ViewNode>,
377}
378
379impl Parse for ViewNode {
380 fn parse(input: ParseStream) -> Result<Self> {
381 if input.peek(Token![<]) {
382 input.parse::<Token![<]>()?;
384 let tag: Ident = input.parse()?;
385
386 let mut props = Vec::new();
388 while !input.peek(Token![>]) && !input.peek(Token![/]) {
389 let name: Ident = input.parse()?;
390 input.parse::<Token![=]>()?;
391 let content;
392 braced!(content in input);
393 let value: Expr = content.parse()?;
394 props.push(Prop { name, value });
395 }
396
397 if input.peek(Token![/]) {
399 input.parse::<Token![/]>()?;
400 input.parse::<Token![>]>()?;
401 return Ok(ViewNode::Element(ElementNode {
402 tag: tag.to_string(),
403 props,
404 children: Vec::new(),
405 }));
406 }
407
408 input.parse::<Token![>]>()?;
409
410 let mut children = Vec::new();
411
412 while !(input.peek(Token![<]) && input.peek2(Token![/])) {
414 if input.is_empty() {
415 return Err(syn::Error::new(
416 tag.span(),
417 format!("Unclosed tag: <{}>", tag),
418 ));
419 }
420 children.push(input.parse()?);
421 }
422
423 input.parse::<Token![<]>()?;
425 input.parse::<Token![/]>()?;
426 let close_tag: Ident = input.parse()?;
427 input.parse::<Token![>]>()?;
428
429 if tag != close_tag {
430 return Err(syn::Error::new(
431 close_tag.span(),
432 format!(
433 "Mismatched tags: expected </{}>, found </{}>",
434 tag, close_tag
435 ),
436 ));
437 }
438
439 Ok(ViewNode::Element(ElementNode {
440 tag: tag.to_string(),
441 props,
442 children,
443 }))
444 } else if input.peek(LitStr) {
445 let lit: LitStr = input.parse()?;
447 Ok(ViewNode::Text(lit.value()))
448 } else if input.peek(syn::token::Brace) {
449 let content;
451 braced!(content in input);
452 let expr: Expr = content.parse()?;
453 Ok(ViewNode::Expr(expr))
454 } else {
455 Err(input.error("Expected <Element>, \"string literal\", or {expression}"))
456 }
457 }
458}
459
460impl ViewNode {
461 fn to_tokens(&self) -> TokenStream2 {
462 match self {
463 ViewNode::Text(s) => {
464 quote! { telex::View::text(#s) }
465 }
466 ViewNode::Expr(expr) => {
467 quote! { telex::View::text(format!("{}", #expr)) }
469 }
470 ViewNode::Element(elem) => elem.to_tokens(),
471 }
472 }
473}
474
475impl ElementNode {
476 fn to_tokens(&self) -> TokenStream2 {
477 match self.tag.as_str() {
478 "Text" => {
479 if let Some(child) = self.children.first() {
481 match child {
482 ViewNode::Text(content) => quote! { telex::View::text(#content) },
483 ViewNode::Expr(expr) => quote! { telex::View::text(format!("{}", #expr)) },
484 _ => quote! { telex::View::text("") },
485 }
486 } else {
487 quote! { telex::View::text("") }
488 }
489 }
490 "VStack" => {
491 let mut builder_calls = Vec::new();
492
493 for prop in &self.props {
495 let name = &prop.name;
496 let value = &prop.value;
497 builder_calls.push(quote! { .#name(#value) });
498 }
499
500 for child in &self.children {
502 let tokens = child.to_tokens();
503 builder_calls.push(quote! { .child(#tokens) });
504 }
505
506 quote! { telex::View::vstack()#(#builder_calls)*.build() }
507 }
508 "HStack" => {
509 let mut builder_calls = Vec::new();
510
511 for prop in &self.props {
513 let name = &prop.name;
514 let value = &prop.value;
515 builder_calls.push(quote! { .#name(#value) });
516 }
517
518 for child in &self.children {
520 let tokens = child.to_tokens();
521 builder_calls.push(quote! { .child(#tokens) });
522 }
523
524 quote! { telex::View::hstack()#(#builder_calls)*.build() }
525 }
526 "Box" => {
527 let mut builder_calls = Vec::new();
528
529 for prop in &self.props {
531 let name = &prop.name;
532 let value = &prop.value;
533 builder_calls.push(quote! { .#name(#value) });
534 }
535
536 if let Some(child) = self.children.first() {
538 let tokens = child.to_tokens();
539 builder_calls.push(quote! { .child(#tokens) });
540 }
541
542 quote! { telex::View::boxed()#(#builder_calls)*.build() }
543 }
544 "Spacer" => {
545 if let Some(prop) = self.props.iter().find(|p| p.name == "flex") {
547 let value = &prop.value;
548 quote! { telex::View::spacer_flex(#value) }
549 } else {
550 quote! { telex::View::spacer() }
551 }
552 }
553 "Button" => {
554 let mut builder_calls = Vec::new();
556
557 for prop in &self.props {
559 let name = &prop.name;
560 let value = &prop.value;
561 builder_calls.push(quote! { .#name(#value) });
562 }
563
564 if let Some(child) = self.children.first() {
566 match child {
567 ViewNode::Text(label) => {
568 builder_calls.push(quote! { .label(#label) });
569 }
570 ViewNode::Expr(expr) => {
571 builder_calls.push(quote! { .label(format!("{}", #expr)) });
572 }
573 _ => {}
574 }
575 }
576
577 quote! { telex::View::button()#(#builder_calls)*.build() }
578 }
579 "List" => {
580 let mut builder_calls = Vec::new();
582
583 for prop in &self.props {
584 let name = &prop.name;
585 let value = &prop.value;
586 builder_calls.push(quote! { .#name(#value) });
587 }
588
589 quote! { telex::View::list()#(#builder_calls)*.build() }
590 }
591 "TextInput" => {
592 let mut builder_calls = Vec::new();
594
595 for prop in &self.props {
596 let name = &prop.name;
597 let value = &prop.value;
598 builder_calls.push(quote! { .#name(#value) });
599 }
600
601 quote! { telex::View::text_input()#(#builder_calls)*.build() }
602 }
603 "Checkbox" => {
604 let mut builder_calls = Vec::new();
606
607 for prop in &self.props {
609 let name = &prop.name;
610 let value = &prop.value;
611 builder_calls.push(quote! { .#name(#value) });
612 }
613
614 if let Some(child) = self.children.first() {
616 match child {
617 ViewNode::Text(label) => {
618 builder_calls.push(quote! { .label(#label) });
619 }
620 ViewNode::Expr(expr) => {
621 builder_calls.push(quote! { .label(format!("{}", #expr)) });
622 }
623 _ => {}
624 }
625 }
626
627 quote! { telex::View::checkbox()#(#builder_calls)*.build() }
628 }
629 "TextArea" => {
630 let mut builder_calls = Vec::new();
632
633 for prop in &self.props {
634 let name = &prop.name;
635 let value = &prop.value;
636 builder_calls.push(quote! { .#name(#value) });
637 }
638
639 quote! { telex::View::text_area()#(#builder_calls)*.build() }
640 }
641 "Modal" => {
642 let mut builder_calls = Vec::new();
644
645 for prop in &self.props {
646 let name = &prop.name;
647 let value = &prop.value;
648 builder_calls.push(quote! { .#name(#value) });
649 }
650
651 if let Some(child) = self.children.first() {
653 let tokens = child.to_tokens();
654 builder_calls.push(quote! { .child(#tokens) });
655 }
656
657 quote! { telex::View::modal()#(#builder_calls)*.build() }
658 }
659 "StyledText" => {
660 let mut content = quote! { "" };
662 let mut bold_val = quote! { false };
663 let mut italic_val = quote! { false };
664 let mut underline_val = quote! { false };
665 let mut dim_val = quote! { false };
666 let mut color_call = quote! {};
667 let mut bg_call = quote! {};
668
669 for prop in &self.props {
671 let name_str = prop.name.to_string();
672 let value = &prop.value;
673
674 match name_str.as_str() {
675 "bold" => bold_val = quote! { #value },
676 "italic" => italic_val = quote! { #value },
677 "underline" => underline_val = quote! { #value },
678 "dim" => dim_val = quote! { #value },
679 "color" => color_call = quote! { .color(#value) },
680 "bg" => bg_call = quote! { .bg(#value) },
681 _ => {}
682 }
683 }
684
685 if let Some(child) = self.children.first() {
687 match child {
688 ViewNode::Text(text) => {
689 content = quote! { #text };
690 }
691 ViewNode::Expr(expr) => {
692 content = quote! { format!("{}", #expr) };
693 }
694 _ => {}
695 }
696 }
697
698 quote! {
700 {
701 let __builder = telex::View::styled_text(#content);
702 let __builder = if #bold_val { __builder.bold() } else { __builder };
703 let __builder = if #italic_val { __builder.italic() } else { __builder };
704 let __builder = if #underline_val { __builder.underline() } else { __builder };
705 let __builder = if #dim_val { __builder.dim() } else { __builder };
706 __builder #color_call #bg_call .build()
707 }
708 }
709 }
710 unknown => {
711 let known_elements = [
713 "Text",
714 "StyledText",
715 "VStack",
716 "HStack",
717 "Box",
718 "Spacer",
719 "Button",
720 "List",
721 "TextInput",
722 "TextArea",
723 "Checkbox",
724 "Modal",
725 ];
726
727 let suggestion = known_elements
729 .iter()
730 .find(|&e| {
731 let e_lower = e.to_lowercase();
732 let u_lower = unknown.to_lowercase();
733 e_lower.starts_with(&u_lower[..1.min(u_lower.len())])
734 || u_lower.starts_with(&e_lower[..1.min(e_lower.len())])
735 || e_lower.contains(&u_lower)
736 || u_lower.contains(&e_lower)
737 });
738
739 let msg = if let Some(suggested) = suggestion {
740 format!(
741 "Unknown element: <{}>. Did you mean <{}>?\n\nAvailable elements: {}",
742 unknown,
743 suggested,
744 known_elements.join(", ")
745 )
746 } else {
747 format!(
748 "Unknown element: <{}>.\n\nAvailable elements: {}",
749 unknown,
750 known_elements.join(", ")
751 )
752 };
753 quote! { compile_error!(#msg) }
754 }
755 }
756 }
757}