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