Skip to content

Commit eb5f3b3

Browse files
committed
use macros to handle stuff, adds more useful errors
1 parent 21399bc commit eb5f3b3

File tree

19 files changed

+621
-261
lines changed

19 files changed

+621
-261
lines changed

crates/byondapi-macros/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "byondapi-macros"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[lib]
9+
crate-type = ["lib"]
10+
11+
[dependencies]
12+
inventory = "0.3.15"
13+
byondapi-impl = { path = "./byondapi-impl", package = "byondapi-impl" }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "byondapi-impl"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[lib]
9+
proc-macro = true
10+
11+
[dependencies]
12+
inventory = "0.3.15"
13+
quote = "1.0"
14+
proc-macro2 = "1.0"
15+
16+
[dependencies.syn]
17+
version = "2.0"
18+
features = ["full", "parsing", "printing"]
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
}

crates/byondapi-macros/src/lib.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use std::io::Write;
2+
3+
pub use byondapi_impl::{bind, bind_raw_args, init};
4+
5+
pub use inventory;
6+
7+
pub struct Bind {
8+
pub proc_path: &'static str,
9+
pub func_name: &'static str,
10+
pub func_arguments: &'static str,
11+
pub is_variadic: bool,
12+
}
13+
14+
inventory::collect!(Bind);
15+
16+
pub fn generate_bindings(libname: &str) {
17+
_ = std::fs::remove_file("./bindings.dm");
18+
let mut file = std::fs::File::create("./bindings.dm").unwrap();
19+
let libname_upper = libname.to_uppercase();
20+
file.write_fmt(format_args!(
21+
"//THIS FILE IS AUTOMATICALLY GENERATED BY {libname_upper}, PLEASE DO NOT TOUCH IT
22+
//PROC DEFINITIONS MAY MOVE AROUND, THIS IS NORMAL
23+
24+
/* This comment bypasses grep checks */ /var/__{libname}
25+
26+
/proc/__detect_{libname}()
27+
if (world.system_type == UNIX)
28+
return __{libname} = \"lib{libname}\"
29+
else
30+
return __{libname} = \"{libname}\"
31+
32+
#define {libname_upper} (__{libname} || __detect_{libname}())
33+
34+
"
35+
))
36+
.unwrap();
37+
for thing in inventory::iter::<Bind> {
38+
let path = thing.proc_path;
39+
let func_name = thing.func_name;
40+
let func_arguments = thing.func_arguments;
41+
let func_arguments_srcless = func_arguments
42+
.to_owned()
43+
.replace("src, ", "")
44+
.replace("src", "");
45+
if thing.is_variadic {
46+
//can't directly modify args, fuck you byond
47+
file.write_fmt(format_args!(
48+
r#"{path}(...)
49+
var/list/args_copy = args.Copy()
50+
args_copy.Insert(1, src)
51+
return call_ext({libname_upper}, "byond:{func_name}")(arglist(args_copy))
52+
53+
"#
54+
))
55+
.unwrap()
56+
} else {
57+
file.write_fmt(format_args!(
58+
r#"{path}({func_arguments_srcless})
59+
return call_ext({libname_upper}, "byond:{func_name}")({func_arguments})
60+
61+
"#
62+
))
63+
.unwrap()
64+
}
65+
}
66+
}

crates/byondapi-rs-test/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ crate-type = ["cdylib"]
1212
byondapi = { path = "../byondapi-rs" }
1313
tempfile = "3.8.1"
1414
cargo_metadata = "0.18.1"
15+
eyre = "0.6.12"

0 commit comments

Comments
 (0)