Skip to main content

sim_lib_lang_clojure/
data.rs

1use std::sync::Arc;
2
3use sim_kernel::{Cx, Error, Expr, NumberLiteral, Result, Symbol, Value};
4use sim_lib_namespace::{Namespace, NamespaceKind};
5use sim_lib_sequence::{TransducerPipeline, persistent_list, sequence_for_profile, transduce};
6
7use crate::{clojure_core_namespace_symbol, clojure_core_profile_symbol};
8
9/// Reducer step used to fold sequence values during a Clojure-profile transduce.
10///
11/// Takes the running accumulator and the next value and returns the next accumulator.
12pub type ClojureReducer =
13    Arc<dyn Fn(&mut Cx, Value, Value) -> Result<Value> + Send + Sync + 'static>;
14
15/// Realizes a decoded EDN [`Expr`] as a runtime [`Value`] using the sequence organ.
16///
17/// Collections become persistent list/vector/set/map values; scalars round-trip
18/// through the factory. Map keys are coerced via the EDN key rules.
19pub fn edn_expr_to_value(cx: &mut Cx, expr: &Expr) -> Result<Value> {
20    match expr {
21        Expr::Nil => cx.factory().nil(),
22        Expr::Bool(value) => cx.factory().bool(*value),
23        Expr::Number(NumberLiteral { domain, canonical }) => cx
24            .factory()
25            .number_literal(domain.clone(), canonical.clone()),
26        Expr::Symbol(symbol) => cx.factory().symbol(symbol.clone()),
27        Expr::String(value) => cx.factory().string(value.clone()),
28        Expr::Bytes(value) => cx.factory().bytes(value.clone()),
29        Expr::List(items) => {
30            let values = exprs_to_values(cx, items)?;
31            sim_lib_sequence::persistent_list(cx, values)
32        }
33        Expr::Vector(items) => {
34            let values = exprs_to_values(cx, items)?;
35            sim_lib_sequence::persistent_vector(cx, values)
36        }
37        Expr::Set(items) => {
38            let values = exprs_to_values(cx, items)?;
39            sim_lib_sequence::persistent_set(cx, values)
40        }
41        Expr::Map(entries) => {
42            let entries = entries
43                .iter()
44                .map(|(key, value)| Ok((map_key(key)?, edn_expr_to_value(cx, value)?)))
45                .collect::<Result<Vec<_>>>()?;
46            sim_lib_sequence::persistent_map(cx, entries)
47        }
48        other => cx.factory().expr(other.clone()),
49    }
50}
51
52/// Builds a Clojure persistent vector value from the given items.
53pub fn clojure_persistent_data(cx: &mut Cx, items: Vec<Value>) -> Result<Value> {
54    sim_lib_sequence::persistent_vector(cx, items)
55}
56
57/// Builds a sequence value tagged for the Clojure-core profile from the given items.
58///
59/// Wraps a persistent list as a sequence object bound to [`clojure_core_profile_symbol`].
60pub fn clojure_profile_sequence(cx: &mut Cx, items: Vec<Value>) -> Result<Value> {
61    let list = persistent_list(cx, items)?;
62    let sequence = sim_lib_sequence::sequence_from_list_value(cx, list)?;
63    sequence_for_profile(cx, clojure_core_profile_symbol(), sequence)
64}
65
66/// Runs a transducer pipeline over a source sequence using the sequence organ.
67///
68/// Thin Clojure-profile wrapper over [`sim_lib_sequence::transduce`] with a
69/// [`ClojureReducer`] fold step.
70pub fn clojure_transduce(
71    cx: &mut Cx,
72    source: &Value,
73    pipeline: TransducerPipeline,
74    init: Value,
75    reducer: ClojureReducer,
76) -> Result<Value> {
77    transduce(cx, source, pipeline, init, reducer)
78}
79
80/// Builds the `clojure.core` namespace mapping surface names onto organ targets.
81///
82/// Maps `map`/`transduce` onto the sequence organ and `recur` onto the control
83/// organ, exporting each.
84pub fn clojure_core_namespace() -> Result<Namespace> {
85    let mut namespace = Namespace::new(clojure_core_namespace_symbol(), NamespaceKind::Module);
86    namespace.define(Symbol::new("map"), Symbol::qualified("sequence", "map.v1"))?;
87    namespace.define(
88        Symbol::new("transduce"),
89        Symbol::qualified("sequence", "transduce.v1"),
90    )?;
91    namespace.define(
92        Symbol::new("recur"),
93        Symbol::qualified("control", "abort.v1"),
94    )?;
95    for exported in [
96        Symbol::new("map"),
97        Symbol::new("transduce"),
98        Symbol::new("recur"),
99    ] {
100        namespace.export(exported)?;
101    }
102    Ok(namespace)
103}
104
105fn exprs_to_values(cx: &mut Cx, items: &[Expr]) -> Result<Vec<Value>> {
106    items
107        .iter()
108        .map(|item| edn_expr_to_value(cx, item))
109        .collect()
110}
111
112fn map_key(expr: &Expr) -> Result<Symbol> {
113    match expr {
114        Expr::Symbol(symbol) => Ok(symbol.clone()),
115        Expr::String(value) => Ok(Symbol::new(value.clone())),
116        _ => Err(Error::TypeMismatch {
117            expected: "EDN symbol, keyword, or string map key",
118            found: expr_kind(expr),
119        }),
120    }
121}
122
123use sim_value::kind::expr_kind;