1use crate::env::Env;
11use crate::error::Error;
12use crate::syntax::{Expr, VarName};
13use crate::value::Value;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct Fuel {
24 limit: u64,
25 remaining: u64,
26}
27
28impl Fuel {
29 #[must_use]
31 pub fn new(limit: u64) -> Self {
32 Self {
33 limit,
34 remaining: limit,
35 }
36 }
37
38 #[must_use]
40 pub fn limit(&self) -> u64 {
41 self.limit
42 }
43
44 #[must_use]
46 pub fn remaining(&self) -> u64 {
47 self.remaining
48 }
49
50 fn spend(self) -> Result<Self, Error> {
51 match self.remaining {
52 0 => Err(Error::FuelExhausted { limit: self.limit }),
53 n => Ok(Self {
54 limit: self.limit,
55 remaining: n - 1,
56 }),
57 }
58 }
59}
60
61pub fn eval(expr: &Expr, env: &Env, fuel: Fuel) -> Result<(Value, Fuel), Error> {
90 let fuel = fuel.spend()?;
91 step(expr, env, fuel)
92}
93
94fn step(expr: &Expr, env: &Env, fuel: Fuel) -> Result<(Value, Fuel), Error> {
95 match expr {
96 Expr::Var(name) => eval_var(name, env, fuel),
97 Expr::Lam { param, body } => Ok((
98 Value::closure(param.clone(), (**body).clone(), env.clone()),
99 fuel,
100 )),
101 Expr::App { func, arg } => eval_app(func, arg, env, fuel),
102 Expr::Let { name, value, body } => eval_let(name, value, body, env, fuel),
103 Expr::Fix { name, body } => eval_fix(expr, name, body, env, fuel),
104 }
105}
106
107fn eval_var(name: &VarName, env: &Env, fuel: Fuel) -> Result<(Value, Fuel), Error> {
108 env.lookup(name)
109 .cloned()
110 .map(|v| (v, fuel))
111 .ok_or_else(|| Error::UnboundVariable { name: name.clone() })
112}
113
114fn eval_app(func: &Expr, arg: &Expr, env: &Env, fuel: Fuel) -> Result<(Value, Fuel), Error> {
115 let (func_v, fuel) = eval(func, env, fuel)?;
116 let (arg_v, fuel) = eval(arg, env, fuel)?;
117 apply(func_v, arg_v, fuel)
118}
119
120fn eval_let(
121 name: &VarName,
122 value: &Expr,
123 body: &Expr,
124 env: &Env,
125 fuel: Fuel,
126) -> Result<(Value, Fuel), Error> {
127 let (value_v, fuel) = eval(value, env, fuel)?;
128 let new_env = env.extend(name.clone(), value_v);
129 eval(body, &new_env, fuel)
130}
131
132fn eval_fix(
133 whole: &Expr,
134 name: &VarName,
135 body: &Expr,
136 env: &Env,
137 fuel: Fuel,
138) -> Result<(Value, Fuel), Error> {
139 let unfolded = subst(body, name, whole);
140 eval(&unfolded, env, fuel)
141}
142
143fn apply(func: Value, arg: Value, fuel: Fuel) -> Result<(Value, Fuel), Error> {
144 match func {
145 Value::Closure { param, body, env } => {
146 let new_env = env.extend(param, arg);
147 eval(&body, &new_env, fuel)
148 }
149 }
150}
151
152fn subst(expr: &Expr, target: &VarName, replacement: &Expr) -> Expr {
153 match expr {
154 Expr::Var(name) => {
155 if name == target {
156 replacement.clone()
157 } else {
158 Expr::Var(name.clone())
159 }
160 }
161 Expr::Lam { param, body } => Expr::Lam {
162 param: param.clone(),
163 body: Box::new(subst_under_binder(body, param, target, replacement)),
164 },
165 Expr::App { func, arg } => Expr::App {
166 func: Box::new(subst(func, target, replacement)),
167 arg: Box::new(subst(arg, target, replacement)),
168 },
169 Expr::Let { name, value, body } => Expr::Let {
170 name: name.clone(),
171 value: Box::new(subst(value, target, replacement)),
172 body: Box::new(subst_under_binder(body, name, target, replacement)),
173 },
174 Expr::Fix { name, body } => Expr::Fix {
175 name: name.clone(),
176 body: Box::new(subst_under_binder(body, name, target, replacement)),
177 },
178 }
179}
180
181fn subst_under_binder(body: &Expr, binder: &VarName, target: &VarName, replacement: &Expr) -> Expr {
182 if binder == target {
183 body.clone()
184 } else {
185 subst(body, target, replacement)
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 fn empty() -> Fuel {
194 Fuel::new(10_000)
195 }
196
197 #[test]
198 fn identity_is_closure() -> Result<(), Error> {
199 let id = Expr::lam("x", Expr::var("x"));
200 let (value, _fuel) = eval(&id, &Env::empty(), empty())?;
201 matches!(value, Value::Closure { .. })
202 .then_some(())
203 .ok_or(Error::UnboundVariable {
204 name: VarName::from("not a closure"),
205 })
206 }
207
208 #[test]
209 fn unbound_variable_errors() -> Result<(), Error> {
210 let expr = Expr::var("missing");
211 let result = eval(&expr, &Env::empty(), empty());
212 match result {
213 Err(Error::UnboundVariable { .. }) => Ok(()),
214 Err(other) => Err(other),
215 Ok(_) => Err(Error::UnboundVariable {
216 name: VarName::from("expected unbound error"),
217 }),
218 }
219 }
220}