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