├── .gitignore ├── Cargo.toml ├── src └── lib.rs └── tests └── my_plugin_tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | *.swp 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "my_plugin" 3 | version = "0.0.1" 4 | authors = ["Greg Chapple "] 5 | 6 | [lib] 7 | name = "my_plugin" 8 | path = "src/lib.rs" 9 | plugin = true 10 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin_registrar)] 2 | #![allow(unstable)] 3 | 4 | extern crate rustc; 5 | extern crate syntax; 6 | 7 | use rustc::plugin::Registry; 8 | use syntax::ast; 9 | use syntax::codemap; 10 | use syntax::parse::token; 11 | use syntax::parse::parser::Parser; 12 | use syntax::ext::base::{ExtCtxt, MacResult, MacItems}; 13 | use syntax::ext::build::AstBuilder; 14 | use syntax::ptr::P; 15 | 16 | #[plugin_registrar] 17 | pub fn plugin_registrar(reg: &mut Registry) { 18 | reg.register_macro("generate_struct", expand); 19 | } 20 | 21 | fn expand(cx: &mut ExtCtxt, _sp: codemap::Span, args: &[ast::TokenTree]) -> Box { 22 | let mut parser = cx.new_parser_from_tts(args); 23 | 24 | let struct_info = parse_struct_info(&mut parser); 25 | 26 | generate_struct(cx, struct_info) 27 | } 28 | 29 | /// Generate a struct with the given StructInfo 30 | /// 31 | /// This covers generating both the declaration and fields 32 | fn generate_struct(cx: &mut ExtCtxt, info: StructInfo) -> Box { 33 | let mut its = vec!(); 34 | its.push(generate_struct_declaration(cx, info)); 35 | 36 | MacItems::new(its.into_iter()) 37 | } 38 | 39 | /// Generate the struct declaration based on the given StructInfo 40 | /// 41 | /// This will give back the full struct declaration including the #[derive] 42 | /// attribute and all fields. 43 | fn generate_struct_declaration(cx: &ExtCtxt, info: StructInfo) -> P { 44 | let name = info.name.clone(); 45 | let mut attrs = vec!(); 46 | 47 | // the raw struct definition 48 | let def = ast::StructDef { 49 | fields: vec!(), 50 | ctor_id: None, 51 | }; 52 | 53 | // set the struct attributes 54 | // things like "allow", "derive" etc. 55 | let mut traits = vec!(); 56 | traits.push_all(&*info.derive); 57 | 58 | attrs.push(attribute(cx, "allow", vec!["non_snake_case"])); 59 | attrs.push(attribute(cx, "derive", traits)); 60 | 61 | let st = cx.item_struct(codemap::DUMMY_SP, name, def); 62 | cx.item(codemap::DUMMY_SP, name, attrs, st.node.clone()).map(|mut it| { 63 | it.vis = ast::Public; 64 | it 65 | }) 66 | } 67 | 68 | fn parse_struct_info(parser: &mut Parser) -> StructInfo { 69 | // parse the 'name' portion 70 | // 71 | // name => Post, 72 | // ^ parse this 73 | parser.parse_ident(); 74 | // parse the fat arrow portion 75 | // 76 | // name => Post, 77 | // ^ parse this 78 | parser.eat(&token::FatArrow); 79 | // parse the struct name 80 | // 81 | // name => Post, 82 | // ^ parse this 83 | let struct_name = parser.parse_ident(); 84 | 85 | // parse up to the comma 86 | // 87 | // name => Post, 88 | // ^ parse to here 89 | parser.eat(&token::Comma); 90 | 91 | // parse the derive keyword 92 | // 93 | // derive => (Show, Copy), 94 | // ^ parse this word 95 | parser.parse_ident(); 96 | 97 | // derive => (Show, Copy), 98 | // ^ parse this 99 | parser.eat(&token::FatArrow); 100 | 101 | // derive => (Show, Copy), 102 | // ^ parse this 103 | parser.eat(&token::OpenDelim(token::Paren)); 104 | 105 | // vec to store the derive traits 106 | let mut derive = vec!(); 107 | 108 | // eat up until the closing paren 109 | // 110 | // derive => (Show, Copy) 111 | // ^ parse until here 112 | while !parser.eat(&token::CloseDelim(token::Paren)) { 113 | derive.push(parser.parse_ident().as_str().to_string()); 114 | parser.eat(&token::Comma); 115 | } 116 | 117 | StructInfo { 118 | name: struct_name, 119 | derive: derive, 120 | } 121 | } 122 | 123 | struct StructInfo { 124 | name: ast::Ident, 125 | derive: Vec, 126 | } 127 | 128 | fn attribute(cx: &ExtCtxt, name: S, items: Vec) -> ast::Attribute 129 | where S: Str, T: Str { 130 | let sp = codemap::DUMMY_SP; 131 | let its = items.into_iter().map(|s| meta_item(cx, s.as_slice())).collect(); 132 | let mi = cx.meta_list(sp, intern(name.as_slice()), its); 133 | cx.attribute(sp, mi) 134 | } 135 | 136 | fn meta_item(cx: &ExtCtxt, s: &str) -> P { 137 | cx.meta_word(codemap::DUMMY_SP, intern(s)) 138 | } 139 | 140 | fn intern(s: &str) -> token::InternedString { 141 | token::intern_and_get_ident(s) 142 | } 143 | -------------------------------------------------------------------------------- /tests/my_plugin_tests.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | 3 | #![plugin(my_plugin)] 4 | 5 | generate_struct!( 6 | name => Post, 7 | derive => (Show, Copy), 8 | fields => { 9 | id: i32, 10 | title: &'static str, 11 | } 12 | ); 13 | 14 | #[test] fn struct_is_generated() { 15 | let post = Post { id: 1i32, title: "Hello world" }; 16 | assert_eq!(post.title, "Hello world"); 17 | } 18 | --------------------------------------------------------------------------------