Skip to main content

lambda_throw_cat/
lib.rs

1//! # lambda-throw-cat
2//!
3//! Lambda calculus with records, prototype chains, ref cells, a tracing GC,
4//! and non-local control flow via `throw` and `try`/`catch`.  Spike 4 of a
5//! comp-cat-rs web-engine reformulation targeting Tauri.
6//!
7//! ## Quick start
8//!
9//! ```
10//! # fn main() -> Result<(), lambda_throw_cat::error::Error> {
11//! use lambda_throw_cat::run;
12//!
13//! let source = r"
14//!     try
15//!         throw (\msg. msg)
16//!     catch e. e
17//! ";
18//! let value = run(source).run()?;
19//! assert_eq!(format!("{value}"), "\\msg. msg");
20//! # Ok(())
21//! # }
22//! ```
23//!
24//! ## Grammar
25//!
26//! ```text
27//! expr        ::= seq_expr
28//! seq_expr    ::= right_expr (";" right_expr)*
29//! right_expr  ::= lambda | let | fix | extend | throw_expr | try_expr | assign_expr
30//! lambda      ::= "\" ident "." expr
31//! let         ::= "let" ident "=" expr "in" expr
32//! fix         ::= "fix" ident "." expr
33//! extend      ::= "extend" atom object_lit
34//! throw_expr  ::= "throw" right_expr
35//! try_expr    ::= "try" right_expr "catch" ident "." right_expr
36//! assign_expr ::= app_expr (":=" right_expr)?
37//! app_expr    ::= atom atom*
38//! atom        ::= base_atom ("." ident)*
39//! base_atom   ::= ident | "(" expr ")" | "ref" atom | "!" atom | object_lit
40//! object_lit  ::= "{" props? "}"
41//! props       ::= property ("," property)*
42//! property    ::= ident "=" right_expr
43//! ```
44
45pub mod env;
46pub mod error;
47pub mod eval;
48pub mod gc;
49pub mod heap;
50pub mod lexer;
51pub mod parser;
52pub mod syntax;
53pub mod value;
54
55use comp_cat_rs::effect::io::Io;
56
57use crate::env::Env;
58use crate::error::Error;
59use crate::eval::Fuel;
60use crate::heap::Heap;
61use crate::value::Value;
62
63/// Default step budget used by [`run`].
64pub const DEFAULT_FUEL: u64 = 10_000;
65
66/// Lex, parse, and evaluate `source` with the default fuel budget.
67///
68/// # Errors
69///
70/// See [`Error`].  An uncaught `throw` surfaces as [`Error::UncaughtException`].
71///
72/// [`Error::UncaughtException`]: crate::error::Error::UncaughtException
73///
74/// # Examples
75///
76/// ```
77/// # fn main() -> Result<(), lambda_throw_cat::error::Error> {
78/// use lambda_throw_cat::run;
79///
80/// let value = run(r"try throw (\x. x) catch e. e").run()?;
81/// assert_eq!(format!("{value}"), "\\x. x");
82/// # Ok(())
83/// # }
84/// ```
85#[must_use]
86pub fn run(source: &str) -> Io<Error, Value> {
87    run_with_fuel(source, Fuel::new(DEFAULT_FUEL))
88}
89
90/// Lex, parse, and evaluate `source` with a caller-supplied fuel budget.
91///
92/// # Errors
93///
94/// See [`Error`].
95///
96/// # Examples
97///
98/// ```
99/// # fn main() -> Result<(), lambda_throw_cat::error::Error> {
100/// use lambda_throw_cat::eval::Fuel;
101/// use lambda_throw_cat::run_with_fuel;
102///
103/// let value = run_with_fuel(r"try throw (\x. x) catch e. e", Fuel::new(100)).run()?;
104/// assert_eq!(format!("{value}"), "\\x. x");
105/// # Ok(())
106/// # }
107/// ```
108#[must_use]
109pub fn run_with_fuel(source: &str, fuel: Fuel) -> Io<Error, Value> {
110    let owned = source.to_owned();
111    Io::suspend(move || {
112        let (value, _heap) = pipeline(&owned, fuel)?;
113        Ok(value)
114    })
115}
116
117/// Lex, parse, and evaluate `source`, returning both the value and the final
118/// heap.  Useful for tests that need to verify GC behaviour.
119///
120/// # Errors
121///
122/// See [`Error`].
123///
124/// # Examples
125///
126/// ```
127/// # fn main() -> Result<(), lambda_throw_cat::error::Error> {
128/// use lambda_throw_cat::eval::Fuel;
129/// use lambda_throw_cat::run_inspecting;
130///
131/// let (_value, heap) = run_inspecting(r"{}", Fuel::new(100)).run()?;
132/// assert_eq!(heap.len(), 1);
133/// # Ok(())
134/// # }
135/// ```
136#[must_use]
137pub fn run_inspecting(source: &str, fuel: Fuel) -> Io<Error, (Value, Heap)> {
138    let owned = source.to_owned();
139    Io::suspend(move || pipeline(&owned, fuel))
140}
141
142fn pipeline(source: &str, fuel: Fuel) -> Result<(Value, Heap), Error> {
143    let tokens = lexer::lex(source)?;
144    let expr = parser::parse(&tokens)?;
145    match eval::eval(&expr, &Env::empty(), Heap::empty(), fuel) {
146        Ok((value, heap, _fuel)) => Ok((value, heap)),
147        Err(Error::Thrown(payload)) => Err(Error::UncaughtException {
148            value: payload.into_parts().0,
149        }),
150        Err(other) => Err(other),
151    }
152}