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