Skip to main content

sim_kernel/eval/
policy.rs

1use std::sync::Arc;
2
3use crate::{
4    Symbol,
5    env::Cx,
6    error::{Error, Result},
7    eval::{Demand, LazyThunkObject, PreparedArgs, ThunkObject},
8    expr::Expr,
9    object::RawArgs,
10    value::Value,
11};
12
13use super::protocol::Phase;
14
15/// The eval-policy contract: how a call site evaluates its arguments and body.
16///
17/// An eval policy decides argument strategy (eager, lazy, by-need, or strict
18/// by [`Demand`]), whether macro expansion is permitted in a given
19/// [`Phase`], how a value is [`force`](EvalPolicy::force)d to satisfy a
20/// demand, and how a bare [`Expr`] is evaluated. The kernel ships several
21/// reference policies ([`EagerPolicy`], [`LazyPolicy`], [`NeedPolicy`],
22/// [`StrictByShapePolicy`], [`HybridPolicy`], [`NoopEvalPolicy`]); libraries
23/// may supply their own. Concrete language semantics stay out of the kernel.
24pub trait EvalPolicy: Send + Sync {
25    /// A stable, human-readable name for this policy.
26    fn name(&self) -> &'static str;
27    /// Whether macro expansion is permitted in the given [`Phase`].
28    ///
29    /// Defaults to `false` (no expansion in any phase).
30    fn allow_macro_expansion(&self, _phase: Phase) -> bool {
31        false
32    }
33    /// Prepares raw call arguments into [`PreparedArgs`] per the demands.
34    ///
35    /// The per-position `demands` slice states how strongly each argument
36    /// must be forced; positions beyond its length take a policy default.
37    fn prepare_call_args(
38        &self,
39        cx: &mut Cx,
40        raw: RawArgs,
41        demands: &[Demand],
42    ) -> Result<PreparedArgs>;
43    /// Forces `value` far enough to satisfy `demand`.
44    fn force(&self, cx: &mut Cx, value: Value, demand: Demand) -> Result<Value>;
45    /// Evaluates a bare expression to a value under this policy.
46    fn eval_expr(&self, cx: &mut Cx, expr: Expr) -> Result<Value>;
47    /// Resolve a symbol that bound to nothing in value position.
48    ///
49    /// The default keeps it as data, which makes eval total and fits the
50    /// symbolic substrate. A strict language policy can override this to fail.
51    fn resolve_unbound_symbol(&self, cx: &mut Cx, symbol: Symbol) -> Result<Value> {
52        cx.factory().symbol(symbol)
53    }
54    /// Resolve a call whose operator bound to nothing.
55    ///
56    /// The default keeps the unevaluated application as data, matching the
57    /// symbolic substrate. A strict language policy can override this to fail.
58    fn resolve_unbound_call(
59        &self,
60        cx: &mut Cx,
61        operator: Symbol,
62        args: Vec<Expr>,
63    ) -> Result<Value> {
64        cx.factory().expr(Expr::Call {
65            operator: Box::new(Expr::Symbol(operator)),
66            args,
67        })
68    }
69}
70
71/// A shared, reference-counted handle to an [`EvalPolicy`].
72pub type EvalPolicyRef = Arc<dyn EvalPolicy>;
73
74/// A policy that quotes every argument and refuses to evaluate expressions.
75///
76/// Useful as a placeholder where no real evaluation strategy is installed:
77/// arguments are passed through as quoted expression values and
78/// [`eval_expr`](EvalPolicy::eval_expr) returns an error.
79pub struct NoopEvalPolicy;
80
81impl EvalPolicy for NoopEvalPolicy {
82    fn name(&self) -> &'static str {
83        "noop"
84    }
85
86    fn prepare_call_args(
87        &self,
88        cx: &mut Cx,
89        raw: RawArgs,
90        _demands: &[Demand],
91    ) -> Result<PreparedArgs> {
92        let values = raw
93            .into_exprs()
94            .into_iter()
95            .map(|expr| cx.factory().expr(expr))
96            .collect::<Result<Vec<_>>>()?;
97        Ok(PreparedArgs::new(values))
98    }
99
100    fn force(&self, _cx: &mut Cx, value: Value, _demand: Demand) -> Result<Value> {
101        Ok(value)
102    }
103
104    fn eval_expr(&self, _cx: &mut Cx, _expr: Expr) -> Result<Value> {
105        Err(Error::Eval(
106            "noop eval policy cannot evaluate expressions".to_owned(),
107        ))
108    }
109}
110
111/// A policy that fully evaluates every argument before the call.
112pub struct EagerPolicy;
113
114impl EvalPolicy for EagerPolicy {
115    fn name(&self) -> &'static str {
116        "eager"
117    }
118
119    fn allow_macro_expansion(&self, phase: Phase) -> bool {
120        matches!(
121            phase,
122            Phase::Read | Phase::Expand | Phase::Compile | Phase::Eval
123        )
124    }
125
126    fn prepare_call_args(
127        &self,
128        cx: &mut Cx,
129        raw: RawArgs,
130        _demands: &[Demand],
131    ) -> Result<PreparedArgs> {
132        let values = raw
133            .into_exprs()
134            .into_iter()
135            .map(|expr| cx.eval_expr(expr))
136            .collect::<Result<Vec<_>>>()?;
137        Ok(PreparedArgs::new(values))
138    }
139
140    fn force(&self, cx: &mut Cx, value: Value, demand: Demand) -> Result<Value> {
141        crate::eval::force_default(cx, value, demand)
142    }
143
144    fn eval_expr(&self, cx: &mut Cx, expr: Expr) -> Result<Value> {
145        crate::eval::eval_expr_default(cx, expr)
146    }
147}
148
149/// A policy that wraps every argument in a re-forcing lazy thunk.
150///
151/// Each argument becomes a [`LazyThunkObject`] that re-evaluates whenever it
152/// is forced; results are not cached between forces.
153pub struct LazyPolicy;
154
155impl EvalPolicy for LazyPolicy {
156    fn name(&self) -> &'static str {
157        "lazy"
158    }
159
160    fn allow_macro_expansion(&self, phase: Phase) -> bool {
161        matches!(
162            phase,
163            Phase::Read | Phase::Expand | Phase::Compile | Phase::Eval
164        )
165    }
166
167    fn prepare_call_args(
168        &self,
169        cx: &mut Cx,
170        raw: RawArgs,
171        _demands: &[Demand],
172    ) -> Result<PreparedArgs> {
173        lazy_args(cx, raw)
174    }
175
176    fn force(&self, cx: &mut Cx, value: Value, demand: Demand) -> Result<Value> {
177        crate::eval::force_default(cx, value, demand)
178    }
179
180    fn eval_expr(&self, cx: &mut Cx, expr: Expr) -> Result<Value> {
181        crate::eval::eval_expr_default(cx, expr)
182    }
183}
184
185/// A call-by-need policy: arguments are thunked and forced at most once.
186///
187/// Each argument becomes a memoizing [`ThunkObject`] whose value is computed
188/// on first force and cached for later uses.
189pub struct NeedPolicy;
190
191impl EvalPolicy for NeedPolicy {
192    fn name(&self) -> &'static str {
193        "lazy-by-need"
194    }
195
196    fn allow_macro_expansion(&self, phase: Phase) -> bool {
197        matches!(
198            phase,
199            Phase::Read | Phase::Expand | Phase::Compile | Phase::Eval
200        )
201    }
202
203    fn prepare_call_args(
204        &self,
205        cx: &mut Cx,
206        raw: RawArgs,
207        _demands: &[Demand],
208    ) -> Result<PreparedArgs> {
209        need_args(cx, raw)
210    }
211
212    fn force(&self, cx: &mut Cx, value: Value, demand: Demand) -> Result<Value> {
213        crate::eval::force_default(cx, value, demand)
214    }
215
216    fn eval_expr(&self, cx: &mut Cx, expr: Expr) -> Result<Value> {
217        crate::eval::eval_expr_default(cx, expr)
218    }
219}
220
221/// A policy that forces or defers each argument by its [`Demand`].
222///
223/// Positions demanding a value are evaluated eagerly; positions demanding an
224/// expression (or nothing) become lazy thunks.
225pub struct StrictByShapePolicy;
226
227impl EvalPolicy for StrictByShapePolicy {
228    fn name(&self) -> &'static str {
229        "strict-by-shape"
230    }
231
232    fn allow_macro_expansion(&self, phase: Phase) -> bool {
233        matches!(
234            phase,
235            Phase::Read | Phase::Expand | Phase::Compile | Phase::Eval
236        )
237    }
238
239    fn prepare_call_args(
240        &self,
241        cx: &mut Cx,
242        raw: RawArgs,
243        demands: &[Demand],
244    ) -> Result<PreparedArgs> {
245        let env = cx.env().clone();
246        let values = raw
247            .into_exprs()
248            .into_iter()
249            .enumerate()
250            .map(|(index, expr)| {
251                let demand = demands.get(index).copied().unwrap_or(Demand::Value);
252                match demand {
253                    Demand::Never | Demand::Expr => Ok(Value::from_arc(Arc::new(
254                        LazyThunkObject::new(expr, env.clone()),
255                    ))),
256                    Demand::Value | Demand::Bool | Demand::Class(_) | Demand::Shape(_) => {
257                        cx.eval_expr(expr)
258                    }
259                }
260            })
261            .collect::<Result<Vec<_>>>()?;
262        Ok(PreparedArgs::new(values))
263    }
264
265    fn force(&self, cx: &mut Cx, value: Value, demand: Demand) -> Result<Value> {
266        crate::eval::force_default(cx, value, demand)
267    }
268
269    fn eval_expr(&self, cx: &mut Cx, expr: Expr) -> Result<Value> {
270        crate::eval::eval_expr_default(cx, expr)
271    }
272}
273
274/// A policy that mixes eager and quoted arguments by their [`Demand`].
275///
276/// Like [`StrictByShapePolicy`], but expression-demanding positions are kept
277/// as quoted expression values rather than lazy thunks.
278pub struct HybridPolicy;
279
280impl EvalPolicy for HybridPolicy {
281    fn name(&self) -> &'static str {
282        "hybrid"
283    }
284
285    fn allow_macro_expansion(&self, phase: Phase) -> bool {
286        matches!(
287            phase,
288            Phase::Read | Phase::Expand | Phase::Compile | Phase::Eval
289        )
290    }
291
292    fn prepare_call_args(
293        &self,
294        cx: &mut Cx,
295        raw: RawArgs,
296        demands: &[Demand],
297    ) -> Result<PreparedArgs> {
298        let values = raw
299            .into_exprs()
300            .into_iter()
301            .enumerate()
302            .map(|(index, expr)| {
303                let demand = demands.get(index).copied().unwrap_or(Demand::Value);
304                match demand {
305                    Demand::Never | Demand::Expr => cx.factory().expr(expr),
306                    Demand::Value | Demand::Bool | Demand::Class(_) | Demand::Shape(_) => {
307                        cx.eval_expr(expr)
308                    }
309                }
310            })
311            .collect::<Result<Vec<_>>>()?;
312        Ok(PreparedArgs::new(values))
313    }
314
315    fn force(&self, cx: &mut Cx, value: Value, demand: Demand) -> Result<Value> {
316        crate::eval::force_default(cx, value, demand)
317    }
318
319    fn eval_expr(&self, cx: &mut Cx, expr: Expr) -> Result<Value> {
320        crate::eval::eval_expr_default(cx, expr)
321    }
322}
323
324fn lazy_args(cx: &mut Cx, raw: RawArgs) -> Result<PreparedArgs> {
325    let env = cx.env().clone();
326    let values = raw
327        .into_exprs()
328        .into_iter()
329        .map(|expr| {
330            Ok(Value::from_arc(Arc::new(LazyThunkObject::new(
331                expr,
332                env.clone(),
333            ))))
334        })
335        .collect::<Result<Vec<_>>>()?;
336    Ok(PreparedArgs::new(values))
337}
338
339fn need_args(cx: &mut Cx, raw: RawArgs) -> Result<PreparedArgs> {
340    let env = cx.env().clone();
341    let values = raw
342        .into_exprs()
343        .into_iter()
344        .map(|expr| {
345            Ok(Value::from_arc(Arc::new(ThunkObject::new(
346                expr,
347                env.clone(),
348            ))))
349        })
350        .collect::<Result<Vec<_>>>()?;
351    Ok(PreparedArgs::new(values))
352}