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 '/' => {
111 if angle_count == 1 && input.peek(Token![>]) {
112 div = Some(syn::token::Slash {
113 spans: [punct.span()],
114 });
115 gt = input.parse()?;
116 break;
117 }
118 }
119 '>' => {
120 angle_count = angle_count.checked_sub(1).ok_or_else(|| {
121 syn::Error::new_spanned(
122 punct,
123 "this tag close has no corresponding tag open",
124 )
125 })?;
126 if angle_count == 0 {
127 gt = syn::token::Gt {
128 spans: [punct.span()],
129 };
130 break;
131 }
132 }
133 '<' => angle_count += 1,
134 _ => {}
135 };
136 }
137
138 inner_trees.push(next);
139 }
140
141 Ok((inner_trees.into_iter().collect(), div, gt))
142 }
143
144 pub fn to_spanned(&self) -> impl ToTokens {
147 let Self { lt, gt, .. } = self;
148 quote! {#lt #gt}
149 }
150}