lambda-throw-cat 0.1.0

Lambda calculus with records, prototype chains, ref cells, GC, and non-local control flow via throw/try/catch. Outcome::Normal/Thrown is threaded purely-functionally through every reduction. Spike 4 of a web-engine reformulation targeting Tauri.
Documentation
//! Runtime values.
//!
//! Spike 3 adds an [`Value::Object`] variant carrying a string-keyed map of
//! property values and an optional prototype [`Address`].  Objects live on
//! the heap; user-facing variables hold [`Value::Ref`] pointing to a cell
//! whose contents is the [`Value::Object`].

use std::collections::BTreeMap;

use crate::env::Env;
use crate::heap::Address;
use crate::syntax::{Expr, VarName};

/// A runtime value.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Value {
    /// A function closed over its lexical environment.
    Closure {
        /// The parameter the function binds when applied.
        param: VarName,
        /// The function body.
        body: Expr,
        /// The captured environment.
        env: Env,
    },
    /// A reference to a cell on the heap.
    Ref(Address),
    /// An object: a string-keyed property map plus an optional prototype.
    Object {
        /// Own properties of the object, sorted by name for determinism.
        properties: BTreeMap<VarName, Value>,
        /// Optional prototype cell on the heap.
        prototype: Option<Address>,
    },
}

impl Value {
    /// Build a closure value.
    #[must_use]
    pub fn closure(param: VarName, body: Expr, env: Env) -> Self {
        Self::Closure { param, body, env }
    }

    /// Build a reference value.
    #[must_use]
    pub fn reference(address: Address) -> Self {
        Self::Ref(address)
    }

    /// Build an object value.
    #[must_use]
    pub fn object(properties: BTreeMap<VarName, Value>, prototype: Option<Address>) -> Self {
        Self::Object {
            properties,
            prototype,
        }
    }
}

impl std::fmt::Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Closure { param, body, .. } => write!(f, "\\{param}. {body}"),
            Self::Ref(address) => write!(f, "ref({address})"),
            Self::Object {
                properties,
                prototype,
            } => write_object(f, properties, *prototype),
        }
    }
}

fn write_object(
    f: &mut std::fmt::Formatter<'_>,
    properties: &BTreeMap<VarName, Value>,
    prototype: Option<Address>,
) -> std::fmt::Result {
    let entries = properties
        .iter()
        .map(|(k, v)| format!("{k} = {v}"))
        .collect::<Vec<_>>()
        .join(", ");
    let prefix = prototype.map_or(String::new(), |a| format!("proto={a} "));
    write!(f, "{{{prefix}{entries}}}")
}