diff --git a/Cargo.toml b/Cargo.toml index cf02c73c..99947b3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,6 @@ members = [ "crates/parser", "crates/filecheck", "crates/triple", + "crates/macros", "crates/interpreter", ] diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml new file mode 100644 index 00000000..408690ef --- /dev/null +++ b/crates/macros/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sonatina-macros" +version = "0.0.3-alpha" +edition = "2021" +authors = ["Sonatina Developers"] +license = "Apache-2.0" +readme = "../../README.md" +homepage = "https://github.com/fe-lang/sonatina/tree/main/crates/ir" +repository = "https://github.com/fe-lang/sonatina" +categories = ["compilers", "wasm"] +keywords = ["compiler", "evm", "wasm", "smart-contract"] + +[lib] +proc_macro = true + +[dependencies] +syn = { version = "1.0", features = ["full"] } +proc-macro2 = "1.0" +quote = "1.0" diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs new file mode 100644 index 00000000..6aa09577 --- /dev/null +++ b/crates/macros/src/lib.rs @@ -0,0 +1,129 @@ +use quote::quote; + +#[proc_macro_derive(Inst)] +pub fn derive_inst(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let item_struct = syn::parse_macro_input!(item as syn::ItemStruct); + + match InstBuilder::new(item_struct).and_then(InstBuilder::build) { + Ok(impls) => quote! { + #impls + } + .into(), + + Err(e) => e.to_compile_error().into(), + } +} + +struct InstBuilder { + struct_name: syn::Ident, + fields: syn::FieldsNamed, +} + +impl InstBuilder { + fn new(item_struct: syn::ItemStruct) -> syn::Result { + let struct_ident = item_struct.ident; + let syn::Fields::Named(fields) = item_struct.fields else { + return Err(syn::Error::new_spanned( + item_struct.fields, + "tuple struct is not allowed for inst types", + )); + }; + + if item_struct.generics.lt_token.is_some() { + Err(syn::Error::new_spanned( + item_struct.generics, + "generics is not allowed for inst types", + )) + } else { + Ok(Self { + struct_name: struct_ident, + fields, + }) + } + } + + fn build(self) -> syn::Result { + let mut fields = Vec::with_capacity(self.fields.named.len()); + + for f in &self.fields.named { + if !matches!(f.vis, syn::Visibility::Inherited) { + return Err(syn::Error::new_spanned( + &f.vis, + "all members of inst types should be private", + )); + }; + + fields.push((f.ident.clone().unwrap(), f.ty.clone())); + } + + let ctor = self.make_ctor(&fields); + let accessors = self.make_accessors(&fields); + let cast_fn = self.make_cast_fn(); + + let struct_name = &self.struct_name; + Ok(quote! { + impl #struct_name { + #ctor + + #accessors + + #cast_fn + } + }) + } + + fn make_ctor(&self, fields: &[(syn::Ident, syn::Type)]) -> proc_macro2::TokenStream { + let ctor_args = fields.iter().map(|(ident, ty)| quote! {#ident: #ty}); + let field_names = fields.iter().map(|(ident, _)| ident); + quote! { + pub fn new(hi: &dyn crate::HasInst, #(#ctor_args),*) -> Self { + Self { + #(#field_names: #field_names),* + } + } + } + } + + fn make_cast_fn(&self) -> proc_macro2::TokenStream { + quote! { + pub fn cast<'i>(hi: &dyn crate::HasInst, inst: &'i dyn Inst) -> Option<&'i Self> { + if hi.is(inst) { + unsafe { Some(&*(inst as *const dyn Inst as *const Self)) } + } else { + None + } + } + + pub fn cast_mut<'i>( + hi: &dyn HasInst, + inst: &'i mut dyn Inst, + ) -> Option<&'i mut Self> { + if hi.is(inst) { + unsafe { Some(&mut *(inst as *mut dyn Inst as *mut Self)) } + } else { + None + } + } + } + } + + fn make_accessors(&self, fields: &[(syn::Ident, syn::Type)]) -> proc_macro2::TokenStream { + let accessors = fields.iter().map(|(ident, ty)| { + let getter = quote::format_ident!("{ident}"); + let get_mut = quote::format_ident!("{ident}_mut"); + quote! { + pub fn #getter(&self) -> &#ty { + &self.#ident + } + + pub fn #get_mut(&mut self) -> &mut #ty{ + &mut self.#ident + } + } + }); + + quote! { + #(#accessors)* + } + } +}