css_parse/macros/
optionals.rs

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