alef_adapters/
sync_function.rs1use alef_core::config::{AdapterConfig, AlefConfig, Language};
2
3pub fn generate_body(adapter: &AdapterConfig, language: Language, config: &AlefConfig) -> anyhow::Result<String> {
5 let body = match language {
6 Language::Python => gen_python_body(adapter, config),
7 Language::Node => gen_node_body(adapter, config),
8 Language::Ruby => gen_ruby_body(adapter, config),
9 Language::Php => gen_php_body(adapter, config),
10 Language::Elixir => gen_elixir_body(adapter, config),
11 Language::Wasm => gen_wasm_body(adapter, config),
12 Language::Ffi => gen_ffi_body(adapter, config),
13 Language::Go => gen_go_body(adapter, config),
14 Language::Java => gen_java_body(adapter, config),
15 Language::Csharp => gen_csharp_body(adapter, config),
16 Language::R => gen_r_body(adapter, config),
17 Language::Rust => anyhow::bail!("Rust does not need generated binding adapters"),
18 };
19 Ok(body)
20}
21
22fn call_args(adapter: &AdapterConfig) -> Vec<String> {
24 adapter
25 .params
26 .iter()
27 .map(|p| {
28 if p.optional {
29 format!("{}.map(Into::into)", p.name)
30 } else {
31 format!("{}.into()", p.name)
32 }
33 })
34 .collect()
35}
36
37fn gen_python_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
42 let core_path = &adapter.core_path;
43 let returns = adapter.returns.as_deref().unwrap_or("()");
44 let gil_release = adapter.gil_release;
45
46 let args = call_args(adapter);
47 let call_str = args.join(", ");
48
49 if gil_release {
50 format!(
51 "py.allow_threads(|| {{\n \
52 {core_path}({call_str})\n \
53 .map({returns}::from)\n \
54 .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))\n \
55 }})"
56 )
57 } else {
58 format!(
59 "{core_path}({call_str})\n \
60 .map({returns}::from)\n \
61 .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))"
62 )
63 }
64}
65
66fn gen_node_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
71 let core_path = &adapter.core_path;
72 let returns = adapter.returns.as_deref().unwrap_or("()");
73
74 let args = call_args(adapter);
75 let call_str = args.join(", ");
76
77 format!(
78 "{core_path}({call_str})\n \
79 .map({returns}::from)\n \
80 .map_err(|e| napi::Error::from_reason(e.to_string()))"
81 )
82}
83
84fn gen_ruby_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
89 let core_path = &adapter.core_path;
90 let returns = adapter.returns.as_deref().unwrap_or("()");
91
92 let args = call_args(adapter);
93 let call_str = args.join(", ");
94
95 format!(
96 "{core_path}({call_str})\n \
97 .map({returns}::from)\n \
98 .map_err(|e| magnus::Error::new(magnus::exception::runtime_error(), e.to_string()))"
99 )
100}
101
102fn gen_php_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
107 let core_path = &adapter.core_path;
108 let returns = adapter.returns.as_deref().unwrap_or("()");
109
110 let args = call_args(adapter);
111 let call_str = args.join(", ");
112
113 format!(
114 "{core_path}({call_str})\n \
115 .map({returns}::from)\n \
116 .map_err(|e| PhpException::default(e.to_string()))"
117 )
118}
119
120fn gen_elixir_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
125 let core_path = &adapter.core_path;
126 let returns = adapter.returns.as_deref().unwrap_or("()");
127
128 let args = call_args(adapter);
129 let call_str = args.join(", ");
130
131 format!(
132 "{core_path}({call_str})\n \
133 .map({returns}::from)\n \
134 .map_err(|e| e.to_string())"
135 )
136}
137
138fn gen_wasm_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
143 let core_path = &adapter.core_path;
144 let returns = adapter.returns.as_deref().unwrap_or("JsValue");
145
146 let args = call_args(adapter);
147 let call_str = args.join(", ");
148
149 format!(
150 "{core_path}({call_str})\n \
151 .map({returns}::from)\n \
152 .map_err(|e| JsValue::from_str(&e.to_string()))"
153 )
154}
155
156fn gen_ffi_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
161 let core_path = &adapter.core_path;
162 let prefix = config.ffi_prefix();
163 let name = &adapter.name;
164 let _ = prefix;
165 let _ = name;
166
167 let conversions: Vec<String> = adapter
168 .params
169 .iter()
170 .filter_map(|p| {
171 if p.ty == "String" || p.ty == "&str" {
172 Some(format!(
173 "let {name} = unsafe {{ std::ffi::CStr::from_ptr({name}) }}\n \
174 .to_str()\n \
175 .unwrap_or_default()\n \
176 .to_owned();",
177 name = p.name
178 ))
179 } else {
180 None
181 }
182 })
183 .collect();
184
185 let call_args_list: Vec<String> = adapter.params.iter().map(|p| p.name.clone()).collect();
186 let call_str = call_args_list.join(", ");
187 let conversion_block = if conversions.is_empty() {
188 String::new()
189 } else {
190 format!("{}\n", conversions.join("\n"))
191 };
192
193 format!(
194 "{conversion_block}\
195 match {core_path}({call_str}) {{\n \
196 Ok(result) => {{\n \
197 let json = serde_json::to_string(&result).unwrap_or_default();\n \
198 std::ffi::CString::new(json).unwrap_or_default().into_raw()\n \
199 }}\n \
200 Err(e) => {{\n \
201 update_last_error(e);\n \
202 std::ptr::null_mut()\n \
203 }}\n \
204 }}"
205 )
206}
207
208fn gen_go_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
213 let name = &adapter.name;
214 let prefix = config.ffi_prefix();
215 let returns = adapter.returns.as_deref().unwrap_or("string");
216
217 let go_name = to_pascal_case(name);
218
219 let c_call_args: Vec<String> = adapter
220 .params
221 .iter()
222 .map(|p| {
223 if p.ty == "String" || p.ty == "&str" {
224 format!("C.CString({})", p.name)
225 } else {
226 format!("C.{}({})", rust_type_to_c_go(&p.ty), p.name)
227 }
228 })
229 .collect();
230
231 let call_str = c_call_args.join(", ");
232 let _ = go_name;
233
234 format!(
235 "result := C.{prefix}_{name}({call_str})\n \
236 if result == nil {{\n \
237 return nil, fmt.Errorf(\"%s\", lastError())\n \
238 }}\n \
239 defer C.free(unsafe.Pointer(result))\n \
240 var out {returns}\n \
241 if err := json.Unmarshal([]byte(C.GoString(result)), &out); err != nil {{\n \
242 return nil, err\n \
243 }}\n \
244 return &out, nil"
245 )
246}
247
248fn gen_java_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
253 let name = &adapter.name;
254 let prefix = config.ffi_prefix();
255
256 let arg_pass = if adapter.params.is_empty() {
257 String::new()
258 } else {
259 format!(
260 ", {}",
261 adapter
262 .params
263 .iter()
264 .map(|p| {
265 if p.ty == "String" || p.ty == "&str" {
266 format!("arena.allocateFrom({})", p.name)
267 } else {
268 p.name.clone()
269 }
270 })
271 .collect::<Vec<_>>()
272 .join(", ")
273 )
274 };
275
276 format!(
277 "try (var arena = Arena.ofConfined()) {{\n\
278 \x20 var result = (MemorySegment) {prefix}_{name}.invokeExact(arena{arg_pass});\n\
279 \x20 if (result.equals(MemorySegment.NULL)) {{\n\
280 \x20 throw new RuntimeException(lastError());\n\
281 \x20 }}\n\
282 \x20 return result.getString(0);\n\
283 \x20 }}"
284 )
285}
286
287fn gen_csharp_body(adapter: &AdapterConfig, config: &AlefConfig) -> String {
292 let name = &adapter.name;
293 let prefix = config.ffi_prefix();
294
295 let call_args_list: Vec<String> = adapter.params.iter().map(|p| p.name.clone()).collect();
296 let call_str = call_args_list.join(", ");
297
298 format!(
299 "var ptr = {prefix}_{name}_native({call_str});\n\
300 \x20 if (ptr == IntPtr.Zero)\n\
301 \x20 throw new InvalidOperationException(GetLastError());\n\
302 \x20 try {{ return Marshal.PtrToStringUTF8(ptr)!; }}\n\
303 \x20 finally {{ FreeString(ptr); }}"
304 )
305}
306
307fn gen_r_body(adapter: &AdapterConfig, _config: &AlefConfig) -> String {
312 let core_path = &adapter.core_path;
313 let returns = adapter.returns.as_deref().unwrap_or("Robj");
314
315 let args = call_args(adapter);
316 let call_str = args.join(", ");
317
318 format!(
319 "{core_path}({call_str})\n \
320 .map({returns}::from)\n \
321 .map_err(|e| extendr_api::Error::Other(e.to_string()))"
322 )
323}
324
325fn to_pascal_case(s: &str) -> String {
330 s.split('_')
331 .map(|part| {
332 let mut chars = part.chars();
333 match chars.next() {
334 None => String::new(),
335 Some(first) => first.to_uppercase().to_string() + chars.as_str(),
336 }
337 })
338 .collect()
339}
340
341fn rust_type_to_c_go(ty: &str) -> &str {
342 match ty {
343 "bool" => "int",
344 "i32" => "int",
345 "i64" => "longlong",
346 "u32" => "uint",
347 "u64" => "ulonglong",
348 "f32" => "float",
349 "f64" => "double",
350 _ => "int",
351 }
352}