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 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(())
}