jimtcl 0.1.0

Embed Jim Tcl in Rust.
Documentation
//! Jim interpreter module.

use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::str::FromStr;

use jimtcl_sys::Jim_GetExitCode;

use crate::command::JimCmd;
use crate::error::ExitCode;
use crate::object::IntoJimObj;
use crate::object::JimObject;
use crate::object::jim_decref;
use crate::prelude::*;
use crate::sys;

/// Safe wrapper for Jim Tcl interpreter.
pub struct Interp {
    pub(crate) interp: *mut sys::Jim_Interp,
    owned: bool,
}

impl Interp {
    /// Create a new Jim Tcl interpreter with core commands and extensions.
    pub fn new() -> JimResult<Interp> {
        let jim = Interp::new_uninit()?;
        jim.std_init()?;
        Ok(jim)
    }

    /// Create a new, **uninitialized** Jim interpreter.
    pub fn new_uninit() -> JimResult<Interp> {
        let interp = unsafe { sys::Jim_CreateInterp() };
        if interp.is_null() {
            panic!("could not allocate Jim interpreter");
        }
        Ok(Interp {
            interp,
            owned: true,
        })
    }

    /// Wrap a Jim interpreter pointer.
    pub(crate) fn wrap(interp: *mut sys::Jim_Interp) -> Interp {
        if interp.is_null() {
            panic!("cannot wrap null interpreter");
        }
        Interp {
            interp,
            owned: false,
        }
    }

    /// Invoke the standard initialization operations (core commands and built-in extensions).
    pub fn std_init(&self) -> JimResult<()> {
        self.register_core_commands();
        self.init_static_extensions()
    }

    /// Register the Jim Tcl core commands.
    pub fn register_core_commands(&self) {
        unsafe {
            sys::Jim_RegisterCoreCommands(self.interp);
        }
    }

    /// Evaluate Tcl code in global context.
    pub fn eval<'jim>(&'jim self, script: &str) -> JimResult<JimObject<'jim>> {
        let script = CString::from_str(script)?;
        let rc = unsafe { sys::Jim_Eval(self.interp, script.as_ptr()) };
        self.require_ok(rc as u32)?;
        let ptr = unsafe { (*self.interp).result };
        Ok(JimObject::wrap(self, ptr))
    }

    /// Evaluate a file.
    pub fn eval_file<P: AsRef<Path>>(&self, path: P) -> JimResult<()> {
        let pstr = CString::new(path.as_ref().as_os_str().as_bytes())?;
        let rc = unsafe { sys::Jim_EvalFile(self.interp, pstr.as_ptr()) };
        self.require_ok(rc as u32)
    }

    /// Initialize the static (built-in) extensions.
    pub fn init_static_extensions(&self) -> JimResult<()> {
        let rc = unsafe { sys::Jim_InitStaticExtensions(self.interp) };
        self.require_ok(rc as u32)
    }

    /// Get the Jim exit code.
    pub fn exit_code(&self) -> i32 {
        unsafe { Jim_GetExitCode(self.interp) }
    }

    /// Resolve a result into an exit code.  Primarily used to resolve
    /// the results of evaluating scripts.
    pub fn resolve_exit(&self, result: JimResult<()>) -> JimResult<i32> {
        match result {
            Err(JimError::OtherCode(ExitCode::Exit)) => Ok(self.exit_code()),
            Err(e) => Err(e),
            Ok(()) => Ok(0),
        }
    }

    /// Check a return code and succeed if it is OK, returning an error otherwise.
    pub fn require_ok(&self, code: u32) -> JimResult<()> {
        if code == sys::JIM_OK {
            Ok(())
        } else if code == sys::JIM_ERR {
            let obj = unsafe { JimObject::wrap(self, (*self.interp).result) };
            Err(JimError::Error(obj.to_string()))
        } else {
            Err(JimError::OtherCode(code.into()))
        }
    }

    /// Run a Jim interactive prompt.
    pub fn interactive_prompt(&self) -> JimResult<()> {
        let rc = unsafe { sys::Jim_InteractivePrompt(self.interp) as u32 };
        self.require_ok(rc)
    }

    pub fn set_result(&self, result: &mut JimObject<'_>) {
        assert_eq!(self.interp, result.interp.interp);
        let rp = result.as_ref_ptr();
        unsafe {
            let mut result = (*self.interp).result;
            jim_decref(&self, &mut result);
            (*self.interp).result = rp;
        }
    }

    /// Add a command to the interpreter.
    pub fn add_command<CF, R>(&self, name: &str, cmd: CF) -> JimResult<()>
    where
        CF: for<'jim> FnMut(&'jim Interp, &[JimObject<'jim>]) -> JimResult<R> + 'static,
        R: IntoJimObj + 'static,
    {
        let wrap = JimCmd::wrap_fn(name, cmd);
        wrap.register(self)
    }

    /// Free a Jim object.
    pub(crate) fn free_obj(&self, obj_ptr: *mut sys::Jim_Obj) {
        unsafe {
            sys::Jim_FreeObj(self.interp, obj_ptr);
        }
    }
}

impl Drop for Interp {
    fn drop(&mut self) {
        if self.owned {
            unsafe {
                sys::Jim_FreeInterp(self.interp);
            }
        }
    }
}

#[test]
fn test_eval_noresult() -> JimResult<()> {
    let interp = Interp::new()?;
    let rv = interp.eval("puts readme")?;
    assert_eq!(&rv.to_string(), "");
    Ok(())
}

#[test]
fn test_eval_simple() -> JimResult<()> {
    let interp = Interp::new()?;
    let rv = interp.eval("expr {1 + 2}")?;
    assert!(!rv.is_null());
    assert_eq!(&rv.to_string(), "3");
    Ok(())
}