1use std::ffi::{CStr, CString};
7use std::os::raw::{c_char, c_int};
8use std::panic;
9
10use crate::{Aether, Value};
11
12#[repr(C)]
14pub struct AetherHandle {
15 _opaque: [u8; 0],
16}
17
18#[repr(C)]
20pub enum AetherErrorCode {
21 Success = 0,
22 ParseError = 1,
23 RuntimeError = 2,
24 NullPointer = 3,
25 Panic = 4,
26}
27
28#[unsafe(no_mangle)]
32pub extern "C" fn aether_new() -> *mut AetherHandle {
33 let engine = Box::new(Aether::new());
34 Box::into_raw(engine) as *mut AetherHandle
35}
36
37#[unsafe(no_mangle)]
41pub extern "C" fn aether_new_with_permissions() -> *mut AetherHandle {
42 let engine = Box::new(Aether::with_all_permissions());
43 Box::into_raw(engine) as *mut AetherHandle
44}
45
46#[unsafe(no_mangle)]
58pub extern "C" fn aether_eval(
59 handle: *mut AetherHandle,
60 code: *const c_char,
61 result: *mut *mut c_char,
62 error: *mut *mut c_char,
63) -> c_int {
64 #![allow(clippy::not_unsafe_ptr_arg_deref)]
65 if handle.is_null() || code.is_null() || result.is_null() || error.is_null() {
66 return AetherErrorCode::NullPointer as c_int;
67 }
68
69 let panic_result = panic::catch_unwind(|| unsafe {
71 let engine = &mut *(handle as *mut Aether);
72 let code_str = match CStr::from_ptr(code).to_str() {
73 Ok(s) => s,
74 Err(_) => return AetherErrorCode::RuntimeError as c_int,
75 };
76
77 match engine.eval(code_str) {
78 Ok(val) => {
79 let result_str = value_to_string(&val);
80 match CString::new(result_str) {
81 Ok(cstr) => {
82 *result = cstr.into_raw();
83 *error = std::ptr::null_mut();
84 AetherErrorCode::Success as c_int
85 }
86 Err(_) => AetherErrorCode::RuntimeError as c_int,
87 }
88 }
89 Err(e) => {
90 let error_str = e.to_string();
91 match CString::new(error_str) {
92 Ok(cstr) => {
93 *error = cstr.into_raw();
94 *result = std::ptr::null_mut();
95 if e.contains("Parse error") {
97 AetherErrorCode::ParseError as c_int
98 } else {
99 AetherErrorCode::RuntimeError as c_int
100 }
101 }
102 Err(_) => AetherErrorCode::RuntimeError as c_int,
103 }
104 }
105 }
106 });
107
108 match panic_result {
109 Ok(code) => code,
110 Err(_) => {
111 unsafe {
112 let panic_msg = CString::new("Panic occurred during evaluation").unwrap();
113 *error = panic_msg.into_raw();
114 *result = std::ptr::null_mut();
115 }
116 AetherErrorCode::Panic as c_int
117 }
118 }
119}
120
121#[unsafe(no_mangle)]
125pub extern "C" fn aether_version() -> *const c_char {
126 static VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
127 VERSION.as_ptr() as *const c_char
128}
129
130#[unsafe(no_mangle)]
132pub extern "C" fn aether_free(handle: *mut AetherHandle) {
133 if !handle.is_null() {
134 unsafe {
135 let _ = Box::from_raw(handle as *mut Aether);
136 }
137 }
138}
139
140#[unsafe(no_mangle)]
142pub extern "C" fn aether_free_string(s: *mut c_char) {
143 #![allow(clippy::not_unsafe_ptr_arg_deref)]
144 if !s.is_null() {
145 unsafe {
146 let _ = CString::from_raw(s);
147 }
148 }
149}
150
151fn value_to_string(value: &Value) -> String {
153 match value {
154 Value::Number(n) => {
155 if n.fract() == 0.0 {
157 format!("{:.0}", n)
158 } else {
159 n.to_string()
160 }
161 }
162 Value::String(s) => s.clone(),
163 Value::Boolean(b) => b.to_string(),
164 Value::Array(arr) => {
165 let items: Vec<String> = arr.iter().map(value_to_string).collect();
166 format!("[{}]", items.join(", "))
167 }
168 Value::Dict(map) => {
169 let items: Vec<String> = map
170 .iter()
171 .map(|(k, v)| format!("{}: {}", k, value_to_string(v)))
172 .collect();
173 format!("{{{}}}", items.join(", "))
174 }
175 Value::Null => "null".to_string(),
176 Value::Function { .. } => "<function>".to_string(),
177 Value::BuiltIn { name, .. } => format!("<builtin: {}>", name),
178 Value::Generator { .. } => "<generator>".to_string(),
179 Value::Lazy { .. } => "<lazy>".to_string(),
180 Value::Fraction(f) => f.to_string(),
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_ffi_basic_eval() {
190 let handle = aether_new();
191 assert!(!handle.is_null());
192
193 let code = CString::new("Set X 10\n(X + 20)").unwrap();
194 let mut result: *mut c_char = std::ptr::null_mut();
195 let mut error: *mut c_char = std::ptr::null_mut();
196
197 let status = aether_eval(handle, code.as_ptr(), &mut result, &mut error);
198
199 assert_eq!(status, AetherErrorCode::Success as c_int);
200 assert!(!result.is_null());
201 assert!(error.is_null());
202
203 unsafe {
204 let result_str = CStr::from_ptr(result).to_str().unwrap();
205 assert_eq!(result_str, "30");
206 aether_free_string(result);
207 }
208
209 aether_free(handle);
210 }
211
212 #[test]
213 fn test_ffi_error_handling() {
214 let handle = aether_new();
215 let code = CString::new("UNDEFINED_VAR").unwrap();
216 let mut result: *mut c_char = std::ptr::null_mut();
217 let mut error: *mut c_char = std::ptr::null_mut();
218
219 let status = aether_eval(handle, code.as_ptr(), &mut result, &mut error);
220
221 assert_ne!(status, AetherErrorCode::Success as c_int);
222 assert!(result.is_null());
223 assert!(!error.is_null());
224
225 #[allow(unused_unsafe)]
226 unsafe {
227 aether_free_string(error);
228 }
229
230 aether_free(handle);
231 }
232}