css_ast/
constraints.rs

1use crate::CssDiagnostic;
2#[cfg(feature = "visitable")]
3use crate::visit::{Visit, VisitMut, Visitable, VisitableMut};
4use css_parse::{Cursor, Diagnostic, Parse, Parser, Peek, Result, SemanticEq, ToCursors, ToNumberValue, ToSpan};
5use csskit_derives::{ToCursors as DeriveToCursors, ToSpan as DeriveToSpan};
6
7/// A non-negative value wrapper.
8///
9/// This wrapper validates that literal values are >= 0 at parse time.
10#[derive(DeriveToCursors, DeriveToSpan, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
12pub struct NonNegative<T>(pub T);
13
14impl<T: Into<Cursor>> From<NonNegative<T>> for Cursor {
15	fn from(value: NonNegative<T>) -> Self {
16		value.0.into()
17	}
18}
19
20#[cfg(feature = "visitable")]
21impl<T: Visitable> Visitable for NonNegative<T> {
22	fn accept<V: Visit>(&self, visitor: &mut V) {
23		self.0.accept(visitor);
24	}
25}
26
27#[cfg(feature = "visitable")]
28impl<T: VisitableMut> VisitableMut for NonNegative<T> {
29	fn accept_mut<V: VisitMut>(&mut self, visitor: &mut V) {
30		self.0.accept_mut(visitor);
31	}
32}
33
34impl<'a, T: Peek<'a>> Peek<'a> for NonNegative<T> {
35	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
36	where
37		I: Iterator<Item = Cursor> + Clone,
38	{
39		T::peek(p, c)
40	}
41}
42
43impl<'a, T: Parse<'a> + ToNumberValue> Parse<'a> for NonNegative<T> {
44	fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
45	where
46		I: Iterator<Item = Cursor> + Clone,
47	{
48		let cursor = p.peek_n(1);
49		let value = p.parse::<T>()?;
50		if let Some(num) = value.to_number_value()
51			&& num < 0.0
52		{
53			Err(Diagnostic::new(cursor, Diagnostic::non_negative))?;
54		}
55
56		Ok(Self(value))
57	}
58}
59
60impl<T: SemanticEq> SemanticEq for NonNegative<T> {
61	fn semantic_eq(&self, other: &Self) -> bool {
62		self.0.semantic_eq(&other.0)
63	}
64}
65
66impl<T> NonNegative<T> {
67	/// Returns a reference to the inner value.
68	pub fn inner(&self) -> &T {
69		&self.0
70	}
71
72	/// Consumes self and returns the inner value.
73	pub fn into_inner(self) -> T {
74		self.0
75	}
76}
77
78/// A positive value wrapper.
79///
80/// This wrapper validates that literal values are > 0 at parse time.
81#[derive(DeriveToCursors, DeriveToSpan, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
83pub struct Positive<T>(pub T);
84
85impl<T: Into<Cursor>> From<Positive<T>> for Cursor {
86	fn from(value: Positive<T>) -> Self {
87		value.0.into()
88	}
89}
90
91#[cfg(feature = "visitable")]
92impl<T: Visitable> Visitable for Positive<T> {
93	fn accept<V: Visit>(&self, visitor: &mut V) {
94		self.0.accept(visitor);
95	}
96}
97
98#[cfg(feature = "visitable")]
99impl<T: VisitableMut> VisitableMut for Positive<T> {
100	fn accept_mut<V: VisitMut>(&mut self, visitor: &mut V) {
101		self.0.accept_mut(visitor);
102	}
103}
104
105impl<'a, T: Peek<'a>> Peek<'a> for Positive<T> {
106	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
107	where
108		I: Iterator<Item = Cursor> + Clone,
109	{
110		T::peek(p, c)
111	}
112}
113
114impl<'a, T: Parse<'a> + ToNumberValue> Parse<'a> for Positive<T> {
115	fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
116	where
117		I: Iterator<Item = Cursor> + Clone,
118	{
119		let cursor = p.peek_n(1);
120		let value = p.parse::<T>()?;
121
122		if let Some(num) = value.to_number_value()
123			&& num <= 0.0
124		{
125			Err(Diagnostic::new(cursor, Diagnostic::positive))?;
126		}
127
128		Ok(Self(value))
129	}
130}
131
132impl<T: SemanticEq> SemanticEq for Positive<T> {
133	fn semantic_eq(&self, other: &Self) -> bool {
134		self.0.semantic_eq(&other.0)
135	}
136}
137
138impl<T> Positive<T> {
139	/// Returns a reference to the inner value.
140	pub fn inner(&self) -> &T {
141		&self.0
142	}
143
144	/// Consumes self and returns the inner value.
145	pub fn into_inner(self) -> T {
146		self.0
147	}
148}
149
150/// A non-zero value wrapper.
151///
152/// This wrapper validates that literal values are != 0 at parse time.
153#[derive(DeriveToCursors, DeriveToSpan, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
154#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
155pub struct NonZero<T>(pub T);
156
157impl<T: Into<Cursor>> From<NonZero<T>> for Cursor {
158	fn from(value: NonZero<T>) -> Self {
159		value.0.into()
160	}
161}
162
163#[cfg(feature = "visitable")]
164impl<T: Visitable> Visitable for NonZero<T> {
165	fn accept<V: Visit>(&self, visitor: &mut V) {
166		self.0.accept(visitor);
167	}
168}
169
170#[cfg(feature = "visitable")]
171impl<T: VisitableMut> VisitableMut for NonZero<T> {
172	fn accept_mut<V: VisitMut>(&mut self, visitor: &mut V) {
173		self.0.accept_mut(visitor);
174	}
175}
176
177impl<'a, T: Peek<'a>> Peek<'a> for NonZero<T> {
178	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
179	where
180		I: Iterator<Item = Cursor> + Clone,
181	{
182		T::peek(p, c)
183	}
184}
185
186impl<'a, T: Parse<'a> + ToNumberValue> Parse<'a> for NonZero<T> {
187	fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
188	where
189		I: Iterator<Item = Cursor> + Clone,
190	{
191		let cursor = p.peek_n(1);
192		let value = p.parse::<T>()?;
193
194		if let Some(num) = value.to_number_value()
195			&& num == 0.0
196		{
197			Err(Diagnostic::new(cursor, <Diagnostic as CssDiagnostic>::unexpected_zero))?;
198		}
199
200		Ok(Self(value))
201	}
202}
203
204impl<T: SemanticEq> SemanticEq for NonZero<T> {
205	fn semantic_eq(&self, other: &Self) -> bool {
206		self.0.semantic_eq(&other.0)
207	}
208}
209
210impl<T> NonZero<T> {
211	/// Returns a reference to the inner value.
212	pub fn inner(&self) -> &T {
213		&self.0
214	}
215
216	/// Consumes self and returns the inner value.
217	pub fn into_inner(self) -> T {
218		self.0
219	}
220}
221
222/// A range-constrained value wrapper using const generics.
223///
224/// This wrapper validates that literal values fall within [MIN, MAX] at parse time.
225#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
226#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
227pub struct Ranged<T, const MIN: i32, const MAX: i32>(pub T);
228
229impl<T: Into<Cursor>, const MIN: i32, const MAX: i32> From<Ranged<T, MIN, MAX>> for Cursor {
230	fn from(value: Ranged<T, MIN, MAX>) -> Self {
231		value.0.into()
232	}
233}
234
235impl<T: ToCursors, const MIN: i32, const MAX: i32> ToCursors for Ranged<T, MIN, MAX> {
236	fn to_cursors(&self, s: &mut impl css_parse::CursorSink) {
237		self.0.to_cursors(s);
238	}
239}
240
241impl<T: ToSpan, const MIN: i32, const MAX: i32> ToSpan for Ranged<T, MIN, MAX> {
242	fn to_span(&self) -> css_lexer::Span {
243		self.0.to_span()
244	}
245}
246
247#[cfg(feature = "visitable")]
248impl<T: Visitable, const MIN: i32, const MAX: i32> Visitable for Ranged<T, MIN, MAX> {
249	fn accept<V: Visit>(&self, visitor: &mut V) {
250		self.0.accept(visitor);
251	}
252}
253
254#[cfg(feature = "visitable")]
255impl<T: VisitableMut, const MIN: i32, const MAX: i32> VisitableMut for Ranged<T, MIN, MAX> {
256	fn accept_mut<V: VisitMut>(&mut self, visitor: &mut V) {
257		self.0.accept_mut(visitor);
258	}
259}
260
261impl<'a, T: Peek<'a>, const MIN: i32, const MAX: i32> Peek<'a> for Ranged<T, MIN, MAX> {
262	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
263	where
264		I: Iterator<Item = Cursor> + Clone,
265	{
266		if !T::peek(p, c) {
267			return false;
268		}
269		let kind = c.token().kind();
270		if kind == css_lexer::Kind::Number || kind == css_lexer::Kind::Dimension {
271			let num = c.token().value();
272			num >= MIN as f32 && num <= MAX as f32
273		} else {
274			true
275		}
276	}
277}
278
279impl<'a, T: Parse<'a> + ToNumberValue, const MIN: i32, const MAX: i32> Parse<'a> for Ranged<T, MIN, MAX> {
280	fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
281	where
282		I: Iterator<Item = Cursor> + Clone,
283	{
284		let cursor = p.peek_n(1);
285		let value = p.parse::<T>()?;
286
287		if let Some(num) = value.to_number_value()
288			&& (num < MIN as f32 || num > MAX as f32)
289		{
290			Err(Diagnostic::new(cursor, Diagnostic::number_out_of_bounds))?;
291		}
292
293		Ok(Self(value))
294	}
295}
296
297impl<T: SemanticEq, const MIN: i32, const MAX: i32> SemanticEq for Ranged<T, MIN, MAX> {
298	fn semantic_eq(&self, other: &Self) -> bool {
299		self.0.semantic_eq(&other.0)
300	}
301}
302
303impl<T: ToNumberValue, const MIN: i32, const MAX: i32> ToNumberValue for Ranged<T, MIN, MAX> {
304	fn to_number_value(&self) -> Option<f32> {
305		self.0.to_number_value()
306	}
307}
308
309impl<T, const MIN: i32, const MAX: i32> Ranged<T, MIN, MAX> {
310	/// Returns a reference to the inner value.
311	pub fn inner(&self) -> &T {
312		&self.0
313	}
314
315	/// Consumes self and returns the inner value.
316	pub fn into_inner(self) -> T {
317		self.0
318	}
319}
320
321/// An exact value wrapper using const generics.
322///
323/// This wrapper validates that literal values are exactly equal to the specified VALUE at parse time.
324#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
325#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
326pub struct Exact<T, const VALUE: i32>(pub T);
327
328impl<T: Into<Cursor>, const VALUE: i32> From<Exact<T, VALUE>> for Cursor {
329	fn from(value: Exact<T, VALUE>) -> Self {
330		value.0.into()
331	}
332}
333
334impl<T: ToCursors, const VALUE: i32> ToCursors for Exact<T, VALUE> {
335	fn to_cursors(&self, s: &mut impl css_parse::CursorSink) {
336		self.0.to_cursors(s);
337	}
338}
339
340impl<T: ToSpan, const VALUE: i32> ToSpan for Exact<T, VALUE> {
341	fn to_span(&self) -> css_lexer::Span {
342		self.0.to_span()
343	}
344}
345
346#[cfg(feature = "visitable")]
347impl<T: Visitable, const VALUE: i32> Visitable for Exact<T, VALUE> {
348	fn accept<V: Visit>(&self, visitor: &mut V) {
349		self.0.accept(visitor);
350	}
351}
352
353#[cfg(feature = "visitable")]
354impl<T: VisitableMut, const VALUE: i32> VisitableMut for Exact<T, VALUE> {
355	fn accept_mut<V: VisitMut>(&mut self, visitor: &mut V) {
356		self.0.accept_mut(visitor);
357	}
358}
359
360impl<'a, T: Peek<'a>, const VALUE: i32> Peek<'a> for Exact<T, VALUE> {
361	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
362	where
363		I: Iterator<Item = Cursor> + Clone,
364	{
365		if !T::peek(p, c) {
366			return false;
367		}
368		let kind = c.token().kind();
369		if kind == css_lexer::Kind::Number || kind == css_lexer::Kind::Dimension {
370			c.token().value() == VALUE as f32
371		} else {
372			true
373		}
374	}
375}
376
377impl<'a, T: Parse<'a> + ToNumberValue, const VALUE: i32> Parse<'a> for Exact<T, VALUE> {
378	fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
379	where
380		I: Iterator<Item = Cursor> + Clone,
381	{
382		let cursor = p.peek_n(1);
383		let value = p.parse::<T>()?;
384
385		if let Some(num) = value.to_number_value()
386			&& num != VALUE as f32
387		{
388			Err(Diagnostic::new(cursor, Diagnostic::number_out_of_bounds))?;
389		}
390
391		Ok(Self(value))
392	}
393}
394
395impl<T: SemanticEq, const VALUE: i32> SemanticEq for Exact<T, VALUE> {
396	fn semantic_eq(&self, other: &Self) -> bool {
397		self.0.semantic_eq(&other.0)
398	}
399}
400
401impl<T: ToNumberValue, const VALUE: i32> ToNumberValue for Exact<T, VALUE> {
402	fn to_number_value(&self) -> Option<f32> {
403		self.0.to_number_value()
404	}
405}
406
407impl<T, const VALUE: i32> Exact<T, VALUE> {
408	/// Returns a reference to the inner value.
409	pub fn inner(&self) -> &T {
410		&self.0
411	}
412
413	/// Consumes self and returns the inner value.
414	pub fn into_inner(self) -> T {
415		self.0
416	}
417}
418
419#[cfg(test)]
420mod tests {
421	use super::*;
422	use crate::CssAtomSet;
423	use css_parse::{T, assert_parse, assert_parse_error};
424
425	type ExactOne = Exact<T![Number], 1>;
426	type RangedZeroOne = Ranged<T![Number], 0, 1>;
427
428	#[test]
429	fn test_exact_accepts_correct_value() {
430		assert_parse!(CssAtomSet::ATOMS, ExactOne, "1");
431	}
432
433	#[test]
434	fn test_exact_rejects_wrong_value() {
435		assert_parse_error!(CssAtomSet::ATOMS, ExactOne, "2");
436	}
437
438	#[test]
439	fn test_ranged_accepts_within_range() {
440		assert_parse!(CssAtomSet::ATOMS, RangedZeroOne, "0.5");
441	}
442
443	#[test]
444	fn test_ranged_rejects_out_of_range() {
445		assert_parse_error!(CssAtomSet::ATOMS, RangedZeroOne, "1.5");
446	}
447
448	#[test]
449	fn test_non_negative_accepts_zero() {
450		assert_parse!(CssAtomSet::ATOMS, NonNegative<T![Number]>, "0");
451	}
452
453	#[test]
454	fn test_non_negative_rejects_negative() {
455		assert_parse_error!(CssAtomSet::ATOMS, NonNegative<T![Number]>, "-1");
456	}
457
458	#[test]
459	fn test_positive_accepts_positive() {
460		assert_parse!(CssAtomSet::ATOMS, Positive<T![Number]>, "1");
461	}
462
463	#[test]
464	fn test_positive_rejects_zero() {
465		assert_parse_error!(CssAtomSet::ATOMS, Positive<T![Number]>, "0");
466	}
467
468	#[test]
469	fn test_non_zero_accepts_positive() {
470		assert_parse!(CssAtomSet::ATOMS, NonZero<T![Number]>, "1");
471	}
472
473	#[test]
474	fn test_non_zero_accepts_negative() {
475		assert_parse!(CssAtomSet::ATOMS, NonZero<T![Number]>, "-1");
476	}
477
478	#[test]
479	fn test_non_zero_rejects_zero() {
480		assert_parse_error!(CssAtomSet::ATOMS, NonZero<T![Number]>, "0");
481	}
482}