1use proc_macro2::TokenStream;
2use syn::Ident;
3
4use crate::util::*;
5
6#[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, FnPointer,
30 }
32
33#[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, }
42
43#[derive(Clone, Debug, PartialEq)]
45pub enum FfiSymbol {
46 Function(FfiFunction),
47 }
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#[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 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#[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}