awsl_syn/
parser.rs

1//! RSX Parser
2
3use proc_macro2::{Span, TokenStream, TokenTree};
4use syn::{
5    braced,
6    ext::IdentExt,
7    parse::{discouraged::Speculative, Parse, ParseStream, Parser as _, Peek},
8    punctuated::Punctuated,
9    token::{Brace, Colon, Colon2},
10    Block, Error, Expr, ExprBlock, ExprLit, ExprPath, Ident, Path, PathSegment, Result, Token,
11};
12
13use crate::{node::*, punctuation::*};
14
15type TransformBlockFn = dyn Fn(ParseStream) -> Result<Option<TokenStream>>;
16
17/// Configures the `Parser` behavior
18#[derive(Default)]
19pub struct ParserConfig {
20    flat_tree: bool,
21    number_of_top_level_nodes: Option<usize>,
22    type_of_top_level_nodes: Option<NodeType>,
23    transform_block: Option<Box<TransformBlockFn>>,
24}
25
26impl ParserConfig {
27    /// Create new `ParserConfig` with default config
28    pub fn new() -> ParserConfig {
29        ParserConfig::default()
30    }
31
32    /// Return flat tree instead of nested tree
33    pub fn flat_tree(mut self) -> Self {
34        self.flat_tree = true;
35        self
36    }
37
38    /// Exact number of required top level nodes
39    pub fn number_of_top_level_nodes(mut self, number: usize) -> Self {
40        self.number_of_top_level_nodes = Some(number);
41        self
42    }
43
44    /// Enforce the `NodeType` of top level nodes
45    pub fn type_of_top_level_nodes(mut self, node_type: NodeType) -> Self {
46        self.type_of_top_level_nodes = Some(node_type);
47        self
48    }
49
50    /// Transforms the `value` of all `NodeType::Block`s with the given closure
51    /// callback. The provided `ParseStream` is the content of the block.
52    ///
53    /// When `Some(TokenStream)` is returned, the `TokenStream` is parsed as
54    /// Rust block content. The `ParseStream` must be completely consumed in
55    /// this case (no tokens left).
56    ///
57    /// If `None` is returned, the `ParseStream` is parsed as Rust block
58    /// content. The `ParseStream` isn't forked, so partial parsing inside the
59    /// transform callback will break this mechanism - fork if you want to avoid
60    /// breaking.
61    ///
62    /// An example usage might be a custom syntax inside blocks which isn't
63    /// valid Rust. The given example simply translates the `%` character into
64    /// the string `percent`
65    ///
66    /// ```rust
67    /// use quote::quote;
68    /// use syn::Token;
69    /// use syn_rsx::{parse2_with_config, ParserConfig};
70    ///
71    /// let tokens = quote! {
72    ///     <div>{%}</div>
73    /// };
74    ///
75    /// let config = ParserConfig::new().transform_block(|input| {
76    ///     input.parse::<Token![%]>()?;
77    ///     Ok(Some(quote! { "percent" }))
78    /// });
79    ///
80    /// parse2_with_config(tokens, config).unwrap();
81    /// ```
82    pub fn transform_block<F>(mut self, callback: F) -> Self
83    where
84        F: Fn(ParseStream) -> Result<Option<TokenStream>> + 'static,
85    {
86        self.transform_block = Some(Box::new(callback));
87        self
88    }
89}
90
91/// RSX Parser
92pub struct Parser {
93    config: ParserConfig,
94}
95
96impl Parser {
97    /// Create a new parser with the given config
98    pub fn new(config: ParserConfig) -> Parser {
99        Parser { config }
100    }
101
102    /// Parse a given `syn::ParseStream`
103    pub fn parse(&self, input: ParseStream) -> Result<Vec<Node>> {
104        let mut nodes = vec![];
105        let mut top_level_nodes = 0;
106        while !input.cursor().eof() {
107            let parsed_nodes = &mut self.node(input)?;
108
109            if let Some(type_of_top_level_nodes) = &self.config.type_of_top_level_nodes {
110                if &parsed_nodes[0].node_type != type_of_top_level_nodes {
111                    return Err(input.error(format!(
112                        "top level nodes need to be of type {}",
113                        type_of_top_level_nodes
114                    )));
115                }
116            }
117
118            nodes.append(parsed_nodes);
119            top_level_nodes += 1;
120        }
121
122        if let Some(number_of_top_level_nodes) = &self.config.number_of_top_level_nodes {
123            if &top_level_nodes != number_of_top_level_nodes {
124                return Err(input.error(format!(
125                    "saw {} top level nodes but exactly {} are required",
126                    top_level_nodes, number_of_top_level_nodes
127                )));
128            }
129        }
130
131        Ok(nodes)
132    }
133
134    fn node(&self, input: ParseStream) -> Result<Vec<Node>> {
135        let node = if input.peek(Token![<]) {
136            if input.peek2(Token![!]) {
137                if input.peek3(Ident) {
138                    self.doctype(input)
139                } else {
140                    self.comment(input)
141                }
142            } else if input.peek2(Token![>]) {
143                self.fragment(input)
144            } else {
145                self.element(input)
146            }
147        } else if input.peek(Brace) {
148            self.block(input)
149        } else {
150            self.text(input)
151        }?;
152
153        let mut nodes = vec![node];
154        if self.config.flat_tree {
155            let mut children = vec![];
156            children.append(&mut nodes[0].children);
157            nodes.append(&mut children);
158        }
159
160        Ok(nodes)
161    }
162
163    fn text(&self, input: ParseStream) -> Result<Node> {
164        let text = input.parse::<ExprLit>()?.into();
165
166        Ok(Node {
167            name: None,
168            value: Some(text),
169            node_type: NodeType::Text,
170            attributes: vec![],
171            children: vec![],
172        })
173    }
174
175    fn block(&self, input: ParseStream) -> Result<Node> {
176        let block = if self.config.transform_block.is_some() {
177            self.block_transform(input)?
178        } else {
179            self.block_expr(input)?
180        };
181
182        Ok(Node {
183            name: None,
184            value: Some(block),
185            node_type: NodeType::Block,
186            attributes: vec![],
187            children: vec![],
188        })
189    }
190
191    fn block_transform(&self, input: ParseStream) -> Result<Expr> {
192        let transform_block = self.config.transform_block.as_ref().unwrap();
193
194        input.step(|cursor| {
195            if let Some((tree, next)) = cursor.token_tree() {
196                match tree {
197                    TokenTree::Group(block_group) => {
198                        let block_span = block_group.span();
199                        let parser = move |block_content: ParseStream| match transform_block(
200                            block_content,
201                        ) {
202                            Ok(transformed_tokens) => match transformed_tokens {
203                                Some(tokens) => {
204                                    let parser = move |input: ParseStream| {
205                                        Ok(self.block_content_to_block(input, block_span))
206                                    };
207                                    parser.parse2(tokens)?
208                                }
209                                None => self.block_content_to_block(block_content, block_span),
210                            },
211                            Err(error) => Err(error),
212                        };
213                        Ok((parser.parse2(block_group.stream())?, next))
214                    }
215                    _ => Err(cursor.error("unexpected: no Group in TokenTree found")),
216                }
217            } else {
218                Err(cursor.error("unexpected: no TokenTree found"))
219            }
220        })
221    }
222
223    fn block_content_to_block(&self, input: ParseStream, span: Span) -> Result<Expr> {
224        Ok(ExprBlock {
225            attrs: vec![],
226            label: None,
227            block: Block {
228                brace_token: Brace { span },
229                stmts: Block::parse_within(input)?,
230            },
231        }
232        .into())
233    }
234
235    fn block_expr(&self, input: ParseStream) -> Result<Expr> {
236        let fork = input.fork();
237        let content;
238        let brace_token = braced!(content in fork);
239        let block = ExprBlock {
240            attrs: vec![],
241            label: None,
242            block: Block {
243                brace_token,
244                stmts: Block::parse_within(&content)?,
245            },
246        };
247        input.advance_to(&fork);
248
249        Ok(block.into())
250    }
251
252    fn element(&self, input: ParseStream) -> Result<Node> {
253        let fork = &input.fork();
254
255        if self.tag_close(&input.fork()).is_ok() {
256            return Err(fork.error("close tag has no corresponding open tag"));
257        }
258        let (name, attributes, self_closing) = self.tag_open(fork)?;
259
260        let mut children = vec![];
261        if !self_closing {
262            loop {
263                if !self.element_has_children(&name, fork)? {
264                    break;
265                }
266
267                children.append(&mut self.node(fork)?);
268            }
269
270            self.tag_close(fork)?;
271        }
272        input.advance_to(fork);
273
274        Ok(Node {
275            name: Some(name),
276            value: None,
277            node_type: NodeType::Element,
278            attributes,
279            children,
280        })
281    }
282
283    fn element_has_children(&self, tag_open_name: &NodeName, input: ParseStream) -> Result<bool> {
284        // an empty input at this point means the tag wasn't closed
285        if input.is_empty() {
286            return Err(Error::new(
287                tag_open_name.span(),
288                "open tag has no corresponding close tag and is not self-closing",
289            ));
290        }
291
292        if let Ok(tag_close_name) = self.tag_close(&input.fork()) {
293            if tag_open_name == &tag_close_name {
294                // if the next token is a matching close tag then there are no child nodes
295                return Ok(false);
296            } else {
297                // if the next token is a closing tag with a different name it's an invalid tree
298                return Err(input.error("close tag has no corresponding open tag"));
299            }
300        }
301
302        Ok(true)
303    }
304
305    fn tag_open(&self, input: ParseStream) -> Result<(NodeName, Vec<Node>, bool)> {
306        input.parse::<Token![<]>()?;
307        let name = self.node_name(input)?;
308
309        let mut attributes = TokenStream::new();
310        let self_closing = loop {
311            if let Ok(self_closing) = self.tag_open_end(input) {
312                break self_closing;
313            }
314
315            if input.is_empty() {
316                return Err(input.error("expected closing caret >"));
317            }
318
319            let next: TokenTree = input.parse()?;
320            attributes.extend(Some(next));
321        };
322
323        let attributes = if !attributes.is_empty() {
324            let parser = move |input: ParseStream| self.attributes(input);
325            parser.parse2(attributes)?
326        } else {
327            vec![]
328        };
329
330        Ok((name, attributes, self_closing))
331    }
332
333    fn tag_open_end(&self, input: ParseStream) -> Result<bool> {
334        let self_closing = input.parse::<Option<Token![/]>>()?.is_some();
335        input.parse::<Token![>]>()?;
336
337        Ok(self_closing)
338    }
339
340    fn tag_close(&self, input: ParseStream) -> Result<NodeName> {
341        input.parse::<Token![<]>()?;
342        input.parse::<Token![/]>()?;
343        let name = self.node_name(input)?;
344        input.parse::<Token![>]>()?;
345
346        Ok(name)
347    }
348
349    fn attributes(&self, input: ParseStream) -> Result<Vec<Node>> {
350        let mut nodes = vec![];
351
352        loop {
353            if input.is_empty() {
354                break;
355            }
356
357            nodes.push(self.attribute(input)?);
358        }
359
360        Ok(nodes)
361    }
362
363    fn attribute(&self, input: ParseStream) -> Result<Node> {
364        let fork = &input.fork();
365        if fork.peek(Brace) {
366            let value = Some(self.block_expr(fork)?);
367            input.advance_to(fork);
368
369            Ok(Node {
370                name: None,
371                node_type: NodeType::Block,
372                value,
373                attributes: vec![],
374                children: vec![],
375            })
376        } else {
377            let key = self.node_name(fork)?;
378            let eq = fork.parse::<Option<Token![=]>>()?;
379            let value = if eq.is_some() {
380                if fork.is_empty() {
381                    return Err(Error::new(key.span(), "missing attribute value"));
382                }
383
384                if fork.peek(Brace) {
385                    Some(self.block_expr(fork)?)
386                } else {
387                    Some(fork.parse()?)
388                }
389            } else {
390                None
391            };
392            input.advance_to(fork);
393
394            Ok(Node {
395                name: Some(key),
396                node_type: NodeType::Attribute,
397                value,
398                attributes: vec![],
399                children: vec![],
400            })
401        }
402    }
403
404    fn doctype(&self, input: ParseStream) -> Result<Node> {
405        input.parse::<Token![<]>()?;
406        input.parse::<Token![!]>()?;
407        let ident = input.parse::<Ident>()?;
408        if ident.to_string().to_lowercase() != "doctype" {
409            return Err(input.error("expected Doctype"));
410        }
411        let doctype = input.parse::<Ident>()?;
412        input.parse::<Token![>]>()?;
413
414        let mut segments = Punctuated::new();
415        segments.push_value(PathSegment::from(doctype));
416        let value = ExprPath {
417            attrs: vec![],
418            qself: None,
419            path: Path {
420                leading_colon: None,
421                segments,
422            },
423        }
424        .into();
425
426        Ok(Node {
427            name: None,
428            value: Some(value),
429            node_type: NodeType::Doctype,
430            attributes: vec![],
431            children: vec![],
432        })
433    }
434
435    fn comment(&self, input: ParseStream) -> Result<Node> {
436        input.parse::<Token![<]>()?;
437        input.parse::<Token![!]>()?;
438        input.parse::<Token![-]>()?;
439        input.parse::<Token![-]>()?;
440        let comment = input.parse::<ExprLit>()?.into();
441        input.parse::<Token![-]>()?;
442        input.parse::<Token![-]>()?;
443        input.parse::<Token![>]>()?;
444
445        Ok(Node {
446            name: None,
447            value: Some(comment),
448            node_type: NodeType::Comment,
449            attributes: vec![],
450            children: vec![],
451        })
452    }
453
454    fn fragment(&self, input: ParseStream) -> Result<Node> {
455        self.fragment_open(input)?;
456
457        let mut children = vec![];
458        loop {
459            if input.is_empty() {
460                return Err(input.error("unexpected end of input"));
461            }
462
463            if self.fragment_close(&input.fork()).is_ok() {
464                self.fragment_close(input)?;
465                break;
466            }
467
468            children.append(&mut self.node(input)?);
469        }
470
471        Ok(Node {
472            name: None,
473            value: None,
474            node_type: NodeType::Fragment,
475            attributes: vec![],
476            children,
477        })
478    }
479
480    fn fragment_open(&self, input: ParseStream) -> Result<()> {
481        input.parse::<Token![<]>()?;
482        input.parse::<Token![>]>()?;
483
484        Ok(())
485    }
486
487    fn fragment_close(&self, input: ParseStream) -> Result<()> {
488        input.parse::<Token![<]>()?;
489        input.parse::<Token![/]>()?;
490        input.parse::<Token![>]>()?;
491
492        Ok(())
493    }
494
495    fn node_name(&self, input: ParseStream) -> Result<NodeName> {
496        if input.peek2(Colon2) {
497            self.node_name_punctuated_ident::<Colon2, fn(_) -> Colon2, PathSegment>(input, Colon2)
498                .map(|segments| {
499                    NodeName::Path(ExprPath {
500                        attrs: vec![],
501                        qself: None,
502                        path: Path {
503                            leading_colon: None,
504                            segments,
505                        },
506                    })
507                })
508        } else if input.peek2(Colon) {
509            self.node_name_punctuated_ident::<Colon, fn(_) -> Colon, Ident>(input, Colon)
510                .map(NodeName::Colon)
511        } else if input.peek2(Dash) {
512            self.node_name_punctuated_ident::<Dash, fn(_) -> Dash, Ident>(input, Dash)
513                .map(NodeName::Dash)
514        } else if input.peek(Brace) {
515            let fork = &input.fork();
516            let value = self.block_expr(fork)?;
517            input.advance_to(fork);
518            Ok(NodeName::Block(value))
519        } else if input.peek(Ident::peek_any) {
520            let mut segments = Punctuated::new();
521            let ident = Ident::parse_any(input)?;
522            segments.push_value(PathSegment::from(ident));
523            Ok(NodeName::Path(ExprPath {
524                attrs: vec![],
525                qself: None,
526                path: Path {
527                    leading_colon: None,
528                    segments,
529                },
530            }))
531        } else {
532            Err(input.error("invalid tag name or attribute key"))
533        }
534    }
535
536    // we can't replace this with [`Punctuated::parse_separated_nonempty`] since
537    // that doesn't support reserved keywords. might be worth to consider a PR
538    // upstream
539    //
540    // [`Punctuated::parse_separated_nonempty`]: https://docs.rs/syn/1.0.58/syn/punctuated/struct.Punctuated.html#method.parse_separated_nonempty
541    fn node_name_punctuated_ident<T: Parse, F: Peek, X: From<Ident>>(
542        &self,
543        input: ParseStream,
544        punct: F,
545    ) -> Result<Punctuated<X, T>> {
546        let fork = &input.fork();
547        let mut segments = Punctuated::<X, T>::new();
548
549        while !fork.is_empty() && fork.peek(Ident::peek_any) {
550            let ident = Ident::parse_any(fork)?;
551            segments.push_value(ident.clone().into());
552
553            if fork.peek(punct) {
554                segments.push_punct(fork.parse()?);
555            } else {
556                break;
557            }
558        }
559
560        if segments.len() > 1 {
561            input.advance_to(fork);
562            Ok(segments)
563        } else {
564            Err(fork.error("expected punctuated node name"))
565        }
566    }
567}