Skip to main content

sim_lib_binding/
dynamic.rs

1use std::{
2    collections::BTreeMap,
3    sync::{Arc, Mutex, MutexGuard},
4};
5
6use sim_kernel::{Error, Result, Symbol, Value};
7
8type DynamicFrame = BTreeMap<Symbol, Value>;
9
10/// A stack of dynamic-extent binding frames shared across clones.
11///
12/// Holds the fluid (dynamically scoped) bindings of the binding organ. Frames
13/// are pushed for the duration of a body and popped when it returns or unwinds,
14/// giving dynamic rather than lexical extent.
15#[derive(Clone, Debug, Default)]
16pub struct DynamicEnv {
17    frames: Arc<Mutex<Vec<DynamicFrame>>>,
18}
19
20impl DynamicEnv {
21    /// Creates an empty dynamic environment with no active frames.
22    pub fn new() -> Self {
23        Self::default()
24    }
25
26    /// Returns the innermost binding for `name`, or `None` if it is unbound.
27    pub fn lookup(&self, name: &Symbol) -> Result<Option<Value>> {
28        Ok(self
29            .frames()?
30            .iter()
31            .rev()
32            .find_map(|frame| frame.get(name).cloned()))
33    }
34
35    /// Runs `body` with `bindings` installed in a fresh frame.
36    ///
37    /// The frame is popped when `body` returns, including on early return or
38    /// non-local unwind, so the bindings have dynamic extent only.
39    pub fn with_bindings<T>(
40        &self,
41        bindings: Vec<(Symbol, Value)>,
42        body: impl FnOnce() -> Result<T>,
43    ) -> Result<T> {
44        self.frames()?.push(bindings.into_iter().collect());
45        let _guard = DynamicFrameGuard {
46            frames: self.frames.clone(),
47        };
48        body()
49    }
50
51    fn frames(&self) -> Result<MutexGuard<'_, Vec<DynamicFrame>>> {
52        self.frames
53            .lock()
54            .map_err(|_| Error::Eval("dynamic binding frame lock is poisoned".to_owned()))
55    }
56}
57
58struct DynamicFrameGuard {
59    frames: Arc<Mutex<Vec<DynamicFrame>>>,
60}
61
62impl Drop for DynamicFrameGuard {
63    fn drop(&mut self) {
64        if let Ok(mut frames) = self.frames.lock() {
65            frames.pop();
66        }
67    }
68}
69
70/// A dynamic parameter: a named fluid binding with a fallback default.
71///
72/// Backed by a [`DynamicEnv`], a parameter reads the innermost dynamic binding
73/// for its name and falls back to its default when none is active. This is the
74/// binding organ's surface for `parameterize`-style rebinding.
75#[derive(Clone, Debug)]
76pub struct Parameter {
77    name: Symbol,
78    default: Value,
79    dynamic: DynamicEnv,
80}
81
82impl Parameter {
83    /// Creates a parameter over a fresh [`DynamicEnv`] with the given default.
84    pub fn new(name: Symbol, default: Value) -> Self {
85        Self::with_dynamic_env(name, default, DynamicEnv::new())
86    }
87
88    /// Creates a parameter bound to an existing [`DynamicEnv`].
89    ///
90    /// Use this to share one environment across parameters that must observe
91    /// each other's frames.
92    pub fn with_dynamic_env(name: Symbol, default: Value, dynamic: DynamicEnv) -> Self {
93        Self {
94            name,
95            default,
96            dynamic,
97        }
98    }
99
100    /// Returns the parameter's name.
101    pub fn name(&self) -> &Symbol {
102        &self.name
103    }
104
105    /// Returns the current value: the innermost binding, or the default.
106    pub fn get(&self) -> Result<Value> {
107        Ok(self
108            .dynamic
109            .lookup(&self.name)?
110            .unwrap_or_else(|| self.default.clone()))
111    }
112
113    /// Runs `body` with the parameter rebound to `value` for that dynamic extent.
114    ///
115    /// The previous value is restored when `body` returns or unwinds.
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// use std::sync::Arc;
121    /// use sim_kernel::{Cx, DefaultFactory, NoopEvalPolicy, Symbol};
122    /// use sim_lib_binding::Parameter;
123    ///
124    /// let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
125    /// let default = cx.factory().symbol(Symbol::new("default")).unwrap();
126    /// let temporary = cx.factory().symbol(Symbol::new("temporary")).unwrap();
127    /// let parameter = Parameter::new(Symbol::new("current"), default.clone());
128    ///
129    /// parameter
130    ///     .with_value(temporary.clone(), || {
131    ///         assert_eq!(parameter.get()?, temporary);
132    ///         Ok(())
133    ///     })
134    ///     .unwrap();
135    /// assert_eq!(parameter.get().unwrap(), default);
136    /// ```
137    pub fn with_value<T>(&self, value: Value, body: impl FnOnce() -> Result<T>) -> Result<T> {
138        self.dynamic
139            .with_bindings(vec![(self.name.clone(), value)], body)
140    }
141}