bobo_oop/
lib.rs

1/*
2 * Copyright (C) 2024-2025 moluopro. All rights reserved.
3 * Github: https://github.com/moluopro
4 */
5
6extern crate proc_macro;
7
8use proc_macro::TokenStream;
9use quote::quote;
10use syn::{
11    braced,
12    parse::{Parse, ParseStream},
13    parse_macro_input, FnArg, Ident, ImplItem, Token, Type,
14};
15
16struct Class {
17    name: Ident,
18    fields: Vec<(Ident, Type)>,
19    methods: Vec<ImplItem>,
20}
21
22struct Classes {
23    classes: Vec<Class>,
24}
25
26impl Parse for Class {
27    fn parse(input: ParseStream) -> syn::Result<Self> {
28        let name: Ident = input.parse()?;
29        let content;
30        braced!(content in input);
31
32        let mut fields = Vec::new();
33        let mut methods = Vec::new();
34
35        while !content.is_empty() {
36            if content.peek(Token![fn]) {
37                let method: ImplItem = content.parse()?;
38                methods.push(method);
39            } else {
40                let ident: Ident = content.parse()?;
41                content.parse::<Token![:]>()?;
42                let ty: Type = content.parse()?;
43                if content.peek(Token![,]) {
44                    content.parse::<Token![,]>()?;
45                } else {
46                    content.parse::<Token![;]>().ok();
47                }
48                fields.push((ident, ty));
49            }
50        }
51
52        Ok(Class { name, fields, methods })
53    }
54}
55
56impl Parse for Classes {
57    fn parse(input: ParseStream) -> syn::Result<Self> {
58        let mut classes = Vec::new();
59
60        while !input.is_empty() {
61            let class: Class = input.parse()?;
62            classes.push(class);
63
64            if input.peek(Token![,]) {
65                input.parse::<Token![,]>()?;
66            }
67        }
68
69        Ok(Classes { classes })
70    }
71}
72
73#[proc_macro]
74pub fn class(input: TokenStream) -> TokenStream {
75    let classes = parse_macro_input!(input as Classes);
76
77    let class_defs = classes.classes.iter().map(|class| {
78        let name = &class.name;
79        let fields = &class.fields;
80        let methods = &class.methods;
81
82        let field_defs = fields.iter().map(|(ident, ty)| {
83            quote! {
84                pub #ident: #ty,
85            }
86        });
87
88        let method_defs = methods.iter().map(|method| {
89            if let ImplItem::Fn(mut method_fn) = method.clone() {
90                let method_name = &method_fn.sig.ident;
91
92                if method_name.to_string().to_lowercase() != "new" {
93                    if !method_fn.sig.inputs.iter().any(|arg| matches!(arg, FnArg::Receiver(_))) {
94                        method_fn.sig.inputs.insert(0, syn::parse_quote!( &self ));
95                    }
96                }
97
98                quote! {
99                    #method_fn
100                }
101            } else {
102                quote! {}
103            }
104        });
105
106        quote! {
107            pub struct #name {
108                #(#field_defs)*
109            }
110
111            impl #name {
112                #(#method_defs)*
113            }
114        }
115    });
116
117    let expanded = quote! {
118        #(#class_defs)*
119    };
120
121    TokenStream::from(expanded)
122}