1use proc_macro::TokenStream;
13use quote::quote;
14use syn::{
15 Expr, Ident, Path, Result, Token,
16 parse::{Parse, ParseStream},
17 parse_macro_input, parse_quote,
18 punctuated::Punctuated,
19 token,
20};
21
22#[proc_macro]
23pub fn bx(input: TokenStream) -> TokenStream {
24 let input = parse_macro_input!(input as MacroInput);
25 let bx_root: Path = if cfg!(feature = "core-default") {
26 parse_quote!(::builderx_core)
27 } else {
28 parse_quote!(::builderx)
29 };
30 let adapter: Path = input
31 .adapter
32 .unwrap_or_else(|| parse_quote!(#bx_root::DefaultAdapter));
33
34 input.root.to_builder_tokens(&adapter, &bx_root).into()
35}
36
37struct MacroInput {
38 adapter: Option<Path>,
39 root: ViewElement,
40}
41
42impl Parse for MacroInput {
43 fn parse(input: ParseStream) -> Result<Self> {
44 let adapter = if input.peek(Ident) || input.peek(Token![::]) {
45 let fork = input.fork();
46 let _path: Path = fork.parse()?;
47 if fork.peek(Token![=>]) {
48 let path = input.parse()?;
49 input.parse::<Token![=>]>()?;
50 Some(path)
51 } else {
52 None
53 }
54 } else {
55 None
56 };
57
58 let root = input.parse()?;
59 Ok(Self { adapter, root })
60 }
61}
62
63struct ViewElement {
64 tag: Path,
65 args: Option<Punctuated<Expr, Token![,]>>,
66 modifiers: Option<Punctuated<Expr, Token![,]>>,
67 children: Vec<ViewChild>,
68}
69
70enum ViewChild {
71 Element(ViewElement),
72 Expr(Expr),
73 Spread(Expr),
74}
75
76impl Parse for ViewElement {
77 fn parse(input: ParseStream) -> Result<Self> {
78 let tag: Path = input.parse()?;
79
80 let args = if input.peek(token::Paren) {
81 let content;
82 syn::parenthesized!(content in input);
83 Some(Punctuated::parse_terminated(&content)?)
84 } else {
85 None
86 };
87
88 let modifiers = if input.peek(token::Bracket) {
89 let content;
90 syn::bracketed!(content in input);
91 Some(Punctuated::parse_terminated(&content)?)
92 } else {
93 None
94 };
95
96 let mut children = Vec::new();
97 if input.peek(token::Brace) {
98 let content;
99 syn::braced!(content in input);
100 while !content.is_empty() {
101 children.push(content.parse()?);
102 if content.peek(Token![,]) {
103 content.parse::<Token![,]>()?;
104 }
105 }
106 }
107
108 Ok(ViewElement {
109 tag,
110 args,
111 modifiers,
112 children,
113 })
114 }
115}
116
117impl Parse for ViewChild {
118 fn parse(input: ParseStream) -> Result<Self> {
119 if input.peek(Token![..]) {
120 input.parse::<Token![..]>()?;
121 let expr: Expr = input.parse()?;
122 return Ok(ViewChild::Spread(expr));
123 }
124
125 if input.peek(Ident) || input.peek(Token![::]) {
128 let fork = input.fork();
129 if let Ok(_) = fork.parse::<Path>() {
130 if fork.peek(token::Paren) {
131 let content;
132 syn::parenthesized!(content in fork);
133 let _ = Punctuated::<Expr, Token![,]>::parse_terminated(&content);
134 }
135 if fork.peek(token::Bracket) || fork.peek(token::Brace) {
136 return Ok(ViewChild::Element(input.parse()?));
137 }
138 }
139 }
140
141 Ok(ViewChild::Expr(input.parse()?))
143 }
144}
145
146impl ViewElement {
147 fn to_builder_tokens(&self, adapter: &Path, bx_root: &Path) -> proc_macro2::TokenStream {
148 let tag = &self.tag;
149 let args = &self.args;
150
151 let mut builder = if let Some(args) = args {
153 quote! { #tag(#args) }
154 } else {
155 quote! { #tag() }
156 };
157
158 builder = quote! { <#adapter as #bx_root::BxAdapter>::start(#builder) };
159
160 if let Some(mods) = &self.modifiers {
162 for modifier in mods {
163 match modifier {
164 Expr::Path(_) => {
165 builder = quote! { #builder.#modifier() };
166 }
167 _ => {
168 builder = quote! { #builder.#modifier };
169 }
170 }
171 }
172 }
173
174 for child in &self.children {
176 builder = match child {
177 ViewChild::Element(el) => {
178 let child_tokens = el.to_builder_tokens(adapter, bx_root);
179 quote! { <#adapter as #bx_root::BxAdapter>::attach(#builder, #child_tokens) }
180 }
181 ViewChild::Expr(expr) => {
182 quote! { <#adapter as #bx_root::BxAdapter>::attach(#builder, #expr) }
183 }
184 ViewChild::Spread(expr) => {
185 quote! { <#adapter as #bx_root::BxAdapter>::attach_many(#builder, #expr) }
186 }
187 };
188 }
189
190 builder
191 }
192}