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;
pub struct Interp {
pub(crate) interp: *mut sys::Jim_Interp,
owned: bool,
}
impl Interp {
pub fn new() -> JimResult<Interp> {
let jim = Interp::new_uninit()?;
jim.std_init()?;
Ok(jim)
}
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,
})
}
pub(crate) fn wrap(interp: *mut sys::Jim_Interp) -> Interp {
if interp.is_null() {
panic!("cannot wrap null interpreter");
}
Interp {
interp,
owned: false,
}
}
pub fn std_init(&self) -> JimResult<()> {
self.register_core_commands();
self.init_static_extensions()
}
pub fn register_core_commands(&self) {
unsafe {
sys::Jim_RegisterCoreCommands(self.interp);
}
}
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))
}
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)
}
pub fn init_static_extensions(&self) -> JimResult<()> {
let rc = unsafe { sys::Jim_InitStaticExtensions(self.interp) };
self.require_ok(rc as u32)
}
pub fn exit_code(&self) -> i32 {
unsafe { Jim_GetExitCode(self.interp) }
}
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),
}
}
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()))
}
}
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;
}
}
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)
}
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(())
}