use eclisp::{Lisp, LispClass, LispContext, LispError, lisp_impl};
#[derive(LispClass)]
#[lisp(name = "impl-player", impl)]
struct Player {
#[lisp]
health: i32,
}
#[lisp_impl]
impl Player {
#[method]
fn damage(&mut self, amount: i32) {
self.health -= amount;
}
#[method]
fn is_alive(&self) -> bool {
self.health > 0
}
#[method(name = "maximum-health")]
fn max_health() -> i32 {
100
}
#[method]
fn checked_health(&self) -> Result<i32, LispError> {
Ok(self.health)
}
#[method]
fn validate_damage(&self, amount: i32) -> Result<(), LispError> {
if amount < 0 {
return Err(LispError::RuntimeError("Negative damage".into()));
}
Ok(())
}
#[method]
fn panic_native(&self) {
panic!("method panic")
}
}
#[lisp_impl(name = "player_combat")]
impl Player {
#[method]
fn heal(&mut self, amount: i32) {
self.health += amount;
}
}
#[lisp_impl(name = "player_movement")]
impl Player {
#[method]
fn move_by(&self, dx: f32, dy: f32) -> f32 {
dx + dy
}
}
#[derive(LispClass)]
#[lisp(name = "impl-other")]
struct Other {
#[lisp]
value: i32,
}
fn test_generated_methods(ctx: &LispContext<'_>) -> Result<(), LispError> {
ctx.defclass(Player::class())?;
ctx.defclass(Other::class())?;
ctx.defimpl(player_combat::methods())?;
ctx.defimpl(player_movement::methods())?;
let mut player = Player { health: 75 };
ctx.with(&mut player, |object| {
let amount = ctx.int(25)?;
ctx.call("impl-player-damage", &[object, &amount])?;
assert_eq!(ctx.call("impl-player-health", &[object])?.try_as_i64()?, 50);
assert!(ctx.call("impl-player-is-alive", &[object])?.try_as_bool()?);
assert_eq!(
ctx.call("impl-player-checked-health", &[object])?
.try_as_i64()?,
50
);
let heal = ctx.int(10)?;
ctx.call("impl-player-heal", &[object, &heal])?;
assert_eq!(ctx.call("impl-player-health", &[object])?.try_as_i64()?, 60);
let dx = ctx.float(2.5)?;
let dy = ctx.float(4.0)?;
assert_eq!(
ctx.call("impl-player-move-by", &[object, &dx, &dy])?
.try_as_f64()?,
6.5
);
Ok(())
})?;
assert_eq!(player.health, 60);
assert_eq!(ctx.eval("(impl-player-maximum-health)")?.try_as_i64()?, 100);
Ok(())
}
fn test_generated_errors(ctx: &LispContext<'_>) -> Result<(), LispError> {
let mut player = Player { health: 50 };
ctx.with(&mut player, |object| {
let invalid = ctx.string("invalid")?;
let conversion = ctx
.call("impl-player-damage", &[object, &invalid])
.unwrap_err()
.to_string();
assert!(conversion.contains("Native method impl-player-damage failed"));
assert!(conversion.contains("Expected integer, got other"));
let negative = ctx.int(-1)?;
let result = ctx
.call("impl-player-validate-damage", &[object, &negative])
.unwrap_err()
.to_string();
assert!(result.contains("Negative damage"));
let panic = ctx
.call("impl-player-panic-native", &[object])
.unwrap_err()
.to_string();
assert!(panic.contains("method panic"));
assert_eq!(ctx.eval("(+ 1 2)")?.try_as_i64()?, 3);
Ok(())
})?;
assert_eq!(player.health, 50);
Ok(())
}
fn test_invalid_handles(ctx: &LispContext<'_>) -> Result<(), LispError> {
ctx.eval(
r#"
(defparameter *escaped-impl-player* nil)
(defun escape-impl-player (obj)
(setf *escaped-impl-player* obj))
"#,
)?;
let mut player = Player { health: 50 };
ctx.with(&mut player, |object| {
ctx.call("escape-impl-player", &[object])?;
Ok(())
})?;
assert!(
ctx.eval("(impl-player-is-alive *escaped-impl-player*)")
.is_err()
);
let mut other = Other { value: 10 };
ctx.with(&mut other, |object| {
let handle = ctx.call("%rust-handle", &[object])?;
let class = ctx.eval("'impl-player")?;
let keyword = ctx.eval(":handle")?;
let forged = ctx.call("make-instance", &[&class, &keyword, &handle])?;
assert!(ctx.call("impl-player-is-alive", &[&forged]).is_err());
Ok(())
})?;
assert_eq!(ctx.eval("(+ 2 3)")?.try_as_i64()?, 5);
Ok(())
}
fn run(name: &str, lisp: &LispContext<'_>, test: fn(&LispContext<'_>) -> Result<(), LispError>) {
print!("test {name} ... ");
match test(lisp) {
Ok(()) => println!("ok"),
Err(error) => panic!("{name} failed: {error}"),
}
}
fn main() -> Result<(), LispError> {
let lisp = Lisp::new()?;
lisp.enter(|ctx| {
run("generated_methods", &ctx, test_generated_methods);
run("generated_errors", &ctx, test_generated_errors);
run("invalid_handles", &ctx, test_invalid_handles);
});
Ok(())
}