jimtcl 0.1.0

Embed Jim Tcl in Rust.
Documentation
//! Tcl object wrapper.
use std::ffi::c_char;
use std::ffi::c_long;
use std::fmt;
use std::os::raw::c_double;
use std::os::raw::c_int;
use std::os::raw::c_longlong;
use std::ptr;
use std::ptr::null_mut;
use std::slice;
use std::str;

use crate::JimResult;
use crate::error::JimError;
use crate::interp::Interp;
use crate::sys;

/// Trait for data that can be converted to a Jim object.
pub trait IntoJimObj {
    fn to_jim<'jim>(self, interp: &'jim Interp) -> JimObject<'jim>;
}

/// Rust wrapper for a Tcl object.
pub struct JimObject<'jim> {
    pub(crate) interp: &'jim Interp,
    obj: *mut sys::Jim_Obj,
}

impl<'jim> JimObject<'jim> {
    /// Wrap a raw Jim object pointer in a safe reference.
    pub(crate) fn wrap(interp: &'jim Interp, obj: *mut sys::Jim_Obj) -> JimObject<'jim> {
        jim_incref(obj);
        JimObject { interp, obj }
    }

    pub(crate) fn as_ref_ptr(&mut self) -> *mut sys::Jim_Obj {
        jim_incref(self.obj);
        self.obj
    }

    /// Get a null Jim object.
    pub(crate) fn null(interp: &'jim Interp) -> JimObject<'jim> {
        JimObject {
            interp,
            obj: null_mut(),
        }
    }

    /// Check if this object is null.
    pub(crate) fn is_null(&self) -> bool {
        self.obj.is_null()
    }

    /// Acquire a non-null object.
    #[allow(dead_code)]
    pub(crate) fn non_null(&self) -> JimResult<Self> {
        if self.is_null() {
            Err(JimError::NullObject)
        } else {
            Ok(self.clone())
        }
    }
}

impl<'jim> fmt::Display for JimObject<'jim> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.obj.is_null() {
            f.write_str("<NULL>")
        } else {
            unsafe {
                let mut len: c_int = 0;
                let ptr = sys::Jim_GetString(self.obj, &mut len) as *const u8;
                let slice = slice::from_raw_parts(ptr, len as usize);
                let repr = str::from_utf8(slice).expect("invalid string repr");
                f.write_str(repr)
            }
        }
    }
}

impl<'jim> fmt::Debug for JimObject<'jim> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self)
    }
}

impl<'jim> Clone for JimObject<'jim> {
    fn clone(&self) -> Self {
        unsafe {
            (*self.obj).refCount += 1;
        }
        JimObject {
            interp: self.interp,
            obj: self.obj,
        }
    }
}

impl<'jim> Drop for JimObject<'jim> {
    fn drop(&mut self) {
        jim_decref(self.interp, &mut self.obj);
    }
}

pub(crate) fn jim_incref(obj: *mut sys::Jim_Obj) {
    if !obj.is_null() {
        unsafe { (*obj).refCount += 1 }
    }
}

pub(crate) fn jim_decref(interp: &Interp, obj: &mut *mut sys::Jim_Obj) {
    if !obj.is_null() {
        unsafe {
            (**obj).refCount -= 1;
            if (**obj).refCount <= 0 {
                interp.free_obj(*obj);
                *obj = null_mut();
            }
        }
    }
}

impl IntoJimObj for () {
    fn to_jim<'jim>(self, interp: &'jim Interp) -> JimObject<'jim> {
        JimObject::null(interp)
    }
}

macro_rules! prim_int_jimcvt {
    ($type:ty) => {
        impl IntoJimObj for $type {
            fn to_jim<'jim>(self, interp: &'jim Interp) -> JimObject<'jim> {
                unsafe {
                    JimObject::wrap(
                        interp,
                        sys::Jim_NewIntObj(interp.interp, self as c_longlong),
                    )
                }
            }
        }

        impl<'jim> TryFrom<&JimObject<'jim>> for $type {
            type Error = JimError;

            fn try_from(jim: &JimObject<'jim>) -> Result<$type, Self::Error> {
                let mut int: c_long = 0;
                let rc = unsafe {
                    sys::Jim_GetLong(jim.interp.interp, jim.obj, ptr::from_mut(&mut int))
                };
                jim.interp.require_ok(rc as u32)?;
                int.try_into()
                    .map_err(|_e| JimError::Error(format!("Jim integer out of bounds")))
            }
        }
    };
}

prim_int_jimcvt!(u8);
prim_int_jimcvt!(i8);
prim_int_jimcvt!(u16);
prim_int_jimcvt!(i16);
prim_int_jimcvt!(u32);
prim_int_jimcvt!(i32);
prim_int_jimcvt!(u64);
prim_int_jimcvt!(i64);
prim_int_jimcvt!(usize);
prim_int_jimcvt!(isize);

macro_rules! prim_float_jimcvt {
    ($type:ty) => {
        impl IntoJimObj for $type {
            fn to_jim<'jim>(self, interp: &'jim Interp) -> JimObject<'jim> {
                unsafe {
                    JimObject::wrap(
                        interp,
                        sys::Jim_NewDoubleObj(interp.interp, self as c_double),
                    )
                }
            }
        }

        impl<'jim> TryFrom<&JimObject<'jim>> for $type {
            type Error = JimError;

            fn try_from(jim: &JimObject<'jim>) -> Result<$type, Self::Error> {
                let mut val: c_double = 0.0;
                let rc = unsafe {
                    sys::Jim_GetDouble(jim.interp.interp, jim.obj, ptr::from_mut(&mut val))
                };
                jim.interp.require_ok(rc as u32)?;
                Ok(val as $type)
            }
        }
    };
}

prim_float_jimcvt!(f32);
prim_float_jimcvt!(f64);

impl IntoJimObj for &str {
    fn to_jim<'jim>(self, interp: &'jim Interp) -> JimObject<'jim> {
        unsafe {
            JimObject::wrap(
                interp,
                sys::Jim_NewStringObjUtf8(
                    interp.interp,
                    self.as_ptr() as *const c_char,
                    self.len().try_into().expect("invalid string length"),
                ),
            )
        }
    }
}

impl IntoJimObj for String {
    fn to_jim<'jim>(self, interp: &'jim Interp) -> JimObject<'jim> {
        self.as_str().to_jim(interp)
    }
}

impl<T: IntoJimObj> IntoJimObj for Option<T> {
    fn to_jim<'jim>(self, interp: &'jim Interp) -> JimObject<'jim> {
        match self {
            Some(v) => v.to_jim(interp),
            None => JimObject::null(interp),
        }
    }
}
// impl<T: IntoJimObj> IntoJimObj for &[T] {
//     fn to_jim<'jim>(self, interp: &'jim Interp) -> JimObject<'jim> {
//         let jims = Vec::with_capacity(self.len());
//     }
// }

#[test]
fn test_string_roundtrip() {
    let interp = Interp::new().expect("failed to create Jim");
    let msg = "HACKEM MUCHE";
    let jim = msg.to_jim(&interp);
    assert_eq!(&jim.to_string(), msg);
}

#[test]
fn test_int_roundtrip() {
    let interp = Interp::new().expect("failed to create Jim");
    let jim = 42u32.to_jim(&interp);
    let out = u32::try_from(&jim).expect("failed to convert");
    assert_eq!(out, 42);
}