use proc_macro2::TokenStream;
use syn::Ident;
use crate::util::*;
#[derive(Clone, Debug, PartialEq)]
pub enum FfiType {
U8,
U16,
U32,
U64,
Usize,
I8,
I16,
I32,
I64,
Isize,
F32,
F64,
Void,
Bool,
Pointer,
Buffer,
FnPointer,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FfiFunction {
pub ident: Ident,
pub inputs: Vec<FfiType>,
pub output: FfiType,
pub non_blocking: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub enum FfiSymbol {
Function(FfiFunction),
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FfiInterface {
pub symbols: Vec<FfiSymbol>,
}
impl FfiInterface {
pub fn push_fn(&mut self, ffi_function: FfiFunction) {
self.symbols.push(FfiSymbol::Function(ffi_function));
}
pub fn append(&mut self, other: &mut FfiInterface) {
self.symbols.append(&mut other.symbols);
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FfiLib {
pub interface: FfiInterface,
pub lib_name: String,
pub lazy: bool,
pub export: bool,
}
impl FfiLib {
pub fn file_name(lib_name: &str, target: &str, extension: &str) -> String {
format!("{lib_name}.{target}.{extension}")
}
}
#[rustfmt::skip]
impl ToTokens for FfiType {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(match self {
FfiType::U8 => quote! { "u8" },
FfiType::U16 => quote! { "u16" },
FfiType::U32 => quote! { "u32" },
FfiType::U64 => quote! { "u64" },
FfiType::Usize => quote! { "usize" },
FfiType::I8 => quote! { "i8" },
FfiType::I16 => quote! { "i16" },
FfiType::I32 => quote! { "i32" },
FfiType::I64 => quote! { "i64" },
FfiType::Isize => quote! { "isize" },
FfiType::F32 => quote! { "f32" },
FfiType::F64 => quote! { "f64" },
FfiType::Void => quote! { "void" },
FfiType::Bool => quote! { "bool" },
FfiType::Pointer => quote! { "pointer" },
FfiType::Buffer => quote! { "buffer" },
FfiType::FnPointer => quote! { "function" },
});
}
}
impl ToTokens for FfiFunction {
fn to_tokens(&self, tokens: &mut TokenStream) {
let FfiFunction {
ident,
inputs,
output,
non_blocking,
} = self;
let inputs: Vec<&FfiType> = inputs
.iter()
.filter(|ffi_type| {
if **ffi_type == FfiType::Void {
false
} else {
true
}
})
.collect();
let inputs = quote! { #(#inputs),* };
let non_blocking = if *non_blocking {
quote! { nonblocking: true }
} else {
TokenStream::new()
};
tokens.extend(quote! {
#ident: {
parameters: [#inputs],
result: #output,
#non_blocking
}
});
}
}
impl ToTokens for FfiSymbol {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(match self {
FfiSymbol::Function(ffi_function) => ffi_function.to_token_stream(),
});
}
}
impl ToTokens for FfiInterface {
fn to_tokens(&self, tokens: &mut TokenStream) {
let symbols = &self.symbols;
tokens.extend(quote! {
{ #(#symbols),* }
});
}
}
impl<'a> ToTokens for FfiLib {
fn to_tokens(&self, tokens: &mut TokenStream) {
let FfiLib {
interface,
lib_name,
lazy,
export,
} = self;
let export = if *export {
quote! { export }
} else {
TokenStream::new()
};
let file_name = FfiLib::file_name(
lib_name,
"${Deno.build.target}",
"${({ windows: \"dll\", darwin: \"dylib\" } as Record<string, string>)[Deno.build.os] ?? \"so\"}",
);
let lib_path = raw(format!(
"const __dodo_lib = new URL(`./{file_name}`, import.meta.url);"
));
tokens.extend(if *lazy {
quote! {
#lib_path
let symbols: any;
export function load(path: string | URL = __dodo_lib) {
const { dlopen } = Deno;
const { symbols: symbols_ } = dlopen(path, #interface);
symbols = symbols_;
};
}
} else {
quote! {
#lib_path
#export const { symbols } = Deno.dlopen(__dodo_lib, #interface);
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_print() {
let mut ffi_lib = FfiLib {
lib_name: "lib".to_string(),
export: true,
..FfiLib::default()
};
ffi_lib.interface.push_fn(FfiFunction {
ident: format_ident!("some_symbol"),
inputs: vec![FfiType::Pointer, FfiType::U8],
output: FfiType::Void,
non_blocking: false,
});
ffi_lib.interface.push_fn(FfiFunction {
ident: format_ident!("slow_symbol"),
inputs: vec![FfiType::Buffer],
output: FfiType::U64,
non_blocking: true,
});
let eager = ffi_lib.to_token_stream().to_string();
ffi_lib.lazy = true;
let lazy = ffi_lib.to_token_stream().to_string();
insta::assert_snapshot!(crate::util::deno_fmt(format!("{eager}\n{lazy}")));
}
}