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