eclisp 0.2.0

Bindings for Embeddable Common Lisp
use eclisp::{Lisp, LispClass, LispError, lisp_impl};

// Procedural macro `LispClass` implements a `class` method that returns the `clos::Class` instance
// that descibes the class to the LISP side.
#[derive(LispClass)]
// `name` is the LISP side name, `impl` can take several values:
// - No `impl`: do nothing
// - `impl`: add the methods form `#[lisp_impl]` (see below)
// - `impl = "name"`: add methods form `#[lisp_impl(name = "name)]` (see below)
// - `impl = [default, "name", ...]` add methods from default (just `#[lisp_impl]`, like with a
// simple `impl`, and all the named implementations).
// `auto` automatically adds `#[lisp]` to each value. If `auto` is set, `#[lisp(ignore)]` can be used.
#[lisp(name = "player", impl)]
struct Player {
    // `#[lisp]` exports the value as a getter and setter to lisp: `player-health`. Omit if
    // `#[lisp(auto)]` is set on the structure.
    #[lisp]
    health: i32,

    // `name` can also be changed: `player-movement-speed`
    #[lisp(name = "movement-speed")]
    speed: f32,
}

// `lisp_impl` exports methods marked with `#[method]` into Lisp functions. Use `#[method(name =
// "name")]` to override the default kebab case method name. The generated implementation module
// defaults to `class_impl`.
#[lisp_impl]
impl Player {
    #[method]
    fn is_alive(&self) -> bool {
        self.health > 0
    }

    #[method]
    fn max_health() -> i32 {
        100
    }

    #[method]
    fn label(&self, prefix: String) -> String {
        format!("{prefix}:{}", self.health)
    }

    #[method]
    fn checked_health(&self) -> Result<i32, LispError> {
        Ok(self.health)
    }

    #[method]
    fn check_damage(&self, amount: i32) -> Result<(), LispError> {
        if amount < 0 {
            return Err(LispError::RuntimeError("Negative damage".into()));
        }

        Ok(())
    }
}

// A name can also be provided, and automatically defined in the class by passing that into the
// `#[lisp(impl = "name)]` of the structure.
#[lisp_impl(name = "player_combat")]
impl Player {
    #[method]
    fn damage(&mut self, amount: i32) {
        self.health -= amount;
    }
}

fn main() -> Result<(), LispError> {
    let lisp = Lisp::new()?;

    lisp.enter(|ctx| {
        // `.defclass` defines the class to LISP.
        ctx.defclass(Player::class())?;

        // If the implementation is not defined automatically via the `#[lisp(impl)]` on the class,
        // it can be defined manually.
        ctx.defimpl(player_combat::methods())?;

        let mut player = Player {
            health: 100,
            speed: 2.0,
        };

        println!("Player before:");
        println!("  Health: {}", player.health);
        println!("  Speed: {}", player.speed);

        // `.with` mirrors the object to LISP for the duration of the lambda.
        ctx.with(&mut player, |obj| {
            let amount = ctx.int(25)?;

            // Within the lambda, object can be passed as a value.
            ctx.call("player-damage", &[obj, &amount])?;

            println!("\nCalled (player-damage player 25)");

            let alive = ctx.call("player-is-alive", &[obj])?;
            println!("Player is alive: {}", alive.try_as_bool()?);

            let prefix = ctx.string("hp")?;
            let label = ctx.call("player-label", &[obj, &prefix])?;
            println!("Player label: {}", label.try_as_string()?);

            let health = ctx.call("player-checked-health", &[obj])?;
            println!("Checked health: {}", health.try_as_i64()?);

            let speed = ctx.call("player-movement-speed", &[obj])?;
            println!("Movement speed: {}", speed.try_as_f64()?);

            // Object does not exist in LISP after this point.
            Ok::<(), LispError>(())
        })?;

        let maximum = ctx.eval("(player-max-health)")?;
        println!("Maximum health: {}", maximum.try_as_i64()?);

        println!("\nPlayer after:");
        println!("  Health: {}", player.health);
        println!("  Speed: {}", player.speed);

        Ok::<(), LispError>(())
    })
}