Skip to main content

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