Skip to content

Instantly share code, notes, and snippets.

@monadplus
Created May 14, 2026 11:24
Show Gist options
  • Select an option

  • Save monadplus/9eb57dd2945fac9f4b995ae589398da4 to your computer and use it in GitHub Desktop.

Select an option

Save monadplus/9eb57dd2945fac9f4b995ae589398da4 to your computer and use it in GitHub Desktop.
Declarative macros in Rust
#![feature(trace_macros)]
#![feature(log_syntax)]
// Rule of thumb
// - write macro rules from most-specific to least-specific.
// - Write macros at the top of your module.
// To remember:
// - Once the parser begins consuming tokens for a capture, it cannot stop or backtrack.
// - Substitution is not token-based.
// - hygienic: all identifiers have an invisible "syntax context" inside a macro
// - `self` is both a keyword and an identifier.
// - Macros will remain visible in submodules
// - Only accessible after their definition
// - Macros do not leak out of their module
// - Any public macro must bring with it all other macros that it depends on
// - Macros must result in a complete, supported syntax element.
//////////////////////////////////////////////////////////////////
// Metavariables
/*
- item: modules, use, functions, type, struct, enum, union, const, static, trait, impl,
- block: { stmt* }, async { }
- stmt: item, let, expr;
- pat/pat_param: any kind of pattern (let decl, function/closure params, match, if let, while let, for)
- expr
- ty
- ident: foo, _ident, r#true, Москва
- path: ::foo::faa..
- tt:
- meta: #![] , #[]
- lifetime: 'a, '_
- vis: pub, pub (crate/self/super/in path)
- literal: 'a', "hello", r"", 0b, 0o 0x, 12e27,
https://doc.rust-lang.org/reference/macros-by-example.html#metavariables
*/
//////////////////////////////////////////////////////////////////
// Captures and Expansion Redux
// Once the parser begins consuming tokens for a capture, it cannot stop or backtrack.
// Second rule will never match.
// For example, dead_rule!(x+) will be matched against $e:expr but it will fail because of '+' not having a rhs.
// macro_rules! dead_rule {
// ($e:expr) => { ... };
// ($i:ident +) => { ... };
// }
// What can follow a captured group:
// item: anything.
// block: anything.
// stmt: => , ;
// pat: => , = if in
// expr: => , ;
// ty: , => : = > ; as
// ident: anything.
// path: , => : = > ; as
// meta: anything.
// tt: anything.
macro_rules! capture_then_match_tokens {
($e:expr) => {
match_tokens!($e)
};
}
macro_rules! match_tokens {
($a:tt + $b:tt) => {
"got an addition"
};
(($i:ident)) => {
"got an identifier"
};
($($other:tt)*) => {
"got something else"
};
}
// Substitution is not token-based, but AST-based.
// capture_then_match_tokens!(3 + 6) will not match because
// 3 + 6 has been merged into a single node before caling match_tokens
#[test]
fn match_tokens_test() {
// got an identifier
// got an addition
// got something else
println!(
"{}\n{}\n{}\n",
match_tokens!((caravan)),
match_tokens!(3 + 6),
match_tokens!(5)
);
// got something else
// got something else
// got something else
println!(
"{}\n{}\n{}",
capture_then_match_tokens!((caravan)),
capture_then_match_tokens!(3 + 6),
capture_then_match_tokens!(5)
);
}
// To avoid this, use `tt` to capture.
//////////////////////////////////////////////////////////////////
// Hygiene
// All identifiers have an invisible "syntax context" inside a macro
macro_rules! using_a {
($a:ident, $e:expr) => {{
let $a = 42;
$e
}};
}
#[test]
fn hygiene_test() {
let four = using_a!(a, a / 10);
assert_eq!(four, 4);
}
// Recall to use $create::foo::faa::f() to refer to identifiers.
//////////////////////////////////////////////////////////////////
// Identifiers
// `self` is both a keyword and an identifier.
// WRONG
//
// macro_rules! make_mutable {
// ($i:ident) => {let mut $i = $i;};
// }
//
// struct Dummy(i32);
//
// impl Dummy {
// fn double(self) -> Dummy {
// make_mutable!(self);
// self.0 *= 2;
// self
// }
// }
macro_rules! double_method {
($self_:ident, $body:expr) => {
fn double(mut $self_) -> Dummy {
$body
}
};
}
struct Dummy(i32);
impl Dummy {
double_method! {self, {
self.0 *= 2;
self
}}
}
// macro_rules! double_method2 {
// ($self_:ident, $body:expr) => {
// fn double($self_) -> Dummy2 {
// Dummy2($body)
// }
// };
// }
macro_rules! double_method2 {
($self_:pat, $body:expr) => {
fn double($self_: Self) -> Dummy2 {
Dummy2($body)
}
};
}
struct Dummy2(i32);
impl Dummy2 {
double_method2! {_, 0}
}
//////////////////////////////////////////////////////////////////
// Debugging
// - trace_macros!(true); ...; trace_macros!(false);
// - log_syntax!($tt);
//////////////////////////////////////////////////////////////////
// Scoping
// - Macros will remain visible in submodules
// - Only accessible after their definition (textual scoping)
// - Unless you use #[macro_export]
// - Macros do not leak out of their module
// - Can be declared multiple times
// - #[macro_use]
// - declare on top of the module containing the macro
// - export all macros from mod
// - #[macro_export]
// - acts like declared in the crate root
// - export from crate
//////////////////////////////////////////////////////////////////
// Practical examples
#[macro_export]
macro_rules! expr_count {
() => (0);
($e:expr) => (1);
($e:expr; $($other_e:expr);*) => ({
log_syntax!($e); 1 $(+ $crate::expr_count!($other_e) )*
});
($e:expr; $($other_e:expr);* ; ) => (
$crate::expr_count! { $e; $($other_e);* }
);
}
#[test]
fn expr_count_test() {
assert_eq!(expr_count!(1;2;3;4;), 4);
}
/////////////////
macro_rules! identity {
(#[$m:meta] $ty: item) => {
#[$m]
$ty
};
}
#[test]
fn identity_test() {
trace_macros!(true);
identity!(
#[allow(dead_code)]
type Foo = i64;
);
trace_macros!(false);
}
/////////////////
#[macro_export]
macro_rules! hash_map {
($($key:expr => $val:expr),* ,) => (
$crate::hash_map!($($key => $val),*)
);
($($key:expr => $val:expr),*) => {
{
let capacity = $crate::expr_count!($($key);*);
let mut dict = ::std::collections::HashMap::with_capacity(capacity);
$( dict.insert($key, $val); )*
dict
}
};
}
#[test]
fn hashmap_macro() {
use std::collections::HashMap;
let dict: HashMap<u64, u64> = hash_map!(0 => 0, 1 => 2);
assert_eq!(dict.get(&0u64), Some(&0u64));
assert_eq!(dict.get(&1u64), Some(&2u64));
let dict: HashMap<u64, u64> = hash_map! {
0 => 0,
1 => 2,
};
assert_eq!(dict.get(&0u64), Some(&0u64));
assert_eq!(dict.get(&1u64), Some(&2u64));
}
/////////////////
macro_rules! recurrence {
// recurrence![a[n]: u64 = 0, 1 ; ... ; a[n-1] + a[n-2]];
// a [ n ]: u64 = 0, 1 ; ... ; (a[n-1] + a[n-2])
( $seq:ident [ $ind: ident ]: $sty:ty = $($inits:expr),+ ; ... ; $recur:expr ) => {
{
/*
What follows here is *literally* the code from before,
cut and pasted into a new position. No other changes
have been made.
*/
use std::ops::Index;
const MEM_SIZE: usize = expr_count!($($inits);*);
struct Recurrence {
mem: [$sty; MEM_SIZE],
pos: usize,
}
struct IndexOffset<'a> {
slice: &'a [$sty; MEM_SIZE],
offset: usize,
}
impl<'a> Index<usize> for IndexOffset<'a> {
type Output = $sty;
#[inline(always)]
fn index<'b>(&'b self, index: usize) -> &'b $sty {
use std::num::Wrapping;
let index = Wrapping(index);
let offset = Wrapping(self.offset);
let window = Wrapping(MEM_SIZE);
let real_index = index - offset + window;
&self.slice[real_index.0]
}
}
impl Iterator for Recurrence {
type Item = $sty;
#[inline]
fn next(&mut self) -> Option<$sty> {
let next_val =
if self.pos < MEM_SIZE {
self.mem[self.pos]
} else {
let next_val = {
let $ind = self.pos;
let $seq = IndexOffset { slice: &self.mem, offset: $ind };
$recur // (a[n-1] + a[n-2])
};
self.mem.rotate_left(1);
self.mem[MEM_SIZE-1] = next_val;
next_val
};
self.pos += 1;
Some(next_val)
}
}
Recurrence { mem: [$($inits),+], pos: 0 }
}
};
}
#[test]
fn recurrence_test() {
let fib = recurrence![a[n]: u64 = 1, 1 ; ... ; a[n-1] + a[n-2]];
assert_eq!(
fib.take(10).collect::<Vec<_>>(),
vec!(1, 1, 2, 3, 5, 8, 13, 21, 34, 55),
);
}
//////////////////////////////////////////////////////////////////
// Callbacks
macro_rules! call_with_larch {
($callback:ident) => {
$callback!(larch)
};
}
macro_rules! expand_to_larch {
() => {
larch
};
}
macro_rules! recognise_tree {
(larch) => {
println!("#1, the Larch.")
};
(redwood) => {
println!("#2, the Mighty Redwood.")
};
(fir) => {
println!("#3, the Fir.")
};
(chestnut) => {
println!("#4, the Horse Chestnut.")
};
(pine) => {
println!("#5, the Scots Pine.")
};
($($other:tt)*) => {
println!("I don't know; some kind of birch maybe?")
};
}
// Using a tt repetition, one can also forward arbitrary arguments to a callback.
macro_rules! callback {
($callback:ident($($args:tt)*)) => {
$callback!($($args)*)
};
}
#[test]
fn callback_test() {
// Macro expan_to_larch! is not expanded before evaluating recognise_tree
// I don't know; some kind of birch maybe?
recognise_tree!(expand_to_larch!());
// #1, the Larch.
call_with_larch!(recognise_tree);
// "Yes, this *was* unnecessary."
callback!(callback(println("Yes, this *was* unnecessary.")));
}
//////////////////////////////////////////////////////////////////
// Incremental TT Munchers
// A "TT muncher" is a recursive macro that works by incrementally processing
// its input one step at a time. At each step, it matches and removes (munches)
// some sequence of tokens from the start of its input, generates some intermediate output,
// then recurses on the input tail.
macro_rules! mixed_rules {
() => {};
(trace $name:ident; $($tail:tt)*) => {
{
println!(concat!(stringify!($name), " = {:?}"), $name);
mixed_rules!($($tail)*);
}
};
(trace $name:ident = $init:expr; $($tail:tt)*) => {
{
let $name = $init;
println!(concat!(stringify!($name), " = {:?}"), $name);
mixed_rules!($($tail)*);
}
};
}
#[test]
fn incremental_tt_test() {
let x: u64 = 0;
mixed_rules! {
trace a = 0;
trace b = 1;
trace x;
}
}
//////////////////////////////////////////////////////////////////
// Internal rules
// Any public macro must bring with it all other macros that it depends on
// A good solution is to conceal what would otherwise be other public macros
// inside the macro being exported.
#[macro_export]
macro_rules! all {
// Using '@' is a widespread convention
(@as_expr $e:expr) => {$e};
($($tts:tt)*) => {
all!(@as_expr $($tts)*)
};
}
/*
macro_rules! crate_name_util {
(@as_expr $e:expr) => {$e};
(@as_item $i:item) => {$i};
(@count_tts) => {0usize};
// ...
}
*/
//////////////////////////////////////////////////////////////////
// Push-down accumulation
// Macros must result in a complete, supported syntax element.
// Does not compile:
//
// macro_rules! init_array {
// (@accum 0, $_e:expr) => {/* empty */};
// (@accum 1, $e:expr) => {$e};
// (@accum 2, $e:expr) => {$e, init_array!(@accum 1, $e)};
// (@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)};
// [$e:expr; $n:tt] => {
// {
// let e = $e;
// [init_array!(@accum $n, e)]
// }
// };
// }
//
// [init_array!(@accum 3, e)]
// [e, init_array!(@accum 2, e)] <- each expansion must be a complete expression
// [e, e, init_array!(@accum 1, e)]
// [e, e, e]
macro_rules! init_array {
(@as_expr $e:expr) => {$e};
(@accum (0, $_e:expr) -> ($($body:tt)*))
=> {[$($body)*]};
(@accum (1, $e:expr) -> ($($body:tt)*))
// Trailing ',' is ignored in array
=> {init_array!(@accum (0, $e) -> ($($body)* $e,))};
(@accum (2, $e:expr) -> ($($body:tt)*))
=> {init_array!(@accum (1, $e) -> ($($body)* $e,))};
(@accum (3, $e:expr) -> ($($body:tt)*))
=> {init_array!(@accum (2, $e) -> ($($body)* $e,))};
[$e:expr; $n:tt] => {
{
let e = $e;
init_array!(@accum ($n, e.clone()) -> ())
}
};
}
// init_array! { String:: from ( "hi!" ) ; 3 }
// init_array! { @ accum ( 3 , e.clone() ) -> () } <-- this is ok since the expansion is inside a macro
// init_array! { @ accum ( 2 , e.clone() ) -> ( e.clone() , ) }
// init_array! { @ accum ( 1 , e.clone() ) -> ( e.clone() , e.clone() , ) }
// init_array! { @ accum ( 0 , e.clone() ) -> ( e.clone() , e.clone() , e.clone() , ) }
// init_array! { @ as_expr [ e.clone() , e.clone() , e.clone() , ] }
#[test]
fn internal_rules_test() {
let strings: [String; 3] = init_array![String::from("hi!"); 3];
assert_eq!(
strings,
[
String::from("hi!"),
String::from("hi!"),
String::from("hi!")
]
);
}
//////////////////////////////////////////////////////////////////
// Repetition Replacement
macro_rules! tuple_default {
($($tup_tys:ty),*) => {
(
$( <$tup_tys as Default>::default() ),*
)
};
}
macro_rules! replace_expr {
($_t:tt $sub:expr) => {
$sub
};
}
macro_rules! tuple_default_v2 {
($($tup_tys:ty),*) => {
( $( replace_expr!{($tup_tys) Default::default()} ),* ) as ( $( $tup_tys ),* )
};
}
#[test]
fn repetition_replacement_test() {
let x = tuple_default!(u32, String, char);
assert_eq!(x, (0u32, "".to_owned(), '\u{0}'));
let x = tuple_default_v2!(u32, String, char);
assert_eq!(x, (0u32, "".to_owned(), '\u{0}'));
}
//////////////////////////////////////////////////////////////////
// Trailing separators
macro_rules! match_exprs {
// $($exprs:expr),* no trailing commas
// $($exprs:expr,)* trailing commas
($($exprs:expr),* $(,)*) => {...}; // both
}
//////////////////////////////////////////////////////////////////
// TT Bundling
// Complex macros requires lots of arguments.
// Bundle them in a 'tt' until they are needed
macro_rules! call_a_or_b_on_tail {
((a: $a:expr, b: $b:expr), call a: $($tail:tt)*) => {
$a(stringify!($($tail)*))
};
((a: $a:expr, b: $b:expr), call b: $($tail:tt)*) => {
$b(stringify!($($tail)*))
};
// Start here
($ab:tt, $_skip:tt $($tail:tt)*) => {
call_a_or_b_on_tail!($ab, $($tail)*)
};
}
fn compute_len(s: &str) -> Option<usize> {
Some(s.len())
}
fn show_tail(s: &str) -> Option<usize> {
println!("tail: {:?}", s);
None
}
#[test]
fn tt_bundling_test() {
trace_macros!(true);
assert_eq!(
call_a_or_b_on_tail!(
(a: compute_len, b: show_tail),
the recursive part that skips over all these
or call b: only the terminal rules care.
),
None
);
trace_macros!(false);
assert_eq!(
call_a_or_b_on_tail!(
(a: compute_len, b: show_tail),
and now, to justify the existence of two paths
we will also call a: its input should somehow
some ninety one!
),
Some(41)
);
}
//////////////////////////////////////////////////////////////////
// Provisional
// push-down accumulation + incremental TT
macro_rules! abacus {
((- $($moves:tt)*) -> (+ $($count:tt)*)) => {
abacus!(($($moves)*) -> ($($count)*))
};
((- $($moves:tt)*) -> ($($count:tt)*)) => {
abacus!(($($moves)*) -> (- $($count)*))
};
((+ $($moves:tt)*) -> (- $($count:tt)*)) => {
abacus!(($($moves)*) -> ($($count)*))
};
((+ $($moves:tt)*) -> ($($count:tt)*)) => {
abacus!(($($moves)*) -> (+ $($count)*))
};
// Check if the final result is zero.
(() -> ()) => { true };
(() -> ($($count:tt)+)) => { false };
}
#[test]
fn abacus_test() {
let equals_zero = abacus!((++-+-+++--++---++----+) -> ());
assert_eq!(equals_zero, true);
}
//////////////////////////////////////////////////////////////////
// AST Coercion
// No longer needed.
//////////////////////////////////////////////////////////////////
// Counting
// Counting is tricky
macro_rules! replace_expr {
($_t:tt $sub:expr) => {
$sub
};
}
// crashes on inputs of 500 tokens
macro_rules! count_tts {
($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*};
}
#[test]
fn count_tts_test() {
assert_eq!(
700,
count_tts!(
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
// Repetition breaks somewhere after this
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
)
);
}
// Recursion limit is hit very fast
// macro_rules! count_tts {
// () => {0usize};
// ($_head:tt $($tail:tt)*) => {1usize + count_tts!($($tail)*)};
// }
macro_rules! count_tts_rec {
($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt
$_f:tt $_g:tt $_h:tt $_i:tt $_j:tt
$_k:tt $_l:tt $_m:tt $_n:tt $_o:tt
$_p:tt $_q:tt $_r:tt $_s:tt $_t:tt
$($tail:tt)*)
=> {20usize + count_tts_rec!($($tail)*)};
($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt
$_f:tt $_g:tt $_h:tt $_i:tt $_j:tt
$($tail:tt)*)
=> {10usize + count_tts_rec!($($tail)*)};
($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt
$($tail:tt)*)
=> {5usize + count_tts_rec!($($tail)*)};
($_a:tt
$($tail:tt)*)
=> {1usize + count_tts_rec!($($tail)*)};
() => {0usize};
}
#[test]
fn count_tts_rec_test() {
assert_eq!(
700,
count_tts_rec!(
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
// Repetition breaks somewhere after this
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
)
);
}
macro_rules! replace_expr {
($_t:tt $sub:expr) => {
$sub
};
}
// Only drawback is that it is not a constant
macro_rules! count_tts_slice {
($($tts:tt)*) => {<[()]>::len(&[$(replace_expr!($tts ())),*])};
}
#[test]
fn count_tts_slic_test() {
assert_eq!(
700,
count_tts_slice!(
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
// Repetition breaks somewhere after this
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
)
);
}
//////////////////////////////////////////////////////////////////
// Enum parsing
// Parse enum where all variants are unitary and calls the callback with the list of variants.
macro_rules! parse_unitary_variants {
(@as_expr $e:expr) => {$e};
(@as_item $($i:item)+) => {$($i)+};
// Exit rules.
(
@collect_unitary_variants ($callback:ident ( $($args:tt)* )),
($(,)*) -> ($($var_names:ident,)*)
) => {
parse_unitary_variants! {
@as_expr
$callback!{ $($args)* ($($var_names),*) }
}
};
(
@collect_unitary_variants ($callback:ident { $($args:tt)* }),
($(,)*) -> ($($var_names:ident,)*)
) => {
parse_unitary_variants! {
@as_item
$callback!{ $($args)* ($($var_names),*) }
}
};
// Consume an attribute.
(
@collect_unitary_variants $fixed:tt,
(#[$_attr:meta] $($tail:tt)*) -> ($($var_names:tt)*)
) => {
parse_unitary_variants! {
@collect_unitary_variants $fixed,
($($tail)*) -> ($($var_names)*)
}
};
// Handle a variant, optionally with an with initialiser.
(
@collect_unitary_variants $fixed:tt,
($var:ident $(= $_val:expr)?, $($tail:tt)*) -> ($($var_names:tt)*)
) => {
parse_unitary_variants! {
@collect_unitary_variants $fixed,
($($tail)*) -> ($($var_names)* $var,)
}
};
// Abort on variant with a payload.
(
@collect_unitary_variants $fixed:tt,
($var:ident $_struct:tt, $($tail:tt)*) -> ($($var_names:tt)*)
) => {
const _error: () = "cannot parse unitary variants from enum with non-unitary variants";
};
// Entry rule.
(enum $name:ident {$($body:tt)*} => $callback:ident $arg:tt) => {
parse_unitary_variants! {
@collect_unitary_variants
($callback $arg), ($($body)*,) -> ()
}
};
}
// TODO: I have no idea how to call this macro.
// macro_rules! print_callback {
// ($($names:expr),*) => (log_syntax!($($names),*); ());
// }
// #[test]
// fn parse_unitary_variants_test() {
// parse_unitary_variants!{
// enum A {
// A, B, C
// } => print_callback!()
// }
// }
/////////////////////////////////////////////////
// Examples
macro_rules! read_stdin {
($v: ident : $t: ty) => {
let mut buf = String::new();
let _ = ::std::io::stdin()
.read_line(&mut buf)
.expect("Failed to read input");
let $v = buf.trim().trim_end().parse::<$t>().unwrap();
};
($v: ident : vec $t: ty) => {
let mut buf = String::new();
let _ = ::std::io::stdin()
.read_line(&mut buf)
.expect("Failed to read input");
let $v: Vec<$t> = buf
.split_whitespace()
.map(|x| x.trim().trim_end().parse::<$t>().unwrap())
.collect::<Vec<$t>>();
};
}
#[test]
#[ignore = "depends on stdin (blocking)"]
fn test_read_stdin() {
read_stdin!(n: i32);
read_stdin!(v: vec f64);
}
// Source: https://github.com/thepacketgeek/rust-macros-demo/blob/master/timeit/src/lib.rs
#[macro_export]
macro_rules! timeit {
// Attempt to match function name & args
// ```ignore
// timeit!(something_slow());
// ```
// > 'wait_for_it' took 2000 ms
($n:ident ( $($args:expr),*)) => {{
let _start = std::time::Instant::now();
let _res = $n($($args,)*);
// Use the function name (ident) in the log
eprintln!("'{}' took {:.3} ms", stringify!($n), _start.elapsed().as_millis());
_res
}};
// Otherwise take a function by name:
// ```ignore
// timeit!(my_func);
// ```
// > Took 2000 ms
($e:expr) => {{
let _start = std::time::Instant::now();
let _res = $e();
eprintln!("Took {:.3} ms", _start.elapsed().as_millis());
_res
}};
// Otherwise take a function by name, and a log prefix
// ```ignore
// timeit!(my_func, "My Func");
// ```
// > My Func took 2000 ms
($e:expr, $desc:literal) => {{
let _start = std::time::Instant::now();
let _res = $e();
eprintln!("{} took {:.3} ms", $desc, _start.elapsed().as_millis());
_res
}};
}
#[test]
fn timeit_test() {
timeit!(|| { std::thread::sleep(std::time::Duration::from_millis(100)) });
timeit!(
|| { std::thread::sleep(std::time::Duration::from_millis(100)) },
"Sleeping"
);
fn wait_for_it() -> String {
std::thread::sleep(std::time::Duration::from_millis(100));
return String::from("...Legendary!");
}
timeit!(wait_for_it);
fn slow_sum(a: u32, b: u32) -> u32 {
std::thread::sleep(std::time::Duration::from_millis(100));
a + b
}
assert_eq!(14, timeit!(slow_sum(5, 9)));
}
// Source: https://github.com/thepacketgeek/rust-macros-demo/blob/master/retryable/src/lib.rs
/// Expand a variadic number of macro args to a function call w/ args
///
/// ```
/// fn double_sum(a: u32, b: u32) -> u32 {
/// (a + b) * 2
/// }
///
/// assert_eq!(_wrapper!(double_sum, 4, 2), 12);
/// assert_eq!(_wrapper!(double_sum, 4, 2,), 12);
/// ```
macro_rules! _wrapper {
// Single expression (like a function name or closure)
($f:expr) => {{
$f()
}};
// Variadic number of args (Allowing trailing comma)
($f:expr, $( $args:expr $(,)? )* ) => {{
$f( $( $args, )* )
}};
}
/// A simple retry macro to immediately attempt a function call after failure
///
/// To use, pass a function and arguments:
/// ```ignore
/// retry!(my_fallible_func, 0, "something");
/// ```
/// Default retry count is 3 (3rd failure will return Err())
///
/// Specify a different number of retries like:
/// ```ignore
/// retry!(my_fallible_func, 0, "something"; retries=5);
/// ```
#[macro_export]
macro_rules! retry {
($( $args:expr$(,)? )+; retries=$r:literal) => {{
let mut retries = $r;
loop {
let res = _wrapper!($( $args, )*);
if res.is_ok() {
break res;
}
if retries > 0 {
retries -= 1;
continue;
}
break res;
}
}};
// Function & args only, use default of 3 retries
($( $args:expr$(,)? )+) => {{
retry!($( $args, )*; retries = 3)
}};
}
#[test]
fn retry_test() {
use rand::Rng;
macro_rules! succeed_after {
($count:expr) => {{
let mut _iter = (0..$count).into_iter();
move || {
if _iter.next().is_some() {
return Err(());
}
Ok(())
}
}};
}
fn sometimes_fail(failure_rate: u8) -> Result<(), ()> {
assert!(failure_rate <= 100, "Failure rate is a % (0..=100)");
let mut rng = rand::thread_rng();
let val = rng.gen_range(0u8..100u8);
if val > failure_rate {
Ok(())
} else {
Err(())
}
}
let mut eventually_succeed = succeed_after!(2);
let res = retry!(eventually_succeed);
assert!(res.is_ok());
let mut eventually_succeed = succeed_after!(5);
let res = retry!(eventually_succeed; retries=4);
assert!(res.is_err());
let mut eventually_succeed = succeed_after!(5);
let res = retry!(eventually_succeed; retries=10);
assert!(res.is_ok());
let fallible = || sometimes_fail(50);
let res = retry!(fallible);
assert!(res.is_ok());
let res = retry!(sometimes_fail, 50);
assert!(res.is_ok());
}
use std::collections::BTreeMap;
trait ToValue {
fn to_value(self) -> Value;
}
impl ToValue for bool {
fn to_value(self) -> Value {
Value::Bool(self)
}
}
impl ToValue for &str {
fn to_value(self) -> Value {
Value::String(self.to_string())
}
}
impl ToValue for String {
fn to_value(self) -> Value {
Value::String(self)
}
}
impl ToValue for i32 {
fn to_value(self) -> Value {
Value::Number(self as f64)
}
}
impl ToValue for u32 {
fn to_value(self) -> Value {
Value::Number(self as f64)
}
}
impl ToValue for u64 {
fn to_value(self) -> Value {
Value::Number(self as f64)
}
}
impl ToValue for f32 {
fn to_value(self) -> Value {
Value::Number(self as f64)
}
}
impl ToValue for f64 {
fn to_value(self) -> Value {
Value::Number(self)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
String(String),
Number(f64),
Array(Vec<Value>),
Object(BTreeMap<String, Value>),
}
#[macro_export]
macro_rules! json {
////////////////////////////////////////////
// Array
////////////////////////////////////////////
// Base case
(@array [$($elems:expr),* $(,)?]) => {
vec![$($elems),*]
};
(@array [$($elems:expr),*] []) => {
json!(@array [$($elems,)*])
};
// Parse last element and go to base case
(@array [$($elems:expr),*] [$last:tt]) => {
json!(@array [$($elems,)* json!($last)])
};
// Parse value, keep processing
(@array [$($elems:expr),*] [$head:tt, $($tail:tt)*]) => {
json!(@array [ $($elems,)* json!($head) ] [ $($tail)* ])
};
////////////////////////////////////////////
// Object
////////////////////////////////////////////
// Base case
(@object $object:ident () ()) => {
};
// Insert KV
(@object $object:ident [$($key:tt)+] ($value:expr, $($rest:tt)*)) => {
let _ = $object.insert(($($key)+).into(), $value);
json!(@object $object () ($($rest)*));
};
// Insert KV (last)
(@object $object:ident [$($key:tt)+] ($value:expr)) => {
let _ = $object.insert(($($key)+).into(), $value);
};
// Parse value
(@object $object:ident ($($key:tt)+) (: $value:tt , $($rest:tt)*)) => {
json!(@object $object [$($key)+] (json!($value), $($rest)*));
};
// Parse value (last)
(@object $object:ident ($($key:tt)+) (: $value:tt)) => {
json!(@object $object [$($key)+] (json!($value)));
};
// Parse key
(@object $object:ident () ($key:literal : $($rest:tt)*)) => {
json!(@object $object ($key) (: $($rest)*));
};
////////////////////////////////////////////
// Base
////////////////////////////////////////////
(null) => {
$crate::Value::Null
};
// Empty array
([]) => {
$crate::Value::Array(json!(@array []))
};
// Array
([ $($tt:tt)+ ]) => {
$crate::Value::Array(json!(@array [] [$($tt)+]))
};
// Empty object
({}) => {
$crate::Value::Object(::std::collections::BTreeMap::new())
};
// Object
({ $($tt:tt)+ }) => {
$crate::Value::Object({
let mut object = ::std::collections::BTreeMap::new();
json!(@object object () ($($tt)+));
object
})
};
// Bool, Number and String
($other:expr) => {
$crate::ToValue::to_value($other)
};
}
/// When a token is unexpected, this will produce a nice error.
///
/// ```ignore
/// json_unexpected!{$colon}
/// ```
#[allow(unused_macros)]
macro_rules! json_unexpected {
() => {};
}
#[test]
fn json_test() {
// Null
assert_eq!(json!(null), Value::Null);
// Bool
assert_eq!(json!(false), Value::Bool(false));
assert_eq!(json!(true), Value::Bool(true));
// String
assert_eq!(json!("Hello"), Value::String("Hello".to_string()));
assert_eq!(
json!("Hello".to_string()),
Value::String("Hello".to_string())
);
// Number
assert_eq!(json!(0), Value::Number(0.0_f64));
assert_eq!(json!(99), Value::Number(99_f64));
assert_eq!(json!(0.0), Value::Number(0.0_f64));
assert_eq!(json!(99.99), Value::Number(99.99_f64));
// Array
assert_eq!(json!([]), Value::Array(vec![]));
assert_eq!(json!([null]), Value::Array(vec![Value::Null]));
assert_eq!(
json!([[null]]),
Value::Array(vec! {Value::Array(vec![Value::Null])})
);
assert_eq!(
json!([[null], [true]]),
Value::Array(vec! {
Value::Array(vec![Value::Null]),
Value::Array(vec![Value::Bool(true)]),
})
);
assert_eq!(json!([true]), Value::Array(vec![Value::Bool(true)]));
assert_eq!(json!([true,]), Value::Array(vec![Value::Bool(true)]));
assert_eq!(
json!([true, null]),
Value::Array(vec![Value::Bool(true), Value::Null])
);
assert_eq!(
json!([true, "hello", 12.5]),
Value::Array(vec![Value::Bool(true), "hello".to_value(), 12.5.to_value()])
);
// Object
assert_eq!(json!({}), Value::Object(BTreeMap::new()));
let mut object = BTreeMap::new();
object.insert("name".into(), Value::String("arnau".into()));
let expected = Value::Object(object);
let result = json!({
"name" : "arnau"
});
assert_eq!(expected, result);
let mut object = BTreeMap::new();
object.insert("name".into(), Value::String("arnau".into()));
object.insert("pet".into(), Value::String("dog".into()));
let expected = Value::Object(object);
let result = json!({
"name" : "arnau",
"pet" : "dog"
});
assert_eq!(result, expected);
let result = json!({
"name" : "arnau",
"pet" : "dog",
});
assert_eq!(result, expected);
let mut object = BTreeMap::new();
object.insert("name".into(), Value::String("arnau".into()));
object.insert(
"arr".into(),
Value::Array(vec![Value::Bool(true), Value::Null]),
);
let expected = Value::Object(object);
let result = json!({
"name" : "arnau",
"arr" : [
true,
null
]
});
assert_eq!(result, expected);
let mut object = BTreeMap::new();
object.insert("name".into(), Value::String("arnau".into()));
object.insert(
"arr".into(),
Value::Array(vec![Value::Bool(true), Value::Null]),
);
object.insert(
"obj".into(),
Value::Object({
let mut object = BTreeMap::new();
object.insert("name".into(), Value::String("arnau".into()));
object.insert("pet".into(), Value::String("dog".into()));
object
}),
);
let expected = Value::Object(object);
let result = json!({
"name" : "arnau",
"arr" : [
true,
null,
],
"obj" : {
"name": "arnau",
"pet": "dog",
},
});
assert_eq!(result, expected);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment