-
-
Save durka/763519a8403b229750c5 to your computer and use it in GitHub Desktop.
Rust tt-muncher macro implementing C++-like visibility labels for struct members
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
/// Wrapper for a struct declaration using C++-like "pub:" and "priv:" labels instead of Rust's individual member annotations | |
/// | |
/// Syntax is similar to a normal pub struct declaration (see example below) | |
/// The struct is given an automatic pub fn new() method which simply takes all members in order -- without this, there would be no way to construct an instance due to the private members | |
macro_rules! sticky_visibility { | |
// START INTERNAL RULES | |
// defeat the parser | |
(@as_item $i:item) => ($i); | |
// following rules are an incremental tt-munching parser for the input struct body | |
// $cur tracks the current visibility state | |
// accumulators build up the output struct body ($members), plus the element names ($elemname) and types ($elemtyp) which we will need to implement new() | |
// end of the input struct body: output the struct and an impl containing new() | |
(@parse [$typ:ident] $_cur:tt | |
$members:tt [$($elemname:ident),*] [$($elemtyp:ty),*] | |
{ $(,)* } | |
) => { | |
sticky_visibility! { @as_item | |
pub struct $typ $members | |
} | |
impl $typ { | |
pub fn new($($elemname: $elemtyp),*) -> $typ { | |
$typ { | |
$($elemname: $elemname),* | |
} | |
} | |
} | |
}; | |
// switch the current visibility to public | |
(@parse $t_:tt $_cur:tt | |
$members:tt | |
$en_:tt $et_:tt | |
{ pub: $($body:tt)* } | |
) => { | |
sticky_visibility! { @parse $t_ [pub] | |
$members | |
$en_ $et_ | |
{ $($body)* } | |
} | |
}; | |
// switch the current visibility to private | |
(@parse $t_:tt $_cur:tt | |
$members:tt | |
$en_:tt $et_:tt | |
{ priv: $($body:tt)* } | |
) => { | |
sticky_visibility! { @parse $t_ [] | |
$members | |
$en_ $et_ | |
{ $($body)* } | |
} | |
}; | |
// eat a struct member (not the last one) | |
(@parse $t_:tt [$($cur:ident)*] | |
{ $($members:tt)* } | |
[$($elemname:ident),*] [$($elemtyp:ty),*] | |
{ $n:ident: $t:ty, $($body:tt)* } | |
) => { | |
sticky_visibility! { @parse $t_ [$($cur)*] | |
{ $($members)* $($cur)* $n: $t, } | |
[$($elemname,)* $n] [$($elemtyp,)* $t] | |
{ $($body)* } | |
} | |
}; | |
// eat the last struct member | |
(@parse $t_:tt [$($cur:ident)*] | |
{ $($members:tt)* } | |
[$($elemname:ident),*] [$($elemtyp:ty),*] | |
{ $n:ident: $t:ty } | |
) => { | |
sticky_visibility! { @parse $t_ [$($cur)*] | |
{ $($members)* $($cur)* $n: $t, } | |
[$($elemname,)* $n] [$($elemtyp,)* $t] | |
{ } | |
} | |
}; | |
// END INTERNAL RULES | |
// macro entry point | |
(pub struct $typ:ident { $($body:tt)* }) => { | |
sticky_visibility!(@parse [$typ] [] {} [] [] { $($body)* }); | |
}; | |
} | |
// there's no point testing visibility without an inner module | |
mod inner { | |
sticky_visibility! { | |
// struct must be public (otherwise why would you care about visibility?) | |
pub struct Foo { // no generics allowed (limitation of the current macro) | |
pub: | |
a: i32, | |
b: bool, // this comma required (fundamental limitation of macro syntax) | |
priv: | |
c: (i32, bool), | |
d: char, // this comma not required | |
} | |
} | |
} | |
use inner::Foo; | |
fn main() { | |
// constructor works ok, but let's try accessing the members... | |
let foo = Foo::new(42, true, (1, false), '\0'); | |
println!("{:?} {:?}", foo.a, foo.b); // ok | |
println!("{:?} {:?}", foo.c, foo.d); // error | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment