1
2extern crate proc_macro;
3use proc_macro::{TokenStream, TokenTree, Ident, Delimiter, Group, Literal};
4
5#[proc_macro]
19pub fn html(item: TokenStream) -> TokenStream {
20
21 let tokens: Vec<TokenTree> = item.into_iter().collect();
22
23 if false {
24 for token in &tokens {
25 match token {
26 TokenTree::Ident(ident) => {
27 println!("Ident: {}", ident);
28 }
29 TokenTree::Literal(literal) => {
30 println!("Literal: {}", literal);
31 }
32 TokenTree::Punct(punct) => {
33 println!("Punct: {}", punct);
34 }
35 TokenTree::Group(group) => {
36 println!("Group: {}", group);
37 }
38 }
39 }
40 }
41
42 let elements = parse_mutliple_elements(&tokens);
43
44 if elements.is_none() {
45 panic!("Invalid HTML syntax");
46 }
47
48 let elements = elements.unwrap();
49
50 fn elements_to_token_stream(elements: &[Element]) -> TokenStream {
53
54 let elements: proc_macro2::TokenStream = elements_to_with_token_stream(&elements).into();
55
56 quote::quote!(
57 ::write_html::tags::fragment(::write_html::Empty)
58 #elements
59 ).into()
60 }
61
62 fn elements_to_with_token_stream(elements: &[Element]) -> TokenStream {
63 let mut tokens = Vec::new();
64 for element in elements {
65 let element: proc_macro2::TokenStream = element_to_token_stream(element).into();
66 let new_tokens: TokenStream = quote::quote!(
67 .child(#element)
68 ).into();
69 tokens.extend(new_tokens.into_iter());
70 }
71
72 tokens.into_iter().collect()
73 }
74
75 fn element_to_token_stream(element: &Element) -> TokenStream {
76 let mut tokens = Vec::new();
77
78 match element {
79 Element::Expression(group) => {
80 tokens.extend(group.clone());
81 }
82 Element::Literal(literal) => {
83 let literal: proc_macro2::TokenStream = TokenStream::from(TokenTree::Literal(literal.clone())).into();
84 let new_tokens: TokenStream = quote::quote!(
85 ::write_html::HtmlTextStr(#literal)
86 ).into();
87 tokens.extend(new_tokens);
88 }
89 Element::Tag(tag) => {
90 tokens.push(TokenTree::Punct(proc_macro::Punct::new(':', proc_macro::Spacing::Joint)));
91 tokens.push(TokenTree::Punct(proc_macro::Punct::new(':', proc_macro::Spacing::Alone)));
92 tokens.push(TokenTree::Ident(Ident::new("write_html", tag.identifier_span)));
93 tokens.push(TokenTree::Punct(proc_macro::Punct::new(':', proc_macro::Spacing::Joint)));
94 tokens.push(TokenTree::Punct(proc_macro::Punct::new(':', proc_macro::Spacing::Alone)));
95 tokens.push(TokenTree::Ident(Ident::new("tags", tag.identifier_span)));
96 tokens.push(TokenTree::Punct(proc_macro::Punct::new(':', proc_macro::Spacing::Joint)));
97 tokens.push(TokenTree::Punct(proc_macro::Punct::new(':', proc_macro::Spacing::Alone)));
98 if is_ident(&tag.identifier) {
99 tokens.push(TokenTree::Ident(Ident::new(&tag.identifier, tag.identifier_span)));
100 {
101 let args = "(::write_html::Empty, ::write_html::Empty)";
102 let token_stream: TokenStream = args.parse().unwrap();
103 tokens.extend(token_stream.into_iter());
104 }
105 } else {
106 tokens.push(TokenTree::Ident(Ident::new("tag", tag.identifier_span)));
107 {
108 let mut args = Vec::new();
109 args.push(TokenTree::Literal(Literal::string(&tag.identifier)));
110 let token_stream: TokenStream = ", ::write_html::Empty, ::write_html::Empty, ::write_html::Compactability::Yes{final_slash: true}".parse().unwrap();
111 args.extend(token_stream.into_iter());
112 let group = Group::new(Delimiter::Parenthesis, args.into_iter().collect());
113 tokens.push(TokenTree::Group(group));
114 }
115 }
116 for (key, value) in &tag.attributes {
117 tokens.push(TokenTree::Punct(proc_macro::Punct::new('.', proc_macro::Spacing::Alone)));
118 tokens.push(TokenTree::Ident(Ident::new("attr", tag.identifier_span)));
119 {
120 let mut args = Vec::new();
121 args.push(TokenTree::Literal(Literal::string(&key)));
122 args.push(TokenTree::Punct(proc_macro::Punct::new(',', proc_macro::Spacing::Alone)));
123 if let Some(value) = value {
124 match value {
125 AttributeValue::Literal(literal) => {
126 args.push(TokenTree::Literal(literal.clone()));
127 }
128 AttributeValue::Expression(stream) => {
129 args.extend(stream.clone().into_iter());
130 }
131 }
132 } else {
133 args.push(TokenTree::Literal(Literal::string("None")));
134 }
135 let group = Group::new(Delimiter::Parenthesis, args.into_iter().collect());
136 tokens.push(TokenTree::Group(group));
137 }
138 }
139 tokens.extend(elements_to_with_token_stream(&tag.children));
140 }
141 }
142
143 tokens.into_iter().collect()
144 }
145
146 let result = elements_to_token_stream(&elements);
149
150 result
151}
152
153fn is_ident(id: &str) -> bool {
154 if id.is_empty() {
155 return false;
156 }
157 if id.chars().next().unwrap().is_numeric() {
158 return false;
159 }
160 for c in id.chars() {
161 if !c.is_alphanumeric() && c != '_' {
162 return false;
163 }
164 }
165 true
166}
167
168#[derive(Debug, Clone)]
169enum Element {
170 Expression(TokenStream),
171 Literal(Literal),
172 Tag(Tag),
173}
174
175#[derive(Debug, Clone)]
176struct Parsed<T> {
177 parsed: T,
178 next: usize,
179}
180
181fn parse_mutliple_elements(tokens: &[TokenTree]) -> Option<Vec<Element>> {
182 let mut elements = Vec::new();
183
184 let mut idx = 0;
185 loop {
186 if idx >= tokens.len() {
187 break;
188 }
189
190 let element = parse_element(&tokens[idx..])?;
191 elements.push(element.parsed);
192 idx += element.next;
193 }
194
195 Some(elements)
196}
197
198fn parse_element(tokens: &[TokenTree]) -> Option<Parsed<Element>> {
199
200 let first = tokens.first()?;
201
202 match first {
203 TokenTree::Ident(_ident) => {
204 let tag = parse_tag_element(tokens)?;
205 Some(Parsed {
206 parsed: Element::Tag(tag.tag),
207 next: tag.next,
208 })
209 }
210 TokenTree::Literal(literal) => {
211 Some(Parsed {
212 parsed: Element::Literal(literal.clone()),
213 next: 1,
214 })
215 }
216 TokenTree::Punct(_punct) => {
217 None
218 }
219 TokenTree::Group(group) => {
220 match group.delimiter() {
221 Delimiter::Parenthesis => {
222 Some(Parsed {
223 parsed: Element::Expression(group.stream()),
224 next: 1,
225 })
226 }
227 Delimiter::Brace => {
228 None
229 }
230 Delimiter::Bracket => {
231 None
232 }
233 Delimiter::None => {
234 None
235 }
236 }
237 }
238 }
239}
240
241#[derive(Debug, Clone)]
242struct Tag {
243 identifier: String,
244 identifier_span: proc_macro::Span,
245 attributes: Vec<(String, Option<AttributeValue>)>,
246 children: Vec<Element>,
247}
248
249#[derive(Debug, Clone)]
250struct ParsedTag {
251 tag: Tag,
252 next: usize,
253}
254
255fn parse_tag_element(tokens: &[TokenTree]) -> Option<ParsedTag> {
256
257 let (identifier, next) = parse_html_identifier(tokens)?;
258
259 let mut attributes = Vec::new();
260
261 let mut idx = next;
262 loop {
263 if idx >= tokens.len() {
264 break;
265 }
266
267 if let Some(inner) = parse_tag_inner(tokens, idx) {
268 return Some(ParsedTag {
269 tag: Tag {
270 identifier,
271 identifier_span: tokens[0].span(), attributes,
273 children: inner.parsed,
274 },
275 next: inner.next,
276 })
277 }
278
279 if let Some((attribute, next2)) = parse_attribute(&tokens[idx..]) {
280 attributes.push(attribute);
281 idx += next2;
282 } else {
283 return None;
284 }
285 }
286
287 None
288}
289
290fn parse_tag_inner(tokens: &[TokenTree], start: usize) -> Option<Parsed<Vec<Element>>> {
291 if tokens.len() <= start {
292 return None;
293 }
294 let token = &tokens[start];
295
296 match token {
297 TokenTree::Group(group) => {
298 if group.delimiter() == Delimiter::Brace {
299 let delimited = group.stream().into_iter().collect::<Vec<TokenTree>>();
300 let children = parse_mutliple_elements(&delimited)?;
301 return Some(Parsed {
302 parsed: children,
303 next: start + 1,
304 });
305 } else {
306 None
307 }
308 }
309 TokenTree::Punct(punct) => {
310 if punct.as_char() == ';' {
311 return Some(Parsed {
312 parsed: Vec::new(),
313 next: start + 1,
314 });
315 } else {
316 None
317 }
318 }
319 _ => None,
320 }
321}
322
323#[derive(Debug, Clone)]
324enum AttributeValue {
325 Literal(Literal),
326 Expression(TokenStream),
327}
328
329fn parse_attribute(tokens: &[TokenTree]) -> Option<((String, Option<AttributeValue>), usize)> {
330 if tokens.is_empty() {
333 return None;
334 }
335
336 if let TokenTree::Punct(punct) = &tokens[0] {
338 if punct.as_char() == '.' {
339 let (identifier, next) = parse_html_identifier(&tokens[1..])?;
341 return Some((("class".to_string(), Some(AttributeValue::Literal(Literal::string(&identifier)))), 1 + next));
342 }
343 }
344
345 if let TokenTree::Punct(punct) = &tokens[0] {
347 if punct.as_char() == '#' {
348 let (identifier, next) = parse_html_identifier(&tokens[1..])?;
350 return Some((("id".to_string(), Some(AttributeValue::Literal(Literal::string(&identifier)))), 1 + next));
351 }
352 }
353
354 let (identifier, next) = parse_html_identifier(tokens)?;
355
356 if next < tokens.len() {
357 let token = &tokens[next];
358 if let TokenTree::Punct(punct) = token {
359 if punct.as_char() == '=' {
360 if let Some((value_string, next2)) = parse_html_identifier(&tokens[next + 1..]) {
362 return Some(((identifier, Some(AttributeValue::Literal(Literal::string(&value_string)))), next + 1 + next2));
363 } else {
364 let next_token_index = next + 1;
365 if next_token_index >= tokens.len() {
366 return Some(((identifier, None), next_token_index));
367 }
368 let next_token = &tokens[next_token_index];
369 match next_token {
370 TokenTree::Literal(literal) => {
371 return Some(((identifier, Some(AttributeValue::Literal(literal.clone()))), next_token_index + 1));
372 }
373 TokenTree::Group(group) => {
374 if group.delimiter() == Delimiter::Parenthesis {
375 return Some(((identifier, Some(AttributeValue::Expression(group.stream()))), next_token_index + 1));
376 }
377 }
378 _ => {
379 return Some(((identifier, None), next_token_index));
380 }
381 }
382 }
383 }
384
385 return Some(((identifier, None), next));
386 }
387 }
388
389 Some(((identifier, None), next))
390}
391
392fn parse_html_identifier(tokens: &[TokenTree]) -> Option<(String, usize)> {
393 let mut identifier = String::new();
394
395 let mut next = 0;
396
397 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
398 enum Type {
399 Ident,
400 Punct,
401 }
402
403 let mut prev_type = None;
404
405 for token in tokens {
407 match token {
408 TokenTree::Ident(ident) => {
409 let typ = Type::Ident;
410 if prev_type == Some(typ) {
411 break;
412 }
413 identifier.push_str(&ident.to_string());
414 prev_type = Some(typ);
415 }
416 TokenTree::Literal(_literal) => {
417 break;
419 }
420 TokenTree::Punct(punct) => {
421 if punct.as_char() == '-' {
422 let typ = Type::Punct;
423 if prev_type == Some(typ) {
424 break;
425 }
426 identifier.push_str("-");
427 prev_type = Some(typ);
428 } else {
429 break;
430 }
431 }
432 TokenTree::Group(_group) => {
433 break;
434 }
435 }
436
437 next += 1;
438 }
439
440 if next == 0 || identifier.is_empty() {
441 None
442 } else {
443 Some((identifier, next))
444 }
445}