loro_common/
macros.rs

1/// A macro for creating `LoroValue`. It works just like the `json!` macro in
2/// `serde_json`.
3///
4/// # Example
5///
6/// ```
7/// use loro_common::loro_value;
8/// loro_value!({
9///     "name": "John",
10///     "age": 12,
11///     "collections": [
12///         {
13///             "binary-data": b"1,2,3"
14///         }
15///     ],
16///     "float": 1.2,
17///     "null": null,
18///     "bool": true,
19/// });
20/// ```
21///
22#[macro_export(local_inner_macros)]
23macro_rules! loro_value {
24    // Hide distracting implementation details from the generated rustdoc.
25    ($($json:tt)+) => {
26        value_internal!($($json)+)
27    };
28}
29
30// Rocket relies on this because they export their own `json!` with a different
31// doc comment than ours, and various Rust bugs prevent them from calling our
32// `json!` from their `json!` so they call `value_internal!` directly. Check with
33// @SergioBenitez before making breaking changes to this macro.
34//
35// Changes are fine as long as `value_internal!` does not call any new helper
36// macros and can still be invoked as `value_internal!($($json)+)`.
37#[macro_export(local_inner_macros)]
38#[doc(hidden)]
39macro_rules! value_internal {
40    //////////////////////////////////////////////////////////////////////////
41    // TT muncher for parsing the inside of an array [...]. Produces a vec![...]
42    // of the elements.
43    //
44    // Must be invoked as: value_internal!(@array [] $($tt)*)
45    //////////////////////////////////////////////////////////////////////////
46
47    // Done with trailing comma.
48    (@array [$($elems:expr,)*]) => {
49        json_internal_vec![$($elems,)*]
50    };
51
52    // Done without trailing comma.
53    (@array [$($elems:expr),*]) => {
54        json_internal_vec![$($elems),*]
55    };
56
57    // Next element is `null`.
58    (@array [$($elems:expr,)*] null $($rest:tt)*) => {
59        value_internal!(@array [$($elems,)* value_internal!(null)] $($rest)*)
60    };
61
62    // Next element is `true`.
63    (@array [$($elems:expr,)*] true $($rest:tt)*) => {
64        value_internal!(@array [$($elems,)* value_internal!(true)] $($rest)*)
65    };
66
67    // Next element is `false`.
68    (@array [$($elems:expr,)*] false $($rest:tt)*) => {
69        value_internal!(@array [$($elems,)* value_internal!(false)] $($rest)*)
70    };
71
72    // Next element is an array.
73    (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
74        value_internal!(@array [$($elems,)* value_internal!([$($array)*])] $($rest)*)
75    };
76
77    // Next element is a map.
78    (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
79        value_internal!(@array [$($elems,)* value_internal!({$($map)*})] $($rest)*)
80    };
81
82    // Next element is an expression followed by comma.
83    (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
84        value_internal!(@array [$($elems,)* value_internal!($next),] $($rest)*)
85    };
86
87    // Last element is an expression with no trailing comma.
88    (@array [$($elems:expr,)*] $last:expr) => {
89        value_internal!(@array [$($elems,)* value_internal!($last)])
90    };
91
92    // Comma after the most recent element.
93    (@array [$($elems:expr),*] , $($rest:tt)*) => {
94        value_internal!(@array [$($elems,)*] $($rest)*)
95    };
96
97    // Unexpected token after most recent element.
98    (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
99        json_unexpected!($unexpected)
100    };
101
102    //////////////////////////////////////////////////////////////////////////
103    // TT muncher for parsing the inside of an object {...}. Each entry is
104    // inserted into the given map variable.
105    //
106    // Must be invoked as: value_internal!(@object $map () ($($tt)*) ($($tt)*))
107    //
108    // We require two copies of the input tokens so that we can match on one
109    // copy and trigger errors on the other copy.
110    //////////////////////////////////////////////////////////////////////////
111
112    // Done.
113    (@object $object:ident () () ()) => {};
114
115    // Insert the current entry followed by trailing comma.
116    (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
117        let _ = $object.insert(($($key)+).into(), $value);
118        value_internal!(@object $object () ($($rest)*) ($($rest)*));
119    };
120
121    // Current entry followed by unexpected token.
122    (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
123        json_unexpected!($unexpected);
124    };
125
126    // Insert the last entry without trailing comma.
127    (@object $object:ident [$($key:tt)+] ($value:expr)) => {
128        let _ = $object.insert(($($key)+).into(), $value);
129    };
130
131    // Next value is `null`.
132    (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
133        value_internal!(@object $object [$($key)+] (value_internal!(null)) $($rest)*);
134    };
135
136    // Next value is `true`.
137    (@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
138        value_internal!(@object $object [$($key)+] (value_internal!(true)) $($rest)*);
139    };
140
141    // Next value is `false`.
142    (@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
143        value_internal!(@object $object [$($key)+] (value_internal!(false)) $($rest)*);
144    };
145
146    // Next value is an array.
147    (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
148        value_internal!(@object $object [$($key)+] (value_internal!([$($array)*])) $($rest)*);
149    };
150
151    // Next value is a map.
152    (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
153        value_internal!(@object $object [$($key)+] (value_internal!({$($map)*})) $($rest)*);
154    };
155
156    // Next value is an expression followed by comma.
157    (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
158        value_internal!(@object $object [$($key)+] (value_internal!($value)) , $($rest)*);
159    };
160
161    // Last value is an expression with no trailing comma.
162    (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
163        value_internal!(@object $object [$($key)+] (value_internal!($value)));
164    };
165
166    // Missing value for last entry. Trigger a reasonable error message.
167    (@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
168        // "unexpected end of macro invocation"
169        value_internal!();
170    };
171
172    // Missing colon and value for last entry. Trigger a reasonable error
173    // message.
174    (@object $object:ident ($($key:tt)+) () $copy:tt) => {
175        // "unexpected end of macro invocation"
176        value_internal!();
177    };
178
179    // Misplaced colon. Trigger a reasonable error message.
180    (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
181        // Takes no arguments so "no rules expected the token `:`".
182        json_unexpected!($colon);
183    };
184
185    // Found a comma inside a key. Trigger a reasonable error message.
186    (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
187        // Takes no arguments so "no rules expected the token `,`".
188        json_unexpected!($comma);
189    };
190
191    // Key is fully parenthesized. This avoids clippy double_parens false
192    // positives because the parenthesization may be necessary here.
193    (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
194        value_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*));
195    };
196
197    // Refuse to absorb colon token into key expression.
198    (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
199        json_expect_expr_comma!($($unexpected)+);
200    };
201
202    // Munch a token into the current key.
203    (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
204        value_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
205    };
206
207    //////////////////////////////////////////////////////////////////////////
208    // The main implementation.
209    //
210    // Must be invoked as: value_internal!($($json)+)
211    //////////////////////////////////////////////////////////////////////////
212
213    (null) => {
214        $crate::LoroValue::Null
215    };
216
217    (true) => {
218        $crate::LoroValue::Bool(true)
219    };
220
221    (false) => {
222        $crate::LoroValue::Bool(false)
223    };
224
225    ([]) => {
226        $crate::LoroValue::List(json_internal_vec![].into())
227    };
228
229    ([ $($tt:tt)+ ]) => {
230        $crate::LoroValue::List(value_internal!(@array [] $($tt)+).into())
231    };
232
233    ({}) => {
234        $crate::LoroValue::Map(Default::default())
235    };
236
237    ({ $($tt:tt)+ }) => {
238        ({
239            let mut object = $crate::FxHashMap::default();
240            value_internal!(@object object () ($($tt)+) ($($tt)+));
241            $crate::LoroValue::Map(object.into())
242        })
243    };
244
245    // Any Serialize type: numbers, strings, struct literals, variables etc.
246    // Must be below every other rule.
247    ($other:expr) => {
248        $crate::to_value($other)
249    };
250}
251
252#[macro_export]
253#[doc(hidden)]
254macro_rules! json_unexpected {
255    () => {};
256}
257
258// The json_internal macro above cannot invoke vec directly because it uses
259// local_inner_macros. A vec invocation there would resolve to $crate::vec.
260// Instead invoke vec here outside of local_inner_macros.
261#[macro_export]
262#[doc(hidden)]
263macro_rules! json_internal_vec {
264    ($($content:tt)*) => {
265        vec![$($content)*]
266    };
267}
268
269#[cfg(test)]
270mod test {
271    #[test]
272    fn test_value_macro() {
273        let v = loro_value!([1, 2, 3]);
274        let list = v.into_list().unwrap();
275        assert_eq!(&*list, &[1.into(), 2.into(), 3.into()]);
276
277        let map = loro_value!({
278            "hi": true,
279            "false": false,
280            "null": null,
281            "list": [],
282            "integer": 123,
283            "float": 123.123,
284            "map": {
285                "a": "1"
286            },
287            "binary": b"123",
288        });
289
290        let map = map.into_map().unwrap();
291        assert_eq!(map.len(), 8);
292        assert!(*map.get("hi").unwrap().as_bool().unwrap());
293        assert!(!(*map.get("false").unwrap().as_bool().unwrap()));
294        assert!(map.get("null").unwrap().is_null());
295        assert_eq!(map.get("list").unwrap().as_list().unwrap().len(), 0);
296        assert_eq!(*map.get("integer").unwrap().as_i64().unwrap(), 123);
297        assert_eq!(*map.get("float").unwrap().as_double().unwrap(), 123.123);
298        assert_eq!(map.get("map").unwrap().as_map().unwrap().len(), 1);
299        assert_eq!(
300            &**map
301                .get("map")
302                .unwrap()
303                .as_map()
304                .unwrap()
305                .get("a")
306                .unwrap()
307                .as_string()
308                .unwrap(),
309            "1"
310        );
311        assert_eq!(
312            &**map.get("binary").unwrap().as_binary().unwrap(),
313            &b"123".to_vec()
314        );
315    }
316}