|
| 1 | +use proc_macro::TokenStream; |
| 2 | +use proc_macro2::Ident; |
| 3 | +use quote::quote; |
| 4 | +use syn::{spanned::Spanned, Lit}; |
| 5 | + |
| 6 | +fn extract_args(a: &syn::FnArg) -> &syn::PatType { |
| 7 | + match a { |
| 8 | + syn::FnArg::Typed(p) => p, |
| 9 | + _ => panic!("Not supported on types with `self`!"), |
| 10 | + } |
| 11 | +} |
| 12 | +//this is an example, mr clippy |
| 13 | +#[allow(clippy::test_attr_in_doctest)] |
| 14 | +/// Macro for generating byond binds |
| 15 | +/// Usage: |
| 16 | +/// ``` |
| 17 | +/// use byondapi::prelude::*; |
| 18 | +/// #[byondapi::bind] |
| 19 | +/// fn example() {Ok(ByondValue::null())} |
| 20 | +/// |
| 21 | +/// #[byondapi::bind("/datum/example/proc/other_example")] |
| 22 | +/// fn example_other(_: ByondValue, _: ByondValue) {Ok(ByondValue::null())} |
| 23 | +/// |
| 24 | +/// ``` |
| 25 | +/// Then generate the bindings.dm file with |
| 26 | +/// ``` |
| 27 | +/// #[test] |
| 28 | +/// fn generate_binds() { |
| 29 | +/// byondapi::byondapi_macros::generate_bindings(env!("CARGO_CRATE_NAME")); |
| 30 | +/// } |
| 31 | +/// ``` |
| 32 | +/// and run cargo test to actually create the file |
| 33 | +/// |
| 34 | +#[proc_macro_attribute] |
| 35 | +pub fn bind(attr: TokenStream, item: TokenStream) -> TokenStream { |
| 36 | + let input = syn::parse_macro_input!(item as syn::ItemFn); |
| 37 | + let proc = syn::parse_macro_input!(attr as Option<syn::Lit>); |
| 38 | + |
| 39 | + let func_name = &input.sig.ident; |
| 40 | + let func_name_disp = quote!(#func_name).to_string(); |
| 41 | + |
| 42 | + let func_name_ffi = format!("{func_name_disp}_ffi"); |
| 43 | + let func_name_ffi = Ident::new(&func_name_ffi, func_name.span()); |
| 44 | + let func_name_ffi_disp = quote!(#func_name_ffi).to_string(); |
| 45 | + |
| 46 | + let args = &input.sig.inputs; |
| 47 | + |
| 48 | + //Check for returns |
| 49 | + match &input.sig.output { |
| 50 | + syn::ReturnType::Default => {} // |
| 51 | + |
| 52 | + syn::ReturnType::Type(_, ty) => { |
| 53 | + return syn::Error::new(ty.span(), "Do not specify the return value of binds") |
| 54 | + .to_compile_error() |
| 55 | + .into() |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + let signature = quote! { |
| 60 | + #[no_mangle] |
| 61 | + pub unsafe extern "C" fn #func_name_ffi ( |
| 62 | + __argc: ::byondapi::sys::u4c, |
| 63 | + __argv: *mut ::byondapi::value::ByondValue |
| 64 | + ) -> ::byondapi::value::ByondValue |
| 65 | + }; |
| 66 | + |
| 67 | + let body = &input.block; |
| 68 | + let mut arg_names: syn::punctuated::Punctuated<syn::Ident, syn::Token![,]> = |
| 69 | + syn::punctuated::Punctuated::new(); |
| 70 | + let mut proc_arg_unpacker: syn::punctuated::Punctuated< |
| 71 | + proc_macro2::TokenStream, |
| 72 | + syn::Token![,], |
| 73 | + > = syn::punctuated::Punctuated::new(); |
| 74 | + |
| 75 | + for arg in args.iter().map(extract_args) { |
| 76 | + if let syn::Pat::Ident(p) = &*arg.pat { |
| 77 | + arg_names.push(p.ident.clone()); |
| 78 | + let index = arg_names.len() - 1; |
| 79 | + proc_arg_unpacker.push(quote! { |
| 80 | + args.get(#index).map(::byondapi::value::ByondValue::clone).unwrap_or_default() |
| 81 | + }); |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + let arg_names_disp = quote!(#arg_names).to_string(); |
| 86 | + |
| 87 | + //Submit to inventory |
| 88 | + let cthook_prelude = match &proc { |
| 89 | + Some(Lit::Str(p)) => { |
| 90 | + quote! { |
| 91 | + ::byondapi::byondapi_macros::inventory::submit!({ |
| 92 | + ::byondapi::byondapi_macros::Bind { |
| 93 | + proc_path: #p, |
| 94 | + func_name: #func_name_ffi_disp, |
| 95 | + func_arguments: #arg_names_disp, |
| 96 | + is_variadic: false, |
| 97 | + } |
| 98 | + }); |
| 99 | + } |
| 100 | + } |
| 101 | + Some(other_literal) => { |
| 102 | + return syn::Error::new( |
| 103 | + other_literal.span(), |
| 104 | + "Bind attributes must be a string literal or empty", |
| 105 | + ) |
| 106 | + .to_compile_error() |
| 107 | + .into() |
| 108 | + } |
| 109 | + None => { |
| 110 | + let mut func_name_disp = func_name_disp.clone(); |
| 111 | + func_name_disp.insert_str(0, "/proc/"); |
| 112 | + quote! { |
| 113 | + ::byondapi::byondapi_macros::inventory::submit!({ |
| 114 | + ::byondapi::byondapi_macros::Bind{ |
| 115 | + proc_path: #func_name_disp, |
| 116 | + func_name: #func_name_ffi_disp, |
| 117 | + func_arguments: #arg_names_disp, |
| 118 | + is_variadic: false, |
| 119 | + } |
| 120 | + }); |
| 121 | + } |
| 122 | + } |
| 123 | + }; |
| 124 | + |
| 125 | + let result = quote! { |
| 126 | + #cthook_prelude |
| 127 | + #signature { |
| 128 | + let args = unsafe { ::byondapi::parse_args(__argc, __argv) }; |
| 129 | + match #func_name(#proc_arg_unpacker) { |
| 130 | + Ok(val) => val, |
| 131 | + Err(e) => { |
| 132 | + let error_string = ::byondapi::value::ByondValue::try_from(e.0).unwrap(); |
| 133 | + ::byondapi::global_call::call_global_id(byond_string!("stack_trace"), &[error_string]).unwrap(); |
| 134 | + ::byondapi::value::ByondValue::null() |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + } |
| 139 | + fn #func_name(#args) -> Result<::byondapi::value::ByondValue, ::byondapi::BindError> |
| 140 | + #body |
| 141 | + }; |
| 142 | + result.into() |
| 143 | +} |
| 144 | + |
| 145 | +/// Same as [`bind`] but accepts variable amount of args, with src in the beginning if there's a src |
| 146 | +/// The args are just a variable named `args` in the macro'd function |
| 147 | +#[proc_macro_attribute] |
| 148 | +pub fn bind_raw_args(attr: TokenStream, item: TokenStream) -> TokenStream { |
| 149 | + let input = syn::parse_macro_input!(item as syn::ItemFn); |
| 150 | + let proc = syn::parse_macro_input!(attr as Option<syn::Lit>); |
| 151 | + |
| 152 | + let func_name = &input.sig.ident; |
| 153 | + let func_name_disp = quote!(#func_name).to_string(); |
| 154 | + |
| 155 | + let func_name_ffi = format!("{func_name_disp}_ffi"); |
| 156 | + let func_name_ffi = Ident::new(&func_name_ffi, func_name.span()); |
| 157 | + let func_name_ffi_disp = quote!(#func_name_ffi).to_string(); |
| 158 | + |
| 159 | + //Check for returns |
| 160 | + match &input.sig.output { |
| 161 | + syn::ReturnType::Default => {} // |
| 162 | + |
| 163 | + syn::ReturnType::Type(_, ty) => { |
| 164 | + return syn::Error::new(ty.span(), "Do not specify the return value of binds") |
| 165 | + .to_compile_error() |
| 166 | + .into() |
| 167 | + } |
| 168 | + } |
| 169 | + |
| 170 | + if !input.sig.inputs.is_empty() { |
| 171 | + return syn::Error::new( |
| 172 | + input.sig.inputs.span(), |
| 173 | + "Do not specify arguments for raw arg binds", |
| 174 | + ) |
| 175 | + .to_compile_error() |
| 176 | + .into(); |
| 177 | + } |
| 178 | + |
| 179 | + let signature = quote! { |
| 180 | + #[no_mangle] |
| 181 | + pub unsafe extern "C" fn #func_name_ffi ( |
| 182 | + __argc: ::byondapi::sys::u4c, |
| 183 | + __argv: *mut ::byondapi::value::ByondValue |
| 184 | + ) -> ::byondapi::value::ByondValue |
| 185 | + }; |
| 186 | + |
| 187 | + let body = &input.block; |
| 188 | + |
| 189 | + //Submit to inventory |
| 190 | + let cthook_prelude = match proc { |
| 191 | + Some(Lit::Str(p)) => { |
| 192 | + quote! { |
| 193 | + ::byondapi::byondapi_macros::inventory::submit!({ |
| 194 | + ::byondapi::byondapi_macros::Bind { |
| 195 | + proc_path: #p, |
| 196 | + func_name: #func_name_ffi_disp, |
| 197 | + func_arguments: "", |
| 198 | + is_variadic: true, |
| 199 | + } |
| 200 | + }); |
| 201 | + } |
| 202 | + } |
| 203 | + Some(other_literal) => { |
| 204 | + return syn::Error::new( |
| 205 | + other_literal.span(), |
| 206 | + "Bind attributes must be a string literal or empty", |
| 207 | + ) |
| 208 | + .to_compile_error() |
| 209 | + .into() |
| 210 | + } |
| 211 | + None => { |
| 212 | + let mut func_name_disp = func_name_disp.clone(); |
| 213 | + func_name_disp.insert_str(0, "/proc/"); |
| 214 | + quote! { |
| 215 | + ::byondapi::byondapi_macros::inventory::submit!({ |
| 216 | + ::byondapi::byondapi_macros::Bind{ |
| 217 | + proc_path: #func_name_disp, |
| 218 | + func_name: #func_name_ffi_disp, |
| 219 | + func_arguments: "", |
| 220 | + is_variadic: true, |
| 221 | + } |
| 222 | + }); |
| 223 | + } |
| 224 | + } |
| 225 | + }; |
| 226 | + |
| 227 | + let result = quote! { |
| 228 | + #cthook_prelude |
| 229 | + #signature { |
| 230 | + let mut args = unsafe { ::byondapi::parse_args(__argc, __argv) }; |
| 231 | + match #func_name(args) { |
| 232 | + Ok(val) => val, |
| 233 | + Err(e) => { |
| 234 | + let error_string = ::byondapi::value::ByondValue::try_from(e.0).unwrap(); |
| 235 | + ::byondapi::global_call::call_global_id(byond_string!("stack_trace"), &[error_string]).unwrap(); |
| 236 | + ::byondapi::value::ByondValue::null() |
| 237 | + } |
| 238 | + } |
| 239 | + } |
| 240 | + fn #func_name(args: &mut [::byondapi::value::ByondValue]) -> Result<::byondapi::value::ByondValue, ::byondapi::BindError> |
| 241 | + #body |
| 242 | + }; |
| 243 | + result.into() |
| 244 | +} |
| 245 | + |
| 246 | +#[proc_macro_attribute] |
| 247 | +pub fn init(_: TokenStream, item: TokenStream) -> TokenStream { |
| 248 | + let input = syn::parse_macro_input!(item as syn::ItemFn); |
| 249 | + let func_name = &input.sig.ident; |
| 250 | + quote! { |
| 251 | + #input |
| 252 | + ::byondapi::byondapi_macros::inventory::submit!({::byondapi::InitFunc(#func_name)}); |
| 253 | + } |
| 254 | + .into() |
| 255 | +} |
0 commit comments