css_ast/selector/
nth.rs

1use crate::{CSSInt, CssAtomSet, CssDiagnostic};
2use css_parse::{
3	Cursor, CursorSink, Diagnostic, Kind, KindSet, Parse, Parser, Peek, Result as ParserResult, SemanticEq, Span, T,
4	ToCursors, ToSpan,
5};
6
7#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
9#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
10pub enum Nth {
11	Odd(T![Ident]),
12	Even(T![Ident]),
13	Integer(CSSInt),
14	Anb(i32, i32, [Cursor; 4]),
15}
16
17impl<'a> Peek<'a> for Nth {
18	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
19	where
20		I: Iterator<Item = Cursor> + Clone,
21	{
22		<T![Number]>::peek(p, c) || <T![Ident]>::peek(p, c)
23	}
24}
25
26impl<'a> Parse<'a> for Nth {
27	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
28	where
29		I: Iterator<Item = Cursor> + Clone,
30	{
31		if p.peek::<T![Number]>() {
32			let number = p.parse::<CSSInt>()?;
33			return Ok(Self::Integer(number));
34		} else if p.peek::<T![Ident]>() {
35			let peek_cursor = p.peek_n(1);
36			let atom = p.to_atom::<CssAtomSet>(peek_cursor);
37			if atom == CssAtomSet::Odd {
38				let ident = p.parse::<T![Ident]>()?;
39				return Ok(Self::Odd(ident));
40			} else if atom == CssAtomSet::Even {
41				let ident = p.parse::<T![Ident]>()?;
42				return Ok(Self::Even(ident));
43			}
44		}
45
46		let mut c = p.next();
47
48		let a;
49		let mut b_sign = 0;
50		let mut cursors = [c, Cursor::EMPTY, Cursor::EMPTY, Cursor::EMPTY];
51
52		if c == '+' {
53			let skip = p.set_skip(KindSet::NONE);
54			c = p.next();
55			p.set_skip(skip);
56			debug_assert!(cursors[1] == Cursor::EMPTY);
57			cursors[1] = c;
58		}
59		if !matches!(c.token().kind(), Kind::Number | Kind::Dimension | Kind::Ident) {
60			Err(Diagnostic::new(c, Diagnostic::unexpected))?
61		}
62		if c.token().is_float() {
63			Err(Diagnostic::new(c, Diagnostic::expected_int))?
64		}
65
66		if p.equals_atom(c, &CssAtomSet::_NDash) {
67			b_sign = -1;
68			a = if c.token().is_int() { c.token().value() as i32 } else { 1 };
69		} else {
70			let source_cursor = p.to_source_cursor(c);
71			let anb = source_cursor.parse(p.bump());
72			let mut chars = anb.chars();
73			let mut char = chars.next();
74			a = if c.token().is_int() {
75				c.token().value() as i32
76			} else if char == Some('-') {
77				char = chars.next();
78				-1
79			} else {
80				1
81			};
82			if !matches!(char, Some('n') | Some('N')) {
83				Err(Diagnostic::new(c, Diagnostic::unexpected))?
84			}
85			if let Ok(b) = chars.as_str().parse::<i32>() {
86				return Ok(Self::Anb(a, b, cursors));
87			} else if !chars.as_str().is_empty() {
88				Err(Diagnostic::new(c, Diagnostic::unexpected))?
89			}
90		}
91
92		if b_sign == 0 {
93			if p.peek::<T![+]>() {
94				b_sign = 1;
95				c = p.parse::<T![+]>()?.into();
96				debug_assert!(cursors[2] == Cursor::EMPTY);
97				cursors[2] = c;
98			} else if p.peek::<T![-]>() {
99				b_sign = -1;
100				c = p.parse::<T![-]>()?.into();
101				debug_assert!(cursors[2] == Cursor::EMPTY);
102				cursors[2] = c;
103			}
104		}
105
106		let b = if p.peek::<T![Number]>() {
107			c = p.parse::<T![Number]>()?.into();
108			debug_assert!(cursors[3] == Cursor::EMPTY);
109			cursors[3] = c;
110			if c.token().is_float() {
111				Err(Diagnostic::new(c, Diagnostic::expected_int))?
112			}
113			if c.token().has_sign() && b_sign != 0 {
114				Err(Diagnostic::new(c, Diagnostic::expected_unsigned))?
115			}
116			if b_sign == 0 {
117				b_sign = 1;
118			}
119			let i = c.token().value();
120			(i.abs() as i32) * b_sign
121		} else {
122			0
123		};
124		Ok(Self::Anb(a, b, cursors))
125	}
126}
127
128impl ToCursors for Nth {
129	fn to_cursors(&self, s: &mut impl CursorSink) {
130		match self {
131			Self::Odd(c) => ToCursors::to_cursors(c, s),
132			Self::Even(c) => ToCursors::to_cursors(c, s),
133			Self::Integer(c) => ToCursors::to_cursors(c, s),
134			Self::Anb(_, _, cursors) => {
135				for c in cursors {
136					if *c != Cursor::EMPTY {
137						s.append(*c);
138					}
139				}
140			}
141		}
142	}
143}
144
145impl SemanticEq for Nth {
146	fn semantic_eq(&self, other: &Self) -> bool {
147		match (self, other) {
148			(Self::Odd(a), Self::Odd(b)) => a.semantic_eq(b),
149			(Self::Even(a), Self::Even(b)) => a.semantic_eq(b),
150			(Self::Integer(a), Self::Integer(b)) => a.semantic_eq(b),
151			(Self::Anb(a1, b1, _), Self::Anb(a2, b2, _)) => a1 == a2 && b1 == b2,
152			_ => false,
153		}
154	}
155}
156
157impl ToSpan for Nth {
158	fn to_span(&self) -> Span {
159		match self {
160			Nth::Odd(c) => c.to_span(),
161			Nth::Even(c) => c.to_span(),
162			Nth::Integer(c) => c.to_span(),
163			Nth::Anb(_, _, cursors) => {
164				let mut span = Span::ZERO;
165				for c in cursors {
166					if *c != Cursor::EMPTY {
167						span = span + (*c).into()
168					}
169				}
170				span
171			}
172		}
173	}
174}
175
176#[cfg(test)]
177mod tests {
178	use super::*;
179	use crate::CssAtomSet;
180	use css_parse::{assert_parse, assert_parse_error};
181
182	#[test]
183	fn size_test() {
184		assert_eq!(std::mem::size_of::<Nth>(), 60);
185	}
186
187	#[test]
188	fn test_writes() {
189		assert_parse!(CssAtomSet::ATOMS, Nth, "odd");
190		assert_parse!(CssAtomSet::ATOMS, Nth, "ODD");
191		assert_parse!(CssAtomSet::ATOMS, Nth, "eVeN");
192		assert_parse!(CssAtomSet::ATOMS, Nth, "5");
193		assert_parse!(CssAtomSet::ATOMS, Nth, "n");
194		assert_parse!(CssAtomSet::ATOMS, Nth, "+n");
195		assert_parse!(CssAtomSet::ATOMS, Nth, "+N");
196		assert_parse!(CssAtomSet::ATOMS, Nth, "-n");
197		assert_parse!(CssAtomSet::ATOMS, Nth, "+5");
198		assert_parse!(CssAtomSet::ATOMS, Nth, "5n");
199		assert_parse!(CssAtomSet::ATOMS, Nth, "+5n");
200		assert_parse!(CssAtomSet::ATOMS, Nth, "-5n");
201		assert_parse!(CssAtomSet::ATOMS, Nth, "n-4");
202		assert_parse!(CssAtomSet::ATOMS, Nth, "-n-4");
203		assert_parse!(CssAtomSet::ATOMS, Nth, "+n-4");
204		assert_parse!(CssAtomSet::ATOMS, Nth, "+n+4");
205		assert_parse!(CssAtomSet::ATOMS, Nth, "+n-123456789");
206		assert_parse!(CssAtomSet::ATOMS, Nth, "2n");
207		assert_parse!(CssAtomSet::ATOMS, Nth, "2n+1");
208		assert_parse!(CssAtomSet::ATOMS, Nth, "+2n+1");
209		assert_parse!(CssAtomSet::ATOMS, Nth, "-2n+1");
210		assert_parse!(CssAtomSet::ATOMS, Nth, "-2n-1");
211		assert_parse!(CssAtomSet::ATOMS, Nth, "+2n-1");
212		assert_parse!(CssAtomSet::ATOMS, Nth, "3n+4");
213		assert_parse!(CssAtomSet::ATOMS, Nth, "3n+1");
214		assert_parse!(CssAtomSet::ATOMS, Nth, "n+ 3");
215		assert_parse!(CssAtomSet::ATOMS, Nth, "-n+3");
216
217		// Ported from https://github.com/web-platform-tests/wpt/blob/c1247636413abebe66ca11a2ca3476de771c99cb/css/selectors/parsing/parse-anplusb.html
218		assert_parse!(CssAtomSet::ATOMS, Nth, "1n+0");
219		assert_parse!(CssAtomSet::ATOMS, Nth, "n+0");
220		assert_parse!(CssAtomSet::ATOMS, Nth, "n");
221		assert_parse!(CssAtomSet::ATOMS, Nth, "-n+0");
222		assert_parse!(CssAtomSet::ATOMS, Nth, "-n");
223		assert_parse!(CssAtomSet::ATOMS, Nth, "N");
224		assert_parse!(CssAtomSet::ATOMS, Nth, "+n+3");
225		assert_parse!(CssAtomSet::ATOMS, Nth, "+n + 7 ");
226		assert_parse!(CssAtomSet::ATOMS, Nth, "N- 123");
227		assert_parse!(CssAtomSet::ATOMS, Nth, "n- 10");
228		assert_parse!(CssAtomSet::ATOMS, Nth, "-n\n- 1");
229		assert_parse!(CssAtomSet::ATOMS, Nth, " 23n\n\n+\n\n123 ");
230	}
231
232	#[test]
233	fn test_errors() {
234		assert_parse_error!(CssAtomSet::ATOMS, Nth, "3n + -6");
235		assert_parse_error!(CssAtomSet::ATOMS, Nth, "3 n");
236		assert_parse_error!(CssAtomSet::ATOMS, Nth, "+ 2n");
237		assert_parse_error!(CssAtomSet::ATOMS, Nth, "+ 2");
238
239		// Ported from https://github.com/web-platform-tests/wpt/blob/c1247636413abebe66ca11a2ca3476de771c99cb/css/selectors/parsing/parse-anplusb.html
240		assert_parse_error!(CssAtomSet::ATOMS, Nth, "n- 1 2");
241		assert_parse_error!(CssAtomSet::ATOMS, Nth, "n-b1");
242		assert_parse_error!(CssAtomSet::ATOMS, Nth, "n-+1");
243		assert_parse_error!(CssAtomSet::ATOMS, Nth, "n-1n");
244		assert_parse_error!(CssAtomSet::ATOMS, Nth, "-n -b1");
245		assert_parse_error!(CssAtomSet::ATOMS, Nth, "-1n- b1");
246		assert_parse_error!(CssAtomSet::ATOMS, Nth, "-n-13b1");
247		assert_parse_error!(CssAtomSet::ATOMS, Nth, "-n-+1");
248		assert_parse_error!(CssAtomSet::ATOMS, Nth, "-n+n");
249		assert_parse_error!(CssAtomSet::ATOMS, Nth, "+ 1n");
250		assert_parse_error!(CssAtomSet::ATOMS, Nth, "  n +12 3");
251		assert_parse_error!(CssAtomSet::ATOMS, Nth, "  12 n ");
252		assert_parse_error!(CssAtomSet::ATOMS, Nth, "+12n-0+1");
253		assert_parse_error!(CssAtomSet::ATOMS, Nth, "+12N -- 1");
254		assert_parse_error!(CssAtomSet::ATOMS, Nth, "+12 N ");
255		assert_parse_error!(CssAtomSet::ATOMS, Nth, "+ n + 7");
256	}
257
258	// #[cfg(feature = "serde")]
259	// #[test]
260	// fn test_serializes() {
261	// 	assert_json!(Nth, "odd", { "node": [2, 1], "start": 0, "end": 3 });
262	// 	assert_json!(Nth, "3n+1", { "node": [3, 1], "start": 0, "end": 4 });
263	// }
264}