jimtcl 0.1.0

Embed Jim Tcl in Rust.
Documentation
//! Implementing Jim commands.

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>>;
}

/// Internal private data for a Jim command.
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(())
}