1use crate::CssDiagnostic;
2use css_parse::{Cursor, Diagnostic, KindSet, Parse, Parser, Peek, Result, ToNumberValue};
3use csskit_derives::*;
4
5#[derive(Peek, IntoCursor, ToCursors, ToSpan, SemanticEq, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
10#[cfg_attr(feature = "visitable", derive(Visitable), visit(children))]
11#[derive(NodeWithMetadata)]
12pub struct NonNegative<T>(pub T);
13
14impl<'a, T: Parse<'a> + ToNumberValue> Parse<'a> for NonNegative<T> {
15 fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
16 where
17 I: Iterator<Item = Cursor> + Clone,
18 {
19 let cursor = p.peek_n(1);
20 let value = p.parse::<T>()?;
21 if let Some(num) = value.to_number_value()
22 && num < 0.0
23 {
24 Err(Diagnostic::new(cursor, Diagnostic::non_negative))?;
25 }
26
27 Ok(Self(value))
28 }
29}
30
31impl<T> NonNegative<T> {
32 pub fn inner(&self) -> &T {
34 &self.0
35 }
36
37 pub fn into_inner(self) -> T {
39 self.0
40 }
41}
42
43#[derive(IntoCursor, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
48#[cfg_attr(feature = "visitable", derive(Visitable), visit(children))]
49#[derive(NodeWithMetadata)]
50pub struct Positive<T>(pub T);
51
52impl<'a, T: Parse<'a> + ToNumberValue> Parse<'a> for Positive<T> {
53 fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
54 where
55 I: Iterator<Item = Cursor> + Clone,
56 {
57 let cursor = p.peek_n(1);
58 let value = p.parse::<T>()?;
59
60 if let Some(num) = value.to_number_value()
61 && num <= 0.0
62 {
63 Err(Diagnostic::new(cursor, Diagnostic::positive))?;
64 }
65
66 Ok(Self(value))
67 }
68}
69
70impl<T> Positive<T> {
71 pub fn inner(&self) -> &T {
73 &self.0
74 }
75
76 pub fn into_inner(self) -> T {
78 self.0
79 }
80}
81
82#[derive(IntoCursor, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
87#[cfg_attr(feature = "visitable", derive(Visitable), visit(children))]
88#[derive(NodeWithMetadata)]
89pub struct NonZero<T>(pub T);
90
91impl<'a, T: Parse<'a> + ToNumberValue> Parse<'a> for NonZero<T> {
92 fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
93 where
94 I: Iterator<Item = Cursor> + Clone,
95 {
96 let cursor = p.peek_n(1);
97 let value = p.parse::<T>()?;
98
99 if let Some(num) = value.to_number_value()
100 && num == 0.0
101 {
102 Err(Diagnostic::new(cursor, <Diagnostic as CssDiagnostic>::unexpected_zero))?;
103 }
104
105 Ok(Self(value))
106 }
107}
108
109impl<T> NonZero<T> {
110 pub fn inner(&self) -> &T {
112 &self.0
113 }
114
115 pub fn into_inner(self) -> T {
117 self.0
118 }
119}
120
121#[derive(ToSpan, IntoCursor, ToCursors, SemanticEq, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
126#[cfg_attr(feature = "visitable", derive(Visitable), visit(children))]
127#[derive(NodeWithMetadata)]
128pub struct Ranged<T, const MIN: i32, const MAX: i32>(pub T);
129
130impl<'a, T: Peek<'a>, const MIN: i32, const MAX: i32> Peek<'a> for Ranged<T, MIN, MAX> {
131 const PEEK_KINDSET: KindSet = T::PEEK_KINDSET;
132
133 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
134 where
135 I: Iterator<Item = Cursor> + Clone,
136 {
137 if !T::peek(p, c) {
138 return false;
139 }
140 let kind = c.token().kind();
141 if kind == css_lexer::Kind::Number || kind == css_lexer::Kind::Dimension {
142 let num = c.token().value();
143 num >= MIN as f32 && num <= MAX as f32
144 } else {
145 true
146 }
147 }
148}
149
150impl<'a, T: Parse<'a> + ToNumberValue, const MIN: i32, const MAX: i32> Parse<'a> for Ranged<T, MIN, MAX> {
151 fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
152 where
153 I: Iterator<Item = Cursor> + Clone,
154 {
155 let cursor = p.peek_n(1);
156 let value = p.parse::<T>()?;
157
158 if let Some(num) = value.to_number_value()
159 && (num < MIN as f32 || num > MAX as f32)
160 {
161 Err(Diagnostic::new(cursor, Diagnostic::number_out_of_bounds))?;
162 }
163
164 Ok(Self(value))
165 }
166}
167
168impl<T: ToNumberValue, const MIN: i32, const MAX: i32> ToNumberValue for Ranged<T, MIN, MAX> {
169 fn to_number_value(&self) -> Option<f32> {
170 self.0.to_number_value()
171 }
172}
173
174impl<T, const MIN: i32, const MAX: i32> Ranged<T, MIN, MAX> {
175 pub fn inner(&self) -> &T {
177 &self.0
178 }
179
180 pub fn into_inner(self) -> T {
182 self.0
183 }
184}
185
186#[derive(IntoCursor, ToCursors, ToSpan, SemanticEq, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
190#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
191#[cfg_attr(feature = "visitable", derive(Visitable), visit(children))]
192#[derive(NodeWithMetadata)]
193pub struct Exact<T, const VALUE: i32>(pub T);
194
195impl<'a, T: Peek<'a>, const VALUE: i32> Peek<'a> for Exact<T, VALUE> {
196 const PEEK_KINDSET: KindSet = T::PEEK_KINDSET;
197
198 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
199 where
200 I: Iterator<Item = Cursor> + Clone,
201 {
202 if !T::peek(p, c) {
203 return false;
204 }
205 let kind = c.token().kind();
206 if kind == css_lexer::Kind::Number || kind == css_lexer::Kind::Dimension {
207 c.token().value() == VALUE as f32
208 } else {
209 true
210 }
211 }
212}
213
214impl<'a, T: Parse<'a> + ToNumberValue, const VALUE: i32> Parse<'a> for Exact<T, VALUE> {
215 fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
216 where
217 I: Iterator<Item = Cursor> + Clone,
218 {
219 let cursor = p.peek_n(1);
220 let value = p.parse::<T>()?;
221
222 if let Some(num) = value.to_number_value()
223 && num != VALUE as f32
224 {
225 Err(Diagnostic::new(cursor, Diagnostic::number_out_of_bounds))?;
226 }
227
228 Ok(Self(value))
229 }
230}
231
232impl<T: ToNumberValue, const VALUE: i32> ToNumberValue for Exact<T, VALUE> {
233 fn to_number_value(&self) -> Option<f32> {
234 self.0.to_number_value()
235 }
236}
237
238impl<T, const VALUE: i32> Exact<T, VALUE> {
239 pub fn inner(&self) -> &T {
241 &self.0
242 }
243
244 pub fn into_inner(self) -> T {
246 self.0
247 }
248}
249
250#[derive(Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
257#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
258#[cfg_attr(feature = "visitable", derive(Visitable), visit(children))]
259#[derive(NodeWithMetadata)]
260pub struct NonEmpty<T>(pub T);
261
262impl<'a, T, Item> Parse<'a> for NonEmpty<T>
263where
264 T: Peek<'a> + Parse<'a> + std::ops::Deref<Target = [Item]>,
265{
266 fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
267 where
268 I: Iterator<Item = Cursor> + Clone,
269 {
270 let cursor = p.peek_n(1);
271 let value = p.parse::<T>()?;
272 if value.is_empty() {
273 Err(Diagnostic::new(cursor, <Diagnostic as CssDiagnostic>::empty_collection))?;
274 }
275 Ok(Self(value))
276 }
277}
278
279impl<T> NonEmpty<T> {
280 pub fn inner(&self) -> &T {
281 &self.0
282 }
283
284 pub fn into_inner(self) -> T {
285 self.0
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use crate::CssAtomSet;
293 use bumpalo::collections::Vec;
294 use css_parse::{T, assert_parse, assert_parse_error, assert_peek_false};
295
296 type ExactOne = Exact<T![Number], 1>;
297 type RangedZeroOne = Ranged<T![Number], 0, 1>;
298
299 #[test]
300 fn test_exact_accepts_correct_value() {
301 assert_parse!(CssAtomSet::ATOMS, ExactOne, "1");
302 }
303
304 #[test]
305 fn test_exact_rejects_wrong_value() {
306 assert_peek_false!(CssAtomSet::ATOMS, ExactOne, "2");
307 }
308
309 #[test]
310 fn test_ranged_accepts_within_range() {
311 assert_parse!(CssAtomSet::ATOMS, RangedZeroOne, "0.5");
312 }
313
314 #[test]
315 fn test_ranged_rejects_out_of_range() {
316 assert_peek_false!(CssAtomSet::ATOMS, RangedZeroOne, "1.5");
317 }
318
319 #[test]
320 fn test_non_negative_accepts_zero() {
321 assert_parse!(CssAtomSet::ATOMS, NonNegative<T![Number]>, "0");
322 }
323
324 #[test]
325 fn test_non_negative_rejects_negative() {
326 assert_parse_error!(CssAtomSet::ATOMS, NonNegative<T![Number]>, "-1");
327 }
328
329 #[test]
330 fn test_positive_accepts_positive() {
331 assert_parse!(CssAtomSet::ATOMS, Positive<T![Number]>, "1");
332 }
333
334 #[test]
335 fn test_positive_rejects_zero() {
336 assert_parse_error!(CssAtomSet::ATOMS, Positive<T![Number]>, "0");
337 }
338
339 #[test]
340 fn test_non_zero_accepts_positive() {
341 assert_parse!(CssAtomSet::ATOMS, NonZero<T![Number]>, "1");
342 }
343
344 #[test]
345 fn test_non_zero_accepts_negative() {
346 assert_parse!(CssAtomSet::ATOMS, NonZero<T![Number]>, "-1");
347 }
348
349 #[test]
350 fn test_non_zero_rejects_zero() {
351 assert_parse_error!(CssAtomSet::ATOMS, NonZero<T![Number]>, "0");
352 }
353
354 #[test]
355 fn test_non_empty_accepts_one() {
356 assert_parse!(CssAtomSet::ATOMS, NonEmpty<Vec<T![Ident]>>, "foo");
357 }
358
359 #[test]
360 fn test_non_empty_accepts_multiple() {
361 assert_parse!(CssAtomSet::ATOMS, NonEmpty<Vec<T![Ident]>>, "foo bar");
362 }
363
364 #[test]
365 fn test_non_empty_rejects_empty() {
366 assert_peek_false!(CssAtomSet::ATOMS, NonEmpty<Vec<T![Ident]>>, "");
367 }
368}