jimtcl 0.5.0-beta3

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

/// Trait for implementing Jim commands with functions.  This trait
/// is implemented for several function types, including:
///
/// - a function with an interpreter and a slice of args, in which case
///   the first arg is the command name.
/// - a function with an interpreter and zero or more individual args.
pub trait JimCommand<'jim, Args> {
    /// Invoke the command. The first arg is the command name.
    fn invoke_command(
        &mut self,
        interp: &'jim Interp,
        args: &[JimObject<'jim>],
    ) -> JimResult<JimObject<'jim>>;
}

/// Holder object for Jim commands.
struct JimCommandWrapper<'jim, F, Args>
where
    F: FnMut(&'jim Interp, &[JimObject<'jim>]) -> JimResult<JimObject<'jim>>,
{
    func: F,
    _args: &'jim PhantomData<Args>,
}

/// Trait for Rust Jim commands.
trait JimCommandInvoke<'jim> {
    /// Invoke the Jim command.
    ///
    /// **Note:** `args` will include the command name as the first element.
    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)
    };
}