Skip to main content

dodo_bindgen_codegen/
ffi.rs

1use proc_macro2::TokenStream;
2use syn::Ident;
3
4use crate::util::*;
5
6/* -------------------------------------------------------------------------- */
7
8// MARK: api
9// https://docs.deno.com/runtime/reference/deno_namespace_apis/#supported-types
10#[derive(Clone, Debug, PartialEq)]
11pub enum FfiType {
12    U8,
13    U16,
14    U32,
15    U64,
16    Usize,
17    I8,
18    I16,
19    I32,
20    I64,
21    Isize,
22    F32,
23    F64,
24    Void,
25    Bool,
26    Pointer,
27    Buffer, /* cannot be constructed directly. must be passed through the utility modules for
28             * conversion from opaque pointer to access internal buffer */
29    FnPointer,
30    // Struct, // [!TODO] support for struct type
31}
32
33// https://docs.deno.com/api/deno/~/Deno.ForeignFunction
34#[derive(Clone, Debug, PartialEq)]
35pub struct FfiFunction {
36    pub ident: Ident,
37    pub inputs: Vec<FfiType>,
38    pub output: FfiType,
39    pub non_blocking: bool, /* [!ISSUE] how to prevent data races and enforce integrity
40                             * pub optional:     bool, */
41}
42
43// https://docs.deno.com/api/deno/~/Deno.ForeignLibraryInterface
44#[derive(Clone, Debug, PartialEq)]
45pub enum FfiSymbol {
46    Function(FfiFunction),
47    // Static(FfiStatic),
48}
49
50#[derive(Clone, Debug, Default, PartialEq)]
51pub struct FfiInterface {
52    pub symbols: Vec<FfiSymbol>,
53}
54
55impl FfiInterface {
56    pub fn push_fn(&mut self, ffi_function: FfiFunction) {
57        self.symbols.push(FfiSymbol::Function(ffi_function));
58    }
59    pub fn append(&mut self, other: &mut FfiInterface) {
60        self.symbols.append(&mut other.symbols);
61    }
62}
63
64#[derive(Clone, Debug, Default, PartialEq)]
65pub struct FfiLib {
66    pub interface: FfiInterface,
67    pub lib_name: String,
68    pub lazy: bool,
69    pub export: bool,
70}
71
72impl FfiLib {
73    pub fn file_name(lib_name: &str, target: &str, extension: &str) -> String {
74        format!("{lib_name}.{target}.{extension}")
75    }
76}
77
78/* -------------------------------------------------------------------------- */
79
80// MARK: print
81
82#[rustfmt::skip]
83impl ToTokens for FfiType {
84    fn to_tokens(&self, tokens: &mut TokenStream) {
85        tokens.extend(match self {
86            FfiType::U8        => quote! { "u8" },
87            FfiType::U16       => quote! { "u16" },
88            FfiType::U32       => quote! { "u32" },
89            FfiType::U64       => quote! { "u64" },
90            FfiType::Usize     => quote! { "usize" },
91            FfiType::I8        => quote! { "i8" },
92            FfiType::I16       => quote! { "i16" },
93            FfiType::I32       => quote! { "i32" },
94            FfiType::I64       => quote! { "i64" },
95            FfiType::Isize     => quote! { "isize" },
96            FfiType::F32       => quote! { "f32" },
97            FfiType::F64       => quote! { "f64" },
98            FfiType::Void      => quote! { "void" },
99            FfiType::Bool      => quote! { "bool" },
100            FfiType::Pointer   => quote! { "pointer" },
101            FfiType::Buffer    => quote! { "buffer" },
102            FfiType::FnPointer => quote! { "function" },
103        });
104    }
105}
106
107impl ToTokens for FfiFunction {
108    fn to_tokens(&self, tokens: &mut TokenStream) {
109        let FfiFunction {
110            ident,
111            inputs,
112            output,
113            non_blocking,
114        } = self;
115
116        let inputs: Vec<&FfiType> = inputs
117            .iter()
118            .filter(|ffi_type| {
119                if **ffi_type == FfiType::Void {
120                    false
121                } else {
122                    true
123                }
124            })
125            .collect();
126
127        let inputs = quote! { #(#inputs),* };
128        let non_blocking = if *non_blocking {
129            quote! { nonblocking: true }
130        } else {
131            TokenStream::new()
132        };
133
134        tokens.extend(quote! {
135            #ident: {
136                parameters: [#inputs],
137                result: #output,
138                #non_blocking
139            }
140        });
141    }
142}
143
144impl ToTokens for FfiSymbol {
145    fn to_tokens(&self, tokens: &mut TokenStream) {
146        tokens.extend(match self {
147            FfiSymbol::Function(ffi_function) => ffi_function.to_token_stream(),
148        });
149    }
150}
151
152impl ToTokens for FfiInterface {
153    fn to_tokens(&self, tokens: &mut TokenStream) {
154        let symbols = &self.symbols;
155        tokens.extend(quote! {
156            { #(#symbols),* }
157        });
158    }
159}
160
161impl<'a> ToTokens for FfiLib {
162    fn to_tokens(&self, tokens: &mut TokenStream) {
163        let FfiLib {
164            interface,
165            lib_name,
166            lazy,
167            export,
168        } = self;
169
170        let export = if *export {
171            quote! { export }
172        } else {
173            TokenStream::new()
174        };
175
176        let file_name = FfiLib::file_name(
177            lib_name,
178            "${Deno.build.target}",
179            "${({ windows: \"dll\", darwin: \"dylib\" } as Record<string, string>)[Deno.build.os] ?? \"so\"}",
180        );
181        let lib_path = raw(format!(
182            "const __dodo_lib = new URL(`./{file_name}`, import.meta.url);"
183        ));
184
185        tokens.extend(if *lazy {
186            // [!TODO] refactor to a class?
187            quote! {
188                #lib_path
189                let symbols: any;
190                export function load(path: string | URL = __dodo_lib) {
191                    const { dlopen } = Deno;
192                    const { symbols: symbols_ } = dlopen(path, #interface);
193                    symbols = symbols_;
194                };
195            }
196        } else {
197            quote! {
198                #lib_path
199                #export const { symbols } = Deno.dlopen(__dodo_lib, #interface);
200            }
201        });
202    }
203}
204
205/* -------------------------------------------------------------------------- */
206
207// MARK: tests
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_print() {
215        let mut ffi_lib = FfiLib {
216            lib_name: "lib".to_string(),
217            export: true,
218            ..FfiLib::default()
219        };
220        ffi_lib.interface.push_fn(FfiFunction {
221            ident: format_ident!("some_symbol"),
222            inputs: vec![FfiType::Pointer, FfiType::U8],
223            output: FfiType::Void,
224            non_blocking: false,
225        });
226        ffi_lib.interface.push_fn(FfiFunction {
227            ident: format_ident!("slow_symbol"),
228            inputs: vec![FfiType::Buffer],
229            output: FfiType::U64,
230            non_blocking: true,
231        });
232        let eager = ffi_lib.to_token_stream().to_string();
233        ffi_lib.lazy = true;
234        let lazy = ffi_lib.to_token_stream().to_string();
235        insta::assert_snapshot!(crate::util::deno_fmt(format!("{eager}\n{lazy}")));
236    }
237}