Skip to main content

lambda_ref_cat/
lib.rs

1//! # lambda-ref-cat
2//!
3//! Lambda calculus with mutable reference cells and a pure-functional mark-
4//! sweep garbage collector, built on [`comp_cat_rs`].  Spike 2 of a web-
5//! engine reformulation targeting Tauri.
6//!
7//! ## Quick start
8//!
9//! ```
10//! # fn main() -> Result<(), lambda_ref_cat::error::Error> {
11//! use lambda_ref_cat::run;
12//!
13//! let value = run(r"let r = ref (\x. x) in r := (\y. y) ; !r").run()?;
14//! assert_eq!(format!("{value}"), "\\y. y");
15//! # Ok(())
16//! # }
17//! ```
18//!
19//! ## Grammar
20//!
21//! ```text
22//! expr        ::= seq_expr
23//! seq_expr    ::= right_expr (";" right_expr)*
24//! right_expr  ::= lambda | let | fix | assign_expr
25//! lambda      ::= "\" ident "." expr
26//! let         ::= "let" ident "=" expr "in" expr
27//! fix         ::= "fix" ident "." expr
28//! assign_expr ::= app_expr (":=" right_expr)?
29//! app_expr    ::= atom atom*
30//! atom        ::= ident | "(" expr ")" | "ref" atom | "!" atom
31//! ```
32//!
33//! [`Io`]: comp_cat_rs::effect::io::Io
34//! [`Fuel`]: crate::eval::Fuel
35
36pub mod env;
37pub mod error;
38pub mod eval;
39pub mod gc;
40pub mod heap;
41pub mod lexer;
42pub mod parser;
43pub mod syntax;
44pub mod value;
45
46use comp_cat_rs::effect::io::Io;
47
48use crate::env::Env;
49use crate::error::Error;
50use crate::eval::Fuel;
51use crate::heap::Heap;
52use crate::value::Value;
53
54/// Default step budget used by [`run`].
55pub const DEFAULT_FUEL: u64 = 10_000;
56
57/// Lex, parse, and evaluate `source` with the default fuel budget, returning
58/// just the resulting value.
59///
60/// # Errors
61///
62/// Any of the underlying passes can fail; see [`Error`].
63///
64/// # Examples
65///
66/// ```
67/// # fn main() -> Result<(), lambda_ref_cat::error::Error> {
68/// use lambda_ref_cat::run;
69///
70/// let value = run(r"!ref (\x. x)").run()?;
71/// assert_eq!(format!("{value}"), "\\x. x");
72/// # Ok(())
73/// # }
74/// ```
75#[must_use]
76pub fn run(source: &str) -> Io<Error, Value> {
77    run_with_fuel(source, Fuel::new(DEFAULT_FUEL))
78}
79
80/// Lex, parse, and evaluate `source` with a caller-supplied fuel budget.
81///
82/// # Errors
83///
84/// Same as [`run`].
85///
86/// # Examples
87///
88/// ```
89/// # fn main() -> Result<(), lambda_ref_cat::error::Error> {
90/// use lambda_ref_cat::eval::Fuel;
91/// use lambda_ref_cat::run_with_fuel;
92///
93/// let value = run_with_fuel(r"ref (\x. x)", Fuel::new(100)).run()?;
94/// // The result is a fresh reference; the cell contents are not displayed.
95/// assert!(format!("{value}").starts_with("ref("));
96/// # Ok(())
97/// # }
98/// ```
99#[must_use]
100pub fn run_with_fuel(source: &str, fuel: Fuel) -> Io<Error, Value> {
101    let owned = source.to_owned();
102    Io::suspend(move || {
103        let (value, _heap) = pipeline(&owned, fuel)?;
104        Ok(value)
105    })
106}
107
108/// Lex, parse, and evaluate `source`, returning both the value and the final
109/// heap.  Useful for tests that want to verify garbage-collection behaviour.
110///
111/// # Errors
112///
113/// Same as [`run`].
114///
115/// # Examples
116///
117/// ```
118/// # fn main() -> Result<(), lambda_ref_cat::error::Error> {
119/// use lambda_ref_cat::eval::Fuel;
120/// use lambda_ref_cat::run_inspecting;
121///
122/// let (_value, heap) = run_inspecting(r"ref (\x. x)", Fuel::new(100)).run()?;
123/// assert_eq!(heap.len(), 1);
124/// # Ok(())
125/// # }
126/// ```
127#[must_use]
128pub fn run_inspecting(source: &str, fuel: Fuel) -> Io<Error, (Value, Heap)> {
129    let owned = source.to_owned();
130    Io::suspend(move || pipeline(&owned, fuel))
131}
132
133fn pipeline(source: &str, fuel: Fuel) -> Result<(Value, Heap), Error> {
134    let tokens = lexer::lex(source)?;
135    let expr = parser::parse(&tokens)?;
136    let (value, heap, _fuel) = eval::eval(&expr, &Env::empty(), Heap::empty(), fuel)?;
137    Ok((value, heap))
138}