use std::ffi::CString;
use std::ffi::c_int;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::ptr;
use std::str::FromStr;
use jimtcl_sys::Jim_CreateCommand;
use crate::JimResult;
use crate::check_or_error;
use crate::error::JimError;
use crate::object::IntoJimObj;
use crate::sys;
use super::Interp;
use super::JimObject;
pub trait JimCommand<'jim, Args> {
fn invoke_command(
&mut self,
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>>;
}
struct JimCommandWrapper<'jim, F, Args>
where
F: FnMut(&'jim Interp, &[JimObject<'jim>]) -> JimResult<JimObject<'jim>>,
{
func: F,
_args: &'jim PhantomData<Args>,
}
trait JimCommandInvoke<'jim> {
fn invoke_inner(
&mut self,
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>>;
}
#[repr(transparent)]
struct JimCmdData<'jim> {
cmd: Box<dyn JimCommandInvoke<'jim> + 'jim>,
}
impl<'jim, F, Args> JimCommandInvoke<'jim> for JimCommandWrapper<'jim, F, Args>
where
F: FnMut(&'jim Interp, &[JimObject<'jim>]) -> JimResult<JimObject<'jim>>,
{
fn invoke_inner(
&mut self,
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>> {
(self.func)(interp, args)
}
}
impl<'jim, PF, R> JimCommand<'jim, &[JimObject<'_>]> for PF
where
PF: FnMut(&'jim Interp, &[JimObject<'jim>]) -> JimResult<R>,
R: IntoJimObj + 'jim,
{
fn invoke_command(
&mut self,
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>> {
Ok((self)(interp, args)?.to_jim(interp))
}
}
impl<'jim, PF, R> JimCommand<'jim, ()> for PF
where
PF: FnMut(&'jim Interp) -> JimResult<R>,
R: IntoJimObj + 'jim,
{
fn invoke_command(
&mut self,
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>> {
check_or_error!(
args.len() == 1,
"{}: expected 0 args, got {}",
args[0].as_str()?,
args.len() - 1
);
Ok((self)(interp)?.to_jim(interp))
}
}
impl<'jim, PF, R> JimCommand<'jim, JimObject<'jim>> for PF
where
PF: FnMut(&'jim Interp, &JimObject<'jim>) -> JimResult<R>,
R: IntoJimObj + 'jim,
{
fn invoke_command(
&mut self,
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>> {
check_or_error!(
args.len() == 2,
"{}: expected 1 arg, got {}",
args[0].as_str()?,
args.len() - 1
);
Ok((self)(interp, &args[1])?.to_jim(interp))
}
}
impl<'jim, PF, R> JimCommand<'jim, (JimObject<'jim>, JimObject<'jim>)> for PF
where
PF: FnMut(&'jim Interp, &JimObject<'jim>, &JimObject<'jim>) -> JimResult<R>,
R: IntoJimObj + 'jim,
{
fn invoke_command(
&mut self,
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>> {
check_or_error!(
args.len() == 3,
"{}: expected 2 arg, got {}",
args[0].as_str()?,
args.len() - 1
);
Ok((self)(interp, &args[1], &args[2])?.to_jim(interp))
}
}
impl<'jim, PF, R> JimCommand<'jim, (JimObject<'jim>, JimObject<'jim>, JimObject<'jim>)> for PF
where
PF: FnMut(&'jim Interp, &JimObject<'jim>, &JimObject<'jim>, &JimObject<'jim>) -> JimResult<R>,
R: IntoJimObj + 'jim,
{
fn invoke_command(
&mut self,
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>> {
check_or_error!(
args.len() == 4,
"{}: expected 3 arg, got {}",
args[0].as_str()?,
args.len() - 1
);
Ok((self)(interp, &args[1], &args[2], &args[3])?.to_jim(interp))
}
}
impl<'jim, PF, R>
JimCommand<
'jim,
(
JimObject<'jim>,
JimObject<'jim>,
JimObject<'jim>,
JimObject<'jim>,
),
> for PF
where
PF: FnMut(
&'jim Interp,
&JimObject<'jim>,
&JimObject<'jim>,
&JimObject<'jim>,
&JimObject<'jim>,
) -> JimResult<R>,
R: IntoJimObj + 'jim,
{
fn invoke_command(
&mut self,
interp: &'jim Interp,
args: &[JimObject<'jim>],
) -> JimResult<JimObject<'jim>> {
check_or_error!(
args.len() == 5,
"{}: expected 4 arg, got {}",
args[0].as_str()?,
args.len()
);
Ok((self)(interp, &args[1], &args[2], &args[3], &args[4])?.to_jim(interp))
}
}
pub(crate) fn register_command<'jim, C, Args>(
interp: &'jim Interp,
name: &str,
cmd: C,
) -> JimResult<()>
where
C: JimCommand<'jim, Args> + 'jim,
Args: 'jim,
{
let name = CString::from_str(name)?;
let wrap = JimCmdData {
cmd: Box::new(wrap_command(cmd)),
};
let data = Box::leak(Box::new(wrap));
let res = unsafe {
Jim_CreateCommand(
interp.interp,
name.as_ptr(),
Some(rust_jim_command),
ptr::from_mut(data).cast(),
Some(rust_free_command),
)
};
if res == 0 {
Ok(())
} else {
Err(JimError::OtherCode((res as u32).into()))
}
}
fn wrap_command<'jim, C, Args>(
mut cmd: C,
) -> JimCommandWrapper<
'jim,
impl FnMut(&'jim Interp, &[JimObject<'jim>]) -> JimResult<JimObject<'jim>>,
Args,
>
where
C: JimCommand<'jim, Args>,
{
JimCommandWrapper {
func: move |interp, args| cmd.invoke_command(interp, args),
_args: &PhantomData,
}
}
unsafe extern "C" fn rust_jim_command(
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: *mut JimCmdData = (*interp.interp).cmdPrivData.cast();
(*data).cmd.invoke_inner(&interp, &args)
};
match res {
Err(JimError::OtherCode(c)) => c.into(),
Err(JimError::Error(msg)) => {
interp.set_result(&msg.to_jim(&interp));
sys::JIM_ERR as c_int
}
Err(e) => {
let msg = e.to_string();
interp.set_result(&msg.to_jim(&interp));
sys::JIM_ERR as c_int
}
Ok(v) => {
if !v.is_null() {
interp.set_result(&v);
}
0
}
}
}
unsafe extern "C" fn rust_free_command(_interp: *mut sys::Jim_Interp, data: *mut c_void) {
let _ = unsafe {
let data: *mut JimCmdData = data.cast();
Box::from_raw(data)
};
}