1mod boundary_attribute;
2mod derive_attribute;
3
4use proc_macro::TokenStream;
5use quote::{quote, ToTokens};
6use syn::parse::{Parse, ParseStream};
7use syn::punctuated::Punctuated;
8use syn::token::{Comma, Dot, Eq, Paren};
9use syn::{parse_macro_input, DeriveInput, Expr, ExprLit, Ident, ItemFn, Path, Type};
10
11#[proc_macro_derive(Attribute, attributes(attribute))]
12pub fn derive_attribute(item: TokenStream) -> TokenStream {
13 let input = parse_macro_input!(item as DeriveInput);
14 match derive_attribute::derive_attribute(input) {
15 Ok(ts) => ts.into(),
16 Err(err) => err.into_compile_error().into(),
17 }
18}
19
20#[proc_macro_attribute]
21pub fn boundary(attr: TokenStream, item: TokenStream) -> TokenStream {
22 let events = parse_macro_input!(attr with Punctuated::<Type, Comma>::parse_terminated);
23 let input = parse_macro_input!(item as ItemFn);
24 match boundary_attribute::boundary_attribute(input, events) {
25 Ok(ts) => ts.into(),
26 Err(err) => err.into_compile_error().into(),
27 }
28}
29
30#[derive(Debug, Hash)]
31struct OptionExpr {
32 key: Ident,
33 value: Option<Expr>,
34}
35
36impl Parse for OptionExpr {
37 fn parse(input: ParseStream) -> syn::Result<Self> {
38 let key = Ident::parse(input)?;
39 let value = if Option::<Eq>::parse(input)?.is_some() {
40 Some(Expr::parse(input)?)
41 } else {
42 None
43 };
44 Ok(OptionExpr { key, value })
45 }
46}
47
48#[derive(Debug, Hash)]
49enum StyleExpr {
50 Path {
52 path: Path,
53 method_calls: StyleMethodCalls,
55 },
56 Call {
58 func: Path,
59 paren_token: Paren,
60 args: Punctuated<ExprLit, Comma>,
61 method_calls: StyleMethodCalls,
63 },
64 Tuple {
66 paren_token: Paren,
67 args: Punctuated<StyleExpr, Comma>,
68 method_calls: StyleMethodCalls,
69 },
70}
71
72#[derive(Debug, Hash, Clone)]
73struct StyleMethodCalls {
74 method_calls: Option<Vec<StyleMethodCall>>,
75}
76
77#[derive(Debug, Hash, Clone)]
78struct StyleMethodCall {
79 dot_token: Dot,
80 method: Option<Ident>, paren_token: Option<Paren>, }
83
84impl Parse for StyleExpr {
85 fn parse(input: ParseStream) -> syn::Result<Self> {
86 if input.peek(Paren) {
87 let content;
88 return Ok(StyleExpr::Tuple {
89 paren_token: syn::parenthesized!(content in input),
90 args: content.parse_terminated(StyleExpr::parse, Comma)?,
91 method_calls: input.parse()?,
92 });
93 }
94
95 let path = Path::parse(input)?;
96
97 if input.peek(Paren) {
98 let content;
99 Ok(StyleExpr::Call {
100 func: path,
101 paren_token: syn::parenthesized!(content in input),
102 args: content.parse_terminated(ExprLit::parse, Comma)?,
103 method_calls: input.parse()?,
104 })
105 } else {
106 Ok(StyleExpr::Path {
107 path,
108 method_calls: input.parse()?,
109 })
110 }
111 }
112}
113
114impl Parse for StyleMethodCalls {
115 fn parse(input: ParseStream) -> syn::Result<Self> {
116 let mut method_calls = Vec::with_capacity(1);
117 while input.peek(Dot) {
118 method_calls.push(StyleMethodCall {
119 dot_token: input.parse()?,
120 method: if input.peek(Ident) {
121 Some(input.parse()?)
122 } else {
123 None
124 },
125 paren_token: if input.peek(Paren) {
126 #[allow(unused)]
127 let content;
128 Some(syn::parenthesized!(content in input))
129 } else {
130 None
131 },
132 });
133 }
134
135 Ok(Self {
136 method_calls: if method_calls.is_empty() {
137 None
138 } else {
139 Some(method_calls)
140 },
141 })
142 }
143}
144
145impl ToTokens for StyleExpr {
146 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
147 match self {
148 StyleExpr::Path { path, method_calls } => {
149 path.to_tokens(tokens);
150 method_calls.to_tokens(tokens);
151 }
152 StyleExpr::Call {
153 func,
154 paren_token,
155 args,
156 method_calls,
157 } => {
158 func.to_tokens(tokens);
159 paren_token.surround(tokens, |tokens| {
160 args.to_tokens(tokens);
161 });
162 method_calls.to_tokens(tokens);
163 }
164 StyleExpr::Tuple {
165 paren_token,
166 args,
167 method_calls,
168 } => {
169 paren_token.surround(tokens, |tokens| {
170 args.to_tokens(tokens);
171 });
172 method_calls.to_tokens(tokens);
173 }
174 }
175 }
176}
177
178impl ToTokens for StyleMethodCall {
179 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
180 let StyleMethodCall {
181 dot_token,
182 method,
183 paren_token,
184 } = self;
185 dot_token.to_tokens(tokens);
186 method.to_tokens(tokens);
187 if let Some(paren_token) = paren_token {
188 paren_token.surround(tokens, |_| {});
189 }
190 }
191}
192
193impl ToTokens for StyleMethodCalls {
194 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
195 if let Some(method_calls) = &self.method_calls {
196 for method_call in method_calls {
197 method_call.to_tokens(tokens);
198 }
199 }
200 }
201}
202
203#[derive(Debug, Hash)]
204struct Styles {
205 styles: Punctuated<StyleExpr, Comma>,
206}
207
208impl Parse for Styles {
209 fn parse(input: ParseStream) -> syn::Result<Self> {
210 Ok(Styles {
211 styles: Punctuated::<StyleExpr, Comma>::parse_terminated(input)?,
212 })
213 }
214}
215
216fn flatten_recursively(iter: impl Iterator<Item = StyleExpr>) -> impl Iterator<Item = StyleExpr> {
217 iter.flat_map(|style| match style {
218 s @ StyleExpr::Path { .. } => Box::new(std::iter::once(s)),
219 s @ StyleExpr::Call { .. } => Box::new(std::iter::once(s)),
220 StyleExpr::Tuple {
221 args,
222 method_calls: parent_method_calls,
223 ..
224 } => Box::new(flatten_recursively(args.into_iter()).map(move |mut s| {
225 if parent_method_calls.method_calls.is_some() {
226 match &mut s {
227 StyleExpr::Call { method_calls, .. } | StyleExpr::Path { method_calls, .. } => {
228 if method_calls.method_calls.is_none() {
229 method_calls.method_calls = parent_method_calls.method_calls.clone();
230 } else {
231 method_calls
232 .method_calls
233 .as_mut()
234 .unwrap()
235 .extend(parent_method_calls.method_calls.clone().unwrap())
236 }
237 }
238 StyleExpr::Tuple { .. } => unreachable!(),
239 }
240 }
241 s
242 })) as Box<dyn Iterator<Item = StyleExpr>>,
243 })
244}
245
246#[proc_macro]
247pub fn tw(item: TokenStream) -> TokenStream {
248 let input = parse_macro_input!(item as Styles);
249 let styles = flatten_recursively(input.styles.into_iter());
254
255 quote! {
256 {
257 static NAME: ::cabin::private::OnceCell<String> = ::cabin::private::OnceCell::new();
258
259 #[::cabin::private::linkme::distributed_slice(::cabin_tailwind::registry::STYLES)]
260 #[linkme(crate = ::cabin::private::linkme)]
261 fn __register(r: &mut ::cabin_tailwind::registry::StyleRegistry) {
262 let name = r.add(&[#(&#styles,)*]);
263 NAME.set(name).ok();
264 }
265
266 ::cabin_tailwind::Class(::std::borrow::Cow::Borrowed(
267 NAME.get().map(|s| s.as_str()).unwrap_or_default()
268 ))
269 }
270 }
271 .into()
272}