write_html_macro/
lib.rs

1
2extern crate proc_macro;
3use proc_macro::{TokenStream, TokenTree, Ident, Delimiter, Group, Literal};
4
5/*#[proc_macro]
6pub fn make_answer(item: TokenStream) -> TokenStream {
7
8    dbg!(item);
9    dbg!(32);
10    //return item;
11
12    //panic!("ciao");
13    //println!("ciao");
14
15    "fn answer() -> u32 { 42 }".parse().unwrap()
16}*/
17
18#[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    //dbg!(element);
51
52    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    //"42".parse().unwrap()
147    //let result = element_to_token_stream(element);
148    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(), // TODO this is not correct
272                    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    // TODO handle # and .
331
332    if tokens.is_empty() {
333        return None;
334    }
335
336    // class
337    if let TokenTree::Punct(punct) = &tokens[0] {
338        if punct.as_char() == '.' {
339            // TODO use @attribute_value_parsing made in a function
340            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    // id
346    if let TokenTree::Punct(punct) = &tokens[0] {
347        if punct.as_char() == '#' {
348            // TODO use @attribute_value_parsing made in a function
349            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                // @attribute_value_parsing
361                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    // iterate over the tokens until we find a non-ident or non "-" token
406    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                // TODO handle numbers
418                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}