lambda-ref-cat 0.1.0

Lambda calculus with mutable reference cells and a pure-functional mark-sweep garbage collector, built on comp-cat-rs. Spike 2 of a web-engine reformulation targeting Tauri.
Documentation
//! # lambda-ref-cat
//!
//! Lambda calculus with mutable reference cells and a pure-functional mark-
//! sweep garbage collector, built on [`comp_cat_rs`].  Spike 2 of a web-
//! engine reformulation targeting Tauri.
//!
//! ## Quick start
//!
//! ```
//! # fn main() -> Result<(), lambda_ref_cat::error::Error> {
//! use lambda_ref_cat::run;
//!
//! let value = run(r"let r = ref (\x. x) in r := (\y. y) ; !r").run()?;
//! assert_eq!(format!("{value}"), "\\y. y");
//! # Ok(())
//! # }
//! ```
//!
//! ## Grammar
//!
//! ```text
//! expr        ::= seq_expr
//! seq_expr    ::= right_expr (";" right_expr)*
//! right_expr  ::= lambda | let | fix | assign_expr
//! lambda      ::= "\" ident "." expr
//! let         ::= "let" ident "=" expr "in" expr
//! fix         ::= "fix" ident "." expr
//! assign_expr ::= app_expr (":=" right_expr)?
//! app_expr    ::= atom atom*
//! atom        ::= ident | "(" expr ")" | "ref" atom | "!" atom
//! ```
//!
//! [`Io`]: comp_cat_rs::effect::io::Io
//! [`Fuel`]: crate::eval::Fuel

pub mod env;
pub mod error;
pub mod eval;
pub mod gc;
pub mod heap;
pub mod lexer;
pub mod parser;
pub mod syntax;
pub mod value;

use comp_cat_rs::effect::io::Io;

use crate::env::Env;
use crate::error::Error;
use crate::eval::Fuel;
use crate::heap::Heap;
use crate::value::Value;

/// Default step budget used by [`run`].
pub const DEFAULT_FUEL: u64 = 10_000;

/// Lex, parse, and evaluate `source` with the default fuel budget, returning
/// just the resulting value.
///
/// # Errors
///
/// Any of the underlying passes can fail; see [`Error`].
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), lambda_ref_cat::error::Error> {
/// use lambda_ref_cat::run;
///
/// let value = run(r"!ref (\x. x)").run()?;
/// assert_eq!(format!("{value}"), "\\x. x");
/// # Ok(())
/// # }
/// ```
#[must_use]
pub fn run(source: &str) -> Io<Error, Value> {
    run_with_fuel(source, Fuel::new(DEFAULT_FUEL))
}

/// Lex, parse, and evaluate `source` with a caller-supplied fuel budget.
///
/// # Errors
///
/// Same as [`run`].
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), lambda_ref_cat::error::Error> {
/// use lambda_ref_cat::eval::Fuel;
/// use lambda_ref_cat::run_with_fuel;
///
/// let value = run_with_fuel(r"ref (\x. x)", Fuel::new(100)).run()?;
/// // The result is a fresh reference; the cell contents are not displayed.
/// assert!(format!("{value}").starts_with("ref("));
/// # Ok(())
/// # }
/// ```
#[must_use]
pub fn run_with_fuel(source: &str, fuel: Fuel) -> Io<Error, Value> {
    let owned = source.to_owned();
    Io::suspend(move || {
        let (value, _heap) = pipeline(&owned, fuel)?;
        Ok(value)
    })
}

/// Lex, parse, and evaluate `source`, returning both the value and the final
/// heap.  Useful for tests that want to verify garbage-collection behaviour.
///
/// # Errors
///
/// Same as [`run`].
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), lambda_ref_cat::error::Error> {
/// use lambda_ref_cat::eval::Fuel;
/// use lambda_ref_cat::run_inspecting;
///
/// let (_value, heap) = run_inspecting(r"ref (\x. x)", Fuel::new(100)).run()?;
/// assert_eq!(heap.len(), 1);
/// # Ok(())
/// # }
/// ```
#[must_use]
pub fn run_inspecting(source: &str, fuel: Fuel) -> Io<Error, (Value, Heap)> {
    let owned = source.to_owned();
    Io::suspend(move || pipeline(&owned, fuel))
}

fn pipeline(source: &str, fuel: Fuel) -> Result<(Value, Heap), Error> {
    let tokens = lexer::lex(source)?;
    let expr = parser::parse(&tokens)?;
    let (value, heap, _fuel) = eval::eval(&expr, &Env::empty(), Heap::empty(), fuel)?;
    Ok((value, heap))
}