├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── codegen ├── Cargo.toml └── src │ ├── conf.rs │ ├── genstruct.rs │ ├── lib.rs │ └── util.rs ├── confs └── method.toml ├── docs └── imgs │ ├── proc_macro.png │ └── topic.png └── src ├── conf.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | .idea -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "codegen" 5 | version = "0.1.0" 6 | dependencies = [ 7 | "heck", 8 | "proc-macro2", 9 | "quote", 10 | "serde", 11 | "syn", 12 | "toml", 13 | ] 14 | 15 | [[package]] 16 | name = "heck" 17 | version = "0.3.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 20 | dependencies = [ 21 | "unicode-segmentation", 22 | ] 23 | 24 | [[package]] 25 | name = "proc-macro2" 26 | version = "1.0.18" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 29 | dependencies = [ 30 | "unicode-xid", 31 | ] 32 | 33 | [[package]] 34 | name = "proc_codegen" 35 | version = "0.1.0" 36 | dependencies = [ 37 | "codegen", 38 | "heck", 39 | "serde", 40 | "toml", 41 | ] 42 | 43 | [[package]] 44 | name = "quote" 45 | version = "1.0.7" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 48 | dependencies = [ 49 | "proc-macro2", 50 | ] 51 | 52 | [[package]] 53 | name = "serde" 54 | version = "1.0.111" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" 57 | dependencies = [ 58 | "serde_derive", 59 | ] 60 | 61 | [[package]] 62 | name = "serde_derive" 63 | version = "1.0.111" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" 66 | dependencies = [ 67 | "proc-macro2", 68 | "quote", 69 | "syn", 70 | ] 71 | 72 | [[package]] 73 | name = "syn" 74 | version = "1.0.30" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" 77 | dependencies = [ 78 | "proc-macro2", 79 | "quote", 80 | "unicode-xid", 81 | ] 82 | 83 | [[package]] 84 | name = "toml" 85 | version = "0.5.6" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" 88 | dependencies = [ 89 | "serde", 90 | ] 91 | 92 | [[package]] 93 | name = "unicode-segmentation" 94 | version = "1.6.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 97 | 98 | [[package]] 99 | name = "unicode-xid" 100 | version = "0.2.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 103 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proc_codegen" 3 | version = "0.1.0" 4 | authors = ["blackanger "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | heck = "0.3.1" 11 | toml = "0.5" 12 | serde = {version="1.0", features=["derive"]} 13 | 14 | [dependencies.codegen] 15 | version = "0.1" 16 | path = "codegen" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 过程宏:根据配置文件自动生成代码 2 | 3 | [如何加入「觉•学社」?](https://zhuanlan.zhihu.com/p/59517478) 4 | 5 | ![img](docs/imgs/topic.png) 6 | 7 | #### 一、 宏的工作原理介绍 8 | 9 | ![img](docs/imgs/proc_macro.png) 10 | 11 | ### 二、 相关库介绍:syn 和 quote 12 | 13 | - [syn]() 用于生成AST 14 | - [quote]() 用于生成TokenStream 15 | - [proc-macro2]() TokenStream API接口 16 | 17 | ### 三、 根据配置文件生成代码代码实现 18 | 19 | ### 小结: 20 | 21 | 1. 注意区分编译时和运行时 22 | 2. 宏自动迭代生成的字段、参数默认是字典序排列(因为配置文件里的是Map类型) 23 | 3. 宏在编译时不会对生成代码进行类型检查,所以需要小心 24 | 25 | ### 扩展练习: 26 | 27 | 1. 处理各种边界条件判断,重构 28 | 2. 支持结构体类型判断,当前只支持具名结构体,扩展为支持三种类型结构体。 29 | 3. 可以指定配置文件路径 30 | 4. 想办法固定生成函数参数的顺序 31 | 5. 自由发挥 32 | -------------------------------------------------------------------------------- /codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "codegen" 3 | version = "0.1.0" 4 | authors = ["blackanger "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | syn = "1.0.30" 14 | quote = "1.0.6" 15 | proc-macro2 = "1.0.18" 16 | heck = "0.3.1" 17 | toml = "0.5" 18 | serde = {version="1.0", features=["derive"]} -------------------------------------------------------------------------------- /codegen/src/conf.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use toml::map::Map; 3 | use toml::Value; 4 | use std::fs::File; 5 | use std::io::Read; 6 | use crate::CONF_FILE_PATH; 7 | 8 | #[derive(Deserialize)] 9 | #[derive(Debug)] 10 | pub(crate) struct Basic { 11 | pub(crate) derive: String, 12 | } 13 | 14 | #[derive(Deserialize)] 15 | #[derive(Debug)] 16 | pub(crate) struct StructEntry { 17 | pub(crate) name: String, 18 | pub(crate) tagged: String, 19 | pub(crate) fields: Option>, 20 | } 21 | 22 | #[derive(Deserialize)] 23 | #[derive(Debug)] 24 | pub(crate) struct Conf{ 25 | pub(crate) basic: Basic, 26 | pub(crate) structs: Vec, 27 | } 28 | 29 | impl Conf { 30 | 31 | pub fn read_config() -> Self { 32 | let file_path = CONF_FILE_PATH; 33 | let mut file = match File::open(file_path) { 34 | Ok(f) => f, 35 | Err(e) => panic!("no such file {} exception:{}", file_path, e) 36 | }; 37 | 38 | let mut str_val = String::new(); 39 | match file.read_to_string(&mut str_val) { 40 | Ok(s) => s, 41 | Err(e) => panic!("Error Reading file: {}", e) 42 | }; 43 | // Conditions for Unwrap: 44 | // Struct field names must correspond to toml configuration files 45 | toml::from_str::(&str_val).unwrap() 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /codegen/src/genstruct.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// gen_struct! { 4 | /// Person -> {name: String, age: u32} 5 | /// } 6 | 7 | pub(crate) struct Genstruct { 8 | pub name: Ident, 9 | pub brace_token: token::Brace, 10 | pub fields: Punctuated, 11 | } 12 | 13 | impl Parse for Genstruct { 14 | fn parse(input: ParseStream) -> Result { 15 | let fields; 16 | 17 | let name: Ident = input.parse()?; 18 | input.parse::]>()?; 19 | let brace_token = braced!(fields in input); 20 | let fields = fields.parse_terminated(Field::parse_named)?; 21 | 22 | Ok(Genstruct { 23 | name, 24 | brace_token, 25 | fields, 26 | }) 27 | } 28 | } 29 | 30 | 31 | pub(crate) fn struct_to_tokenstream(input: TokenStream) -> TokenStream { 32 | let Genstruct { 33 | name, 34 | brace_token: _, 35 | fields, 36 | } = parse_macro_input!(input as Genstruct); 37 | 38 | let fields_quote = fields.iter().map( 39 | |field| { 40 | let name = &field.ident; 41 | let ty = &field.ty; 42 | quote!(#name: #ty) 43 | } 44 | ); 45 | 46 | let args_quote = fields.iter().map( 47 | |field| { 48 | let name = &field.ident; 49 | let ty = &field.ty; 50 | quote!(#name: #ty) 51 | } 52 | ); 53 | 54 | 55 | let args_name_quote = fields.iter().map( 56 | |field| { 57 | let name = &field.ident; 58 | quote!(#name) 59 | } 60 | ); 61 | 62 | let struct_name = util::to_ident(name.to_string().to_camel_case().as_ref()); 63 | 64 | let expanded = quote! { 65 | 66 | #[derive(Debug)] 67 | pub struct #struct_name { 68 | #( #fields_quote, )* 69 | } 70 | 71 | impl #struct_name { 72 | pub fn new( #(#args_quote,)* ) -> Self { 73 | #struct_name { #(#args_name_quote,)* } 74 | } 75 | } 76 | 77 | }; 78 | 79 | TokenStream::from(expanded) 80 | } 81 | 82 | 83 | pub(crate) struct ConfPath { 84 | pub dir: Ident, 85 | pub file: Ident, 86 | } 87 | 88 | 89 | // Conf { 90 | // 91 | // basic: Basic { derive: "Debug" }, 92 | // 93 | // structs: [ 94 | // StructEntry { 95 | // name: "person", 96 | // tagged: "named", 97 | // fields: Some({"age": String("u32"), "name": String("String")}) 98 | // }, 99 | // ] } 100 | 101 | pub(crate) fn struct_by_conf_to_tokenstream(input: TokenStream) -> TokenStream { 102 | let conf = Conf::read_config(); 103 | 104 | let derive_name = util::to_ident(conf.basic.derive.as_ref()); 105 | let structs_quote = conf.structs.iter().map(|s| { 106 | let mut fields_quote = { 107 | let mut args = vec![]; 108 | for (k, v) in s.fields.as_ref().unwrap().iter() { 109 | let name = util::to_ident(k); 110 | let ty = util::to_ident(v.as_str().unwrap()); 111 | args.push(quote!(#name: #ty)) 112 | } 113 | args 114 | }; 115 | 116 | let mut args_quote = { 117 | let mut args = vec![]; 118 | for (k, v) in s.fields.as_ref().unwrap().iter() { 119 | let name = util::to_ident(k); 120 | let ty = util::to_ident(v.as_str().unwrap()); 121 | args.push(quote!(#name: #ty)) 122 | } 123 | args 124 | }; 125 | 126 | let mut args_name_quote = { 127 | let mut args = vec![]; 128 | for (k, _v) in s.fields.as_ref().unwrap().iter() { 129 | let name = util::to_ident(k); 130 | args.push(quote!(#name)) 131 | } 132 | args 133 | }; 134 | 135 | let struct_name = util::to_ident(s.name.to_string().to_camel_case().as_ref()); 136 | quote!{ 137 | #[derive(#derive_name)] 138 | pub struct #struct_name { 139 | #( #fields_quote, )* 140 | } 141 | 142 | impl #struct_name { 143 | pub fn new( #(#args_quote,)* ) -> Self { 144 | #struct_name { #(#args_name_quote,)* } 145 | } 146 | } 147 | } 148 | 149 | }); 150 | 151 | 152 | let expanded = quote! { 153 | #( #structs_quote )* 154 | }; 155 | 156 | TokenStream::from(expanded) 157 | } 158 | 159 | -------------------------------------------------------------------------------- /codegen/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "128"] 2 | extern crate proc_macro; 3 | use { 4 | self::proc_macro::TokenStream, 5 | proc_macro2, 6 | quote::*, 7 | syn::parse::{Parse, ParseStream, Result}, 8 | syn::punctuated::Punctuated, 9 | syn::{parenthesized, braced, Field, Expr, Type, parse_macro_input, token, Ident, Token}, 10 | heck, 11 | }; 12 | 13 | 14 | mod conf; 15 | mod genstruct; 16 | mod util; 17 | 18 | use toml; 19 | 20 | use std::io::prelude::*; 21 | 22 | use crate::conf::Conf; 23 | 24 | 25 | use heck::CamelCase; 26 | use std::fs::File; 27 | use std::io::Read; 28 | 29 | const CONF_FILE_PATH : &'static str = "confs/method.toml"; 30 | 31 | 32 | #[proc_macro] 33 | pub fn gen_struct(input: TokenStream) -> TokenStream { 34 | 35 | genstruct::struct_to_tokenstream(input) 36 | } 37 | 38 | #[proc_macro] 39 | pub fn gen_struct_by_conf(_input: TokenStream) -> TokenStream { 40 | genstruct::struct_by_conf_to_tokenstream(_input) 41 | } -------------------------------------------------------------------------------- /codegen/src/util.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(crate) fn to_ident(name: &str) -> Ident { 4 | Ident::new(name, proc_macro2::Span::call_site()) 5 | } 6 | -------------------------------------------------------------------------------- /confs/method.toml: -------------------------------------------------------------------------------- 1 | [basic] 2 | derive = "Debug" 3 | 4 | [[structs]] 5 | name = "person" 6 | tagged = "named" 7 | fields = {"name" = "String", "age" = "u32"} 8 | 9 | #[[structs]] 10 | #name = "Person" 11 | #tagged = "unit" 12 | # 13 | #[[structs]] 14 | #name = "Person" 15 | #tagged = "tuple" 16 | #fields = {"_a" = "String", "_b" = "u32"} -------------------------------------------------------------------------------- /docs/imgs/proc_macro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhangHanDong/proc_codegen/1d69502f52daa56298466089122cb4c441de18f9/docs/imgs/proc_macro.png -------------------------------------------------------------------------------- /docs/imgs/topic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhangHanDong/proc_codegen/1d69502f52daa56298466089122cb4c441de18f9/docs/imgs/topic.png -------------------------------------------------------------------------------- /src/conf.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use toml::map::Map; 3 | use toml::Value; 4 | use std::fs::File; 5 | use std::io::Read; 6 | use crate::CONF_FILE_PATH; 7 | 8 | #[derive(Deserialize)] 9 | #[derive(Debug)] 10 | pub(crate) struct Basic { 11 | pub(crate) derive: String, 12 | } 13 | 14 | #[derive(Deserialize)] 15 | #[derive(Debug)] 16 | pub(crate) struct StructEntry { 17 | pub(crate) name: String, 18 | pub(crate) tagged: String, 19 | pub(crate) fields: Option>, 20 | } 21 | 22 | #[derive(Deserialize)] 23 | #[derive(Debug)] 24 | pub(crate) struct Conf{ 25 | pub(crate) basic: Basic, 26 | pub(crate) structs: Vec, 27 | } 28 | 29 | impl Conf { 30 | 31 | pub fn read_config() -> Self { 32 | let file_path = CONF_FILE_PATH; 33 | let mut file = match File::open(file_path) { 34 | Ok(f) => f, 35 | Err(e) => panic!("no such file {} exception:{}", file_path, e) 36 | }; 37 | 38 | let mut str_val = String::new(); 39 | match file.read_to_string(&mut str_val) { 40 | Ok(s) => s, 41 | Err(e) => panic!("Error Reading file: {}", e) 42 | }; 43 | // Conditions for Unwrap: 44 | // Struct field names must correspond to toml configuration files 45 | toml::from_str::(&str_val).unwrap() 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // use codegen::gen_struct; 2 | use codegen::gen_struct_by_conf; 3 | 4 | mod conf; 5 | 6 | // #[derive(Debug)] 7 | // struct Person { 8 | // name: String, 9 | // age: u32, 10 | // } 11 | // 12 | // impl Person { 13 | // fn new(name: String, age: u32) -> Self { 14 | // Person { name, age } 15 | // } 16 | // } 17 | 18 | 19 | // gen_struct! { 20 | // Person -> {name: String, age: u32} 21 | // } 22 | 23 | gen_struct_by_conf!{ confs / method.toml } 24 | 25 | const CONF_FILE_PATH : &'static str = "confs/method.toml"; 26 | 27 | fn main() { 28 | let person = Person::new(18, "Alex".to_string()); 29 | println!("{:?}", person); 30 | 31 | let conf = conf::Conf::read_config(); 32 | println!("{:?}", conf); 33 | } 34 | --------------------------------------------------------------------------------