1use css_lexer::{Cursor, Kind};
2
3pub trait SemanticEq {
10 fn semantic_eq(&self, other: &Self) -> bool;
12}
13
14impl SemanticEq for Cursor {
16 fn semantic_eq(&self, other: &Self) -> bool {
17 match self.token().kind() {
20 Kind::Delim => {
21 self.token().with_associated_whitespace(css_lexer::AssociatedWhitespaceRules::none())
22 == other.token().with_associated_whitespace(css_lexer::AssociatedWhitespaceRules::none())
23 }
24 _ => self.token() == other.token(),
25 }
26 }
27}
28
29impl<T> SemanticEq for Option<T>
30where
31 T: SemanticEq,
32{
33 fn semantic_eq(&self, s: &Self) -> bool {
34 match (self, s) {
35 (Some(a), Some(b)) => a.semantic_eq(b),
36 (None, None) => true,
37 (_, _) => false,
38 }
39 }
40}
41
42impl<'a, T> SemanticEq for bumpalo::collections::Vec<'a, T>
43where
44 T: SemanticEq,
45{
46 fn semantic_eq(&self, s: &Self) -> bool {
47 if self.len() != s.len() {
48 return false;
49 }
50 for i in 0..self.len() {
51 if !self[i].semantic_eq(&s[i]) {
52 return false;
53 }
54 }
55 true
56 }
57}
58
59macro_rules! impl_tuple {
60 ($($T:ident [ $A:ident, $B:ident ]),+) => {
61 impl<$($T),*> SemanticEq for ($($T),*)
62 where
63 $($T: SemanticEq,)*
64 {
65 fn semantic_eq(&self, o: &Self) -> bool {
66 let ($($A),*) = self;
67 let ($($B),*) = o;
68 $($A.semantic_eq(&$B))&&*
69 }
70 }
71 };
72}
73
74impl_tuple!(A[sa,oa], B[sb,ob]);
75impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc]);
76impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od]);
77impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe]);
78impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of]);
79impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og]);
80impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og], H[sh,oh]);
81impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og], H[sh,oh], I[si,oi]);
82impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og], H[sh,oh], I[si,oi], J[sj,oj]);
83impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og], H[sh,oh], I[si,oi], J[sj,oj], K[sk,ok]);
84impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og], H[sh,oh], I[si,oi], J[sj,oj], K[sk,ok], L[sl,ol]);
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::{ComponentValues, Parse, Parser, ToCursors};
90 use bumpalo::Bump;
91 use css_lexer::EmptyAtomSet;
92
93 fn parse<'a, T: Parse<'a> + ToCursors>(bump: &'a Bump, source: &'a str) -> T {
94 let lexer = css_lexer::Lexer::new(&EmptyAtomSet::ATOMS, source);
95 let mut parser = Parser::new(bump, source, lexer);
96 let result = parser.parse_entirely::<T>();
97 result.output.unwrap()
98 }
99
100 #[test]
101 fn test_cursor_semantic_eq_ignores_offset() {
102 let token = css_lexer::Token::COMMA;
103 let cursor1 = Cursor::new(css_lexer::SourceOffset(0), token);
104 let cursor2 = Cursor::new(css_lexer::SourceOffset(100), token);
105
106 assert!(cursor1.semantic_eq(&cursor2));
108
109 assert_ne!(cursor1, cursor2);
111 }
112
113 #[test]
114 fn test_component_values_ignores_whitespace() {
115 let source1 = "1px solid red";
116 let source2 = "1px solid red"; let bump = Bump::new();
119 let values1 = parse::<ComponentValues>(&bump, source1);
120 let values2 = parse::<ComponentValues>(&bump, source2);
121
122 assert!(values1.semantic_eq(&values2));
124 }
125
126 #[test]
127 fn test_component_values_different_values() {
128 let source1 = "1px solid red";
129 let source2 = "2px solid red";
130
131 let bump = Bump::new();
132 let values1 = parse::<ComponentValues>(&bump, source1);
133 let values2 = parse::<ComponentValues>(&bump, source2);
134
135 assert!(!values1.semantic_eq(&values2));
137 }
138}