Skip to main content

sim_lib_standard_core/
polyglot.rs

1//! Polyglot profile functions callable across language profiles.
2
3use std::{collections::BTreeMap, sync::Arc};
4
5use sim_kernel::{
6    Args, CORE_FUNCTION_CLASS_ID, Callable, ClassRef, Cx, Error, Object, ObjectCompat, Result,
7    Symbol, Value,
8};
9
10use crate::{GuestRuntimeKit, LanguageProfile, ProfileRegistry};
11
12/// Body of a [`ProfileFunction`]: a callable closure over `Cx` and `Args`.
13pub type ProfileFunctionBody = Arc<dyn Fn(&mut Cx, Args) -> Result<Value> + Send + Sync>;
14
15/// A callable runtime object owned by a profile and scoped to an organ.
16#[derive(Clone)]
17pub struct ProfileFunction {
18    defining_profile: Symbol,
19    organ: Symbol,
20    function: Symbol,
21    body: ProfileFunctionBody,
22}
23
24impl ProfileFunction {
25    /// Build a profile function for `function` in `organ`, defined by
26    /// `defining_profile`, calling `body`.
27    pub fn new<F>(defining_profile: Symbol, organ: Symbol, function: Symbol, body: F) -> Self
28    where
29        F: Fn(&mut Cx, Args) -> Result<Value> + Send + Sync + 'static,
30    {
31        Self {
32            defining_profile,
33            organ,
34            function,
35            body: Arc::new(body),
36        }
37    }
38
39    /// Symbol of the profile that defined the function.
40    pub fn defining_profile(&self) -> &Symbol {
41        &self.defining_profile
42    }
43
44    /// Symbol of the organ the function belongs to.
45    pub fn organ(&self) -> &Symbol {
46        &self.organ
47    }
48
49    /// Symbol naming the function.
50    pub fn function(&self) -> &Symbol {
51        &self.function
52    }
53}
54
55impl Callable for ProfileFunction {
56    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
57        (self.body)(cx, args)
58    }
59}
60
61impl Object for ProfileFunction {
62    fn display(&self, _cx: &mut Cx) -> Result<String> {
63        Ok(format!(
64            "#<profile-function {} defined-by {}>",
65            self.function, self.defining_profile
66        ))
67    }
68
69    fn as_any(&self) -> &dyn std::any::Any {
70        self
71    }
72}
73
74impl ObjectCompat for ProfileFunction {
75    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
76        cx.factory().class_stub(
77            CORE_FUNCTION_CLASS_ID,
78            Symbol::qualified("core", "Function"),
79        )
80    }
81
82    fn as_callable(&self) -> Option<&dyn Callable> {
83        Some(self)
84    }
85}
86
87/// A function registered in a [`SharedOrganRuntime`], with its owning profile,
88/// organ, name, and callable value.
89#[derive(Clone, Debug)]
90pub struct ProfileFunctionBinding {
91    /// Symbol of the profile that defined the function.
92    pub defining_profile: Symbol,
93    /// Organ the function belongs to.
94    pub organ: Symbol,
95    /// Symbol naming the function.
96    pub function: Symbol,
97    /// The callable value.
98    pub value: Value,
99}
100
101/// Runtime sharing organ functions across profiles: profiles may call a
102/// function defined by another profile only when both use the function's organ.
103#[derive(Clone, Debug, Default)]
104pub struct SharedOrganRuntime {
105    registry: ProfileRegistry,
106    functions: BTreeMap<Symbol, ProfileFunctionBinding>,
107    kits: BTreeMap<Symbol, GuestRuntimeKit>,
108}
109
110impl SharedOrganRuntime {
111    /// Create an empty runtime.
112    pub fn new() -> Self {
113        Self::default()
114    }
115
116    /// Register a profile so its organs and functions become available.
117    pub fn register_profile(&mut self, profile: LanguageProfile) -> Result<()> {
118        self.registry.register_profile(profile)
119    }
120
121    /// Look up a registered profile by symbol.
122    pub fn profile(&self, symbol: &Symbol) -> Option<&LanguageProfile> {
123        self.registry.profile(symbol)
124    }
125
126    /// Iterate the registered profiles.
127    pub fn profiles(&self) -> impl Iterator<Item = &LanguageProfile> {
128        self.registry.profiles()
129    }
130
131    /// Register a guest runtime policy kit for a known profile.
132    ///
133    /// Fails if the profile is unknown or already has a kit registered.
134    pub fn register_kit(&mut self, profile: &Symbol, kit: GuestRuntimeKit) -> Result<()> {
135        if self.registry.profile(profile).is_none() {
136            return Err(Error::UnknownSymbol {
137                symbol: profile.clone(),
138            });
139        }
140        if self.kits.contains_key(profile) {
141            return Err(Error::DuplicateExport {
142                kind: "standard-guest-runtime-kit",
143                symbol: profile.clone(),
144            });
145        }
146        self.kits.insert(profile.clone(), kit);
147        Ok(())
148    }
149
150    /// Look up a registered guest runtime policy kit by profile symbol.
151    pub fn kit(&self, profile: &Symbol) -> Option<&GuestRuntimeKit> {
152        self.kits.get(profile)
153    }
154
155    /// Define a callable `function` in `organ`, attributed to `defining_profile`.
156    ///
157    /// Fails if the profile does not use the organ, the value is not callable, or
158    /// the function name is already defined.
159    pub fn define_function(
160        &mut self,
161        defining_profile: &Symbol,
162        organ: Symbol,
163        function: Symbol,
164        value: Value,
165    ) -> Result<()> {
166        self.require_profile_uses_organ(defining_profile, &organ)?;
167        if value.object().as_callable().is_none() {
168            return Err(Error::TypeMismatch {
169                expected: "callable",
170                found: "non-callable",
171            });
172        }
173        if self.functions.contains_key(&function) {
174            return Err(Error::DuplicateExport {
175                kind: "standard-profile-function",
176                symbol: function,
177            });
178        }
179        self.functions.insert(
180            function.clone(),
181            ProfileFunctionBinding {
182                defining_profile: defining_profile.clone(),
183                organ,
184                function,
185                value,
186            },
187        );
188        Ok(())
189    }
190
191    /// Look up a defined function by symbol.
192    pub fn function(&self, function: &Symbol) -> Option<&ProfileFunctionBinding> {
193        self.functions.get(function)
194    }
195
196    /// Call `function` on behalf of `calling_profile`, requiring that profile to
197    /// also use the function's organ.
198    pub fn call_function(
199        &self,
200        cx: &mut Cx,
201        calling_profile: &Symbol,
202        function: &Symbol,
203        args: Vec<Value>,
204    ) -> Result<Value> {
205        let binding = self
206            .functions
207            .get(function)
208            .ok_or_else(|| Error::UnknownFunction {
209                function: function.clone(),
210            })?;
211        self.require_profile_uses_organ(calling_profile, &binding.organ)?;
212        let callable = binding
213            .value
214            .object()
215            .as_callable()
216            .ok_or(Error::TypeMismatch {
217                expected: "callable",
218                found: "non-callable",
219            })?;
220        callable.call(cx, Args::new(args))
221    }
222
223    fn require_profile_uses_organ(&self, profile: &Symbol, organ: &Symbol) -> Result<()> {
224        let profile_record =
225            self.registry
226                .profile(profile)
227                .ok_or_else(|| Error::UnknownSymbol {
228                    symbol: profile.clone(),
229                })?;
230        if profile_record
231            .organs
232            .iter()
233            .any(|used| &used.organ == organ)
234        {
235            Ok(())
236        } else {
237            Err(Error::Eval(format!(
238                "profile {profile} does not use organ {organ}"
239            )))
240        }
241    }
242}
243
244/// Wrap `body` as a callable [`ProfileFunction`] runtime value.
245pub fn profile_function_value<F>(
246    cx: &mut Cx,
247    defining_profile: Symbol,
248    organ: Symbol,
249    function: Symbol,
250    body: F,
251) -> Result<Value>
252where
253    F: Fn(&mut Cx, Args) -> Result<Value> + Send + Sync + 'static,
254{
255    cx.factory().opaque(Arc::new(ProfileFunction::new(
256        defining_profile,
257        organ,
258        function,
259        body,
260    )))
261}