use super::prelude::*;
use css_lexer::{AssociatedWhitespaceRules, Kind, KindSet, SourceCursor};
pub trait SemanticEq {
fn semantic_eq(&self, other: &Self, source_text: &str) -> bool;
}
impl SemanticEq for Cursor {
fn semantic_eq(&self, other: &Self, source_text: &str) -> bool {
let kind = self.token().kind();
if kind != other.token().kind() {
return false;
}
if KindSet::NAMED.contains(kind) {
let this = SourceCursor::from(*self, self.str_slice(source_text));
let other = SourceCursor::from(*other, other.str_slice(source_text));
return this.semantic_eq(&other, source_text);
}
self.token().with_associated_whitespace(AssociatedWhitespaceRules::NONE)
== other.token().with_associated_whitespace(AssociatedWhitespaceRules::NONE)
}
}
impl SemanticEq for SourceCursor<'_> {
fn semantic_eq(&self, other: &Self, _source_text: &str) -> bool {
let token = self.token();
let other_token = other.token();
let kind = token.kind();
if kind != other_token.kind() {
return false;
}
match kind {
Kind::Ident | Kind::Function | Kind::AtKeyword => {
token.is_dashed_ident() == other_token.is_dashed_ident()
&& match (token.atom_bits(), other_token.atom_bits()) {
(0, 0) => self.eq_parsed_ignore_ascii_case(other),
(a, b) => a == b,
}
}
Kind::Dimension => {
token.value() == other_token.value()
&& match (token.atom_bits(), other_token.atom_bits()) {
(0, 0) => self.eq_parsed_ignore_ascii_case(other),
(a, b) => {
a == b && token.len() - token.leading_len() == other_token.len() - other_token.leading_len()
}
}
}
Kind::String | Kind::Url | Kind::Hash => self.eq_parsed(other),
Kind::UnicodeRange => {
token.unicode_range_start() == other_token.unicode_range_start()
&& token.unicode_range_end() == other_token.unicode_range_end()
}
_ if KindSet::NAMED.contains(kind) => self.source() == other.source(),
_ => {
self.token().with_associated_whitespace(AssociatedWhitespaceRules::NONE)
== other.token().with_associated_whitespace(AssociatedWhitespaceRules::NONE)
}
}
}
}
impl<T> SemanticEq for Option<T>
where
T: SemanticEq,
{
fn semantic_eq(&self, s: &Self, source_text: &str) -> bool {
match (self, s) {
(Some(a), Some(b)) => a.semantic_eq(b, source_text),
(None, None) => true,
(_, _) => false,
}
}
}
impl<'a, T, A: Allocator> SemanticEq for Vec<'a, T, A>
where
T: SemanticEq,
{
fn semantic_eq(&self, s: &Self, source_text: &str) -> bool {
if self.len() != s.len() {
return false;
}
for i in 0..self.len() {
if !self[i].semantic_eq(&s[i], source_text) {
return false;
}
}
true
}
}
macro_rules! impl_tuple {
($($T:ident [ $A:ident, $B:ident ]),+) => {
impl<$($T),*> SemanticEq for ($($T),*)
where
$($T: SemanticEq,)*
{
fn semantic_eq(&self, o: &Self, source_text: &str) -> bool {
let ($($A),*) = self;
let ($($B),*) = o;
$($A.semantic_eq(&$B, source_text))&&*
}
}
};
}
impl_tuple!(A[sa,oa], B[sb,ob]);
impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc]);
impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od]);
impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe]);
impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of]);
impl_tuple!(A[sa,oa], B[sb,ob], C[sc,oc], D[sd,od], E[se,oe], F[sf,of], G[sg,og]);
impl_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]);
impl_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]);
impl_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]);
impl_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]);
impl_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]);
#[cfg(test)]
mod tests {
use super::*;
use crate::Arena;
use crate::{ComponentValues, Parse, Parser, SimpleBlock, T, ToCursors, assert_semantic_eq, assert_semantic_ne};
use css_lexer::EmptyAtomSet;
fn parse<'a, T: Parse<'a> + ToCursors>(alloc: &'a Arena, source: &'a str) -> T {
let lexer = css_lexer::Lexer::new(&EmptyAtomSet::ATOMS, source);
let mut parser = Parser::new(alloc, source, lexer);
let result = parser.parse_entirely::<T>();
result.output.unwrap()
}
#[test]
fn test_cursor_semantic_eq_ignores_offset() {
let token = css_lexer::Token::COMMA;
let cursor1 = Cursor::new(css_lexer::SourceOffset(0), token);
let cursor2 = Cursor::new(css_lexer::SourceOffset(100), token);
assert!(cursor1.semantic_eq(&cursor2, ""));
assert_ne!(cursor1, cursor2);
}
#[test]
fn test_cursor_semantic_eq_ignores_associated_whitespace_for_all_delim_like_kinds() {
for token in [
css_lexer::Token::COLON,
css_lexer::Token::SEMICOLON,
css_lexer::Token::COMMA,
css_lexer::Token::LEFT_PAREN,
css_lexer::Token::RIGHT_PAREN,
css_lexer::Token::LEFT_CURLY,
css_lexer::Token::RIGHT_CURLY,
css_lexer::Token::LEFT_SQUARE,
css_lexer::Token::RIGHT_SQUARE,
] {
let plain = Cursor::new(css_lexer::SourceOffset(0), token);
let with_whitespace_rule = Cursor::new(
css_lexer::SourceOffset(0),
token.with_associated_whitespace(css_lexer::AssociatedWhitespaceRules::EnforceBefore),
);
assert!(
plain.semantic_eq(&with_whitespace_rule, ""),
"{:?} should be semantic_eq regardless of associated whitespace",
token.kind()
);
}
}
fn pair<'a>(alloc: &'a Arena, source: &'a str) -> (ComponentValues<'a>, ComponentValues<'a>) {
let (first, second) = parse::<(SimpleBlock, SimpleBlock)>(alloc, source);
(first.values, second.values)
}
#[test]
fn test_component_values_ignores_whitespace() {
let alloc = Arena::new();
let source = "(1px solid red)(1px solid red)";
let (first, second) = pair(&alloc, source);
assert!(first.semantic_eq(&second, source));
}
#[test]
fn test_component_values_different_values() {
let alloc = Arena::new();
let source = "(1px solid red)(2px solid red)";
let (first, second) = pair(&alloc, source);
assert!(!first.semantic_eq(&second, source));
}
#[test]
fn test_idents_of_equal_length_are_told_apart() {
let alloc = Arena::new();
let source = "(foo)(bar)";
let (first, second) = pair(&alloc, source);
assert!(!first.semantic_eq(&second, source));
let source = "(foo)(foo)";
let (first, second) = pair(&alloc, source);
assert!(first.semantic_eq(&second, source));
}
#[test]
fn test_strings_of_equal_length_are_told_apart() {
let alloc = Arena::new();
let source = "(\"i\")(\"j\")";
let (first, second) = pair(&alloc, source);
assert!(!first.semantic_eq(&second, source));
}
#[test]
fn test_ident_spellings_are_equal() {
let alloc = Arena::new();
let source = "(Foo)(foo)";
let (first, second) = pair(&alloc, source);
assert!(first.semantic_eq(&second, source));
let source = "(b\\61r)(bar)";
let (first, second) = pair(&alloc, source);
assert!(first.semantic_eq(&second, source));
}
#[test]
fn test_string_spellings() {
let alloc = Arena::new();
let source = "(\"A\")(\"a\")";
let (first, second) = pair(&alloc, source);
assert!(!first.semantic_eq(&second, source));
let source = "(\"a\")('a')";
let (first, second) = pair(&alloc, source);
assert!(first.semantic_eq(&second, source));
let source = "(\"\\61\")(\"a\")";
let (first, second) = pair(&alloc, source);
assert!(first.semantic_eq(&second, source));
}
#[test]
fn test_hashes_are_case_sensitive() {
let alloc = Arena::new();
let source = "(#Foo)(#foo)";
let (first, second) = pair(&alloc, source);
assert!(!first.semantic_eq(&second, source));
}
#[test]
fn test_dimension_spellings_are_equal() {
let alloc = Arena::new();
let source = "(1.0Px)(1px)";
let (first, second) = pair(&alloc, source);
assert!(first.semantic_eq(&second, source));
let source = "(1px)(2px)";
let (first, second) = pair(&alloc, source);
assert!(!first.semantic_eq(&second, source));
}
#[derive(Debug, Default, derive_atom_set::AtomSet, Copy, Clone, PartialEq)]
pub enum TestAtomSet {
#[default]
_None,
Foo,
Bar,
Px,
}
impl TestAtomSet {
const ATOMS: TestAtomSet = TestAtomSet::_None;
}
#[test]
fn test_ident_atoms() {
assert_semantic_eq!(TestAtomSet::ATOMS, T![Ident], "Foo", "foo");
assert_semantic_ne!(TestAtomSet::ATOMS, T![Ident], "foo", "bar");
assert_semantic_ne!(TestAtomSet::ATOMS, T![Ident], "--foo", "foo");
}
#[test]
fn test_dimension_unit_atoms() {
assert_semantic_eq!(TestAtomSet::ATOMS, T![Dimension], "1.0Px", "1px");
assert_semantic_ne!(TestAtomSet::ATOMS, T![Dimension], "1px", "2px");
assert_semantic_ne!(TestAtomSet::ATOMS, T![Dimension], "1--px", "1px");
}
}