From c9f0758de962c17f9fec197947b1fbdb17b463d2 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 4 Nov 2022 15:30:36 -0400 Subject: [PATCH] Replace Colon and Dash with a merged variant --- src/node.rs | 63 ++++++++++++++++++++++++++++++--------------------- src/parser.rs | 43 +++++++++++++++++++++++++++++------ tests/test.rs | 14 ++++++++++++ 3 files changed, 87 insertions(+), 33 deletions(-) diff --git a/src/node.rs b/src/node.rs index 4bf6288..e9955ff 100644 --- a/src/node.rs +++ b/src/node.rs @@ -2,11 +2,14 @@ use std::{convert::TryFrom, fmt, ops::Deref}; -use proc_macro2::TokenStream; +use proc_macro2::{Punct, TokenStream}; use quote::ToTokens; -use syn::{punctuated::Punctuated, token::Colon, Expr, ExprBlock, ExprLit, ExprPath, Ident, Lit}; +use syn::{ + punctuated::{Pair, Punctuated}, + Expr, ExprBlock, ExprLit, ExprPath, Ident, Lit, +}; -use crate::{punctuation::Dash, Error}; +use crate::Error; /// Node types. #[derive(Debug, PartialEq, Eq)] @@ -226,11 +229,9 @@ pub enum NodeName { /// be separated by double colons, e.g. ``. Path(ExprPath), - /// Name separated by dashes, e.g. `
`. - Dash(Punctuated), - - /// Name separated by colons, e.g. `
`. - Colon(Punctuated), + /// Name separated by punctuation, e.g. `
` or `
`. + Punctuated(Punctuated), /// Arbitrary rust code in braced `{}` blocks. Block(Expr), @@ -256,12 +257,23 @@ impl PartialEq for NodeName { Self::Path(other) => this == other, _ => false, }, - Self::Dash(this) => match other { - Self::Dash(other) => this == other, - _ => false, - }, - Self::Colon(this) => match other { - Self::Colon(other) => this == other, + // can't be derived automatically because `Punct` doesn't impl `PartialEq` + Self::Punctuated(this) => match other { + Self::Punctuated(other) => { + this.pairs() + .zip(other.pairs()) + .all(|(this, other)| match (this, other) { + ( + Pair::Punctuated(this_ident, this_punct), + Pair::Punctuated(other_ident, other_punct), + ) => { + this_ident == other_ident + && this_punct.as_char() == other_punct.as_char() + } + (Pair::End(this), Pair::End(other)) => this == other, + _ => false, + }) + } _ => false, }, Self::Block(this) => match other { @@ -276,8 +288,7 @@ impl ToTokens for NodeName { fn to_tokens(&self, tokens: &mut TokenStream) { match self { NodeName::Path(name) => name.to_tokens(tokens), - NodeName::Dash(name) => name.to_tokens(tokens), - NodeName::Colon(name) => name.to_tokens(tokens), + NodeName::Punctuated(name) => name.to_tokens(tokens), NodeName::Block(name) => name.to_tokens(tokens), } } @@ -290,16 +301,16 @@ impl fmt::Display for NodeName { "{}", match self { NodeName::Path(expr) => path_to_string(expr), - NodeName::Dash(name) => name - .iter() - .map(|ident| ident.to_string()) - .collect::>() - .join("-"), - NodeName::Colon(name) => name - .iter() - .map(|ident| ident.to_string()) - .collect::>() - .join(":"), + NodeName::Punctuated(name) => { + name.pairs() + .flat_map(|pair| match pair { + Pair::Punctuated(ident, punct) => { + [ident.to_string(), punct.to_string()] + } + Pair::End(ident) => [ident.to_string(), "".to_string()], + }) + .collect::() + } NodeName::Block(_) => String::from("{}"), } ) diff --git a/src/parser.rs b/src/parser.rs index d41ca7d..0893f93 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,7 +2,7 @@ use std::vec; -use proc_macro2::{Span, TokenStream, TokenTree}; +use proc_macro2::{Punct, Span, TokenStream, TokenTree}; use syn::{ braced, ext::IdentExt, @@ -424,12 +424,11 @@ impl Parser { }, }) }) - } else if input.peek2(Colon) { - self.node_name_punctuated_ident:: Colon, Ident>(input, Colon) - .map(NodeName::Colon) - } else if input.peek2(Dash) { - self.node_name_punctuated_ident:: Dash, Ident>(input, Dash) - .map(NodeName::Dash) + } else if input.peek2(Colon) || input.peek2(Dash) { + self.node_name_punctuated_ident_with_alternate:: Colon, fn(_) -> Dash, Ident>( + input, Colon, Dash, + ) + .map(NodeName::Punctuated) } else if input.peek(Brace) { let fork = &input.fork(); let value = self.block_expr(fork)?; @@ -485,4 +484,34 @@ impl Parser { Err(fork.error("expected punctuated node name")) } } + + /// Parse the stream as punctuated idents, with two possible punctuations + /// available + fn node_name_punctuated_ident_with_alternate>( + &self, + input: ParseStream, + punct: F, + alternate_punct: G, + ) -> Result> { + let fork = &input.fork(); + let mut segments = Punctuated::::new(); + + while !fork.is_empty() && fork.peek(Ident::peek_any) { + let ident = Ident::parse_any(fork)?; + segments.push_value(ident.clone().into()); + + if fork.peek(punct) || fork.peek(alternate_punct) { + segments.push_punct(fork.parse()?); + } else { + break; + } + } + + if segments.len() > 1 { + input.advance_to(fork); + Ok(segments) + } else { + Err(fork.error("expected punctuated node name")) + } + } } diff --git a/tests/test.rs b/tests/test.rs index 31e843a..1051716 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -170,6 +170,20 @@ fn test_coloned_attribute_name() -> Result<()> { Ok(()) } +#[test] +fn test_mixed_colon_and_dash_attribute_name() -> Result<()> { + let tokens = quote! { +
+ }; + + let nodes = parse2(tokens)?; + let attribute = get_element_attribute(&nodes, 0, 0); + + assert_eq!(attribute.key.to_string(), "on:ce-click"); + + Ok(()) +} + #[test] fn test_block_as_attribute() -> Result<()> { let tokens = quote! {