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