expr/
macros.rs

1/// Construct an `expr::Value` from a json-like literal.
2///
3/// Compared to json:
4///
5/// * Trailing commas are allowed
6/// * `nil` is used instead of `null`
7///
8/// ```
9/// let value = expr::value!({
10///     "code": 200,
11///     "success": true,
12///     "payload": {
13///         "features": [
14///             "expr",
15///             "macro",
16///         ],
17///         "homepage": nil,
18///     },
19/// });
20/// ```
21///
22/// Variables or expressions can be interpolated into the literal. Any type
23/// interpolated into an array element or map value must implement Into<Value>,
24/// while any type interpolated into a map key must implement `Into<String>`.
25/// If the interpolated type contains a map with non-string keys, the `value!`
26/// macro will panic.
27///
28/// ```
29/// let code = 200;
30/// let features = vec!["expr", "macro"];
31///
32/// let value = expr::value!({
33///     "code": code,
34///     "success": code == 200,
35///     "payload": {
36///         features[0]: features[1],
37///     },
38/// });
39/// ```
40#[macro_export]
41macro_rules! value {
42    //////////////////////////////////////////////////////////////////////////
43    // TT muncher for parsing the inside of an array [...]. Produces a vec![...]
44    // of the elements.
45    //
46    // Must be invoked as: value!(@array [] $($tt)*)
47    //////////////////////////////////////////////////////////////////////////
48
49    // Done with trailing comma.
50    (@array [$($elems:expr,)*]) => {
51        vec![$($elems,)*]
52    };
53
54    // Done without trailing comma.
55    (@array [$($elems:expr),*]) => {
56        vec![$($elems),*]
57    };
58
59    // Next element is `nil`.
60    (@array [$($elems:expr,)*] nil $($rest:tt)*) => {
61        $crate::value!(@array [$($elems,)* $crate::value!(nil)] $($rest)*)
62    };
63
64    // Next element is `true`.
65    (@array [$($elems:expr,)*] true $($rest:tt)*) => {
66        $crate::value!(@array [$($elems,)* $crate::value!(true)] $($rest)*)
67    };
68
69    // Next element is `false`.
70    (@array [$($elems:expr,)*] false $($rest:tt)*) => {
71        $crate::value!(@array [$($elems,)* $crate::value!(false)] $($rest)*)
72    };
73
74    // Next element is an array.
75    (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
76        $crate::value!(@array [$($elems,)* $crate::value!([$($array)*])] $($rest)*)
77    };
78
79    // Next element is a map.
80    (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
81        $crate::value!(@array [$($elems,)* $crate::value!({$($map)*})] $($rest)*)
82    };
83
84    // Next element is an expression followed by comma.
85    (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
86        $crate::value!(@array [$($elems,)* $crate::value!($next),] $($rest)*)
87    };
88
89    // Last element is an expression with no trailing comma.
90    (@array [$($elems:expr,)*] $last:expr) => {
91        $crate::value!(@array [$($elems,)* $crate::value!($last)])
92    };
93
94    // Comma after the most recent element.
95    (@array [$($elems:expr),*] , $($rest:tt)*) => {
96        $crate::value!(@array [$($elems,)*] $($rest)*)
97    };
98
99    // Unexpected token after most recent element.
100    (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
101        $crate::value_unexpected!($unexpected)
102    };
103
104    //////////////////////////////////////////////////////////////////////////
105    // TT muncher for parsing the inside of an map {...}. Each entry is
106    // inserted into the given map variable.
107    //
108    // Must be invoked as: value!(@map $map () ($($tt)*) ($($tt)*))
109    //
110    // We require two copies of the input tokens so that we can match on one
111    // copy and trigger errors on the other copy.
112    //////////////////////////////////////////////////////////////////////////
113
114    // Done.
115    (@map $map:ident () () ()) => {};
116
117    // Insert the current entry followed by trailing comma.
118    (@map $map:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
119        let _ = $map.insert(($($key)+).into(), $value);
120        $crate::value!(@map $map () ($($rest)*) ($($rest)*));
121    };
122
123    // Current entry followed by unexpected token.
124    (@map $map:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
125        $crate::value_unexpected!($unexpected);
126    };
127
128    // Insert the last entry without trailing comma.
129    (@map $map:ident [$($key:tt)+] ($value:expr)) => {
130        let _ = $map.insert(($($key)+).into(), $value);
131    };
132
133    // Next value is `nil`.
134    (@map $map:ident ($($key:tt)+) (: nil $($rest:tt)*) $copy:tt) => {
135        $crate::value!(@map $map [$($key)+] ($crate::value!(nil)) $($rest)*);
136    };
137
138    // Next value is `true`.
139    (@map $map:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
140        $crate::value!(@map $map [$($key)+] ($crate::value!(true)) $($rest)*);
141    };
142
143    // Next value is `false`.
144    (@map $map:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
145        $crate::value!(@map $map [$($key)+] ($crate::value!(false)) $($rest)*);
146    };
147
148    // Next value is an array.
149    (@map $map:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
150        $crate::value!(@map $map [$($key)+] ($crate::value!([$($array)*])) $($rest)*);
151    };
152
153    // Next value is a map.
154    (@map $map:ident ($($key:tt)+) (: {$($inner:tt)*} $($rest:tt)*) $copy:tt) => {
155        $crate::value!(@map $map [$($key)+] ($crate::value!({$($inner)*})) $($rest)*);
156    };
157
158    // Next value is an expression followed by comma.
159    (@map $map:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
160        $crate::value!(@map $map [$($key)+] ($crate::value!($value)) , $($rest)*);
161    };
162
163    // Last value is an expression with no trailing comma.
164    (@map $map:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
165        $crate::value!(@map $map [$($key)+] ($crate::value!($value)));
166    };
167
168    // Missing value for last entry. Trigger a reasonable error message.
169    (@map $map:ident ($($key:tt)+) (:) $copy:tt) => {
170        // "unexpected end of macro invocation"
171        $crate::value!();
172    };
173
174    // Missing colon and value for last entry. Trigger a reasonable error
175    // message.
176    (@map $map:ident ($($key:tt)+) () $copy:tt) => {
177        // "unexpected end of macro invocation"
178        $crate::value!();
179    };
180
181    // Misplaced colon. Trigger a reasonable error message.
182    (@map $map:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
183        // Takes no arguments so "no rules expected the token `:`".
184        $crate::value_unexpected!($colon);
185    };
186
187    // Found a comma inside a key. Trigger a reasonable error message.
188    (@map $map:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
189        // Takes no arguments so "no rules expected the token `,`".
190        $crate::value_unexpected!($comma);
191    };
192
193    // Key is fully parenthesized. This avoids clippy double_parens false
194    // positives because the parenthesization may be necessary here.
195    (@map $map:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
196        $crate::value!(@map $map ($key) (: $($rest)*) (: $($rest)*));
197    };
198
199    // Refuse to absorb colon token into key expression.
200    (@map $map:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
201        $crate::value_expect_expr_comma!($($unexpected)+);
202    };
203
204    // Munch a token into the current key.
205    (@map $map:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
206        $crate::value!(@map $map ($($key)* $tt) ($($rest)*) ($($rest)*));
207    };
208
209    //////////////////////////////////////////////////////////////////////////
210    // The main implementation.
211    //
212    // Must be invoked as: value!($($data)+)
213    //////////////////////////////////////////////////////////////////////////
214
215    (nil) => {
216        $crate::Value::Nil
217    };
218
219    (true) => {
220        $crate::Value::Bool(true)
221    };
222
223    (false) => {
224        $crate::Value::Bool(false)
225    };
226
227    ([]) => {
228        $crate::Value::Array(vec![])
229    };
230
231    ([ $($tt:tt)+ ]) => {
232        $crate::Value::Array($crate::value!(@array [] $($tt)+))
233    };
234
235    ({}) => {
236        $crate::Value::Map($crate::__private::IndexMap::new())
237    };
238
239    ({ $($tt:tt)+ }) => {
240        $crate::Value::Map({
241            let mut map: $crate::__private::IndexMap<String, $crate::Value> = $crate::__private::IndexMap::new();
242            $crate::value!(@map map () ($($tt)+) ($($tt)+));
243            map
244        })
245    };
246
247    // Any type that implements Into<Value>: numbers, strings, variables etc.
248    // Must be below every other rule.
249    ($other:expr) => {
250        $crate::Value::from($other)
251    };
252}
253
254#[macro_export]
255#[doc(hidden)]
256macro_rules! value_unexpected {
257    () => {};
258}
259
260#[macro_export]
261#[doc(hidden)]
262macro_rules! value_expect_expr_comma {
263    ($e:expr , $($tt:tt)*) => {};
264}
265
266#[cfg(test)]
267mod tests {
268    use crate::Value;
269    use indexmap::IndexMap;
270
271    #[test]
272    fn test_value_macro_boolean() {
273        let true_val = value!(true);
274        assert_eq!(true_val, Value::Bool(true));
275
276        let false_val = value!(false);
277        assert_eq!(false_val, Value::Bool(false));
278    }
279
280    #[test]
281    fn test_value_macro_string() {
282        let string_val = value!("foo");
283        assert_eq!(string_val, Value::String("foo".to_string()));
284
285        let empty_string = value!("");
286        assert_eq!(empty_string, Value::String("".to_string()));
287    }
288
289    #[test]
290    fn test_value_macro_nil() {
291        let nil_val = value!(nil);
292        assert_eq!(nil_val, Value::Nil);
293    }
294
295    #[test]
296    fn test_value_macro_numeric() {
297        let integer_val = value!(42);
298        assert_eq!(integer_val, Value::Number(42));
299
300        let float_val = value!(3.14);
301        assert_eq!(float_val, Value::Float(3.14));
302
303        let negative_val = value!(-10.5);
304        assert_eq!(negative_val, Value::Float(-10.5));
305    }
306
307    #[test]
308    fn test_value_macro_array() {
309        let empty_array = value!([]);
310        assert_eq!(empty_array, Value::Array(vec![]));
311
312        let simple_array = value!([1, 2, 3]);
313        assert_eq!(
314            simple_array,
315            Value::Array(vec![
316                Value::Number(1),
317                Value::Number(2),
318                Value::Number(3),
319            ])
320        );
321
322        let mixed_array = value!([true, "hello", nil, 42.5]);
323        assert_eq!(
324            mixed_array,
325            Value::Array(vec![
326                Value::Bool(true),
327                Value::String("hello".to_string()),
328                Value::Nil,
329                Value::Float(42.5),
330            ])
331        );
332
333        let nested_array = value!([[1, 2], ["a", "b"]]);
334        assert_eq!(
335            nested_array,
336            Value::Array(vec![
337                Value::Array(vec![Value::Number(1), Value::Number(2)]),
338                Value::Array(vec![
339                    Value::String("a".to_string()),
340                    Value::String("b".to_string()),
341                ]),
342            ])
343        );
344    }
345
346    #[test]
347    fn test_value_macro_map() {
348        let empty_map = value!({});
349        assert_eq!(empty_map, Value::Map(IndexMap::new()));
350
351        let simple_map = value!({
352            "key1": "value1",
353            "key2": 42,
354        });
355        let expected_simple_map = Value::from_iter([
356            ("key1", Value::from("value1")),
357            ("key2", Value::from(42)),
358        ]);
359        assert_eq!(simple_map, expected_simple_map);
360
361        let complex_map = value!({
362            "boolean": true,
363            "string": "hello",
364            "nil": nil,
365            "number": 3.14,
366            "array": [1, 2, 3],
367            "nested_map": {
368                "inner_key": "inner_value",
369            },
370        });
371        let expected_complex_map = Value::from_iter([
372            ("boolean", Value::Bool(true)),
373            ("string", Value::String("hello".to_string())),
374            ("nil", Value::Nil),
375            ("number", Value::Float(3.14)),
376            ("array", Value::from(vec![
377                Value::Number(1),
378                Value::Number(2),
379                Value::Number(3),
380            ])),
381            ("nested_map", Value::from_iter([
382                ("inner_key", Value::String("inner_value".to_string()))
383            ])),
384        ]);
385        assert_eq!(complex_map, expected_complex_map);
386    }
387
388    #[test]
389    fn test_interpolation() {
390        let bool_var = true;
391        let string_var = "hello".to_string();
392        let int_var = 42;
393        let float_var = 3.14;
394        let nil_var = Value::Nil;
395        let array_var = vec![1, 2, 3];
396        let map_var: IndexMap<String, Value> = IndexMap::from([
397            ("key".to_string(), Value::String("value".to_string())),
398        ]);
399
400        let interpolated_value = value!({
401            "bool": bool_var,
402            "string": string_var,
403            "int": int_var,
404            "float": float_var,
405            "nil": nil_var,
406            "array": array_var,
407            "map": map_var,
408        });
409        let expected_value = Value::from_iter([
410            ("bool", Value::from(true)),
411            ("string", Value::from("hello")),
412            ("int", Value::from(42)),
413            ("float", Value::from(3.14)),
414            ("nil", Value::Nil),
415            ("array", Value::from(vec![
416                Value::from(1),
417                Value::from(2),
418                Value::from(3),
419            ])),
420            ("map", Value::from_iter([
421                ("key", Value::from("value")),
422            ])),
423        ]);
424        assert_eq!(interpolated_value, expected_value);
425    }
426}