jimtcl 0.3.0

Embed Jim Tcl in Rust.
Documentation
//! Implementing Jim commands.
//!
//! Most code will not need to directly interact with this module. Instead, add
//! commands with [Interp::add_command]. This module provides the traits needed
//! to make that method work.

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 for Rust Jim commands.
pub trait JimCommand {
    /// Invoke the Jim command.
    ///
    /// **Note:** `args` will include the command name as the first element.
    fn invoke_cmd<'jim>(
        &mut self,
        interp: &'jim Interp,
        args: &[JimObject<'jim>],
    ) -> JimResult<JimObject<'jim>>;
}

/// Intermediate trait for Jim command functions with lifetimes.
///
/// **Note:** `args` will include the command name as the first element.
pub trait JimCommandFunc<'jim> {
    fn invoke_func(
        &mut self,
        interp: &'jim Interp,
        args: &[JimObject<'jim>],
    ) -> JimResult<JimObject<'jim>>;
}

#[repr(transparent)]
struct JimCmdData {
    cmd: Box<dyn JimCommand>,
}

impl<'jim, PF, R> JimCommandFunc<'jim> for PF
where
    PF: FnMut(&'jim Interp, &[JimObject<'jim>]) -> JimResult<R>,
    R: IntoJimObj<'jim>,
{
    fn invoke_func(
        &mut self,
        interp: &'jim Interp,
        args: &[JimObject<'jim>],
    ) -> JimResult<JimObject<'jim>> {
        Ok((self)(interp, args)?.to_jim(interp))
    }
}

impl<PF> JimCommand for PF
where
    PF: for<'jim> JimCommandFunc<'jim>,
{
    fn invoke_cmd<'jim>(
        &mut self,
        interp: &'jim Interp,
        args: &[JimObject<'jim>],
    ) -> JimResult<JimObject<'jim>> {
        self.invoke_func(interp, args)
    }
}

pub(crate) fn register_command<C: JimCommand + 'static>(
    interp: &Interp,
    name: &str,
    cmd: C,
) -> JimResult<()> {
    let name = CString::from_str(name)?;
    let wrap = JimCmdData { cmd: Box::new(cmd) };
    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()))
    }
}

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 JimCmdData;
        (*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 JimCmdData;
        Box::from_raw(data)
    };
}