css_parse/macros/
optionals.rs

1use crate::{Cursor, CursorSink, Parse, Parser, Peek, Result as ParserResult, SemanticEq, Span, ToCursors, ToSpan};
2
3macro_rules! impl_optionals {
4	($($name:ident, ($($T:ident[ $A:ident, $B:ident ]),+))+) => {
5		$(
6			#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
7			#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
8			pub struct $name<$($T),+>($(pub Option<$T>),+);
9
10			impl<'a, $($T),+> Peek<'a> for $name<$($T),+>
11			where
12				$($T: Parse<'a> + Peek<'a>,)+
13			{
14				fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
15				where
16					I: Iterator<Item = crate::Cursor> + Clone,
17				{
18					$($T::peek(p, c) ||)+ false
19				}
20			}
21
22			impl<'a, $($T),+> Parse<'a> for $name<$($T),+>
23			where
24				$($T: Parse<'a> + Peek<'a>,)+
25			{
26				fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
27				where
28					I: Iterator<Item = crate::Cursor> + Clone,
29				{
30					let ($($A),+) = parse_optionals!(p, $($A:$T),+);
31					Ok(Self($($A),+))
32				}
33			}
34
35			impl<'a, $($T),+> ToCursors for $name<$($T),+>
36			where
37				$($T: ToCursors,)+
38			{
39				fn to_cursors(&self, s: &mut impl CursorSink) {
40					let $name($($A),+) = self;
41					$($A.to_cursors(s);)+
42			 }
43			}
44
45			impl<$($T),+> ToSpan for  $name<$($T),+>
46			where
47				$($T: ToSpan,)+
48			{
49				fn to_span(&self) -> Span {
50					let $name($($A),+) = self;
51					Span::DUMMY $(+$A.to_span())+
52				}
53			}
54
55			impl<$($T),+> SemanticEq for  $name<$($T),+>
56			where
57				$($T: SemanticEq,)+
58			{
59				fn semantic_eq(&self, o: &Self) -> bool {
60					let $name($($A),+) = self;
61					let $name($($B),+) = o;
62					$($A.semantic_eq($B))&&+
63				}
64			}
65
66			impl<$($T),+> From<$name<$($T),+>> for ($(Option<$T>),+)
67			{
68				fn from(value: $name<$($T),+>) -> Self {
69					let $name($($A),+) = value;
70					($($A),+)
71				}
72			}
73
74			impl<$($T),+> From<($(Option<$T>),+)> for $name<$($T),+>
75			{
76				fn from(value: ($(Option<$T>),+)) -> Self {
77					let ($($A),+) = value;
78					Self($($A),+)
79				}
80			}
81		)+
82	};
83}
84
85#[macro_export]
86macro_rules! parse_optionals {
87	($p: ident, $($name:ident: $T:ty),+) => {
88		{
89			$(let mut $name: Option<$T> = None;)+
90
91			while $($name.is_none())||+ {
92				$(
93					if $name.is_none() {
94							$name = $p.parse_if_peek::<$T>()?;
95							if $name.is_some() { continue; }
96					}
97				)+
98
99				break;
100			}
101
102			if $($name.is_none())&&+ {
103				Err($crate::Diagnostic::new($p.next(), $crate::Diagnostic::unexpected))?
104			}
105
106			(($($name),+))
107		 }
108	};
109}
110
111/// A helper type for parsing optional CSS grammar patterns where items can appear in any order
112/// but at most once each (the `||` combinator in CSS grammar).
113///
114/// # Example
115/// ```ignore
116/// // For CSS grammar: [ foo | <number> ]
117/// let (foo, num) = p.parse::<Optionals![Ident, Number]>()?;
118/// ```
119#[macro_export]
120macro_rules! Optionals {
121	($t:ty) => { compile_error!("Use Option<T> dummy"); };
122	($t:ty, $u:ty) => { $crate::Optionals2<$t, $u> };
123	($t:ty, $u:ty, $v:ty) => { $crate::Optionals3<$t, $u, $v> };
124	($t:ty, $u:ty, $v:ty, $w:ty) => { $crate::Optionals4<$t, $u, $v, $w> };
125	($t:ty, $u:ty, $v:ty, $w:ty, $x:ty) => { $crate::Optionals5<$t, $u, $v, $w, $x> };
126}
127
128impl_optionals! {
129	Optionals2, (A[sa, oa], B[sb, ob])
130	Optionals3, (A[sa, oa], B[sb, ob], C[sc, oc])
131	Optionals4, (A[sa, oa], B[sb, ob], C[sc, oc], D[sd, od])
132	Optionals5, (A[sa, oa], B[sb, ob], C[sc, oc], D[sd, od], E[se, oe])
133}
134
135#[cfg(test)]
136mod tests {
137
138	use super::*;
139	use crate::{EmptyAtomSet, test_helpers::*, token_macros::*};
140
141	type CaseA = Optionals![Number, Ident];
142	type CaseB = Optionals![Number, Ident, String];
143	type CaseC = Optionals![Number, Ident, String, Ident];
144	type CaseD = Optionals![Number, Ident, String, Ident, Dimension];
145
146	#[test]
147	fn size_test() {
148		assert_eq!(std::mem::size_of::<Optionals2<Ident, Number>>(), 32);
149	}
150
151	#[test]
152	fn test_writes() {
153		assert_parse!(EmptyAtomSet::ATOMS, CaseA, "123 foo", Optionals2(Some(_), Some(_)));
154		assert_parse!(EmptyAtomSet::ATOMS, CaseA, "foo 123", Optionals2(Some(_), Some(_)));
155		assert_parse!(EmptyAtomSet::ATOMS, CaseA, "123", Optionals2(Some(_), None));
156		assert_parse!(EmptyAtomSet::ATOMS, CaseA, "foo", Optionals2(None, Some(_)));
157
158		assert_parse!(EmptyAtomSet::ATOMS, CaseB, "123 foo 'bar'", Optionals3(Some(_), Some(_), Some(_)));
159		// assert_parse!(EmptyAtomSet::ATOMS, CaseB, "foo 'bar' 123", Optionals3(Some(_), Some(_), Some(_)));
160		assert_parse!(EmptyAtomSet::ATOMS, CaseB, "123", Optionals3(Some(_), None, None));
161		assert_parse!(EmptyAtomSet::ATOMS, CaseB, "'foo'", Optionals3(None, None, Some(_)));
162
163		assert_parse!(EmptyAtomSet::ATOMS, CaseC, "foo 123 bar 'bar'", Optionals4(Some(_), Some(_), Some(_), Some(_)));
164	}
165
166	#[test]
167	fn test_spans() {
168		assert_parse_span!(
169			EmptyAtomSet::ATOMS,
170			CaseA,
171			r#"
172			foo 123 bar
173			^^^^^^^
174		"#
175		);
176
177		assert_parse_span!(
178			EmptyAtomSet::ATOMS,
179			CaseA,
180			r#"
181			123 foo bar
182			^^^^^^^
183		"#
184		);
185
186		assert_parse_span!(
187			EmptyAtomSet::ATOMS,
188			CaseA,
189			r#"
190			123 'foo'
191			^^^
192		"#
193		);
194
195		assert_parse_span!(
196			EmptyAtomSet::ATOMS,
197			CaseD,
198			r#"
199			45px foo 123 'bar' 'baz'
200			^^^^^^^^^^^^^^^^^^
201		"#
202		);
203
204		assert_parse!(
205			EmptyAtomSet::ATOMS,
206			CaseD,
207			"foo 123 40px bar 'bar'",
208			Optionals5(Some(_), Some(_), Some(_), Some(_), Some(_))
209		);
210	}
211}