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 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 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 }