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