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 dylib_path: String,
68    pub lazy: bool,
69    pub export: bool,
70}
71
72/* -------------------------------------------------------------------------- */
73
74#[rustfmt::skip]
75impl Parse for FfiType {
76    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
77        let lit_str = input.parse::<LitStr>()?;
78        let ffi_type = match lit_str.value().as_str() {
79            "u8"       => FfiType::U8,
80            "u16"      => FfiType::U16,
81            "u32"      => FfiType::U32,
82            "u64"      => FfiType::U64,
83            "usize"    => FfiType::Usize,
84            "i8"       => FfiType::I8,
85            "i16"      => FfiType::I16,
86            "i32"      => FfiType::I32,
87            "i64"      => FfiType::I64,
88            "isize"    => FfiType::Isize,
89            "f32"      => FfiType::F32,
90            "f64"      => FfiType::F64,
91            "void"     => FfiType::Void,
92            "bool"     => FfiType::Bool,
93            "pointer"  => FfiType::Pointer,
94            "buffer"   => FfiType::Buffer,
95            "function" => FfiType::FnPointer,
96            _ => return Err(syn::Error::new(lit_str.span(), "unknown ffi type"))
97        };
98        Ok(ffi_type)
99    }
100}
101
102impl Parse for FfiFunction {
103    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
104        // ident: {}
105        let ident = input.parse()?;
106        input.parse::<Token![:]>()?;
107
108        // {...}
109        let content;
110        braced!(content in input);
111
112        // parameters: []...
113        let _ident = content.call(<Ident as syn::ext::IdentExt>::parse_any)?;
114        if _ident.to_string().as_str() != "parameters" {
115            return Err(syn::Error::new(_ident.span(), "expected key `parameters`"));
116        }
117        content.parse::<Token![:]>()?;
118
119        // ["..."]
120        let _content;
121        bracketed!(_content in content);
122        let mut inputs = Vec::new();
123        while !_content.is_empty() {
124            inputs.push(_content.parse()?);
125            if _content.is_empty() {
126                break;
127            }
128            _content.parse::<Token![,]>()?;
129        }
130        // [...],
131        content.parse::<Token![,]>()?;
132
133        let _ident = content.call(<Ident as syn::ext::IdentExt>::parse_any)?;
134        if _ident.to_string().as_str() != "result" {
135            return Err(syn::Error::new(_ident.span(), "expected key `result`"));
136        }
137        content.parse::<Token![:]>()?;
138        let output = content.parse()?;
139        content.parse::<Option<Token![,]>>()?;
140
141        Ok(Self {
142            ident,
143            inputs,
144            output,
145            non_blocking: false,
146        })
147    }
148}
149
150impl Parse for FfiInterface {
151    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
152        let mut symbols = Vec::new();
153
154        while !input.is_empty() {
155            symbols.push(FfiSymbol::Function(input.parse::<FfiFunction>()?));
156            if input.is_empty() {
157                break;
158            }
159            input.parse::<Token![,]>()?;
160        }
161
162        Ok(Self { symbols })
163    }
164}
165
166/* -------------------------------------------------------------------------- */
167
168// MARK: print
169
170#[rustfmt::skip]
171impl ToTokens for FfiType {
172    fn to_tokens(&self, tokens: &mut TokenStream) {
173        tokens.extend(match self {
174            FfiType::U8        => quote! { "u8" },
175            FfiType::U16       => quote! { "u16" },
176            FfiType::U32       => quote! { "u32" },
177            FfiType::U64       => quote! { "u64" },
178            FfiType::Usize     => quote! { "usize" },
179            FfiType::I8        => quote! { "i8" },
180            FfiType::I16       => quote! { "i16" },
181            FfiType::I32       => quote! { "i32" },
182            FfiType::I64       => quote! { "i64" },
183            FfiType::Isize     => quote! { "isize" },
184            FfiType::F32       => quote! { "f32" },
185            FfiType::F64       => quote! { "f64" },
186            FfiType::Void      => quote! { "void" },
187            FfiType::Bool      => quote! { "bool" },
188            FfiType::Pointer   => quote! { "pointer" },
189            FfiType::Buffer    => quote! { "buffer" },
190            FfiType::FnPointer => quote! { "function" },
191        });
192    }
193}
194
195impl ToTokens for FfiFunction {
196    fn to_tokens(&self, tokens: &mut TokenStream) {
197        let FfiFunction {
198            ident,
199            inputs,
200            output,
201            non_blocking,
202        } = self;
203
204        let inputs: Vec<&FfiType> = inputs
205            .iter()
206            .filter(|ffi_type| {
207                if **ffi_type == FfiType::Void {
208                    false
209                } else {
210                    true
211                }
212            })
213            .collect();
214
215        let inputs = quote! { #(#inputs),* };
216        let non_blocking = if *non_blocking {
217            quote! { nonblocking: true }
218        } else {
219            TokenStream::new()
220        };
221
222        tokens.extend(quote! {
223            #ident: {
224                parameters: [#inputs],
225                result: #output,
226                #non_blocking
227            }
228        });
229    }
230}
231
232impl ToTokens for FfiSymbol {
233    fn to_tokens(&self, tokens: &mut TokenStream) {
234        tokens.extend(match self {
235            FfiSymbol::Function(ffi_function) => ffi_function.to_token_stream(),
236        });
237    }
238}
239
240impl ToTokens for FfiInterface {
241    fn to_tokens(&self, tokens: &mut TokenStream) {
242        let symbols = &self.symbols;
243        tokens.extend(quote! {
244            { #(#symbols),* }
245        });
246    }
247}
248
249impl<'a> ToTokens for FfiLib {
250    fn to_tokens(&self, tokens: &mut TokenStream) {
251        let FfiLib {
252            interface,
253            dylib_path,
254            lazy,
255            export,
256        } = self;
257
258        let export = if *export {
259            quote! { export }
260        } else {
261            TokenStream::new()
262        };
263
264        tokens.extend(if *lazy {
265            // [!TODO] refactor to a class?
266            quote! {
267                let symbols: any;
268                export function load(path: string = #dylib_path) {
269                    const { dlopen } = Deno;
270                    const { symbols: symbols_ } = dlopen(path, #interface);
271                    symbols = symbols_;
272                };
273            }
274        } else {
275            quote! {
276                #export const { symbols } = Deno.dlopen(#dylib_path, #interface);
277            }
278        });
279    }
280}
281
282/* -------------------------------------------------------------------------- */
283
284// MARK: tests
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    #[test]
291    fn test_print() {
292        let ffi_function = FfiFunction {
293            ident: format_ident!("some_symbol"),
294            inputs: vec![FfiType::Pointer, FfiType::U8],
295            output: FfiType::Void,
296            non_blocking: false,
297        };
298        println!("{}", ffi_function.to_token_stream().to_string());
299    }
300}