scale_value/
macros.rs

1// Copyright (C) 2022-2023 Parity Technologies (UK) Ltd. (admin@parity.io)
2// This file is a part of the scale-value crate.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//         http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16/// Construct a `scale_value::Value`
17///
18///
19/// Supports unnamed and named composites and variants:
20/// ```
21/// use scale_value::value;
22///
23/// let val = value!({
24///     name: "localhost",
25///     address: V4(127, 0, 0, 1),
26///     payload: {
27///         bytes: (255, 3, 4, 9),
28///         method: ("Post", 3000),
29///     },
30/// });
31///
32/// ```
33/// Values can be nested in each other:
34/// ```
35/// use scale_value::value;
36///
37/// let data_value = value!((1, v1(1, 2), 3));
38/// let val = value!(POST { data: data_value });
39/// ```
40/// Trailing commas are optional.
41#[macro_export(local_inner_macros)]
42macro_rules! value {
43    ($($tt:tt)*) => {
44        value_internal!($($tt)*)
45    };
46}
47
48/// All patterns can be grouped into 4 main patterns:
49///
50/// ### `value_internal!(@unnamed [ELEMENTS] (REST))`
51///
52/// checks `REST` for certain patterns and if a suitable element pattern is found,
53/// the element is added to the comma seperated `ELEMENTS` list. When `REST` is empty,
54/// we collect all `ELEMENTS` into a Vec of values.
55///
56/// ### `value_internal!(@named [KEYVALUEPAIRS] (PARTIALKEY) (REST))`
57///
58/// goes over the REST tokens, to build up the PARTIALKEY until it is a proper KEY.
59/// This happens as soon as a colon `:` token is encountered, then we switch to the next pattern:
60///
61/// ### `value_internal!(@named [KEYVALUEPAIRS] [KEY] (REST))`
62///
63/// The square brackets now indicate that the key is fully formed. Now REST is scanned for the next
64/// `VALUE`. Together with the `KEY`, they are added as a key value pair tuple into the KEYVALUEPAIRS list.
65/// At that point we switch back to the partial key pattern above, e.g. `value_internal!(@named [KEYVALUEPAIRS] () (REST))`
66/// now with a new empty partial key that has to be filled.
67/// When `REST` is empty, we collect all `KEYVALUEPAIRS` into a Vec of key-value tuples.
68///
69/// ### `value_internal!(BASEPATTERN)`
70///
71/// These patterns check if the input represents a composite or variant type or can be made into a valid `$crate::Value`.
72#[macro_export(local_inner_macros)]
73#[doc(hidden)]
74macro_rules! value_internal {
75
76    ////////////////////////////////////////////////////////////////////////////
77    // collecting unnamed fields
78    ////////////////////////////////////////////////////////////////////////////
79
80    // done, put all the fields in a vec
81    (@unnamed [$($e:expr, )*] ()) => { vec_wrapper![$($e, )*] };
82
83    // Next value is an unnamed composite
84    (@unnamed [$($e:expr, )*] (($($array:tt)*) $($rest:tt)*)) => {
85        value_internal!(@unnamed [$($e, )*] (value_internal!(($($array)*))) $($rest)*)
86    };
87
88    // Next value is an unnamed variant
89    (@unnamed [$($e:expr, )*] ($variant:ident ($($array:tt)*) $($rest:tt)*)) => {
90        value_internal!(@unnamed [$($e, )*] (value_internal!($variant ($($array)*))) $($rest)*)
91    };
92
93    // Next value is a named composite
94    (@unnamed [$($e:expr, )*] ({$($map:tt)*} $($rest:tt)*)) => {
95        value_internal!(@unnamed [$($e, )*] (value_internal!({$($map)*})) $($rest)*)
96    };
97
98    // Next value is a named variant
99    (@unnamed [$($e:expr, )*] ($variant:ident {$($map:tt)*} $($rest:tt)*)) => {
100        value_internal!(@unnamed [$($e, )*] (value_internal!($variant {$($map)*})) $($rest)*)
101    };
102
103    // Insert the current entry followed by trailing comma
104    (@unnamed [$($e:expr, )*] ($value:expr) , $($rest:tt)*) => {
105        value_internal!(@unnamed [$($e, )* $value , ] ($($rest)*))
106    };
107
108    // Current entry followed by unexpected token.
109    // There needs to be a comma, which would match the previous matcher or no further tokens at all matching the next matcher
110     (@unnamed [$($e:expr, )*] ($value:expr) $unexpected:tt $($rest:tt)*) => {
111        let token = core::stringify!($unexpected);
112        compile_error!("unexpected token after expression: {}", token);
113    };
114
115    // Insert the last entry without trailing comma
116    (@unnamed [$($e:expr, )*] ($value:expr)) => {
117        vec_wrapper![ $($e, )* value_internal!($value) ]
118    };
119
120    // Next value is an expression followed by comma
121    (@unnamed [$($e:expr, )*] ($value:expr , $($rest:tt)*)) => {
122        value_internal!(@unnamed [$($e, )*] (value_internal!($value)) , $($rest)*)
123    };
124
125    ////////////////////////////////////////////////////////////////////////////
126    // collecting named fields
127    ////////////////////////////////////////////////////////////////////////////
128
129    // done, put all the key value pairs in a vec
130    (@named [$(($k:expr, $v:expr), )*] () ()) => { vec_wrapper![ $(($k, $v), )* ] };
131
132    // Insert the current entry followed by trailing comma.
133    (@named [$(($k:expr, $v:expr), )*] [$key:expr] ($value:expr) , $($rest:tt)*) => {
134        {
135            let field_name = literal_aware_stringify!($key);
136            value_internal!(@named [$(($k, $v), )* (field_name, $value), ] () ($($rest)*))
137        }
138    };
139
140    // Current entry followed by unexpected token.
141    // There needs to be a comma, which would match the previous matcher or no further tokens at all matching the next matcher
142    (@named [$(($k:expr, $v:expr), )*] [$key:expr] ($value:expr) $unexpected:tt $($rest:tt)*) => {
143        let token = core::stringify!($unexpected);
144        compile_error!("unexpected token after expression: {}", token);
145    };
146
147    // Insert the last entry without trailing comma.
148    (@named [$(($k:expr, $v:expr), )*] [$key:expr] ($value:expr)) => {
149        {
150            let field_name = literal_aware_stringify!($key);
151            vec_wrapper![ $(($k, $v), )* (field_name, $value) ]
152        }
153    };
154
155    // Next value is an unnamed composite
156    (@named [$(($k:expr, $v:expr), )*] ($key:expr) (: ($($array:tt)*) $($rest:tt)*)) => {
157        value_internal!(@named [$(($k, $v), )*] [$key] (value_internal!(($($array)*))) $($rest)*)
158    };
159
160    // Next value is an unnamed variant
161    (@named [$(($k:expr, $v:expr), )*] ($key:expr) (: $variant:ident ($($array:tt)*) $($rest:tt)*)) => {
162        value_internal!(@named [$(($k, $v), )*] [$key] (value_internal!($variant ($($array)*))) $($rest)*)
163    };
164
165    // Next value is a named composite
166    (@named [$(($k:expr, $v:expr), )*] ($key:expr) (: {$($map:tt)*} $($rest:tt)*)) => {
167        value_internal!(@named [$(($k, $v), )*] [$key] (value_internal!({$($map)*})) $($rest)*)
168    };
169
170    // Next value is a named variant
171    (@named [$(($k:expr, $v:expr), )*] ($key:expr) (: $variant:ident {$($map:tt)*} $($rest:tt)*)) => {
172        value_internal!(@named [$(($k, $v), )*] [$key] (value_internal!($variant {$($map)*})) $($rest)*)
173    };
174
175    // // Next value is an expression followed by comma
176    (@named [$(($k:expr, $v:expr), )*] ($key:expr) (: $value:expr , $($rest:tt)*)) => {
177        value_internal!(@named [$(($k, $v), )*] [$key] (value_internal!($value)) , $($rest)*)
178    };
179
180    // Last value is an expression with no trailing comma
181    (@named [$(($k:expr, $v:expr), )*] ($key:expr) (: $value:expr)) => {
182        value_internal!(@named [$(($k, $v), )*] [$key] (value_internal!($value)))
183    };
184
185    // Error pattern: Missing value for last entry
186    (@named [$(($k:expr, $v:expr), )*] ($key:expr) (:)) => {
187        compile_error!("missing value for last entry");
188    };
189
190    // Error pattern: Missing colon and value for last entry
191    (@named [$(($k:expr, $v:expr), )*] ($key:expr) ()) => {
192        compile_error!("missing colon and value for last entry");
193    };
194
195    // Error pattern: colon as first token
196    (@named [$(($k:expr, $v:expr), )*] () (: $($rest:tt)*)) => {
197        compile_error!("colon in wrong position");
198    };
199
200    // Error pattern: comma inside key
201    (@named [$(($k:expr, $v:expr), )*] ($($key:tt)*) (, $($rest:tt)*)) => {
202        compile_error!("comma in key of named composite");
203    };
204
205    (@named [$(($k:expr, $v:expr), )*] () (($key:expr) : $($rest:tt)*)) => {
206        value_internal!(@named [$(($k, $v), )*] ($key) (: $($rest)*))
207    };
208
209    // add a token into the current key.
210    (@named [$(($k:expr, $v:expr), )*] ($($key:tt)*) ($tt:tt $($rest:tt)*)) => {
211        value_internal!(@named [$(($k, $v), )*] ($($key)* $tt) ($($rest)*))
212    };
213
214    ////////////////////////////////////////////////////////////////////////////
215    // Main implementation of patterns:
216    ////////////////////////////////////////////////////////////////////////////
217
218    // empty composites:
219    () => {
220        $crate::Value::unnamed_composite(Vec::<$crate::Value>::new())
221    };
222    (()) => {
223        $crate::Value::unnamed_composite(Vec::<$crate::Value>::new())
224    };
225    ({}) => {
226        $crate::Value::named_composite(Vec::<(String, $crate::Value)>::new())
227    };
228
229    // named composites e.g. { age: 1, nice: false }
230    ({ $($tt:tt)* }) => {
231        {
232            let fields: Vec::<(String, $crate::Value)> = value_internal!(@named [] () ($($tt)*));
233            $crate::Value::named_composite(fields)
234        }
235    };
236
237    // named variants e.g. v1 { age: 1, nice: false }
238    ($variant:ident { $($tt:tt)* }) => {
239        {
240            let variant_name = literal_aware_stringify!($variant);
241            let fields: Vec::<(String, $crate::Value)> = value_internal!(@named [] () ($($tt)*));
242            $crate::Value::named_variant(variant_name,fields)
243        }
244    };
245
246    // unnamed composites with (..) syntax e.g. (1,"hello",3)
247    (( $($tt:tt)* )) => {
248        {
249            let fields = value_internal!(@unnamed [] ($($tt)*));
250            $crate::Value::unnamed_composite(fields)
251        }
252    };
253
254    // unnamed variants e.g. v1 (1,2,3,4)
255    ($variant:ident ( $($tt:tt)* )) => {
256        {
257            let variant_name = literal_aware_stringify!($variant);
258            let fields = value_internal!(@unnamed [] ($($tt)*));
259            $crate::Value::unnamed_variant(variant_name,fields)
260        }
261    };
262
263    // any other expressions
264    ($val:expr) => {
265        $crate::Value::from($val)
266    };
267}
268
269// The value_internal macro above cannot invoke vec directly because it uses
270// local_inner_macros. A vec invocation there would resolve to $crate::vec.
271#[macro_export]
272#[doc(hidden)]
273macro_rules! vec_wrapper {
274    ($($content:tt)*) => {
275        vec![$($content)*]
276    };
277}
278
279/// Just using core::stringify!($key).to_string() on a $key that is a string literal
280/// causes doubled quotes to appear. This macro fixes that.
281#[macro_export]
282#[doc(hidden)]
283macro_rules! literal_aware_stringify {
284    ($tt:literal) => {
285        $tt.to_string()
286    };
287    ($($tt:tt)*) => {
288        stringify!($($tt)*).to_string()
289    };
290}
291
292#[cfg(test)]
293#[macro_use]
294mod test {
295    use crate::prelude::*;
296    use crate::{value, Value};
297
298    #[test]
299    fn macro_tests() {
300        // primitives:
301        assert_eq!(value!(1), Value::from(1));
302        assert_eq!(value!(-122193223i64), Value::from(-122193223i64));
303        assert_eq!(value!(89usize), Value::from(89usize));
304        assert_eq!(value!(false), Value::from(false));
305        assert_eq!(value!(true), Value::from(true));
306        assert_eq!(value!('h'), Value::from('h'));
307        assert_eq!(value!("Hello"), Value::from("Hello"));
308        assert_eq!(value!("Hello".to_string()), Value::from("Hello"));
309        let s = "Hello".to_string();
310        assert_eq!(value!(s), Value::from("Hello"));
311
312        // unnamed composites:
313        let unnamed_composite =
314            Value::unnamed_composite([Value::from(1), Value::from("nice"), Value::from('t')]);
315
316        assert_eq!(value!((1, "nice", 't')), unnamed_composite);
317        assert_eq!(value!((1, "nice", 't',)), unnamed_composite);
318
319        let empty_composite = Value::unnamed_composite([]);
320        assert_eq!(value!(()), empty_composite);
321
322        // named composites:
323        let named_composite =
324            Value::named_composite([("num", Value::from(3)), ("item", Value::from("tea"))]);
325        assert_eq!(value!({num: 3, item: "tea"}), named_composite);
326        assert_eq!(value!({num: 3, item: "tea", }), named_composite);
327        // variants:
328        let variant_no_fields = Value::variant("v1", crate::Composite::Unnamed(vec![]));
329        assert_eq!(value!(v1()), variant_no_fields);
330        let named_variant = Value::variant(
331            "V2",
332            crate::Composite::named([("num", Value::from(3)), ("item", Value::from("tea"))]),
333        );
334        assert_eq!(value!(V2 { num: 3, item: "tea" }), named_variant);
335        // string literal as key:
336        let value = value!({ "string key": 123 });
337        assert_eq!(value, Value::named_composite([("string key", Value::from(123))]));
338        // wild combination, just check if compiles:
339        let _ = value!({ unnamed: unnamed_composite, vals: (v1{name: "berry", age: 34}, named_variant), named: named_composite });
340    }
341}