Skip to main content

telex_macro/
lib.rs

1//! Procedural macros for Telex.
2//!
3//! - `state!` — creates order-independent state (no hook ordering rules)
4//! - `effect!` — creates order-independent effects with dependencies
5//! - `effect_once!` — creates order-independent effects that run once
6//! - `with!` — clones state handles into closures
7//! - `view!` — JSX-like syntax for building UI trees
8
9use 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/// The view! macro for building UI trees with JSX-like syntax.
22#[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
29/// Input for the with! macro: `ident1, ident2 => expr`
30struct WithInput {
31    idents: Vec<Ident>,
32    expr: Expr,
33}
34
35impl Parse for WithInput {
36    fn parse(input: ParseStream) -> Result<Self> {
37        // Parse comma-separated identifiers
38        let idents: Punctuated<Ident, Token![,]> = Punctuated::parse_separated_nonempty(input)?;
39        let idents: Vec<Ident> = idents.into_iter().collect();
40
41        // Parse the => separator
42        input.parse::<Token![=>]>()?;
43
44        // Parse the expression (typically a closure)
45        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
70/// Global counter for generating unique type names.
71static STATE_COUNTER: AtomicU64 = AtomicU64::new(0);
72
73/// Input for the state! macro: `cx, || init_expr`
74struct StateInput {
75    scope: Expr,
76    init: Expr,
77}
78
79impl Parse for StateInput {
80    fn parse(input: ParseStream) -> Result<Self> {
81        // Parse the scope expression (usually just `cx`)
82        let scope: Expr = input.parse()?;
83
84        // Parse the comma separator
85        input.parse::<Token![,]>()?;
86
87        // Parse the initializer expression (usually a closure)
88        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        // Generate a unique type name using an atomic counter.
100        // This ensures each macro invocation gets a distinct type.
101        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/// The state! macro for creating order-independent state.
114///
115/// This is the recommended way to create state in Telex. Unlike traditional
116/// hooks, state created with this macro can be used conditionally or in any
117/// order without causing panics.
118///
119/// Each macro invocation creates a unique anonymous type as the key,
120/// ensuring each call site gets its own independent state.
121///
122/// # Examples
123///
124/// Basic usage:
125/// ```ignore
126/// let count = state!(cx, || 0);
127/// ```
128///
129/// Safe in conditionals:
130/// ```ignore
131/// if show_counter {
132///     let count = state!(cx, || 0);  // This is safe!
133/// }
134/// ```
135///
136/// Multiple independent states:
137/// ```ignore
138/// let name = state!(cx, || String::new());
139/// let count = state!(cx, || 0);
140/// let visible = state!(cx, || true);
141/// ```
142#[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
149/// Global counter for generating unique effect type names.
150static EFFECT_COUNTER: AtomicU64 = AtomicU64::new(0);
151
152/// Input for the effect! macro: `cx, deps, |&d| effect_body`
153struct 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        // Parse the scope expression (usually just `cx`)
162        let scope: Expr = input.parse()?;
163        input.parse::<Token![,]>()?;
164
165        // Parse the dependencies expression
166        let deps: Expr = input.parse()?;
167        input.parse::<Token![,]>()?;
168
169        // Parse the effect closure
170        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/// The effect! macro for creating order-independent effects with dependencies.
199///
200/// This is the recommended way to create effects in Telex. Unlike traditional
201/// hooks, effects created with this macro can be used conditionally or in any
202/// order without causing issues.
203///
204/// Each macro invocation creates a unique anonymous type as the key,
205/// ensuring each call site gets its own independent effect.
206///
207/// # Examples
208///
209/// Basic usage - runs when count changes:
210/// ```ignore
211/// effect!(cx, count.get(), |&c| {
212///     println!("count changed to {}", c);
213///     || {}  // cleanup function
214/// });
215/// ```
216///
217/// Safe in conditionals:
218/// ```ignore
219/// if show_logger {
220///     effect!(cx, value.get(), |&v| {
221///         println!("value: {}", v);
222///         || {}
223///     });
224/// }
225/// ```
226///
227/// Multiple dependencies via tuple:
228/// ```ignore
229/// effect!(cx, (a.get(), b.get()), |&(a, b)| {
230///     println!("a={}, b={}", a, b);
231///     || {}
232/// });
233/// ```
234#[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
241/// Input for the effect_once! macro: `cx, || effect_body`
242struct EffectOnceInput {
243    scope: Expr,
244    effect_fn: Expr,
245}
246
247impl Parse for EffectOnceInput {
248    fn parse(input: ParseStream) -> Result<Self> {
249        // Parse the scope expression (usually just `cx`)
250        let scope: Expr = input.parse()?;
251        input.parse::<Token![,]>()?;
252
253        // Parse the effect closure
254        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/// The effect_once! macro for creating order-independent effects that run once.
278///
279/// This is the recommended way to run one-time initialization effects in Telex.
280/// Unlike traditional hooks, effects created with this macro can be used
281/// conditionally or in any order.
282///
283/// Each macro invocation creates a unique anonymous type as the key,
284/// ensuring each call site gets its own independent effect.
285///
286/// # Examples
287///
288/// Basic usage - runs once on first render:
289/// ```ignore
290/// effect_once!(cx, || {
291///     println!("App initialized");
292///     || {
293///         println!("App cleanup");
294///     }
295/// });
296/// ```
297///
298/// Safe in conditionals:
299/// ```ignore
300/// if feature_enabled {
301///     effect_once!(cx, || {
302///         setup_feature();
303///         || cleanup_feature()
304///     });
305/// }
306/// ```
307#[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/// Global counter for generating unique async/stream/terminal type names.
315static HOOK_COUNTER: AtomicU64 = AtomicU64::new(0);
316
317/// Input for the async_data! macro: `cx, || { ... }`
318struct AsyncDataInput {
319    scope: Expr,
320    func: Expr,
321}
322
323impl Parse for AsyncDataInput {
324    fn parse(input: ParseStream) -> Result<Self> {
325        let scope: Expr = input.parse()?;
326        input.parse::<Token![,]>()?;
327        let func: Expr = input.parse()?;
328        Ok(AsyncDataInput { scope, func })
329    }
330}
331
332impl AsyncDataInput {
333    fn to_tokens(&self) -> TokenStream2 {
334        let scope = &self.scope;
335        let func = &self.func;
336        let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
337        let key_type = format_ident!("__Async_{}", counter);
338
339        quote! {
340            {
341                struct #key_type;
342                #scope.use_async_keyed::<#key_type, _, _>(#func)
343            }
344        }
345    }
346}
347
348/// The async_data! macro for creating order-independent async data loading.
349///
350/// # Examples
351///
352/// ```ignore
353/// let data = async_data!(cx, || {
354///     Ok(fetch_data())
355/// });
356/// ```
357#[proc_macro]
358pub fn async_data(input: TokenStream) -> TokenStream {
359    let input = parse_macro_input!(input as AsyncDataInput);
360    let expanded = input.to_tokens();
361    TokenStream::from(expanded)
362}
363
364/// Input for the stream! macro: `cx, || { ... }`
365struct StreamInput {
366    scope: Expr,
367    func: Expr,
368}
369
370impl Parse for StreamInput {
371    fn parse(input: ParseStream) -> Result<Self> {
372        let scope: Expr = input.parse()?;
373        input.parse::<Token![,]>()?;
374        let func: Expr = input.parse()?;
375        Ok(StreamInput { scope, func })
376    }
377}
378
379impl StreamInput {
380    fn to_tokens(&self) -> TokenStream2 {
381        let scope = &self.scope;
382        let func = &self.func;
383        let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
384        let key_type = format_ident!("__Stream_{}", counter);
385
386        quote! {
387            {
388                struct #key_type;
389                #scope.use_stream_keyed::<#key_type, _, _, _>(#func)
390            }
391        }
392    }
393}
394
395/// The stream! macro for creating order-independent streams.
396///
397/// # Examples
398///
399/// ```ignore
400/// let elapsed = stream!(cx, || {
401///     (0..).inspect(|_| std::thread::sleep(Duration::from_secs(1)))
402/// });
403/// ```
404#[proc_macro]
405pub fn stream(input: TokenStream) -> TokenStream {
406    let input = parse_macro_input!(input as StreamInput);
407    let expanded = input.to_tokens();
408    TokenStream::from(expanded)
409}
410
411/// Input for the text_stream! macro: `cx, || { ... }`
412struct TextStreamInput {
413    scope: Expr,
414    func: Expr,
415}
416
417impl Parse for TextStreamInput {
418    fn parse(input: ParseStream) -> Result<Self> {
419        let scope: Expr = input.parse()?;
420        input.parse::<Token![,]>()?;
421        let func: Expr = input.parse()?;
422        Ok(TextStreamInput { scope, func })
423    }
424}
425
426impl TextStreamInput {
427    fn to_tokens(&self) -> TokenStream2 {
428        let scope = &self.scope;
429        let func = &self.func;
430        let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
431        let key_type = format_ident!("__TextStream_{}", counter);
432
433        quote! {
434            {
435                struct #key_type;
436                #scope.use_text_stream_keyed::<#key_type, _, _>(#func)
437            }
438        }
439    }
440}
441
442/// The text_stream! macro for creating order-independent text streams.
443///
444/// # Examples
445///
446/// ```ignore
447/// let logs = text_stream!(cx, || {
448///     generate_log_entries()
449/// });
450/// ```
451#[proc_macro]
452pub fn text_stream(input: TokenStream) -> TokenStream {
453    let input = parse_macro_input!(input as TextStreamInput);
454    let expanded = input.to_tokens();
455    TokenStream::from(expanded)
456}
457
458/// Input for the text_stream_with_restart! macro: `cx, restart, || { ... }`
459struct TextStreamWithRestartInput {
460    scope: Expr,
461    restart: Expr,
462    func: Expr,
463}
464
465impl Parse for TextStreamWithRestartInput {
466    fn parse(input: ParseStream) -> Result<Self> {
467        let scope: Expr = input.parse()?;
468        input.parse::<Token![,]>()?;
469        let restart: Expr = input.parse()?;
470        input.parse::<Token![,]>()?;
471        let func: Expr = input.parse()?;
472        Ok(TextStreamWithRestartInput { scope, restart, func })
473    }
474}
475
476impl TextStreamWithRestartInput {
477    fn to_tokens(&self) -> TokenStream2 {
478        let scope = &self.scope;
479        let restart = &self.restart;
480        let func = &self.func;
481        let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
482        let key_type = format_ident!("__TextStreamRestart_{}", counter);
483
484        quote! {
485            {
486                struct #key_type;
487                #scope.use_text_stream_with_restart_keyed::<#key_type, _, _>(#restart, #func)
488            }
489        }
490    }
491}
492
493/// The text_stream_with_restart! macro for creating restartable text streams.
494///
495/// # Examples
496///
497/// ```ignore
498/// let stream = text_stream_with_restart!(cx, needs_restart, move || {
499///     stream_response()
500/// });
501/// ```
502#[proc_macro]
503pub fn text_stream_with_restart(input: TokenStream) -> TokenStream {
504    let input = parse_macro_input!(input as TextStreamWithRestartInput);
505    let expanded = input.to_tokens();
506    TokenStream::from(expanded)
507}
508
509/// Input for the terminal! macro: `cx`
510struct TerminalInput {
511    scope: Expr,
512}
513
514impl Parse for TerminalInput {
515    fn parse(input: ParseStream) -> Result<Self> {
516        let scope: Expr = input.parse()?;
517        Ok(TerminalInput { scope })
518    }
519}
520
521impl TerminalInput {
522    fn to_tokens(&self) -> TokenStream2 {
523        let scope = &self.scope;
524        let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
525        let key_type = format_ident!("__Terminal_{}", counter);
526
527        quote! {
528            {
529                struct #key_type;
530                #scope.use_terminal_keyed::<#key_type>()
531            }
532        }
533    }
534}
535
536/// The terminal! macro for creating order-independent terminal handles.
537///
538/// # Examples
539///
540/// ```ignore
541/// let terminal = terminal!(cx);
542/// ```
543#[proc_macro]
544pub fn terminal(input: TokenStream) -> TokenStream {
545    let input = parse_macro_input!(input as TerminalInput);
546    let expanded = input.to_tokens();
547    TokenStream::from(expanded)
548}
549
550/// Input for the reducer! macro: `cx, initial, |state, action| { ... }`
551struct ReducerInput {
552    scope: Expr,
553    initial: Expr,
554    reducer_fn: Expr,
555}
556
557impl Parse for ReducerInput {
558    fn parse(input: ParseStream) -> Result<Self> {
559        let scope: Expr = input.parse()?;
560        input.parse::<Token![,]>()?;
561        let initial: Expr = input.parse()?;
562        input.parse::<Token![,]>()?;
563        let reducer_fn: Expr = input.parse()?;
564        Ok(ReducerInput { scope, initial, reducer_fn })
565    }
566}
567
568impl ReducerInput {
569    fn to_tokens(&self) -> TokenStream2 {
570        let scope = &self.scope;
571        let initial = &self.initial;
572        let reducer_fn = &self.reducer_fn;
573        let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
574        let key_type = format_ident!("__Reducer_{}", counter);
575
576        quote! {
577            {
578                struct #key_type;
579                #scope.use_reducer_keyed::<#key_type, _, _>(#initial, #reducer_fn)
580            }
581        }
582    }
583}
584
585/// The reducer! macro for creating order-independent state with dispatch.
586///
587/// Returns `(state, dispatch)` where `dispatch` sends actions through
588/// a reducer function to produce new state.
589///
590/// # Examples
591///
592/// ```ignore
593/// let (state, dispatch) = reducer!(cx, AppState::Idle, |state, action| {
594///     match (state, action) {
595///         (_, Action::Reset) => AppState::Idle,
596///         (s, _) => s,
597///     }
598/// });
599///
600/// // Dispatch an action
601/// dispatch(Action::Reset);
602/// ```
603#[proc_macro]
604pub fn reducer(input: TokenStream) -> TokenStream {
605    let input = parse_macro_input!(input as ReducerInput);
606    let expanded = input.to_tokens();
607    TokenStream::from(expanded)
608}
609
610/// Input for the channel! macro: `cx, Type`
611struct ChannelInput {
612    scope: Expr,
613    ty: syn::Type,
614}
615
616impl Parse for ChannelInput {
617    fn parse(input: ParseStream) -> Result<Self> {
618        let scope: Expr = input.parse()?;
619        input.parse::<Token![,]>()?;
620        let ty: syn::Type = input.parse()?;
621        Ok(ChannelInput { scope, ty })
622    }
623}
624
625impl ChannelInput {
626    fn to_tokens(&self) -> TokenStream2 {
627        let scope = &self.scope;
628        let ty = &self.ty;
629        let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
630        let key_type = format_ident!("__Channel_{}", counter);
631
632        quote! {
633            {
634                struct #key_type;
635                #scope.use_channel_keyed::<#key_type, #ty>()
636            }
637        }
638    }
639}
640
641/// The channel! macro for creating typed inbound channels.
642///
643/// # Examples
644///
645/// ```ignore
646/// let ch = channel!(cx, String);
647/// let tx = ch.tx();
648/// // Send tx to an external thread via effect_once!
649/// for msg in ch.get() { /* ... */ }
650/// ```
651#[proc_macro]
652pub fn channel(input: TokenStream) -> TokenStream {
653    let input = parse_macro_input!(input as ChannelInput);
654    let expanded = input.to_tokens();
655    TokenStream::from(expanded)
656}
657
658/// Input for the port! macro: `cx, InType, OutType`
659struct PortInput {
660    scope: Expr,
661    in_ty: syn::Type,
662    out_ty: syn::Type,
663}
664
665impl Parse for PortInput {
666    fn parse(input: ParseStream) -> Result<Self> {
667        let scope: Expr = input.parse()?;
668        input.parse::<Token![,]>()?;
669        let in_ty: syn::Type = input.parse()?;
670        input.parse::<Token![,]>()?;
671        let out_ty: syn::Type = input.parse()?;
672        Ok(PortInput { scope, in_ty, out_ty })
673    }
674}
675
676impl PortInput {
677    fn to_tokens(&self) -> TokenStream2 {
678        let scope = &self.scope;
679        let in_ty = &self.in_ty;
680        let out_ty = &self.out_ty;
681        let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
682        let key_type = format_ident!("__Port_{}", counter);
683
684        quote! {
685            {
686                struct #key_type;
687                #scope.use_port_keyed::<#key_type, #in_ty, #out_ty>()
688            }
689        }
690    }
691}
692
693/// The port! macro for creating bidirectional ports.
694///
695/// # Examples
696///
697/// ```ignore
698/// let midi = port!(cx, MidiIn, MidiOut);
699/// let inbound_tx = midi.rx.tx();   // external sends MidiIn here
700/// let outbound_tx = midi.tx();     // component sends MidiOut here
701/// for msg in midi.rx.get() { /* ... */ }
702/// ```
703#[proc_macro]
704pub fn port(input: TokenStream) -> TokenStream {
705    let input = parse_macro_input!(input as PortInput);
706    let expanded = input.to_tokens();
707    TokenStream::from(expanded)
708}
709
710/// Input for the interval! macro: `cx, duration, || { ... }`
711struct IntervalInput {
712    scope: Expr,
713    duration: Expr,
714    callback: Expr,
715}
716
717impl Parse for IntervalInput {
718    fn parse(input: ParseStream) -> Result<Self> {
719        let scope: Expr = input.parse()?;
720        input.parse::<Token![,]>()?;
721        let duration: Expr = input.parse()?;
722        input.parse::<Token![,]>()?;
723        let callback: Expr = input.parse()?;
724        Ok(IntervalInput { scope, duration, callback })
725    }
726}
727
728impl IntervalInput {
729    fn to_tokens(&self) -> TokenStream2 {
730        let scope = &self.scope;
731        let duration = &self.duration;
732        let callback = &self.callback;
733        let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
734        let key_type = format_ident!("__Interval_{}", counter);
735
736        quote! {
737            {
738                struct #key_type;
739                #scope.use_interval_keyed::<#key_type>(#duration, #callback)
740            }
741        }
742    }
743}
744
745/// The interval! macro for creating periodic timers.
746///
747/// The callback runs on the main thread each frame that the timer fires.
748///
749/// # Examples
750///
751/// ```ignore
752/// let count = state!(cx, || 0u64);
753/// let c = count.clone();
754/// interval!(cx, Duration::from_secs(1), move || {
755///     c.update(|n| *n += 1);
756/// });
757/// ```
758#[proc_macro]
759pub fn interval(input: TokenStream) -> TokenStream {
760    let input = parse_macro_input!(input as IntervalInput);
761    let expanded = input.to_tokens();
762    TokenStream::from(expanded)
763}
764
765/// The with! macro for cloning state handles into closures.
766///
767/// State<T> is a handle (like a smart pointer), not the data itself.
768/// When you need to use state in a closure, you must clone the handle
769/// so the closure owns its own copy. This macro makes that pattern concise.
770///
771/// # Examples
772///
773/// Single state:
774/// ```ignore
775/// let count = state!(cx, || 0);
776/// let increment = with!(count => move || count.update(|n| *n += 1));
777/// ```
778///
779/// Multiple states:
780/// ```ignore
781/// let count = state!(cx, || 0);
782/// let name = state!(cx, || String::new());
783///
784/// let handler = with!(count, name => move || {
785///     count.update(|n| *n += 1);
786///     name.set("updated".to_string());
787/// });
788/// ```
789///
790/// The above expands to:
791/// ```ignore
792/// let handler = {
793///     let count = count.clone();
794///     let name = name.clone();
795///     move || {
796///         count.update(|n| *n += 1);
797///         name.set("updated".to_string());
798///     }
799/// };
800/// ```
801#[proc_macro]
802pub fn with(input: TokenStream) -> TokenStream {
803    let input = parse_macro_input!(input as WithInput);
804    let expanded = input.to_tokens();
805    TokenStream::from(expanded)
806}
807
808/// A node in the view tree (during parsing).
809enum ViewNode {
810    /// An element like <Text>...</Text>
811    Element(ElementNode),
812    /// A string literal "Hello"
813    Text(String),
814    /// An expression in braces {expr}
815    Expr(Expr),
816}
817
818/// A prop like on_press={...} or selected={...}
819struct Prop {
820    name: Ident,
821    value: Expr,
822}
823
824struct ElementNode {
825    tag: String,
826    props: Vec<Prop>,
827    children: Vec<ViewNode>,
828}
829
830impl Parse for ViewNode {
831    fn parse(input: ParseStream) -> Result<Self> {
832        if input.peek(Token![<]) {
833            // Parse element: <Tag prop={val}>...</Tag>
834            input.parse::<Token![<]>()?;
835            let tag: Ident = input.parse()?;
836
837            // Parse props
838            let mut props = Vec::new();
839            while !input.peek(Token![>]) && !input.peek(Token![/]) {
840                let name: Ident = input.parse()?;
841                input.parse::<Token![=]>()?;
842                let content;
843                braced!(content in input);
844                let value: Expr = content.parse()?;
845                props.push(Prop { name, value });
846            }
847
848            // Check for self-closing tag: <Tag />
849            if input.peek(Token![/]) {
850                input.parse::<Token![/]>()?;
851                input.parse::<Token![>]>()?;
852                return Ok(ViewNode::Element(ElementNode {
853                    tag: tag.to_string(),
854                    props,
855                    children: Vec::new(),
856                }));
857            }
858
859            input.parse::<Token![>]>()?;
860
861            let mut children = Vec::new();
862
863            // Parse children until we hit the closing tag
864            while !(input.peek(Token![<]) && input.peek2(Token![/])) {
865                if input.is_empty() {
866                    return Err(syn::Error::new(
867                        tag.span(),
868                        format!("Unclosed tag: <{}>", tag),
869                    ));
870                }
871                children.push(input.parse()?);
872            }
873
874            // Parse closing tag: </Tag>
875            input.parse::<Token![<]>()?;
876            input.parse::<Token![/]>()?;
877            let close_tag: Ident = input.parse()?;
878            input.parse::<Token![>]>()?;
879
880            if tag != close_tag {
881                return Err(syn::Error::new(
882                    close_tag.span(),
883                    format!(
884                        "Mismatched tags: expected </{}>, found </{}>",
885                        tag, close_tag
886                    ),
887                ));
888            }
889
890            Ok(ViewNode::Element(ElementNode {
891                tag: tag.to_string(),
892                props,
893                children,
894            }))
895        } else if input.peek(LitStr) {
896            // Parse string literal: "Hello"
897            let lit: LitStr = input.parse()?;
898            Ok(ViewNode::Text(lit.value()))
899        } else if input.peek(syn::token::Brace) {
900            // Parse expression: {expr}
901            let content;
902            braced!(content in input);
903            let expr: Expr = content.parse()?;
904            Ok(ViewNode::Expr(expr))
905        } else {
906            Err(input.error("Expected <Element>, \"string literal\", or {expression}"))
907        }
908    }
909}
910
911impl ViewNode {
912    fn to_tokens(&self) -> TokenStream2 {
913        match self {
914            ViewNode::Text(s) => {
915                quote! { telex::View::text(#s) }
916            }
917            ViewNode::Expr(expr) => {
918                // Convert expression to string for text
919                quote! { telex::View::text(format!("{}", #expr)) }
920            }
921            ViewNode::Element(elem) => elem.to_tokens(),
922        }
923    }
924}
925
926impl ElementNode {
927    fn to_tokens(&self) -> TokenStream2 {
928        match self.tag.as_str() {
929            "Text" => {
930                // <Text>"content"</Text> or <Text>{expr}</Text>
931                if let Some(child) = self.children.first() {
932                    match child {
933                        ViewNode::Text(content) => quote! { telex::View::text(#content) },
934                        ViewNode::Expr(expr) => quote! { telex::View::text(format!("{}", #expr)) },
935                        _ => quote! { telex::View::text("") },
936                    }
937                } else {
938                    quote! { telex::View::text("") }
939                }
940            }
941            "VStack" => {
942                let mut builder_calls = Vec::new();
943
944                // Handle props (spacing)
945                for prop in &self.props {
946                    let name = &prop.name;
947                    let value = &prop.value;
948                    builder_calls.push(quote! { .#name(#value) });
949                }
950
951                // Handle children
952                for child in &self.children {
953                    let tokens = child.to_tokens();
954                    builder_calls.push(quote! { .child(#tokens) });
955                }
956
957                quote! { telex::View::vstack()#(#builder_calls)*.build() }
958            }
959            "HStack" => {
960                let mut builder_calls = Vec::new();
961
962                // Handle props (spacing)
963                for prop in &self.props {
964                    let name = &prop.name;
965                    let value = &prop.value;
966                    builder_calls.push(quote! { .#name(#value) });
967                }
968
969                // Handle children
970                for child in &self.children {
971                    let tokens = child.to_tokens();
972                    builder_calls.push(quote! { .child(#tokens) });
973                }
974
975                quote! { telex::View::hstack()#(#builder_calls)*.build() }
976            }
977            "Box" => {
978                let mut builder_calls = Vec::new();
979
980                // Handle props (border, padding, flex)
981                for prop in &self.props {
982                    let name = &prop.name;
983                    let value = &prop.value;
984                    builder_calls.push(quote! { .#name(#value) });
985                }
986
987                // Handle single child
988                if let Some(child) = self.children.first() {
989                    let tokens = child.to_tokens();
990                    builder_calls.push(quote! { .child(#tokens) });
991                }
992
993                quote! { telex::View::boxed()#(#builder_calls)*.build() }
994            }
995            "Spacer" => {
996                // Spacer with optional flex prop
997                if let Some(prop) = self.props.iter().find(|p| p.name == "flex") {
998                    let value = &prop.value;
999                    quote! { telex::View::spacer_flex(#value) }
1000                } else {
1001                    quote! { telex::View::spacer() }
1002                }
1003            }
1004            "Button" => {
1005                // Parse props and children for Button
1006                let mut builder_calls = Vec::new();
1007
1008                // Handle props
1009                for prop in &self.props {
1010                    let name = &prop.name;
1011                    let value = &prop.value;
1012                    builder_calls.push(quote! { .#name(#value) });
1013                }
1014
1015                // Handle label from children
1016                if let Some(child) = self.children.first() {
1017                    match child {
1018                        ViewNode::Text(label) => {
1019                            builder_calls.push(quote! { .label(#label) });
1020                        }
1021                        ViewNode::Expr(expr) => {
1022                            builder_calls.push(quote! { .label(format!("{}", #expr)) });
1023                        }
1024                        _ => {}
1025                    }
1026                }
1027
1028                quote! { telex::View::button()#(#builder_calls)*.build() }
1029            }
1030            "List" => {
1031                // Parse props for List: items, selected, on_select
1032                let mut builder_calls = Vec::new();
1033
1034                for prop in &self.props {
1035                    let name = &prop.name;
1036                    let value = &prop.value;
1037                    builder_calls.push(quote! { .#name(#value) });
1038                }
1039
1040                quote! { telex::View::list()#(#builder_calls)*.build() }
1041            }
1042            "TextInput" => {
1043                // Parse props for TextInput: value, placeholder, on_change
1044                let mut builder_calls = Vec::new();
1045
1046                for prop in &self.props {
1047                    let name = &prop.name;
1048                    let value = &prop.value;
1049                    builder_calls.push(quote! { .#name(#value) });
1050                }
1051
1052                quote! { telex::View::text_input()#(#builder_calls)*.build() }
1053            }
1054            "Checkbox" => {
1055                // Parse props and children for Checkbox: checked, on_toggle
1056                let mut builder_calls = Vec::new();
1057
1058                // Handle props
1059                for prop in &self.props {
1060                    let name = &prop.name;
1061                    let value = &prop.value;
1062                    builder_calls.push(quote! { .#name(#value) });
1063                }
1064
1065                // Handle label from children
1066                if let Some(child) = self.children.first() {
1067                    match child {
1068                        ViewNode::Text(label) => {
1069                            builder_calls.push(quote! { .label(#label) });
1070                        }
1071                        ViewNode::Expr(expr) => {
1072                            builder_calls.push(quote! { .label(format!("{}", #expr)) });
1073                        }
1074                        _ => {}
1075                    }
1076                }
1077
1078                quote! { telex::View::checkbox()#(#builder_calls)*.build() }
1079            }
1080            "TextArea" => {
1081                // Parse props for TextArea: value, placeholder, rows, cursor_line, cursor_col, on_change
1082                let mut builder_calls = Vec::new();
1083
1084                for prop in &self.props {
1085                    let name = &prop.name;
1086                    let value = &prop.value;
1087                    builder_calls.push(quote! { .#name(#value) });
1088                }
1089
1090                quote! { telex::View::text_area()#(#builder_calls)*.build() }
1091            }
1092            "Modal" => {
1093                // Parse props for Modal: visible, title, width, height, on_dismiss
1094                let mut builder_calls = Vec::new();
1095
1096                for prop in &self.props {
1097                    let name = &prop.name;
1098                    let value = &prop.value;
1099                    builder_calls.push(quote! { .#name(#value) });
1100                }
1101
1102                // Handle single child
1103                if let Some(child) = self.children.first() {
1104                    let tokens = child.to_tokens();
1105                    builder_calls.push(quote! { .child(#tokens) });
1106                }
1107
1108                quote! { telex::View::modal()#(#builder_calls)*.build() }
1109            }
1110            "StyledText" => {
1111                // Parse props for styled text: bold, italic, underline, dim, color, bg
1112                let mut content = quote! { "" };
1113                let mut bold_val = quote! { false };
1114                let mut italic_val = quote! { false };
1115                let mut underline_val = quote! { false };
1116                let mut dim_val = quote! { false };
1117                let mut color_call = quote! {};
1118                let mut bg_call = quote! {};
1119
1120                // Handle props
1121                for prop in &self.props {
1122                    let name_str = prop.name.to_string();
1123                    let value = &prop.value;
1124
1125                    match name_str.as_str() {
1126                        "bold" => bold_val = quote! { #value },
1127                        "italic" => italic_val = quote! { #value },
1128                        "underline" => underline_val = quote! { #value },
1129                        "dim" => dim_val = quote! { #value },
1130                        "color" => color_call = quote! { .color(#value) },
1131                        "bg" => bg_call = quote! { .bg(#value) },
1132                        _ => {}
1133                    }
1134                }
1135
1136                // Handle text content from children
1137                if let Some(child) = self.children.first() {
1138                    match child {
1139                        ViewNode::Text(text) => {
1140                            content = quote! { #text };
1141                        }
1142                        ViewNode::Expr(expr) => {
1143                            content = quote! { format!("{}", #expr) };
1144                        }
1145                        _ => {}
1146                    }
1147                }
1148
1149                // Generate conditional builder chain
1150                quote! {
1151                    {
1152                        let __builder = telex::View::styled_text(#content);
1153                        let __builder = if #bold_val { __builder.bold() } else { __builder };
1154                        let __builder = if #italic_val { __builder.italic() } else { __builder };
1155                        let __builder = if #underline_val { __builder.underline() } else { __builder };
1156                        let __builder = if #dim_val { __builder.dim() } else { __builder };
1157                        __builder #color_call #bg_call .build()
1158                    }
1159                }
1160            }
1161            unknown => {
1162                // Provide helpful error with suggestions
1163                let known_elements = [
1164                    "Text",
1165                    "StyledText",
1166                    "VStack",
1167                    "HStack",
1168                    "Box",
1169                    "Spacer",
1170                    "Button",
1171                    "List",
1172                    "TextInput",
1173                    "TextArea",
1174                    "Checkbox",
1175                    "Modal",
1176                ];
1177
1178                // Find similar element names (simple edit distance check)
1179                let suggestion = known_elements
1180                    .iter()
1181                    .find(|&e| {
1182                        let e_lower = e.to_lowercase();
1183                        let u_lower = unknown.to_lowercase();
1184                        e_lower.starts_with(&u_lower[..1.min(u_lower.len())])
1185                            || u_lower.starts_with(&e_lower[..1.min(e_lower.len())])
1186                            || e_lower.contains(&u_lower)
1187                            || u_lower.contains(&e_lower)
1188                    });
1189
1190                let msg = if let Some(suggested) = suggestion {
1191                    format!(
1192                        "Unknown element: <{}>. Did you mean <{}>?\n\nAvailable elements: {}",
1193                        unknown,
1194                        suggested,
1195                        known_elements.join(", ")
1196                    )
1197                } else {
1198                    format!(
1199                        "Unknown element: <{}>.\n\nAvailable elements: {}",
1200                        unknown,
1201                        known_elements.join(", ")
1202                    )
1203                };
1204                quote! { compile_error!(#msg) }
1205            }
1206        }
1207    }
1208}