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::{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}
108
109impl SharedOrganRuntime {
110    /// Create an empty runtime.
111    pub fn new() -> Self {
112        Self::default()
113    }
114
115    /// Register a profile so its organs and functions become available.
116    pub fn register_profile(&mut self, profile: LanguageProfile) -> Result<()> {
117        self.registry.register_profile(profile)
118    }
119
120    /// Look up a registered profile by symbol.
121    pub fn profile(&self, symbol: &Symbol) -> Option<&LanguageProfile> {
122        self.registry.profile(symbol)
123    }
124
125    /// Iterate the registered profiles.
126    pub fn profiles(&self) -> impl Iterator<Item = &LanguageProfile> {
127        self.registry.profiles()
128    }
129
130    /// Define a callable `function` in `organ`, attributed to `defining_profile`.
131    ///
132    /// Fails if the profile does not use the organ, the value is not callable, or
133    /// the function name is already defined.
134    pub fn define_function(
135        &mut self,
136        defining_profile: &Symbol,
137        organ: Symbol,
138        function: Symbol,
139        value: Value,
140    ) -> Result<()> {
141        self.require_profile_uses_organ(defining_profile, &organ)?;
142        if value.object().as_callable().is_none() {
143            return Err(Error::TypeMismatch {
144                expected: "callable",
145                found: "non-callable",
146            });
147        }
148        if self.functions.contains_key(&function) {
149            return Err(Error::DuplicateExport {
150                kind: "standard-profile-function",
151                symbol: function,
152            });
153        }
154        self.functions.insert(
155            function.clone(),
156            ProfileFunctionBinding {
157                defining_profile: defining_profile.clone(),
158                organ,
159                function,
160                value,
161            },
162        );
163        Ok(())
164    }
165
166    /// Look up a defined function by symbol.
167    pub fn function(&self, function: &Symbol) -> Option<&ProfileFunctionBinding> {
168        self.functions.get(function)
169    }
170
171    /// Call `function` on behalf of `calling_profile`, requiring that profile to
172    /// also use the function's organ.
173    pub fn call_function(
174        &self,
175        cx: &mut Cx,
176        calling_profile: &Symbol,
177        function: &Symbol,
178        args: Vec<Value>,
179    ) -> Result<Value> {
180        let binding = self
181            .functions
182            .get(function)
183            .ok_or_else(|| Error::UnknownFunction {
184                function: function.clone(),
185            })?;
186        self.require_profile_uses_organ(calling_profile, &binding.organ)?;
187        let callable = binding
188            .value
189            .object()
190            .as_callable()
191            .ok_or(Error::TypeMismatch {
192                expected: "callable",
193                found: "non-callable",
194            })?;
195        callable.call(cx, Args::new(args))
196    }
197
198    fn require_profile_uses_organ(&self, profile: &Symbol, organ: &Symbol) -> Result<()> {
199        let profile_record =
200            self.registry
201                .profile(profile)
202                .ok_or_else(|| Error::UnknownSymbol {
203                    symbol: profile.clone(),
204                })?;
205        if profile_record
206            .organs
207            .iter()
208            .any(|used| &used.organ == organ)
209        {
210            Ok(())
211        } else {
212            Err(Error::Eval(format!(
213                "profile {profile} does not use organ {organ}"
214            )))
215        }
216    }
217}
218
219/// Wrap `body` as a callable [`ProfileFunction`] runtime value.
220pub fn profile_function_value<F>(
221    cx: &mut Cx,
222    defining_profile: Symbol,
223    organ: Symbol,
224    function: Symbol,
225    body: F,
226) -> Result<Value>
227where
228    F: Fn(&mut Cx, Args) -> Result<Value> + Send + Sync + 'static,
229{
230    cx.factory().opaque(Arc::new(ProfileFunction::new(
231        defining_profile,
232        organ,
233        function,
234        body,
235    )))
236}