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