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#[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, "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}