1use std::ffi::{CStr, CString};
7use std::os::raw::{c_char, c_int};
8use std::panic;
9use std::sync::Mutex;
10
11use crate::{Aether, Value};
12use serde_json::json;
13
14#[repr(C)]
16pub struct AetherHandle {
17 _opaque: [u8; 0],
18}
19
20#[repr(C)]
22pub enum AetherErrorCode {
23 Success = 0,
24 ParseError = 1,
25 RuntimeError = 2,
26 NullPointer = 3,
27 Panic = 4,
28 InvalidJSON = 5,
29 VariableNotFound = 6,
30}
31
32#[repr(C)]
34pub struct AetherLimits {
35 pub max_steps: c_int,
36 pub max_recursion_depth: c_int,
37 pub max_duration_ms: c_int,
38}
39
40#[repr(C)]
42pub struct AetherCacheStats {
43 pub hits: c_int,
44 pub misses: c_int,
45 pub size: c_int,
46}
47
48struct ThreadSafeEngine {
50 #[allow(dead_code)]
51 engine: Aether,
52 #[allow(dead_code)]
53 mutex: Mutex<()>,
54}
55
56impl ThreadSafeEngine {
57 #[allow(dead_code)]
58 fn new(engine: Aether) -> Self {
59 Self {
60 engine,
61 mutex: Mutex::new(()),
62 }
63 }
64}
65
66#[unsafe(no_mangle)]
70pub extern "C" fn aether_new() -> *mut AetherHandle {
71 let engine = Box::new(Aether::new());
72 Box::into_raw(engine) as *mut AetherHandle
73}
74
75#[unsafe(no_mangle)]
79pub extern "C" fn aether_new_with_permissions() -> *mut AetherHandle {
80 let engine = Box::new(Aether::with_all_permissions());
81 Box::into_raw(engine) as *mut AetherHandle
82}
83
84#[unsafe(no_mangle)]
96pub extern "C" fn aether_eval(
97 handle: *mut AetherHandle,
98 code: *const c_char,
99 result: *mut *mut c_char,
100 error: *mut *mut c_char,
101) -> c_int {
102 #![allow(clippy::not_unsafe_ptr_arg_deref)]
103 if handle.is_null() || code.is_null() || result.is_null() || error.is_null() {
104 return AetherErrorCode::NullPointer as c_int;
105 }
106
107 let panic_result = panic::catch_unwind(|| unsafe {
109 let engine = &mut *(handle as *mut Aether);
110 let code_str = match CStr::from_ptr(code).to_str() {
111 Ok(s) => s,
112 Err(_) => return AetherErrorCode::RuntimeError as c_int,
113 };
114
115 match engine.eval(code_str) {
116 Ok(val) => {
117 let result_str = value_to_string(&val);
118 match CString::new(result_str) {
119 Ok(cstr) => {
120 *result = cstr.into_raw();
121 *error = std::ptr::null_mut();
122 AetherErrorCode::Success as c_int
123 }
124 Err(_) => AetherErrorCode::RuntimeError as c_int,
125 }
126 }
127 Err(e) => {
128 let error_str = e.to_string();
129 match CString::new(error_str) {
130 Ok(cstr) => {
131 *error = cstr.into_raw();
132 *result = std::ptr::null_mut();
133 if e.contains("Parse error") {
135 AetherErrorCode::ParseError as c_int
136 } else {
137 AetherErrorCode::RuntimeError as c_int
138 }
139 }
140 Err(_) => AetherErrorCode::RuntimeError as c_int,
141 }
142 }
143 }
144 });
145
146 match panic_result {
147 Ok(code) => code,
148 Err(_) => {
149 unsafe {
150 let panic_msg = CString::new("Panic occurred during evaluation").unwrap();
151 *error = panic_msg.into_raw();
152 *result = std::ptr::null_mut();
153 }
154 AetherErrorCode::Panic as c_int
155 }
156 }
157}
158
159#[unsafe(no_mangle)]
163pub extern "C" fn aether_version() -> *const c_char {
164 static VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
165 VERSION.as_ptr() as *const c_char
166}
167
168#[unsafe(no_mangle)]
170pub extern "C" fn aether_free(handle: *mut AetherHandle) {
171 if !handle.is_null() {
172 unsafe {
173 let _ = Box::from_raw(handle as *mut Aether);
174 }
175 }
176}
177
178#[unsafe(no_mangle)]
180pub extern "C" fn aether_free_string(s: *mut c_char) {
181 #![allow(clippy::not_unsafe_ptr_arg_deref)]
182 if !s.is_null() {
183 unsafe {
184 let _ = CString::from_raw(s);
185 }
186 }
187}
188
189fn value_to_string(value: &Value) -> String {
191 match value {
192 Value::Number(n) => {
193 if n.fract() == 0.0 {
195 format!("{:.0}", n)
196 } else {
197 n.to_string()
198 }
199 }
200 Value::String(s) => s.clone(),
201 Value::Boolean(b) => b.to_string(),
202 Value::Array(arr) => {
203 let items: Vec<String> = arr.iter().map(value_to_string).collect();
204 format!("[{}]", items.join(", "))
205 }
206 Value::Dict(map) => {
207 let items: Vec<String> = map
208 .iter()
209 .map(|(k, v)| format!("{}: {}", k, value_to_string(v)))
210 .collect();
211 format!("{{{}}}", items.join(", "))
212 }
213 Value::Null => "null".to_string(),
214 Value::Function { .. } => "<function>".to_string(),
215 Value::BuiltIn { name, .. } => format!("<builtin: {}>", name),
216 Value::Generator { .. } => "<generator>".to_string(),
217 Value::Lazy { .. } => "<lazy>".to_string(),
218 Value::Fraction(f) => f.to_string(),
219 }
220}
221
222fn value_to_json(value: &Value) -> String {
224 match value {
225 Value::Number(n) => {
226 json!(n).to_string()
228 }
229 Value::String(s) => json!(s).to_string(),
230 Value::Boolean(b) => json!(b).to_string(),
231 Value::Array(arr) => {
232 let items: Vec<serde_json::Value> = arr.iter().map(json_from_value).collect();
233 json!(items).to_string()
234 }
235 Value::Dict(map) => {
236 let mut obj = serde_json::Map::new();
237 for (k, v) in map {
238 obj.insert(k.clone(), json_from_value(v));
239 }
240 json!(obj).to_string()
241 }
242 Value::Null => "null".to_string(),
243 Value::Function { .. } => json!("<function>").to_string(),
244 Value::BuiltIn { name, .. } => json!(format!("<builtin: {}>", name)).to_string(),
245 Value::Generator { .. } => json!("<generator>").to_string(),
246 Value::Lazy { .. } => json!("<lazy>").to_string(),
247 Value::Fraction(f) => json!(f.to_string()).to_string(),
248 }
249}
250
251fn json_from_value(value: &Value) -> serde_json::Value {
253 match value {
254 Value::Number(n) => json!(n),
255 Value::String(s) => json!(s),
256 Value::Boolean(b) => json!(b),
257 Value::Array(arr) => {
258 let items: Vec<serde_json::Value> = arr.iter().map(json_from_value).collect();
259 json!(items)
260 }
261 Value::Dict(map) => {
262 let mut obj = serde_json::Map::new();
263 for (k, v) in map {
264 obj.insert(k.clone(), json_from_value(v));
265 }
266 json!(obj)
267 }
268 Value::Null => json!(null),
269 Value::Function { .. } => json!("<function>"),
270 Value::BuiltIn { name, .. } => json!(format!("<builtin: {}>", name)),
271 Value::Generator { .. } => json!("<generator>"),
272 Value::Lazy { .. } => json!("<lazy>"),
273 Value::Fraction(f) => json!(f.to_string()),
274 }
275}
276
277fn json_to_value(json_str: &str) -> Result<Value, String> {
279 let v: serde_json::Value =
280 serde_json::from_str(json_str).map_err(|e| format!("Invalid JSON: {}", e))?;
281
282 Ok(match v {
283 serde_json::Value::Number(n) => {
284 if n.is_i64() {
285 Value::Number(n.as_i64().unwrap() as f64)
286 } else {
287 Value::Number(n.as_f64().unwrap())
288 }
289 }
290 serde_json::Value::String(s) => Value::String(s),
291 serde_json::Value::Bool(b) => Value::Boolean(b),
292 serde_json::Value::Array(arr) => {
293 let items: Result<Vec<_>, _> =
294 arr.iter().map(|v| json_to_value(&v.to_string())).collect();
295 Value::Array(items?)
296 }
297 serde_json::Value::Object(obj) => {
298 let mut map = std::collections::HashMap::new();
299 for (k, v) in obj {
300 map.insert(k, json_to_value(&v.to_string())?);
301 }
302 Value::Dict(map)
303 }
304 serde_json::Value::Null => Value::Null,
305 })
306}
307
308#[unsafe(no_mangle)]
328pub unsafe extern "C" fn aether_set_global(
329 handle: *mut AetherHandle,
330 name: *const c_char,
331 value_json: *const c_char,
332) -> c_int {
333 if handle.is_null() || name.is_null() || value_json.is_null() {
334 return AetherErrorCode::NullPointer as c_int;
335 }
336
337 let panic_result = panic::catch_unwind(|| unsafe {
338 let engine = &mut *(handle as *mut Aether);
339 let name_str = match CStr::from_ptr(name).to_str() {
340 Ok(s) => s,
341 Err(_) => return AetherErrorCode::RuntimeError as c_int,
342 };
343 let json_str = match CStr::from_ptr(value_json).to_str() {
344 Ok(s) => s,
345 Err(_) => return AetherErrorCode::RuntimeError as c_int,
346 };
347
348 let value = match json_to_value(json_str) {
350 Ok(v) => v,
351 Err(_) => return AetherErrorCode::InvalidJSON as c_int,
352 };
353
354 engine.set_global(name_str, value);
355 AetherErrorCode::Success as c_int
356 });
357
358 match panic_result {
359 Ok(code) => code,
360 Err(_) => AetherErrorCode::Panic as c_int,
361 }
362}
363
364#[unsafe(no_mangle)]
381pub unsafe extern "C" fn aether_get_global(
382 handle: *mut AetherHandle,
383 name: *const c_char,
384 value_json: *mut *mut c_char,
385) -> c_int {
386 if handle.is_null() || name.is_null() || value_json.is_null() {
387 return AetherErrorCode::NullPointer as c_int;
388 }
389
390 let panic_result = panic::catch_unwind(|| unsafe {
391 let engine = &mut *(handle as *mut Aether);
392 let name_str = match CStr::from_ptr(name).to_str() {
393 Ok(s) => s,
394 Err(_) => return AetherErrorCode::RuntimeError as c_int,
395 };
396
397 let value = engine.evaluator.get_global(name_str);
399
400 match value {
401 Some(val) => {
402 let json_str = value_to_json(&val);
403 match CString::new(json_str) {
404 Ok(cstr) => {
405 *value_json = cstr.into_raw();
406 AetherErrorCode::Success as c_int
407 }
408 Err(_) => AetherErrorCode::RuntimeError as c_int,
409 }
410 }
411 None => AetherErrorCode::VariableNotFound as c_int,
412 }
413 });
414
415 match panic_result {
416 Ok(code) => code,
417 Err(_) => AetherErrorCode::Panic as c_int,
418 }
419}
420
421#[unsafe(no_mangle)]
426pub extern "C" fn aether_reset_env(handle: *mut AetherHandle) {
427 if handle.is_null() {
428 return;
429 }
430
431 let _ = panic::catch_unwind(|| unsafe {
432 let engine = &mut *(handle as *mut Aether);
433 engine.reset_env();
434 });
435}
436
437#[unsafe(no_mangle)]
455pub unsafe extern "C" fn aether_take_trace(
456 handle: *mut AetherHandle,
457 trace_json: *mut *mut c_char,
458) -> c_int {
459 if handle.is_null() || trace_json.is_null() {
460 return AetherErrorCode::NullPointer as c_int;
461 }
462
463 let panic_result = panic::catch_unwind(|| unsafe {
464 let engine = &mut *(handle as *mut Aether);
465 let traces = engine.take_trace();
466
467 let json_array = json!(traces).to_string();
468 match CString::new(json_array) {
469 Ok(cstr) => {
470 *trace_json = cstr.into_raw();
471 AetherErrorCode::Success as c_int
472 }
473 Err(_) => AetherErrorCode::RuntimeError as c_int,
474 }
475 });
476
477 match panic_result {
478 Ok(code) => code,
479 Err(_) => AetherErrorCode::Panic as c_int,
480 }
481}
482
483#[unsafe(no_mangle)]
488pub extern "C" fn aether_clear_trace(handle: *mut AetherHandle) {
489 if handle.is_null() {
490 return;
491 }
492
493 let _ = panic::catch_unwind(|| unsafe {
494 let engine = &mut *(handle as *mut Aether);
495 engine.clear_trace();
496 });
497}
498
499#[unsafe(no_mangle)]
513pub unsafe extern "C" fn aether_trace_records(
514 handle: *mut AetherHandle,
515 trace_json: *mut *mut c_char,
516) -> c_int {
517 if handle.is_null() || trace_json.is_null() {
518 return AetherErrorCode::NullPointer as c_int;
519 }
520
521 let panic_result = panic::catch_unwind(|| unsafe {
522 let engine = &mut *(handle as *mut Aether);
523 let records = engine.trace_records();
524
525 let json_array: Vec<serde_json::Value> = records
527 .iter()
528 .map(|entry| {
529 json!({
530 "level": format!("{:?}", entry.level),
531 "category": entry.category,
532 "timestamp": entry.timestamp.elapsed().as_secs(),
533 "values": entry.values.iter().map(value_to_json).collect::<Vec<_>>(),
534 "label": entry.label,
535 })
536 })
537 .collect();
538
539 match CString::new(json!(json_array).to_string()) {
540 Ok(cstr) => {
541 *trace_json = cstr.into_raw();
542 AetherErrorCode::Success as c_int
543 }
544 Err(_) => AetherErrorCode::RuntimeError as c_int,
545 }
546 });
547
548 match panic_result {
549 Ok(code) => code,
550 Err(_) => AetherErrorCode::Panic as c_int,
551 }
552}
553
554#[unsafe(no_mangle)]
568pub unsafe extern "C" fn aether_trace_stats(
569 handle: *mut AetherHandle,
570 stats_json: *mut *mut c_char,
571) -> c_int {
572 if handle.is_null() || stats_json.is_null() {
573 return AetherErrorCode::NullPointer as c_int;
574 }
575
576 let panic_result = panic::catch_unwind(|| unsafe {
577 let engine = &mut *(handle as *mut Aether);
578 let stats = engine.trace_stats();
579
580 let json_stats = json!({
581 "total_entries": stats.total_entries,
582 "by_level": stats.by_level,
583 "by_category": stats.by_category,
584 "buffer_size": stats.buffer_size,
585 "buffer_full": stats.buffer_full,
586 })
587 .to_string();
588
589 match CString::new(json_stats) {
590 Ok(cstr) => {
591 *stats_json = cstr.into_raw();
592 AetherErrorCode::Success as c_int
593 }
594 Err(_) => AetherErrorCode::RuntimeError as c_int,
595 }
596 });
597
598 match panic_result {
599 Ok(code) => code,
600 Err(_) => AetherErrorCode::Panic as c_int,
601 }
602}
603
604#[unsafe(no_mangle)]
618pub unsafe extern "C" fn aether_set_limits(handle: *mut AetherHandle, limits: *const AetherLimits) {
619 if handle.is_null() || limits.is_null() {
620 return;
621 }
622
623 let _ = panic::catch_unwind(|| unsafe {
624 let engine = &mut *(handle as *mut Aether);
625 let limits_ref = &*limits;
626
627 let rust_limits = crate::runtime::ExecutionLimits {
628 max_steps: if limits_ref.max_steps < 0 {
629 None
630 } else {
631 Some(limits_ref.max_steps as usize)
632 },
633 max_recursion_depth: if limits_ref.max_recursion_depth < 0 {
634 None
635 } else {
636 Some(limits_ref.max_recursion_depth as usize)
637 },
638 max_duration_ms: if limits_ref.max_duration_ms < 0 {
639 None
640 } else {
641 Some(limits_ref.max_duration_ms as u64)
642 },
643 max_memory_bytes: None,
644 };
645
646 engine.set_limits(rust_limits);
647 });
648}
649
650#[unsafe(no_mangle)]
660pub unsafe extern "C" fn aether_get_limits(handle: *mut AetherHandle, limits: *mut AetherLimits) {
661 if handle.is_null() || limits.is_null() {
662 return;
663 }
664
665 let _ = panic::catch_unwind(|| unsafe {
666 let engine = &mut *(handle as *mut Aether);
667 let rust_limits = engine.limits();
668
669 (*limits).max_steps = match rust_limits.max_steps {
670 Some(v) => v as c_int,
671 None => -1,
672 };
673 (*limits).max_recursion_depth = match rust_limits.max_recursion_depth {
674 Some(v) => v as c_int,
675 None => -1,
676 };
677 (*limits).max_duration_ms = match rust_limits.max_duration_ms {
678 Some(v) => v as c_int,
679 None => -1,
680 };
681 });
682}
683
684#[unsafe(no_mangle)]
693pub extern "C" fn aether_clear_cache(handle: *mut AetherHandle) {
694 if handle.is_null() {
695 return;
696 }
697
698 let _ = panic::catch_unwind(|| unsafe {
699 let engine = &mut *(handle as *mut Aether);
700 engine.clear_cache();
701 });
702}
703
704#[unsafe(no_mangle)]
714pub unsafe extern "C" fn aether_cache_stats(
715 handle: *mut AetherHandle,
716 stats: *mut AetherCacheStats,
717) {
718 if handle.is_null() || stats.is_null() {
719 return;
720 }
721
722 let _ = panic::catch_unwind(|| unsafe {
723 let engine = &mut *(handle as *mut Aether);
724 let rust_stats = engine.cache_stats();
725
726 (*stats).hits = rust_stats.hits as c_int;
727 (*stats).misses = rust_stats.misses as c_int;
728 (*stats).size = rust_stats.size as c_int;
729 });
730}
731
732#[unsafe(no_mangle)]
744pub extern "C" fn aether_set_optimization(
745 handle: *mut AetherHandle,
746 constant_folding: c_int,
747 dead_code_elimination: c_int,
748 tail_recursion: c_int,
749) {
750 if handle.is_null() {
751 return;
752 }
753
754 let _ = panic::catch_unwind(|| unsafe {
755 let engine = &mut *(handle as *mut Aether);
756 engine.set_optimization(
757 constant_folding != 0,
758 dead_code_elimination != 0,
759 tail_recursion != 0,
760 );
761 });
762}