json_syntax/
macros.rs

1/// Constructs a `json_syntax::Value` from a JSON literal.
2///
3/// ```
4/// # use json_syntax::{Value, json};
5/// let value = json!({
6///     "code": 200,
7///     "success": true,
8///     "payload": {
9///         "features": [
10///             "json",
11///             "syntax"
12///         ]
13///     }
14/// });
15/// ```
16///
17/// Variables or expressions can be interpolated into the JSON literal.
18///
19/// ```
20/// # use json_syntax::{Value, object::Key, json};
21/// let code = 200;
22/// let features = vec!["json", "syntax"];
23///
24/// let value = json!({
25///     "code": Value::from(code),
26///     "success": Value::from(code == 200),
27///     "payload": {
28///         Key::from(features[0]): Value::from(features[1])
29///     }
30/// });
31/// ```
32///
33/// Trailing commas are allowed inside both arrays and objects.
34///
35/// ```
36/// # use json_syntax::{Value, json};
37/// let value = json!([
38///     "notice",
39///     "the",
40///     "trailing",
41///     "comma -->",
42/// ]);
43/// ```
44#[macro_export(local_inner_macros)]
45macro_rules! json {
46	//////////////////////////////////////////////////////////////////////////
47	// TT muncher for parsing the inside of an array [...]. Produces a vec![...]
48	// of the elements.
49	//
50	// Must be invoked as: json!(@array [] $($tt)*)
51	//////////////////////////////////////////////////////////////////////////
52
53	// Done with trailing comma.
54	(@array [$($elems:expr,)*]) => {
55		json_vec![$($elems,)*]
56	};
57
58	// Done without trailing comma.
59	(@array [$($elems:expr),*]) => {
60		json_vec![$($elems),*]
61	};
62
63	// Next element is `null`.
64	(@array [$($elems:expr,)*] null $($rest:tt)*) => {
65		json!(@array [$($elems,)* json!(null)] $($rest)*)
66	};
67
68	// Next element is `true`.
69	(@array [$($elems:expr,)*] true $($rest:tt)*) => {
70		json!(@array [$($elems,)* json!(true)] $($rest)*)
71	};
72
73	// Next element is `false`.
74	(@array [$($elems:expr,)*] false $($rest:tt)*) => {
75		json!(@array [$($elems,)* json!(false)] $($rest)*)
76	};
77
78	// Next element is a literal.
79	(@array [$($elems:expr,)*] $lit:literal $($rest:tt)*) => {
80		json!(@array [$($elems,)* json!($lit)] $($rest)*)
81	};
82
83	// Next element is an array.
84	(@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
85		json!(@array [$($elems,)* json!([$($array)*])] $($rest)*)
86	};
87
88	// Next element is a map.
89	(@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
90		json!(@array [$($elems,)* json!({$($map)*})] $($rest)*)
91	};
92
93	// Next element is an expression followed by comma.
94	(@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
95		json!(@array [$($elems,)* json!($next),] $($rest)*)
96	};
97
98	// Last element is an expression with no trailing comma.
99	(@array [$($elems:expr,)*] $last:expr) => {
100		json!(@array [$($elems,)* json!($last)])
101	};
102
103	// Comma after the most recent element.
104	(@array [$($elems:expr),*] , $($rest:tt)*) => {
105		json!(@array [$($elems,)*] $($rest)*)
106	};
107
108	// Unexpected token after most recent element.
109	(@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
110		json_unexpected!($unexpected)
111	};
112
113	//////////////////////////////////////////////////////////////////////////
114	// TT muncher for parsing the inside of an object {...}.
115	//
116	// Must be invoked as: json!(@object [] [] ($($tt)*))
117	//
118	// We require two copies of the input tokens so that we can match on one
119	// copy and trigger errors on the other copy.
120	//////////////////////////////////////////////////////////////////////////
121
122	// Done with trailing comma.
123	(@object [$($elems:expr,)*] () () ()) => {
124		$crate::Object::from_vec(json_vec![$($elems,)*])
125	};
126
127	// Done without trailing comma.
128	(@object [$($elems:expr),*] () () ()) => {
129		$crate::Object::from_vec(json_vec![$($elems),*])
130	};
131
132	// Create an entry literal key.
133	(@key ($key:literal)) => {
134		$key.into()
135	};
136
137	// Create an entry key.
138	(@key ($key:expr)) => {
139		$key.into()
140	};
141
142	// Next value is `null`.
143	(@object [$($elems:expr,)*] ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
144		json!(@object [$($elems,)* $crate::object::Entry::new(json!(@key ($($key)+)), json!(null))] () ($($rest)*) ($($rest)*))
145	};
146
147	// Next value is `true`.
148	(@object [$($elems:expr,)*] ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
149		json!(@object [$($elems,)* $crate::object::Entry::new(json!(@key ($($key)+)), json!(true))] () ($($rest)*) ($($rest)*))
150	};
151
152	// Next value is `false`.
153	(@object [$($elems:expr,)*] ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
154		json!(@object [$($elems,)* $crate::object::Entry::new(json!(@key ($($key)+)), json!(false))] () ($($rest)*) ($($rest)*))
155	};
156
157	// Next value is a literal.
158	(@object [$($elems:expr,)*] ($($key:tt)+) (: $lit:literal $($rest:tt)*) $copy:tt) => {
159		json!(@object [$($elems,)* $crate::object::Entry::new(json!(@key ($($key)+)), json!($lit))] () ($($rest)*) ($($rest)*))
160	};
161
162	// Next value is a array.
163	(@object [$($elems:expr,)*] ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
164		json!(@object [$($elems,)* $crate::object::Entry::new(json!(@key ($($key)+)), json!([$($array)*]))] () ($($rest)*) ($($rest)*))
165	};
166
167	// Next value is a map.
168	(@object [$($elems:expr,)*] ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
169		json!(@object [$($elems,)* $crate::object::Entry::new(json!(@key ($($key)+)), json!({$($map)*}))] () ($($rest)*) ($($rest)*))
170	};
171
172	// Next value is an expression followed by comma.
173	(@object [$($elems:expr,)*] ($($key:tt)+) (: $next:expr, $($rest:tt)*) $copy:tt) => {
174		json!(@object [$($elems,)* $crate::object::Entry::new(json!(@key ($($key)+)), json!($next)),] () ($($rest)*) ($($rest)*))
175	};
176
177	// Last value is an expression with no trailing comma.
178	(@object [$($elems:expr,)*] ($($key:tt)+) (: $last:expr) $copy:tt) => {
179		json!(@object [$($elems,)* $crate::object::Entry::new(json!(@key ($($key)+)), json!($last))] () () ())
180	};
181
182	// Comma after the most recent element.
183	(@object [$($elems:expr),*] () (, $($rest:tt)*) $copy:tt) => {
184		json!(@object [$($elems,)*] () ($($rest)*) ($($rest)*))
185	};
186
187	// Missing value for last entry. Trigger a reasonable error message.
188	(@object [$($elems:expr,)*] ($($key:tt)+) (:) $copy:tt) => {
189		// "unexpected end of macro invocation"
190		json!()
191	};
192
193	// Missing colon and value for last entry. Trigger a reasonable error
194	// message.
195	(@object [$($elems:expr,)*] ($($key:tt)+) () $copy:tt) => {
196		// "unexpected end of macro invocation"
197		json!()
198	};
199
200	// Misplaced colon. Trigger a reasonable error message.
201	(@object [$($elems:expr,)*] () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
202		// Takes no arguments so "no rules expected the token `:`".
203		json_unexpected!($colon)
204	};
205
206	// Found a comma inside a key. Trigger a reasonable error message.
207	(@object [$($elems:expr,)*] ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
208		// Takes no arguments so "no rules expected the token `,`".
209		json_unexpected!($comma)
210	};
211
212	// Key is fully parenthesized. This avoids clippy double_parens false
213	// positives because the parenthesization may be necessary here.
214	(@object [$($elems:expr,)*] () (($key:expr) : $($rest:tt)*) $copy:tt) => {
215		json!(@object [$($elems,)*] ($key) (: $($rest)*) (: $($rest)*))
216	};
217
218	// Refuse to absorb colon token into key expression.
219	(@object [$($elems:expr,)*] ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
220		json_expect_expr_comma!($($unexpected)+)
221	};
222
223	// Munch a token into the current key.
224	(@object [$($elems:expr,)*] ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
225		json!(@object [$($elems,)*] ($($key)* $tt) ($($rest)*) ($($rest)*))
226	};
227
228	//////////////////////////////////////////////////////////////////////////
229	// The main implementation.
230	//
231	// Must be invoked as: json!($($json)+)
232	//////////////////////////////////////////////////////////////////////////
233
234	(null) => {
235		$crate::Value::Null
236	};
237
238	(true) => {
239		$crate::Value::Boolean(true)
240	};
241
242	(false) => {
243		$crate::Value::Boolean(false)
244	};
245
246	($lit:literal) => {
247		$crate::Value::try_from($lit).unwrap()
248	};
249
250	([]) => {
251		$crate::Value::Array(json_vec![])
252	};
253
254	([ $($tt:tt)+ ]) => {
255		$crate::Value::Array(json!(@array [] $($tt)+))
256	};
257
258	({}) => {
259		$crate::Value::Object($crate::Object::new())
260	};
261
262	({ $($tt:tt)+ }) => {
263		$crate::Value::Object(json!(@object [] () ($($tt)+) ($($tt)+)))
264	};
265
266	($other:expr) => {
267		$crate::Value::from($other)
268	};
269}
270
271// The json_internal macro above cannot invoke vec directly because it uses
272// local_inner_macros. A vec invocation there would resolve to $crate::vec.
273// Instead invoke vec here outside of local_inner_macros.
274#[macro_export]
275#[doc(hidden)]
276macro_rules! json_vec {
277    ($($content:tt)*) => {
278        vec![$($content)*]
279    };
280}
281
282#[macro_export]
283#[doc(hidden)]
284macro_rules! json_unexpected {
285	() => {};
286}
287
288#[macro_export]
289#[doc(hidden)]
290macro_rules! json_expect_expr_comma {
291	($e:expr , $($tt:tt)*) => {};
292}