Skip to main content

sim_lib_sequence/
runtime.rs

1//! The sequence organ as a loadable kernel [`Lib`].
2//!
3//! Registers the higher-order sequence operations `seq/map`, `seq/filter`, and
4//! `seq/fold` as callables (COOKBOOK_7 Category B). These are ordinary functions
5//! -- both the applied function and the collection are evaluated before the op
6//! runs -- but they are eval-policy organs in that they APPLY a function value
7//! over every element, driving the evaluator once per element via
8//! [`Cx::call_value`].
9
10use std::sync::Arc;
11
12use sim_kernel::{
13    AbiVersion, Args, Callable, ClassRef, Cx, Error, Export, Lib, LibManifest, LibTarget, Linker,
14    Object, ObjectCompat, Result, Symbol, Value, Version, force_list_to_vec,
15};
16
17const SEQUENCE_LIB_ID: &str = "sequence";
18
19/// Returns the `sim/sequence` manifest id under which this lib registers.
20pub fn manifest_name() -> Symbol {
21    Symbol::qualified("sim", SEQUENCE_LIB_ID)
22}
23
24/// One higher-order sequence operation.
25#[derive(Clone, Copy)]
26pub enum SeqOp {
27    /// `(seq/map f list)` -> the list of `f(x)` for each `x`.
28    Map,
29    /// `(seq/filter pred list)` -> the elements for which `pred(x)` is truthy.
30    Filter,
31    /// `(seq/fold f init list)` -> the left fold `f(... f(f(init, x0), x1) ..., xn)`.
32    Fold,
33}
34
35impl SeqOp {
36    /// All sequence operations, in registration order.
37    pub const ALL: [SeqOp; 3] = [SeqOp::Map, SeqOp::Filter, SeqOp::Fold];
38
39    /// The `seq/*` symbol this operation registers under.
40    pub fn symbol(self) -> Symbol {
41        let name = match self {
42            SeqOp::Map => "map",
43            SeqOp::Filter => "filter",
44            SeqOp::Fold => "fold",
45        };
46        Symbol::qualified("seq", name)
47    }
48
49    fn arity(self) -> usize {
50        match self {
51            SeqOp::Map | SeqOp::Filter => 2,
52            SeqOp::Fold => 3,
53        }
54    }
55
56    /// Extracts the elements of `value`, erroring when it is not a list.
57    fn elements(cx: &mut Cx, value: &Value, context: &'static str) -> Result<Vec<Value>> {
58        let object = value.object();
59        let Some(list) = object.as_list() else {
60            return Err(Error::TypeMismatch {
61                expected: "list",
62                found: context,
63            });
64        };
65        force_list_to_vec(cx, list, context)
66    }
67
68    fn run(self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
69        if args.len() != self.arity() {
70            return Err(Error::Eval(format!(
71                "{} expects {} argument(s), got {}",
72                self.symbol(),
73                self.arity(),
74                args.len()
75            )));
76        }
77        match self {
78            SeqOp::Map => {
79                let items = Self::elements(cx, &args[1], "seq/map list")?;
80                let func = args[0].clone();
81                let mut out = Vec::with_capacity(items.len());
82                for item in items {
83                    out.push(cx.call_value(func.clone(), Args::new(vec![item]))?);
84                }
85                cx.factory().list(out)
86            }
87            SeqOp::Filter => {
88                let items = Self::elements(cx, &args[1], "seq/filter list")?;
89                let pred = args[0].clone();
90                let mut out = Vec::new();
91                for item in items {
92                    let keep = cx
93                        .call_value(pred.clone(), Args::new(vec![item.clone()]))?
94                        .object()
95                        .truth(cx)?;
96                    if keep {
97                        out.push(item);
98                    }
99                }
100                cx.factory().list(out)
101            }
102            SeqOp::Fold => {
103                let items = Self::elements(cx, &args[2], "seq/fold list")?;
104                let func = args[0].clone();
105                let mut acc = args[1].clone();
106                for item in items {
107                    acc = cx.call_value(func.clone(), Args::new(vec![acc, item]))?;
108                }
109                Ok(acc)
110            }
111        }
112    }
113}
114
115/// A callable runtime object exposing one [`SeqOp`].
116#[derive(Clone)]
117pub struct SequenceFunction {
118    op: SeqOp,
119}
120
121impl SequenceFunction {
122    /// Builds the callable for `op`.
123    pub fn new(op: SeqOp) -> Self {
124        Self { op }
125    }
126}
127
128impl Object for SequenceFunction {
129    fn display(&self, _cx: &mut Cx) -> Result<String> {
130        Ok(format!("#<function {}>", self.op.symbol()))
131    }
132
133    fn as_any(&self) -> &dyn std::any::Any {
134        self
135    }
136}
137
138impl ObjectCompat for SequenceFunction {
139    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
140        cx.resolve_class(&Symbol::qualified("core", "Function"))
141    }
142
143    fn as_callable(&self) -> Option<&dyn Callable> {
144        Some(self)
145    }
146}
147
148impl Callable for SequenceFunction {
149    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
150        self.op.run(cx, args.into_vec())
151    }
152}
153
154/// The sequence organ lib: installs `seq/map|filter|fold` as callables.
155pub struct SequenceLib;
156
157impl Lib for SequenceLib {
158    fn manifest(&self) -> LibManifest {
159        LibManifest {
160            id: manifest_name(),
161            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
162            abi: AbiVersion { major: 0, minor: 1 },
163            target: LibTarget::HostRegistered,
164            requires: Vec::new(),
165            capabilities: Vec::new(),
166            exports: sequence_exports(),
167        }
168    }
169
170    fn load(&self, cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
171        for op in SeqOp::ALL {
172            let function = SequenceFunction::new(op);
173            linker.function_value(op.symbol(), cx.factory().opaque(Arc::new(function))?)?;
174        }
175        Ok(())
176    }
177}
178
179/// Returns the lib's exported `seq/*` functions as kernel [`Export`]s.
180pub fn sequence_exports() -> Vec<Export> {
181    SeqOp::ALL
182        .into_iter()
183        .map(|op| Export::Function {
184            symbol: op.symbol(),
185            function_id: None,
186        })
187        .collect()
188}
189
190/// Installs the sequence organ into `cx` (idempotent).
191pub fn install_sequence_lib(cx: &mut Cx) -> Result<()> {
192    if cx.registry().lib(&manifest_name()).is_some() {
193        return Ok(());
194    }
195    cx.load_lib(&SequenceLib)?;
196    Ok(())
197}