use alloc::{ffi::CString, format};
use core::ffi::{CStr, c_char, c_int};
use crate::{ast::Parser, token::Tokenizer, val::Val};
#[cfg(feature = "json")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn qcl_eval_json(expression: *const c_char, json_ctx: *const c_char) -> *mut c_char {
if expression.is_null() {
return set_err("null expression pointer".to_string());
}
if json_ctx.is_null() {
return set_err("null json_ctx pointer".to_string());
}
let expr_str = match unsafe { CStr::from_ptr(expression) }.to_str() {
Ok(s) => s,
Err(e) => return set_err(format!("invalid expression UTF-8: {e}")),
};
let ctx_str = match unsafe { CStr::from_ptr(json_ctx) }.to_str() {
Ok(s) => s,
Err(e) => return set_err(format!("invalid context UTF-8: {e}")),
};
let ctx: Val = match crate::de::from_json_str(ctx_str) {
Ok(v) => v,
Err(e) => return set_err(format!("{e}")),
};
eval_inner(expr_str, &ctx)
}
fn eval_inner(expression: &str, ctx: &Val) -> *mut c_char {
let tokens = match Tokenizer::new(expression) {
Ok(t) => t,
Err(e) => return set_err(format!("{e}")),
};
let expr = match Parser::new(&tokens).parse() {
Ok(e) => e,
Err(e) => return set_err(format!("{e}")),
};
match expr.eval(ctx) {
Ok(result) => to_c_string(format!("{result}")),
Err(e) => set_err(format!("{e}")),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn qcl_last_error() -> *mut c_char {
LAST_ERROR.with(|cell| {
let err = cell.take();
match err {
Some(msg) => to_c_string(msg),
None => core::ptr::null_mut(),
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn qcl_free(ptr: *mut c_char) {
if !ptr.is_null() {
drop(unsafe { CString::from_raw(ptr) });
}
}
#[cfg(feature = "json")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn qcl_check_json(expression: *const c_char, json_ctx: *const c_char) -> c_int {
if expression.is_null() {
set_err("null expression pointer".to_string());
return -1;
}
if json_ctx.is_null() {
set_err("null json_ctx pointer".to_string());
return -1;
}
let expr_str = match unsafe { CStr::from_ptr(expression) }.to_str() {
Ok(s) => s,
Err(e) => {
set_err(format!("invalid expression UTF-8: {e}"));
return -1;
}
};
let ctx_str = match unsafe { CStr::from_ptr(json_ctx) }.to_str() {
Ok(s) => s,
Err(e) => {
set_err(format!("invalid context UTF-8: {e}"));
return -1;
}
};
let ctx: Val = match crate::de::from_json_str(ctx_str) {
Ok(v) => v,
Err(e) => {
set_err(format!("{e}"));
return -1;
}
};
let tokens = match Tokenizer::new(expr_str) {
Ok(t) => t,
Err(e) => {
set_err(format!("{e}"));
return -1;
}
};
let expr = match Parser::new(&tokens).parse() {
Ok(e) => e,
Err(e) => {
set_err(format!("{e}"));
return -1;
}
};
match expr.eval(&ctx) {
Ok(Val::Bool(true)) => 1,
Ok(Val::Bool(false) | Val::Nil) => 0,
Ok(_) => 1,
Err(e) => {
set_err(format!("{e}"));
-1
}
}
}
std::thread_local! {
static LAST_ERROR: std::cell::Cell<Option<alloc::string::String>> = const { std::cell::Cell::new(None) };
}
fn set_err(msg: alloc::string::String) -> *mut c_char {
LAST_ERROR.with(|cell| cell.set(Some(msg)));
core::ptr::null_mut()
}
fn to_c_string(s: alloc::string::String) -> *mut c_char {
match CString::new(s) {
Ok(cs) => cs.into_raw(),
Err(e) => {
LAST_ERROR.with(|cell| cell.set(Some(format!("CString::new failed: {e}"))));
core::ptr::null_mut()
}
}
}