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