yew_macro/html_tree/
tag.rs1use proc_macro2::{Span, TokenStream, TokenTree};
2use quote::{quote, ToTokens};
3use syn::parse::{ParseStream, Parser};
4use syn::Token;
5
6fn span_eq_hack(a: &Span, b: &Span) -> bool {
10 format!("{a:?}") == format!("{b:?}")
11}
12
13fn error_replace_span(err: syn::Error, from: Span, to: impl ToTokens) -> syn::Error {
15 let err_it = err.into_iter().map(|err| {
16 if span_eq_hack(&err.span(), &from) {
17 syn::Error::new_spanned(&to, err.to_string())
18 } else {
19 err
20 }
21 });
22
23 crate::join_errors(err_it).unwrap_err()
25}
26
27pub struct TagTokens {
31 pub lt: Token![<],
32 pub div: Option<Token![/]>,
33 pub gt: Token![>],
34}
35impl TagTokens {
36 pub fn parse_start_content<T>(
40 input: ParseStream,
41 parse: impl FnOnce(ParseStream, Self) -> syn::Result<T>,
42 ) -> syn::Result<T> {
43 Self::parse_content(Self::parse_start(input)?, parse)
44 }
45
46 pub fn parse_end_content<T>(
48 input: ParseStream,
49 parse: impl FnOnce(ParseStream, Self) -> syn::Result<T>,
50 ) -> syn::Result<T> {
51 Self::parse_content(Self::parse_end(input)?, parse)
52 }
53
54 fn parse_content<T>(
55 (tag, content): (Self, TokenStream),
56 parse: impl FnOnce(ParseStream, Self) -> syn::Result<T>,
57 ) -> syn::Result<T> {
58 let scope_spanned = tag.to_spanned();
59 let content_parser = |input: ParseStream| {
60 parse(input, tag).map_err(|err| {
61 error_replace_span(err, Span::call_site(), &scope_spanned)
68 })
69 };
70 content_parser.parse2(content)
71 }
72
73 fn parse_start(input: ParseStream) -> syn::Result<(Self, TokenStream)> {
75 let lt = input.parse()?;
76 let (content, div, gt) = Self::parse_until_end(input)?;
77
78 Ok((Self { lt, div, gt }, content))
79 }
80
81 fn parse_end(input: ParseStream) -> syn::Result<(Self, TokenStream)> {
84 let lt = input.parse()?;
85 let div = Some(input.parse()?);
86
87 let (content, end_div, gt) = Self::parse_until_end(input)?;
88 if end_div.is_some() {
89 return Err(syn::Error::new_spanned(
90 end_div,
91 "unexpected `/` in this end tag",
92 ));
93 }
94
95 Ok((Self { lt, div, gt }, content))
96 }
97
98 fn parse_until_end(
99 input: ParseStream,
100 ) -> syn::Result<(TokenStream, Option<Token![/]>, Token![>])> {
101 let mut inner_trees = Vec::new();
102 let mut angle_count: usize = 1;
103 let mut div: Option<Token![/]> = None;
104 let gt: Token![>];
105
106 loop {
107 let next = input.parse()?;
108 if let TokenTree::Punct(punct) = &next {
109 match punct.as_char() {
110 '/' if angle_count == 1 && input.peek(Token![>]) => {
111 div = Some(syn::token::Slash {
112 spans: [punct.span()],
113 });
114 gt = input.parse()?;
115 break;
116 }
117 '>' => {
118 angle_count = angle_count.checked_sub(1).ok_or_else(|| {
119 syn::Error::new_spanned(
120 punct,
121 "this tag close has no corresponding tag open",
122 )
123 })?;
124 if angle_count == 0 {
125 gt = syn::token::Gt {
126 spans: [punct.span()],
127 };
128 break;
129 }
130 }
131 '<' => angle_count += 1,
132 _ => {}
133 };
134 }
135
136 inner_trees.push(next);
137 }
138
139 Ok((inner_trees.into_iter().collect(), div, gt))
140 }
141
142 pub fn to_spanned(&self) -> impl ToTokens {
145 let Self { lt, gt, .. } = self;
146 quote! {#lt #gt}
147 }
148}