Skip to content

Commit 481d9e6

Browse files
authored
RUST-1109 Implement rawbson! and rawdoc! macros (#329)
1 parent 17dc632 commit 481d9e6

File tree

5 files changed

+407
-141
lines changed

5 files changed

+407
-141
lines changed

src/macros.rs

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,212 @@ macro_rules! doc {
212212
object
213213
}};
214214
}
215+
216+
/// Construct a [`crate::RawBson`] value from a literal.
217+
///
218+
/// ```rust
219+
/// use bson::rawbson;
220+
///
221+
/// let value = rawbson!({
222+
/// "code": 200,
223+
/// "success": true,
224+
/// "payload": {
225+
/// "some": [
226+
/// "pay",
227+
/// "loads",
228+
/// ]
229+
/// }
230+
/// });
231+
/// ```
232+
#[macro_export]
233+
macro_rules! rawbson {
234+
//////////////////////////////////////////////////////////////////////////
235+
// TT muncher for parsing the inside of an array [...]. Produces a
236+
// RawArrayBuf containing the elements.
237+
//
238+
// Must be invoked as: bson!(@array [] $($tt)*)
239+
//////////////////////////////////////////////////////////////////////////
240+
241+
// Finished with trailing comma.
242+
(@array [$($elems:expr,)*]) => {
243+
<$crate::RawArrayBuf as std::iter::FromIterator::<$crate::RawBson>>::from_iter(vec![$($elems,)*])
244+
};
245+
246+
// Finished without trailing comma.
247+
(@array [$($elems:expr),*]) => {
248+
<$crate::RawArrayBuf as std::iter::FromIterator::<$crate::RawBson>>::from_iter(vec![$($elems),*])
249+
};
250+
251+
// Next element is `null`.
252+
(@array [$($elems:expr,)*] null $($rest:tt)*) => {
253+
$crate::rawbson!(@array [$($elems,)* $crate::rawbson!(null)] $($rest)*)
254+
};
255+
256+
// Next element is an array.
257+
(@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
258+
$crate::rawbson!(@array [$($elems,)* $crate::rawbson!([$($array)*])] $($rest)*)
259+
};
260+
261+
// Next element is a map.
262+
(@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
263+
$crate::rawbson!(@array [$($elems,)* $crate::rawbson!({$($map)*})] $($rest)*)
264+
};
265+
266+
// Next element is an expression followed by comma.
267+
(@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
268+
$crate::rawbson!(@array [$($elems,)* $crate::rawbson!($next),] $($rest)*)
269+
};
270+
271+
// Last element is an expression with no trailing comma.
272+
(@array [$($elems:expr,)*] $last:expr) => {
273+
$crate::rawbson!(@array [$($elems,)* $crate::rawbson!($last)])
274+
};
275+
276+
// Comma after the most recent element.
277+
(@array [$($elems:expr),*] , $($rest:tt)*) => {
278+
$crate::rawbson!(@array [$($elems,)*] $($rest)*)
279+
};
280+
281+
//////////////////////////////////////////////////////////////////////////
282+
// TT muncher for parsing the inside of an object {...}. Each entry is
283+
// inserted into the given map variable.
284+
//
285+
// Must be invoked as: rawbson!(@object $map () ($($tt)*) ($($tt)*))
286+
//
287+
// We require two copies of the input tokens so that we can match on one
288+
// copy and trigger errors on the other copy.
289+
//////////////////////////////////////////////////////////////////////////
290+
291+
// Finished.
292+
(@object $object:ident () () ()) => {};
293+
294+
// Insert the current entry followed by trailing comma.
295+
(@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
296+
$object.append(($($key)+), $value);
297+
$crate::rawbson!(@object $object () ($($rest)*) ($($rest)*));
298+
};
299+
300+
// Insert the last entry without trailing comma.
301+
(@object $object:ident [$($key:tt)+] ($value:expr)) => {
302+
$object.append(($($key)+), $value);
303+
};
304+
305+
// Next value is `null`.
306+
(@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
307+
$crate::rawbson!(@object $object [$($key)+] ($crate::rawbson!(null)) $($rest)*);
308+
};
309+
310+
// Next value is an array.
311+
(@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
312+
$crate::rawbson!(@object $object [$($key)+] ($crate::rawbson!([$($array)*])) $($rest)*);
313+
};
314+
315+
// Next value is a map.
316+
(@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
317+
$crate::rawbson!(@object $object [$($key)+] ($crate::rawbson!({$($map)*})) $($rest)*);
318+
};
319+
320+
// Next value is an expression followed by comma.
321+
(@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
322+
$crate::rawbson!(@object $object [$($key)+] ($crate::rawbson!($value)) , $($rest)*);
323+
};
324+
325+
// Last value is an expression with no trailing comma.
326+
(@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
327+
$crate::rawbson!(@object $object [$($key)+] ($crate::rawbson!($value)));
328+
};
329+
330+
// Missing value for last entry. Trigger a reasonable error message.
331+
(@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
332+
// "unexpected end of macro invocation"
333+
$crate::rawbson!();
334+
};
335+
336+
// Missing key-value separator and value for last entry.
337+
// Trigger a reasonable error message.
338+
(@object $object:ident ($($key:tt)+) () $copy:tt) => {
339+
// "unexpected end of macro invocation"
340+
$crate::rawbson!();
341+
};
342+
343+
// Misplaced key-value separator. Trigger a reasonable error message.
344+
(@object $object:ident () (: $($rest:tt)*) ($kv_separator:tt $($copy:tt)*)) => {
345+
// Takes no arguments so "no rules expected the token `:`".
346+
unimplemented!($kv_separator);
347+
};
348+
349+
// Found a comma inside a key. Trigger a reasonable error message.
350+
(@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
351+
// Takes no arguments so "no rules expected the token `,`".
352+
unimplemented!($comma);
353+
};
354+
355+
// Key is fully parenthesized. This avoids clippy double_parens false
356+
// positives because the parenthesization may be necessary here.
357+
(@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
358+
$crate::rawbson!(@object $object ($key) (: $($rest)*) (: $($rest)*));
359+
};
360+
361+
// Munch a token into the current key.
362+
(@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
363+
$crate::rawbson!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
364+
};
365+
366+
//////////////////////////////////////////////////////////////////////////
367+
// The main implementation.
368+
//
369+
// Must be invoked as: rawbson!($($bson)+)
370+
//////////////////////////////////////////////////////////////////////////
371+
372+
(null) => {
373+
$crate::RawBson::Null
374+
};
375+
376+
([]) => {
377+
$crate::RawBson::Array($crate::RawArrayBuf::new())
378+
};
379+
380+
([ $($tt:tt)+ ]) => {
381+
$crate::RawBson::Array($crate::rawbson!(@array [] $($tt)+))
382+
};
383+
384+
({}) => {
385+
$crate::RawBson::Document($crate::rawdoc!{})
386+
};
387+
388+
({$($tt:tt)+}) => {
389+
$crate::RawBson::Document($crate::rawdoc!{$($tt)+})
390+
};
391+
392+
// Any Into<RawBson> type.
393+
// Must be below every other rule.
394+
($other:expr) => {
395+
$crate::RawBson::from($other)
396+
};
397+
}
398+
399+
/// Construct a [`crate::RawDocumentBuf`] value.
400+
///
401+
/// ```rust
402+
/// use bson::rawdoc;
403+
///
404+
/// let value = rawdoc! {
405+
/// "code": 200,
406+
/// "success": true,
407+
/// "payload": {
408+
/// "some": [
409+
/// "pay",
410+
/// "loads",
411+
/// ]
412+
/// }
413+
/// };
414+
/// ```
415+
#[macro_export]
416+
macro_rules! rawdoc {
417+
() => {{ $crate::RawDocumentBuf::new() }};
418+
( $($tt:tt)+ ) => {{
419+
let mut object = $crate::RawDocumentBuf::new();
420+
$crate::rawbson!(@object object () ($($tt)+) ($($tt)+));
421+
object
422+
}};
423+
}

0 commit comments

Comments
 (0)