1use std::{
2 convert::TryFrom,
3 fmt::{self, Display},
4};
5
6use proc_macro2::Punct;
7use syn::{
8 ext::IdentExt,
9 parse::{discouraged::Speculative, Parse, ParseStream, Peek},
10 punctuated::{Pair, Punctuated},
11 token::{Brace, Colon, Dot, PathSep},
12 Block, ExprPath, Ident, LitInt, Path, PathSegment,
13};
14
15use super::{atoms::tokens::Dash, path_to_string};
16use crate::{node::parse::block_expr, Error};
17
18#[derive(Clone, Debug, syn_derive::Parse, syn_derive::ToTokens)]
19pub enum NodeNameFragment {
20 #[parse(peek = Ident::peek_any)]
21 Ident(#[parse(Ident::parse_any)] Ident),
22 #[parse(peek = LitInt)]
23 Literal(LitInt),
24 Empty,
26}
27
28impl PartialEq<NodeNameFragment> for NodeNameFragment {
29 fn eq(&self, other: &NodeNameFragment) -> bool {
30 match (self, other) {
31 (NodeNameFragment::Ident(s), NodeNameFragment::Ident(o)) => s == o,
32 (NodeNameFragment::Literal(s), NodeNameFragment::Literal(o)) => {
35 s.to_string() == o.to_string()
36 }
37 (NodeNameFragment::Empty, NodeNameFragment::Empty) => true,
38 _ => false,
39 }
40 }
41}
42impl Eq for NodeNameFragment {}
43
44impl Display for NodeNameFragment {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 match self {
47 NodeNameFragment::Ident(i) => i.fmt(f),
48 NodeNameFragment::Literal(l) => l.fmt(f),
49 NodeNameFragment::Empty => Ok(()),
50 }
51 }
52}
53
54#[derive(Clone, Debug, syn_derive::ToTokens)]
56pub enum NodeName {
57 Path(ExprPath),
60
61 Punctuated(Punctuated<NodeNameFragment, Punct>),
80
81 Block(Block),
83}
84
85impl NodeName {
86 pub fn is_block(&self) -> bool {
91 matches!(self, Self::Block(_))
92 }
93
94 pub fn is_dashed(&self) -> bool {
99 match self {
100 Self::Punctuated(p) => {
101 let p = p.pairs().next().unwrap();
102 p.punct().unwrap().as_char() == '-'
103 }
104 _ => false,
105 }
106 }
107
108 pub fn is_wildcard(&self) -> bool {
113 match self {
114 Self::Path(e) => {
115 if e.path.segments.len() != 1 {
116 return false;
117 }
118 let Some(last_ident) = e.path.segments.last() else {
119 return false;
120 };
121 last_ident.ident == "_"
122 }
123 _ => false,
124 }
125 }
126
127 pub(crate) fn node_name_punctuated_ident<T: Parse, F: Peek, X: From<Ident>>(
135 input: ParseStream,
136 punct: F,
137 ) -> syn::Result<Punctuated<X, T>> {
138 let fork = &input.fork();
139 let mut segments = Punctuated::<X, T>::new();
140
141 while !fork.is_empty() && fork.peek(Ident::peek_any) {
142 let ident = Ident::parse_any(fork)?;
143 segments.push_value(ident.clone().into());
144
145 if fork.peek(punct) {
146 segments.push_punct(fork.parse()?);
147 } else {
148 break;
149 }
150 }
151
152 if segments.len() > 1 {
153 input.advance_to(fork);
154 Ok(segments)
155 } else {
156 Err(fork.error("expected punctuated node name"))
157 }
158 }
159
160 pub(crate) fn node_name_punctuated_ident_with_two_alternate<
163 T: Parse,
164 F: Peek,
165 G: Peek,
166 H: Peek,
167 X: From<NodeNameFragment>,
168 >(
169 input: ParseStream,
170 punct: F,
171 alternate_punct: G,
172 alternate_punct2: H,
173 ) -> syn::Result<Punctuated<X, T>> {
174 let fork = &input.fork();
175 let mut segments = Punctuated::<X, T>::new();
176
177 while !fork.is_empty() {
178 let ident = NodeNameFragment::parse(fork)?;
179 segments.push_value(ident.clone().into());
180
181 if fork.peek(punct) || fork.peek(alternate_punct) || fork.peek(alternate_punct2) {
182 segments.push_punct(fork.parse()?);
183 } else {
184 break;
185 }
186 }
187
188 if segments.len() > 1 {
189 input.advance_to(fork);
190 Ok(segments)
191 } else {
192 Err(fork.error("expected punctuated node name"))
193 }
194 }
195}
196
197impl TryFrom<&NodeName> for Block {
198 type Error = Error;
199
200 fn try_from(node: &NodeName) -> Result<Self, Self::Error> {
201 match node {
202 NodeName::Block(b) => Ok(b.to_owned()),
203 _ => Err(Error::TryFrom(
204 "NodeName does not match NodeName::Block(Expr::Block(_))".into(),
205 )),
206 }
207 }
208}
209
210impl PartialEq for NodeName {
211 fn eq(&self, other: &NodeName) -> bool {
212 match self {
213 Self::Path(this) => match other {
214 Self::Path(other) => this == other,
215 _ => false,
216 },
217 Self::Punctuated(this) => match other {
219 Self::Punctuated(other) => {
220 this.pairs()
221 .zip(other.pairs())
222 .all(|(this, other)| match (this, other) {
223 (
224 Pair::Punctuated(this_ident, this_punct),
225 Pair::Punctuated(other_ident, other_punct),
226 ) => {
227 this_ident == other_ident
228 && this_punct.as_char() == other_punct.as_char()
229 }
230 (Pair::End(this), Pair::End(other)) => this == other,
231 _ => false,
232 })
233 }
234 _ => false,
235 },
236 Self::Block(this) => match other {
237 Self::Block(other) => this == other,
238 _ => false,
239 },
240 }
241 }
242}
243
244impl fmt::Display for NodeName {
245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246 write!(
247 f,
248 "{}",
249 match self {
250 NodeName::Path(expr) => path_to_string(expr),
251 NodeName::Punctuated(name) => {
252 name.pairs()
253 .flat_map(|pair| match pair {
254 Pair::Punctuated(ident, punct) => {
255 [ident.to_string(), punct.to_string()]
256 }
257 Pair::End(ident) => [ident.to_string(), "".to_string()],
258 })
259 .collect::<String>()
260 }
261 NodeName::Block(_) => String::from("{}"),
262 }
263 )
264 }
265}
266
267impl Parse for NodeName {
268 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
269 if input.peek(LitInt) {
270 Err(syn::Error::new(
271 input.span(),
272 "Name must start with latin character",
273 ))
274 } else if input.peek2(PathSep) {
275 NodeName::node_name_punctuated_ident::<PathSep, fn(_) -> PathSep, PathSegment>(
276 input, PathSep,
277 )
278 .map(|segments| {
279 NodeName::Path(ExprPath {
280 attrs: vec![],
281 qself: None,
282 path: Path {
283 leading_colon: None,
284 segments,
285 },
286 })
287 })
288 } else if input.peek2(Colon) || input.peek2(Dash) || input.peek2(Dot) {
289 NodeName::node_name_punctuated_ident_with_two_alternate::<
290 Punct,
291 fn(_) -> Colon,
292 fn(_) -> Dash,
293 fn(_) -> Dot,
294 NodeNameFragment,
295 >(input, Colon, Dash, Dot)
296 .map(NodeName::Punctuated)
297 } else if input.peek(Brace) {
298 let fork = &input.fork();
299 let value = block_expr(fork)?;
300 input.advance_to(fork);
301 Ok(NodeName::Block(value))
302 } else if input.peek(Ident::peek_any) {
303 let mut segments = Punctuated::new();
304 let ident = Ident::parse_any(input)?;
305 segments.push_value(PathSegment::from(ident));
306 Ok(NodeName::Path(ExprPath {
307 attrs: vec![],
308 qself: None,
309 path: Path {
310 leading_colon: None,
311 segments,
312 },
313 }))
314 } else {
315 Err(input.error("invalid tag name or attribute key"))
316 }
317 }
318}