use std::ffi::CString;
use std::ffi::c_int;
use std::ffi::c_void;
use std::ptr;
use std::str::FromStr;
use jimtcl_sys::Jim_CreateCommand;
use crate::JimResult;
use crate::error::JimError;
use crate::object::IntoJimObj;
use crate::sys;
use super::Interp;
use super::JimObject;
trait JimCmdInvoke {
fn invoke_cmd<'jim>(
&mut self,
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>>;
}
pub(crate) struct JimCmd<CF, R>
where
CF: for<'jim> FnMut(&'jim Interp, &[JimObject<'jim>]) -> JimResult<R>,
R: IntoJimObj,
{
name: CString,
proc: CF,
}
#[repr(transparent)]
struct JimCmdWrap {
cmd: Box<dyn JimCmdInvoke>,
}
impl<CF, R> JimCmd<CF, R>
where
CF: for<'jim> FnMut(&'jim Interp, &[JimObject<'jim>]) -> JimResult<R> + 'static,
R: IntoJimObj + 'static,
{
pub(crate) fn wrap_fn(name: &str, proc: CF) -> JimCmd<CF, R> {
JimCmd {
name: CString::from_str(name).expect("command name cannot have null"),
proc,
}
}
pub(crate) fn register(self, interp: &Interp) -> JimResult<()> {
let name = self.name.clone();
let wrap = JimCmdWrap {
cmd: Box::new(self),
};
let data = Box::leak(Box::new(wrap));
let res = unsafe {
Jim_CreateCommand(
interp.interp,
name.as_ptr(),
Some(rust_jim_cmd),
ptr::from_mut(data) as *mut c_void,
Some(rust_free_jim),
)
};
if res == 0 {
Ok(())
} else {
Err(JimError::OtherCode((res as u32).into()))
}
}
}
impl<CF, R> JimCmdInvoke for JimCmd<CF, R>
where
CF: for<'jim> FnMut(&'jim Interp, &[JimObject<'jim>]) -> JimResult<R>,
R: IntoJimObj,
{
fn invoke_cmd<'jim>(
&mut self,
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>> {
let result = (self.proc)(interp, args)?.to_jim(interp);
Ok(result)
}
}
unsafe extern "C" fn rust_jim_cmd(
interp: *mut sys::Jim_Interp,
argc: c_int,
argv: *const *mut sys::Jim_Obj,
) -> c_int {
let interp = Interp::wrap(interp);
let mut args = Vec::with_capacity(argc as usize);
for i in 0..argc {
unsafe {
args.push(JimObject::wrap(&interp, *argv.add(i as usize)));
}
}
let res = unsafe {
let data = (*interp.interp).cmdPrivData as *mut JimCmdWrap;
(*data).cmd.invoke_cmd(&interp, &args)
};
match res {
Err(JimError::OtherCode(c)) => c.into(),
Err(_) => sys::JIM_ERR as c_int,
Ok(mut v) => {
if !v.is_null() {
interp.set_result(&mut v);
}
0
}
}
}
unsafe extern "C" fn rust_free_jim(_interp: *mut sys::Jim_Interp, data: *mut c_void) {
let _ = unsafe {
let data = data as *mut JimCmdWrap;
Box::from_raw(data)
};
}
#[cfg(test)]
fn test_sum<'jim>(_interp: &'jim Interp, args: &[JimObject<'jim>]) -> JimResult<i64> {
let mut total = 0i64;
for arg in &args[1..] {
let v = i64::try_from(arg)?;
total += v;
}
Ok(total)
}
#[test]
fn test_call_sum() -> JimResult<()> {
let interp = Interp::new()?;
interp.add_command("sum", test_sum)?;
let rv = interp.eval("sum 5 2 100")?;
assert!(!rv.is_null());
let sum = i64::try_from(&rv)?;
assert_eq!(sum, 107);
Ok(())
}