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;
pub trait IntoJimObj {
fn to_jim<'jim>(self, interp: &'jim Interp) -> JimObject<'jim>;
}
pub struct JimObject<'jim> {
pub(crate) interp: &'jim Interp,
obj: *mut sys::Jim_Obj,
}
impl<'jim> JimObject<'jim> {
pub fn empty(interp: &'jim Interp) -> JimObject<'jim> {
"".to_jim(interp)
}
pub fn len(&self) -> usize {
let size = unsafe { sys::Jim_Length(self.obj) };
assert!(size >= 0);
size as usize
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
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
}
pub(crate) fn null(interp: &'jim Interp) -> JimObject<'jim> {
JimObject {
interp,
obj: null_mut(),
}
}
pub(crate) fn is_null(&self) -> bool {
self.obj.is_null()
}
pub(crate) fn non_null(self) -> Option<Self> {
if self.is_null() { None } else { Some(self) }
}
pub fn list_length(&self) -> usize {
let len = unsafe { sys::Jim_ListLength(self.interp.interp, self.obj) };
assert!(len >= 0);
len as usize
}
pub fn list_index(&self, index: usize) -> JimResult<JimObject<'jim>> {
let obj = unsafe { sys::Jim_ListGetIndex(self.interp.interp, self.obj, index as c_int) };
if obj.is_null() {
Err(JimError::InvalidIndex(index))
} else {
Ok(JimObject::wrap(self.interp, obj))
}
}
pub fn list_append<T: IntoJimObj>(&self, element: T) {
let obj = element.to_jim(self.interp);
self.list_append_obj(obj);
}
pub fn list_append_obj(&self, obj: JimObject<'jim>) {
unsafe {
sys::Jim_ListAppendElement(self.interp.interp, self.obj, obj.obj);
}
}
pub fn list_iter(&self) -> impl Iterator<Item = JimObject<'jim>> {
let len = self.list_length();
(0..len).map(|i| {
self.list_index(i)
.expect("invalid index, list modified during iteration?")
})
}
pub fn dict_size(&self) -> JimResult<usize> {
let size = unsafe { sys::Jim_DictSize(self.interp.interp, self.obj) };
if size >= 0 {
Ok(size as usize)
} else {
Err(JimError::InvalidDictionary)
}
}
pub fn dict_get(&self, key: &str) -> JimResult<Option<JimObject<'jim>>> {
let key_obj = key.to_jim(self.interp);
let mut out = ptr::null_mut();
let rc = unsafe {
sys::Jim_DictKey(
self.interp.interp,
self.obj,
key_obj.obj,
ptr::from_mut(&mut out),
0,
)
};
if rc < 0 {
return Err(JimError::InvalidDictionary);
}
if rc as u32 == sys::JIM_ERR {
Ok(None)
} else {
self.interp.require_ok(rc as u32)?;
let obj = JimObject::wrap(self.interp, out);
Ok(obj.non_null())
}
}
pub fn dict_set<T: IntoJimObj>(&self, key: &str, val: T) -> JimResult<()> {
let ko = key.to_jim(self.interp);
let obj = val.to_jim(self.interp);
self.dict_set_obj(ko, obj)
}
pub fn dict_set_obj(&self, key: JimObject<'jim>, val: JimObject<'jim>) -> JimResult<()> {
let rc = unsafe { sys::Jim_DictAddElement(self.interp.interp, self.obj, key.obj, val.obj) };
self.interp.require_ok(rc as u32)?;
Ok(())
}
}
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 bool {
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 bool {
type Error = JimError;
fn try_from(jim: &JimObject<'jim>) -> Result<bool, Self::Error> {
let mut int: c_int = 0;
let rc =
unsafe { sys::Jim_GetBoolean(jim.interp.interp, jim.obj, ptr::from_mut(&mut int)) };
jim.interp.require_ok(rc as u32)?;
Ok(int != 0)
}
}
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<'jim> From<&JimObject<'jim>> for String {
fn from(value: &JimObject) -> Self {
value.to_string()
}
}
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<'jim, T> TryFrom<&JimObject<'jim>> for Vec<T>
where
T: for<'a> TryFrom<&'a JimObject<'jim>>,
JimError: for<'a> From<<T as TryFrom<&'a JimObject<'jim>>>::Error>,
{
type Error = JimError;
fn try_from(value: &JimObject<'jim>) -> Result<Self, Self::Error> {
let len = value.list_length();
let mut vec = Vec::with_capacity(len as usize);
for obj in value.list_iter() {
assert!(!obj.is_null());
let val = T::try_from(&obj)?;
vec.push(val);
}
Ok(vec)
}
}
#[test]
fn test_empty_string() -> JimResult<()> {
let interp = Interp::new()?;
let jim = JimObject::empty(&interp);
assert_eq!(&jim.to_string(), "");
assert_eq!(jim.list_length(), 0);
Ok(())
}
#[test]
fn test_string_roundtrip() -> JimResult<()> {
let interp = Interp::new()?;
let msg = "HACKEM MUCHE";
let jim = msg.to_jim(&interp);
assert_eq!(&jim.to_string(), msg);
let out = String::try_from(&jim)?;
assert_eq!(&out, msg);
Ok(())
}
#[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);
}
#[test]
fn test_int_bool() {
let interp = Interp::new().expect("failed to create Jim");
let jim = 42u32.to_jim(&interp);
let out = bool::try_from(&jim).expect("failed to convert");
assert_eq!(out, true);
let jim = 0isize.to_jim(&interp);
let out = bool::try_from(&jim).expect("failed to convert");
assert_eq!(out, false);
}
#[test]
fn test_empty_list() -> JimResult<()> {
let interp = Interp::new()?;
let obj = "".to_jim(&interp);
let list = Vec::<String>::try_from(&obj)?;
assert_eq!(list.len(), 0);
Ok(())
}
#[test]
fn test_single_list() -> JimResult<()> {
let interp = Interp::new()?;
let obj = "bob".to_jim(&interp);
assert_eq!(obj.list_length(), 1);
let list = Vec::<String>::try_from(&obj)?;
assert_eq!(list.len(), 1);
assert_eq!(&list[0], "bob");
Ok(())
}
#[test]
fn test_double_list() -> JimResult<()> {
let interp = Interp::new()?;
let obj = "HACKEM MUCHE".to_jim(&interp);
assert_eq!(obj.list_length(), 2);
let list = Vec::<String>::try_from(&obj)?;
assert_eq!(list.len(), 2);
assert_eq!(&list[0], "HACKEM");
assert_eq!(&list[1], "MUCHE");
Ok(())
}
#[test]
fn test_list_append() -> JimResult<()> {
let interp = Interp::new()?;
let obj = "HACKEM".to_jim(&interp);
obj.list_append("MUCHE");
assert_eq!(obj.list_length(), 2);
assert_eq!(&obj.to_string(), "HACKEM MUCHE");
Ok(())
}
#[test]
fn test_list_append_extra() -> JimResult<()> {
let interp = Interp::new()?;
let obj = "scroll".to_jim(&interp);
obj.list_append("HACKEM MUCHE");
assert_eq!(obj.list_length(), 2);
assert_eq!(&obj.to_string(), "scroll {HACKEM MUCHE}");
Ok(())
}
#[test]
fn test_basic_dict() -> JimResult<()> {
let interp = Interp::new()?;
let obj = "k1 1 k2 5".to_jim(&interp);
assert_eq!(obj.list_length(), 4);
assert_eq!(obj.dict_size()?, 2);
let v = i32::try_from(&obj.dict_get("k1")?.unwrap())?;
assert_eq!(v, 1);
let v = i32::try_from(&obj.dict_get("k2")?.unwrap())?;
assert_eq!(v, 5);
let x = obj.dict_get("nope");
eprintln!("result: {:?}", x);
assert!(x.is_ok());
assert!(x?.is_none());
Ok(())
}
#[test]
fn test_dict_set_value() -> JimResult<()> {
let interp = Interp::new()?;
let obj = "foo bar".to_jim(&interp);
obj.dict_set("scroll", "HACKEM MUCHE")?;
assert_eq!(obj.dict_size()?, 2);
assert_eq!(&obj.to_string(), "foo bar scroll {HACKEM MUCHE}");
assert_eq!(
&obj.dict_get("scroll")?.unwrap().to_string(),
"HACKEM MUCHE"
);
Ok(())
}