css_parse/macros/
optionals.rs

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