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