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
//! Project-wide error type.
//!
//! [`Error::Thrown`] is unusual: it represents a value-carrying control-flow
//! signal rather than an interpreter-internal failure.  The evaluator
//! propagates it like any other error via `?`, and an enclosing
//! [`Expr::TryCatch`] is the only site that pattern-matches it back into a
//! normal evaluation.  An [`Error::Thrown`] that reaches the top boundary
//! is converted to [`Error::UncaughtException`] for the user.  The payload
//! is boxed because it carries the full heap state at the moment of the
//! throw and would otherwise inflate every fallible `Result` size.
//!
//! [`Expr::TryCatch`]: crate::syntax::Expr::TryCatch

use crate::eval::Fuel;
use crate::heap::{Address, Heap};
use crate::syntax::{Position, VarName};
use crate::value::Value;

/// Payload of a propagating throw: the thrown value plus the heap and fuel
/// at the moment the throw was raised.  Carried inside [`Error::Thrown`]
/// behind a [`Box`] so the [`Result`] discriminant stays small.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ThrownPayload {
    value: Value,
    heap: Heap,
    fuel: Fuel,
}

impl ThrownPayload {
    /// Build a payload from its three components.
    #[must_use]
    pub fn new(value: Value, heap: Heap, fuel: Fuel) -> Self {
        Self { value, heap, fuel }
    }

    /// The thrown value.
    #[must_use]
    pub fn value(&self) -> &Value {
        &self.value
    }

    /// Decompose into owned parts so the catch handler can resume with the
    /// thrown value and the heap and fuel as they were at the throw site.
    #[must_use]
    pub fn into_parts(self) -> (Value, Heap, Fuel) {
        (self.value, self.heap, self.fuel)
    }
}

/// All errors in lambda-throw-cat.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
    /// The lexer encountered a character that does not begin any token.
    UnexpectedChar {
        /// Byte offset.
        at: Position,
        /// The offending character.
        ch: char,
    },
    /// Input ended while the parser still required more tokens.
    UnexpectedEnd {
        /// What was expected.
        expected: &'static str,
    },
    /// The parser found a token that does not satisfy the current production.
    UnexpectedToken {
        /// Byte offset.
        at: Position,
        /// What was expected.
        expected: &'static str,
        /// Token that was actually found.
        found: String,
    },
    /// Evaluation referenced a variable not bound in the current environment.
    UnboundVariable {
        /// The unbound name.
        name: VarName,
    },
    /// Evaluation exceeded its step budget.
    FuelExhausted {
        /// The configured step limit.
        limit: u64,
    },
    /// A dereference, store, field access, or field assignment targeted an
    /// address not present in the heap.
    DanglingReference {
        /// The address that could not be resolved.
        address: Address,
    },
    /// Dereference or assignment was attempted on a value that is not a cell
    /// reference.
    NotAReference {
        /// Rendering of the offending non-reference value.
        found: String,
    },
    /// Application was attempted with a non-function value.
    NotAFunction {
        /// Rendering of the offending non-function value.
        found: String,
    },
    /// Field access or extend targeted a value that is not (or does not
    /// resolve to) an object.
    NotAnObject {
        /// Rendering of the offending non-object value.
        found: String,
    },
    /// Field access walked the prototype chain to its end without finding the
    /// requested property.
    PropertyNotFound {
        /// The missing property name.
        name: VarName,
    },
    /// A `throw` is propagating up the evaluation stack.  Carries a boxed
    /// [`ThrownPayload`] with the value, heap, and fuel at the throw site
    /// so an enclosing try-catch can resume from there.  Boxed to keep the
    /// [`Result`] discriminant small.
    Thrown(Box<ThrownPayload>),
    /// A `throw` escaped the top-level evaluation without being caught.
    UncaughtException {
        /// The value that was thrown.
        value: Value,
    },
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::UnexpectedChar { at, ch } => {
                write!(f, "unexpected character {ch:?} at byte {}", at.value())
            }
            Self::UnexpectedEnd { expected } => {
                write!(f, "unexpected end of input; expected {expected}")
            }
            Self::UnexpectedToken {
                at,
                expected,
                found,
            } => {
                write!(
                    f,
                    "unexpected token {found:?} at byte {}; expected {expected}",
                    at.value()
                )
            }
            Self::UnboundVariable { name } => {
                write!(f, "unbound variable {:?}", name.as_str())
            }
            Self::FuelExhausted { limit } => {
                write!(f, "evaluation exceeded step limit of {limit}")
            }
            Self::DanglingReference { address } => {
                write!(f, "dangling reference to address {address}")
            }
            Self::NotAReference { found } => {
                write!(f, "expected a reference, found {found}")
            }
            Self::NotAFunction { found } => {
                write!(f, "expected a function, found {found}")
            }
            Self::NotAnObject { found } => {
                write!(f, "expected an object, found {found}")
            }
            Self::PropertyNotFound { name } => {
                write!(
                    f,
                    "property {:?} not found on object or its prototype chain",
                    name.as_str()
                )
            }
            Self::Thrown(payload) => {
                write!(f, "propagating throw of {}", payload.value())
            }
            Self::UncaughtException { value } => {
                write!(f, "uncaught exception: {value}")
            }
        }
    }
}

impl std::error::Error for Error {}