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}