rstml/node/
atoms.rs

1//!
2//! Tokens that is used as parts of nodes, to simplify parsing.
3//! Example:
4//! `<!--` `<>` `</>` `<!` `/>`
5//!
6//! Also contain some entities that split parsing into several small units,
7//! like: `<open_tag attr />`
8//! `</close_tag>`
9
10use proc_macro2::Ident;
11use proc_macro2_diagnostics::{Diagnostic, Level};
12use syn::{ext::IdentExt, Token};
13
14use crate::{
15    node::{parse, NodeAttribute, NodeName},
16    parser::recoverable::RecoverableContext,
17};
18
19pub(crate) mod tokens {
20    //! Custom syn punctuations
21    use syn::{custom_punctuation, Token};
22
23    use crate::node::parse;
24    // Dash between node-name
25    custom_punctuation!(Dash, -);
26
27    // Later use custom punct, currently it is not compatible with quote;
28
29    // // Start part of doctype tag
30    //     // `<!`
31    //     custom_punctuation!(DocStart, <!);
32
33    //     // Start part of element's close tag.
34    //     // Its commonly used as separator
35    //     // `</`
36    //     custom_punctuation!(CloseTagStart, </);
37
38    //     custom_punctuation!(ComEnd, -->);
39
40    //     //
41    //     // Rest tokens is impossible to implement using custom_punctuation,
42    //     // because they have Option fields, or more than 3 elems
43    //     //
44    /// Start part of doctype tag
45    /// `<!`
46    #[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
47    pub struct DocStart {
48        pub token_lt: Token![<],
49        pub token_not: Token![!],
50    }
51
52    /// Start part of comment tag
53    /// `<!--`
54    #[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
55    pub struct ComStart {
56        pub token_lt: Token![<],
57        pub token_not: Token![!],
58        #[parse(parse::parse_array_of2_tokens)]
59        #[to_tokens(parse::to_tokens_array)]
60        pub token_minus: [Token![-]; 2],
61    }
62
63    /// End part of comment tag
64    /// `-->`
65    #[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
66    pub struct ComEnd {
67        #[parse(parse::parse_array_of2_tokens)]
68        #[to_tokens(parse::to_tokens_array)]
69        pub token_minus: [Token![-]; 2],
70        pub token_gt: Token![>],
71    }
72
73    /// End part of element's open tag
74    /// `/>` or `>`
75    #[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
76    pub struct OpenTagEnd {
77        pub token_solidus: Option<Token![/]>,
78        pub token_gt: Token![>],
79    }
80
81    /// Start part of element's close tag.
82    /// Its commonly used as separator
83    /// `</`
84    #[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
85    pub struct CloseTagStart {
86        pub token_lt: Token![<],
87        pub token_solidus: Token![/],
88    }
89}
90
91pub use tokens::*;
92
93/// Fragment open part
94/// `<>`
95#[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
96pub struct FragmentOpen {
97    pub token_lt: Token![<],
98    pub token_gt: Token![>],
99}
100
101/// Fragment close part
102/// `</>`
103#[derive(Eq, PartialEq, Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
104pub struct FragmentClose {
105    pub start_tag: tokens::CloseTagStart,
106    pub token_gt: Token![>],
107}
108
109impl FragmentClose {
110    pub fn parse_with_start_tag(
111        parser: &mut RecoverableContext,
112        input: syn::parse::ParseStream,
113        start_tag: Option<tokens::CloseTagStart>,
114    ) -> Option<Self> {
115        let start_tag = start_tag?;
116        if input.peek(Ident::peek_any) {
117            let ident_from_invalid_closing = Ident::parse_any(input).expect("parse after peek");
118            parser.push_diagnostic(Diagnostic::spanned(
119                ident_from_invalid_closing.span(),
120                Level::Error,
121                "expected fragment closing, found element closing tag",
122            ));
123        };
124        Some(Self {
125            start_tag,
126            token_gt: parser.parse_simple(input)?,
127        })
128    }
129}
130
131/// Open tag for element, possibly self-closed.
132/// `<name attr=x, attr_flag>`
133#[derive(Clone, Debug, syn_derive::ToTokens)]
134pub struct OpenTag {
135    pub token_lt: Token![<],
136    pub name: NodeName,
137    pub generics: syn::Generics,
138    #[to_tokens(parse::to_tokens_array)]
139    pub attributes: Vec<NodeAttribute>,
140    pub end_tag: tokens::OpenTagEnd,
141}
142
143impl OpenTag {
144    pub fn is_self_closed(&self) -> bool {
145        self.end_tag.token_solidus.is_some()
146    }
147}
148
149/// Open tag for element, `<name attr=x, attr_flag>`
150#[derive(Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
151pub struct CloseTag {
152    pub start_tag: tokens::CloseTagStart,
153    pub name: NodeName,
154    pub generics: syn::Generics,
155    pub token_gt: Token![>],
156}
157
158impl CloseTag {
159    pub fn parse_with_start_tag(
160        parser: &mut RecoverableContext,
161        input: syn::parse::ParseStream,
162        start_tag: Option<tokens::CloseTagStart>,
163    ) -> Option<Self> {
164        Some(Self {
165            start_tag: start_tag?,
166            name: parser.parse_simple(input)?,
167            generics: parser.parse_simple(input)?,
168            token_gt: parser.parse_simple(input)?,
169        })
170    }
171}
172
173#[cfg(test)]
174mod test {
175    use syn::custom_punctuation;
176
177    use super::*;
178
179    macro_rules! parse_quote {
180            ($mod_name:ident, $name: ident=> $($tts:tt)*) => {
181                mod $mod_name {
182                    use super::*;
183                    // use super::tokens::*;
184                    #[test]
185                    fn parse_quote() {
186
187                        let tts = quote::quote!{
188                            $($tts)*
189                        };
190                        syn::parse2::<$name>(tts).unwrap();
191
192                    }
193                }
194            }
195        }
196
197    parse_quote! {docstart, DocStart => <!}
198
199    parse_quote! {comstart, ComStart => <!--}
200
201    parse_quote! {comend, ComEnd => -->}
202
203    parse_quote! {open_tag_end1, OpenTagEnd => >}
204
205    parse_quote! {open_tag_end2, OpenTagEnd => />}
206
207    parse_quote! {close_tag_start, CloseTagStart => </}
208
209    /// Custom punctuation wasnt compatible with quote,
210    /// check if it now compatible to replace simple parser with it  
211    #[test]
212    fn parse_quote_doc_comp_custom_punct() {
213        custom_punctuation!(CloseTagStart2, </);
214        let tts = quote::quote! {
215            </
216        };
217        syn::parse2::<CloseTagStart2>(tts).unwrap_err();
218    }
219}