Created
May 14, 2026 11:24
-
-
Save monadplus/9eb57dd2945fac9f4b995ae589398da4 to your computer and use it in GitHub Desktop.
Declarative macros in Rust
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #![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