jimtcl 0.5.0

Embed Jim Tcl in Rust.
Documentation
//! Tests for Jim custom data.

use std::{
    ffi::CStr,
    fmt::{self, Display},
    sync::LazyLock,
};

use jimtcl::{
    Interp, JimError, JimResult,
    custom::{JimExtensionData, JimExtensionType, jim_extension_type_ptr},
    object::IntoJimObj,
};
use regex::Regex;

static DIE_REGEX: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"^(\d+)d(\d+)([+-]\d+)?$").expect("regex compilation failed"));

#[derive(Clone)]
struct Die {
    number: i32,
    size: i32,
    modifier: i32,
}

impl Die {
    // fn min_roll(&self) -> i32 {
    //     self.number + self.modifier
    // }
    fn max_roll(&self) -> i32 {
        self.number * self.size + self.modifier
    }
}

impl Display for Die {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}d{}", self.number, self.size)?;
        if self.modifier > 0 {
            write!(f, "+{}", self.modifier)?;
        } else if self.modifier < 0 {
            write!(f, "{}", self.modifier)?;
        }
        Ok(())
    }
}

impl JimExtensionData for Die {
    fn to_tcl_string(&self) -> String {
        self.to_string()
    }
}

impl JimExtensionType for Die {
    fn parse_tcl_string(repr: &str) -> JimResult<Self> {
        if let Some(m) = DIE_REGEX.captures(repr) {
            Ok(Die {
                number: m[1].parse()?,
                size: m[2].parse()?,
                modifier: if let Some(p) = m.get(3) {
                    p.as_str().parse()?
                } else {
                    0
                },
            })
        } else {
            Err(JimError::from(format!("invalid die {}", repr)))
        }
    }
}

#[test]
fn type_ptr() {
    let tp = jim_extension_type_ptr();
    assert!(!tp.is_null());
    assert_eq!(jim_extension_type_ptr(), jim_extension_type_ptr());
    let tobj = unsafe { tp.as_ref_unchecked() };
    let cstr = unsafe { CStr::from_ptr(tobj.name) };
    assert_eq!(cstr.to_str().expect("invalid UTF-8"), "jimtcl-rs-extension");
}

#[test]
fn die_str() {
    let mut die = Die {
        number: 3,
        size: 8,
        modifier: 0,
    };
    assert_eq!(&die.to_string(), "3d8");
    die.modifier = 2;
    assert_eq!(&die.to_string(), "3d8+2");
    die.modifier = -1;
    assert_eq!(&die.to_string(), "3d8-1");
}

#[test]
fn extract_die() -> JimResult<()> {
    let interp = Interp::new()?;
    let obj = interp.new_string("8d4+3");
    let die = Die::extract_ref(&obj)?;
    assert_eq!(die.max_roll(), 35);
    Ok(())
}

#[test]
fn make_jim_from_object() -> JimResult<()> {
    let interp = Interp::new()?;
    let repr = Die::parse_tcl_string("2d4")?;
    let jim = repr.to_jim(&interp);
    let raw = jim.get_raw();
    assert!(!raw.is_null());
    unsafe {
        assert!(!(*raw).typePtr.is_null());
        let name = CStr::from_ptr((*(*raw).typePtr).name);
        assert_eq!(
            (*raw).typePtr,
            jim_extension_type_ptr(),
            "unexpected type (name: {:?})",
            name
        );
    }
    assert_eq!(jim.as_str()?, "2d4");
    Ok(())
}