Skip to main content

sim_lib_lang_scheme/
lowering.rs

1use sim_kernel::{Datum, Expr, LocatedExprTree, Origin, Result, Symbol, Term};
2
3use crate::symbols::scheme_symbol;
4
5/// Result of lowering a Scheme `Expr` to a runtime value.
6///
7/// Evaluable forms lower to a [`Term`]; quoted or self-evaluating literals
8/// lower to a [`Datum`].
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub enum SchemeLowered {
11    /// An evaluable term.
12    Term(Term),
13    /// A datum (quoted or self-evaluating literal).
14    Datum(Datum),
15}
16
17/// A [`SchemeLowered`] value paired with its source origin.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct LocatedSchemeLowering {
20    /// The lowered term or datum.
21    pub lowered: SchemeLowered,
22    /// Source origin carried from the located tree, if any.
23    pub origin: Option<Origin>,
24}
25
26/// Lowers a located Scheme tree, preserving its origin.
27pub fn lower_scheme_tree(tree: &LocatedExprTree) -> Result<LocatedSchemeLowering> {
28    Ok(LocatedSchemeLowering {
29        lowered: lower_scheme_expr(&tree.expr)?,
30        origin: tree.origin.clone(),
31    })
32}
33
34/// Lowers a single Scheme `Expr` to a [`SchemeLowered`] term or datum.
35///
36/// `quote` forms and self-evaluating literals become data; symbols, calls, and
37/// blocks become evaluable terms over the canonical `scheme`-qualified surface.
38pub fn lower_scheme_expr(expr: &Expr) -> Result<SchemeLowered> {
39    match expr {
40        Expr::Nil | Expr::Bool(_) | Expr::Number(_) | Expr::String(_) | Expr::Bytes(_) => {
41            Datum::try_from(expr.clone()).map(SchemeLowered::Datum)
42        }
43        Expr::List(items) => lower_scheme_list(items),
44        Expr::Symbol(_) | Expr::Call { .. } | Expr::Block(_) => {
45            Term::lower(canonical_eval_expr(expr.clone())).map(SchemeLowered::Term)
46        }
47        _ => Term::lower(expr.clone()).map(SchemeLowered::Term),
48    }
49}
50
51fn lower_scheme_list(items: &[Expr]) -> Result<SchemeLowered> {
52    let Some(Expr::Symbol(head)) = items.first() else {
53        return Datum::try_from(Expr::List(items.to_vec())).map(SchemeLowered::Datum);
54    };
55    if head == &Symbol::new("quote") {
56        let [_, datum] = items else {
57            return Err(sim_kernel::Error::Eval(
58                "Scheme quote expects exactly one datum".to_owned(),
59            ));
60        };
61        return Datum::try_from(datum.clone()).map(SchemeLowered::Datum);
62    }
63    Term::lower(canonical_call(head.clone(), &items[1..])).map(SchemeLowered::Term)
64}
65
66fn canonical_eval_expr(expr: Expr) -> Expr {
67    match expr {
68        Expr::List(items) => match items.first() {
69            Some(Expr::Symbol(head)) => canonical_call(head.clone(), &items[1..]),
70            _ => Expr::List(items),
71        },
72        other => other,
73    }
74}
75
76fn canonical_call(head: Symbol, args: &[Expr]) -> Expr {
77    if head == Symbol::new("begin") {
78        return Expr::Block(args.iter().cloned().map(canonical_eval_expr).collect());
79    }
80    Expr::Call {
81        operator: Box::new(Expr::Symbol(scheme_symbol(&head.to_string()))),
82        args: args.iter().cloned().map(canonical_eval_expr).collect(),
83    }
84}