plist/macros.rs
1/// Constructs a `plist::Value` from a JSON-like literal.
2///
3/// ```
4/// # use plist::plist;
5/// #
6/// let value = plist!({
7/// "code": 200,
8/// "success": true,
9/// "payload": {
10/// "features": [
11/// "serde",
12/// ]
13/// }
14/// });
15/// ```
16///
17/// Variables or expressions can be interpolated into the literal. Any type
18/// interpolated into an array element or object value must implement the
19/// `Into<Value>` trait, while any type interpolated into a object key must
20/// implement `Into<String>`.
21///
22/// ```
23/// # use plist::plist;
24/// #
25/// let code = 200;
26/// let features = vec!["serde", "plist"];
27///
28/// let value = plist!({
29/// "code": code,
30/// "success": code == 200,
31/// "payload": {
32/// features[0]: features[1]
33/// }
34/// });
35/// ```
36///
37/// Trailing commas are allowed inside both arrays and objects.
38///
39/// ```
40/// # use plist::plist;
41/// #
42/// let value = plist!([
43/// "notice",
44/// "the",
45/// "trailing",
46/// "comma -->",
47/// ]);
48/// ```
49#[macro_export(local_inner_macros)]
50macro_rules! plist {
51 // Hide distracting implementation details from the generated rustdoc.
52 ($($plist:tt)+) => {
53 plist_internal!($($plist)+)
54 };
55}
56
57/// Constructs a `plist::Dictionary` from a JSON-like literal.
58///
59/// ```
60/// # use plist::plist_dict;
61/// #
62/// let value = plist_dict! {
63/// "code": 200,
64/// "success": true,
65/// "payload": {
66/// "features": [
67/// "serde",
68/// ]
69/// }
70/// };
71/// ```
72///
73/// Variables or expressions can be interpolated into the literal. Any type
74/// interpolated into an array element or object value must implement the
75/// `Into<Value>` trait, while any type interpolated into a object key must
76/// implement `Into<String>`.
77///
78/// ```
79/// # use plist::plist_dict;
80/// #
81/// let code = 200;
82/// let features = vec!["serde", "plist"];
83///
84/// let value = plist_dict! {
85/// "code": code,
86/// "success": code == 200,
87/// "payload": {
88/// features[0]: features[1]
89/// }
90/// };
91/// ```
92///
93/// Trailing commas are allowed inside both arrays and objects.
94///
95/// ```
96/// # use plist::plist_dict;
97/// #
98/// let value = plist_dict! {
99/// "notice": 0,
100/// "the": 1,
101/// "trailing": 2,
102/// "comma -->": 3,
103/// };
104/// ```
105#[macro_export(local_inner_macros)]
106macro_rules! plist_dict {
107 () => {
108 $crate::Dictionary::new()
109 };
110 // Allow outer curlies if provided
111 ({$($tt:tt)+}) => {
112 {
113 let mut object = $crate::Dictionary::new();
114 plist_internal!(@object object () ($($tt)+) ($($tt)+));
115 object
116 }
117 };
118 ($($tt:tt)+) => {
119 {
120 let mut object = $crate::Dictionary::new();
121 plist_internal!(@object object () ($($tt)+) ($($tt)+));
122 object
123 }
124 };
125}
126
127#[macro_export(local_inner_macros)]
128#[doc(hidden)]
129macro_rules! plist_internal {
130 //////////////////////////////////////////////////////////////////////////
131 // TT muncher for parsing the inside of an array [...]. Produces a vec![...]
132 // of the elements.
133 //
134 // Must be invoked as: plist_internal!(@array [] $($tt)*)
135 //////////////////////////////////////////////////////////////////////////
136
137 // Done with trailing comma.
138 (@array [$($elems:expr,)*]) => {
139 plist_internal_vec![$($elems,)*]
140 };
141
142 // Done without trailing comma.
143 (@array [$($elems:expr),*]) => {
144 plist_internal_vec![$($elems),*]
145 };
146
147 // Next element is `true`.
148 (@array [$($elems:expr,)*] true $($rest:tt)*) => {
149 plist_internal!(@array [$($elems,)* plist_internal!(true)] $($rest)*)
150 };
151
152 // Next element is `false`.
153 (@array [$($elems:expr,)*] false $($rest:tt)*) => {
154 plist_internal!(@array [$($elems,)* plist_internal!(false)] $($rest)*)
155 };
156
157 // Next element is an array.
158 (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
159 plist_internal!(@array [$($elems,)* plist_internal!([$($array)*])] $($rest)*)
160 };
161
162 // Next element is a map.
163 (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
164 plist_internal!(@array [$($elems,)* plist_internal!({$($map)*})] $($rest)*)
165 };
166
167 // Next element is an expression followed by comma.
168 (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
169 plist_internal!(@array [$($elems,)* plist_internal!($next),] $($rest)*)
170 };
171
172 // Last element is an expression with no trailing comma.
173 (@array [$($elems:expr,)*] $last:expr) => {
174 plist_internal!(@array [$($elems,)* plist_internal!($last)])
175 };
176
177 // Comma after the most recent element.
178 (@array [$($elems:expr),*] , $($rest:tt)*) => {
179 plist_internal!(@array [$($elems,)*] $($rest)*)
180 };
181
182 // Unexpected token after most recent element.
183 (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
184 plist_unexpected!($unexpected)
185 };
186
187 //////////////////////////////////////////////////////////////////////////
188 // TT muncher for parsing the inside of an object {...}. Each entry is
189 // inserted into the given map variable.
190 //
191 // Must be invoked as: plist_internal!(@object $map () ($($tt)*) ($($tt)*))
192 //
193 // We require two copies of the input tokens so that we can match on one
194 // copy and trigger errors on the other copy.
195 //////////////////////////////////////////////////////////////////////////
196
197 // Done.
198 (@object $object:ident () () ()) => {};
199
200 // Insert the current entry followed by trailing comma.
201 (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
202 let _ = $object.insert(($($key)+).into(), $value);
203 plist_internal!(@object $object () ($($rest)*) ($($rest)*));
204 };
205
206 // Current entry followed by unexpected token.
207 (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
208 plist_unexpected!($unexpected);
209 };
210
211 // Insert the last entry without trailing comma.
212 (@object $object:ident [$($key:tt)+] ($value:expr)) => {
213 let _ = $object.insert(($($key)+).into(), $value);
214 };
215
216 // Next value is `true`.
217 (@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
218 plist_internal!(@object $object [$($key)+] (plist_internal!(true)) $($rest)*);
219 };
220
221 // Next value is `false`.
222 (@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
223 plist_internal!(@object $object [$($key)+] (plist_internal!(false)) $($rest)*);
224 };
225
226 // Next value is an array.
227 (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
228 plist_internal!(@object $object [$($key)+] (plist_internal!([$($array)*])) $($rest)*);
229 };
230
231 // Next value is a map.
232 (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
233 plist_internal!(@object $object [$($key)+] (plist_internal!({$($map)*})) $($rest)*);
234 };
235
236 // Next value is an expression followed by comma.
237 (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
238 plist_internal!(@object $object [$($key)+] (plist_internal!($value)) , $($rest)*);
239 };
240
241 // Last value is an expression with no trailing comma.
242 (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
243 plist_internal!(@object $object [$($key)+] (plist_internal!($value)));
244 };
245
246 // Missing value for last entry. Trigger a reasonable error message.
247 (@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
248 // "unexpected end of macro invocation"
249 plist_internal!();
250 };
251
252 // Missing colon and value for last entry. Trigger a reasonable error
253 // message.
254 (@object $object:ident ($($key:tt)+) () $copy:tt) => {
255 // "unexpected end of macro invocation"
256 plist_internal!();
257 };
258
259 // Misplaced colon. Trigger a reasonable error message.
260 (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
261 // Takes no arguments so "no rules expected the token `:`".
262 plist_unexpected!($colon);
263 };
264
265 // Found a comma inside a key. Trigger a reasonable error message.
266 (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
267 // Takes no arguments so "no rules expected the token `,`".
268 plist_unexpected!($comma);
269 };
270
271 // Key is fully parenthesized. This avoids clippy double_parens false
272 // positives because the parenthesization may be necessary here.
273 (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
274 plist_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*));
275 };
276
277 // Refuse to absorb colon token into key expression.
278 (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
279 plist_expect_expr_comma!($($unexpected)+);
280 };
281
282 // Munch a token into the current key.
283 (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
284 plist_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
285 };
286
287 //////////////////////////////////////////////////////////////////////////
288 // The main implementation.
289 //
290 // Must be invoked as: plist_internal!($($plist)+)
291 //////////////////////////////////////////////////////////////////////////
292
293 (true) => {
294 $crate::Value::Boolean(true)
295 };
296
297 (false) => {
298 $crate::Value::Boolean(false)
299 };
300
301 ([]) => {
302 $crate::Value::Array(plist_internal_vec![])
303 };
304
305 ([ $($tt:tt)+ ]) => {
306 $crate::Value::Array(plist_internal!(@array [] $($tt)+))
307 };
308
309 ({}) => {
310 $crate::Value::Dictionary($crate::Dictionary::new())
311 };
312
313 ({ $($tt:tt)+ }) => {
314 $crate::Value::Dictionary({
315 let mut object = $crate::Dictionary::new();
316 plist_internal!(@object object () ($($tt)+) ($($tt)+));
317 object
318 })
319 };
320
321 // Any Into<plist::Value> type: numbers, strings, struct literals, variables etc.
322 // Must be below every other rule.
323 ($other:expr) => {
324 $crate::Value::from($other)
325 };
326}
327
328// The plist_internal macro above cannot invoke vec directly because it uses
329// local_inner_macros. A vec invocation there would resolve to plist::vec.
330// Instead invoke vec here outside of local_inner_macros.
331#[macro_export]
332#[doc(hidden)]
333macro_rules! plist_internal_vec {
334 ($($content:tt)*) => {
335 vec![$($content)*]
336 };
337}
338
339#[macro_export]
340#[doc(hidden)]
341macro_rules! plist_unexpected {
342 () => {};
343}
344
345#[macro_export]
346#[doc(hidden)]
347macro_rules! plist_expect_expr_comma {
348 ($e:expr , $($tt:tt)*) => {};
349}
350
351#[cfg(test)]
352mod tests {
353 use crate::Dictionary;
354
355 #[test]
356 fn plist_dict() {
357 let expected = Dictionary::from_iter([(String::from("foo"), String::from("bar"))]);
358
359 let d = plist_dict! {
360 "foo": "bar",
361 };
362 assert_eq!(&d, &expected);
363
364 let d = plist_dict!({
365 "foo": "bar",
366 });
367 assert_eq!(&d, &expected);
368 }
369}