use crate::env::Env;
use crate::error::Error;
use crate::syntax::{Expr, VarName};
use crate::value::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Fuel {
limit: u64,
remaining: u64,
}
impl Fuel {
#[must_use]
pub fn new(limit: u64) -> Self {
Self {
limit,
remaining: limit,
}
}
#[must_use]
pub fn limit(&self) -> u64 {
self.limit
}
#[must_use]
pub fn remaining(&self) -> u64 {
self.remaining
}
fn spend(self) -> Result<Self, Error> {
match self.remaining {
0 => Err(Error::FuelExhausted { limit: self.limit }),
n => Ok(Self {
limit: self.limit,
remaining: n - 1,
}),
}
}
}
pub fn eval(expr: &Expr, env: &Env, fuel: Fuel) -> Result<(Value, Fuel), Error> {
let fuel = fuel.spend()?;
step(expr, env, fuel)
}
fn step(expr: &Expr, env: &Env, fuel: Fuel) -> Result<(Value, Fuel), Error> {
match expr {
Expr::Var(name) => eval_var(name, env, fuel),
Expr::Lam { param, body } => Ok((
Value::closure(param.clone(), (**body).clone(), env.clone()),
fuel,
)),
Expr::App { func, arg } => eval_app(func, arg, env, fuel),
Expr::Let { name, value, body } => eval_let(name, value, body, env, fuel),
Expr::Fix { name, body } => eval_fix(expr, name, body, env, fuel),
}
}
fn eval_var(name: &VarName, env: &Env, fuel: Fuel) -> Result<(Value, Fuel), Error> {
env.lookup(name)
.cloned()
.map(|v| (v, fuel))
.ok_or_else(|| Error::UnboundVariable { name: name.clone() })
}
fn eval_app(func: &Expr, arg: &Expr, env: &Env, fuel: Fuel) -> Result<(Value, Fuel), Error> {
let (func_v, fuel) = eval(func, env, fuel)?;
let (arg_v, fuel) = eval(arg, env, fuel)?;
apply(func_v, arg_v, fuel)
}
fn eval_let(
name: &VarName,
value: &Expr,
body: &Expr,
env: &Env,
fuel: Fuel,
) -> Result<(Value, Fuel), Error> {
let (value_v, fuel) = eval(value, env, fuel)?;
let new_env = env.extend(name.clone(), value_v);
eval(body, &new_env, fuel)
}
fn eval_fix(
whole: &Expr,
name: &VarName,
body: &Expr,
env: &Env,
fuel: Fuel,
) -> Result<(Value, Fuel), Error> {
let unfolded = subst(body, name, whole);
eval(&unfolded, env, fuel)
}
fn apply(func: Value, arg: Value, fuel: Fuel) -> Result<(Value, Fuel), Error> {
match func {
Value::Closure { param, body, env } => {
let new_env = env.extend(param, arg);
eval(&body, &new_env, fuel)
}
}
}
fn subst(expr: &Expr, target: &VarName, replacement: &Expr) -> Expr {
match expr {
Expr::Var(name) => {
if name == target {
replacement.clone()
} else {
Expr::Var(name.clone())
}
}
Expr::Lam { param, body } => Expr::Lam {
param: param.clone(),
body: Box::new(subst_under_binder(body, param, target, replacement)),
},
Expr::App { func, arg } => Expr::App {
func: Box::new(subst(func, target, replacement)),
arg: Box::new(subst(arg, target, replacement)),
},
Expr::Let { name, value, body } => Expr::Let {
name: name.clone(),
value: Box::new(subst(value, target, replacement)),
body: Box::new(subst_under_binder(body, name, target, replacement)),
},
Expr::Fix { name, body } => Expr::Fix {
name: name.clone(),
body: Box::new(subst_under_binder(body, name, target, replacement)),
},
}
}
fn subst_under_binder(body: &Expr, binder: &VarName, target: &VarName, replacement: &Expr) -> Expr {
if binder == target {
body.clone()
} else {
subst(body, target, replacement)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn empty() -> Fuel {
Fuel::new(10_000)
}
#[test]
fn identity_is_closure() -> Result<(), Error> {
let id = Expr::lam("x", Expr::var("x"));
let (value, _fuel) = eval(&id, &Env::empty(), empty())?;
matches!(value, Value::Closure { .. })
.then_some(())
.ok_or(Error::UnboundVariable {
name: VarName::from("not a closure"),
})
}
#[test]
fn unbound_variable_errors() -> Result<(), Error> {
let expr = Expr::var("missing");
let result = eval(&expr, &Env::empty(), empty());
match result {
Err(Error::UnboundVariable { .. }) => Ok(()),
Err(other) => Err(other),
Ok(_) => Err(Error::UnboundVariable {
name: VarName::from("expected unbound error"),
}),
}
}
}