Skip to main content

runmat_runtime/
user_functions.rs

1use crate::RuntimeError;
2use runmat_thread_local::runmat_thread_local;
3use runmat_types::{CallableFallbackPolicy, CallableIdentity, SourceId};
4use runmat_value::Value;
5use std::cell::RefCell;
6use std::future::Future;
7use std::pin::Pin;
8use std::rc::Rc;
9use std::sync::Arc;
10
11pub type UserFunctionFuture = Pin<Box<dyn Future<Output = Result<Value, RuntimeError>>>>;
12pub type DynamicFunctionLoadFuture =
13    Pin<Box<dyn Future<Output = Option<Result<Value, RuntimeError>>>>>;
14pub type FunctionInvoker = dyn Fn(usize, &[Value], usize) -> UserFunctionFuture;
15#[derive(Debug, Clone)]
16pub struct ExternalFunctionCall {
17    pub function: usize,
18    pub display_name: String,
19    pub arguments: Vec<Value>,
20    pub requested_outputs: usize,
21}
22pub type ExternalFunctionInvoker = dyn Fn(ExternalFunctionCall) -> UserFunctionFuture;
23pub type LexicalFunctionFuture =
24    Pin<Box<dyn Future<Output = Result<crate::call::lexical::LexicalCallResult, RuntimeError>>>>;
25pub type LexicalFunctionInvoker =
26    dyn Fn(crate::call::lexical::LexicalCall) -> LexicalFunctionFuture;
27pub type FunctionResolver = dyn Fn(&str) -> Option<usize> + Send + Sync;
28pub type DynamicFunctionLoader =
29    dyn Fn(String, Vec<Value>, usize) -> DynamicFunctionLoadFuture + Send + Sync;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct SourceFunctionInfo {
33    pub source_id: SourceId,
34    pub name: String,
35    pub function: usize,
36}
37
38#[derive(Debug, Clone)]
39pub struct CallableRequest {
40    identity: CallableIdentity,
41    fallback_policy: CallableFallbackPolicy,
42    args: Vec<Value>,
43    requested_outputs: usize,
44}
45
46impl CallableRequest {
47    pub fn semantic(function: usize, args: Vec<Value>, requested_outputs: usize) -> Self {
48        Self {
49            identity: CallableIdentity::BoundFunction(runmat_types::FunctionId(function)),
50            fallback_policy: CallableFallbackPolicy::None,
51            args,
52            requested_outputs,
53        }
54    }
55
56    pub fn resolved(
57        identity: CallableIdentity,
58        fallback_policy: CallableFallbackPolicy,
59        args: Vec<Value>,
60        requested_outputs: usize,
61    ) -> Self {
62        Self {
63            identity,
64            fallback_policy,
65            args,
66            requested_outputs,
67        }
68    }
69}
70
71runmat_thread_local! {
72    static SEMANTIC_FUNCTION_INVOKER: RefCell<Option<Rc<FunctionInvoker>>> =
73        const { RefCell::new(None) };
74    static EXTERNAL_FUNCTION_INVOKER: RefCell<Option<Rc<ExternalFunctionInvoker>>> =
75        const { RefCell::new(None) };
76    static LEXICAL_FUNCTION_INVOKER: RefCell<Option<Rc<LexicalFunctionInvoker>>> =
77        const { RefCell::new(None) };
78    static SEMANTIC_FUNCTION_RESOLVER: RefCell<Option<Arc<FunctionResolver>>> =
79        const { RefCell::new(None) };
80    static SOURCE_FUNCTION_CATALOG: RefCell<Option<Arc<Vec<SourceFunctionInfo>>>> =
81        const { RefCell::new(None) };
82    static ACTIVE_SEMANTIC_FUNCTION_STACK: RefCell<Vec<usize>> =
83        const { RefCell::new(Vec::new()) };
84}
85
86pub struct FunctionInvokerGuard {
87    previous: Option<Rc<FunctionInvoker>>,
88    state: Option<std::rc::Rc<crate::context::RuntimeContextState>>,
89}
90
91pub struct ExternalFunctionInvokerGuard {
92    previous: Option<Rc<ExternalFunctionInvoker>>,
93    state: Option<std::rc::Rc<crate::context::RuntimeContextState>>,
94}
95
96pub struct LexicalFunctionInvokerGuard {
97    previous: Option<Rc<LexicalFunctionInvoker>>,
98    state: Option<std::rc::Rc<crate::context::RuntimeContextState>>,
99}
100
101pub struct FunctionResolverGuard {
102    previous: Option<Arc<FunctionResolver>>,
103    state: Option<std::rc::Rc<crate::context::RuntimeContextState>>,
104}
105
106pub struct SourceFunctionCatalogGuard {
107    previous: Option<Arc<Vec<SourceFunctionInfo>>>,
108    state: Option<std::rc::Rc<crate::context::RuntimeContextState>>,
109}
110
111pub struct ActiveSemanticFunctionGuard {
112    state: Option<std::rc::Rc<crate::context::RuntimeContextState>>,
113}
114
115impl Drop for FunctionInvokerGuard {
116    fn drop(&mut self) {
117        let previous = self.previous.take();
118        if let Some(state) = &self.state {
119            state.call.borrow_mut().semantic_invoker = previous;
120        } else {
121            SEMANTIC_FUNCTION_INVOKER.with(|slot| {
122                *slot.borrow_mut() = previous;
123            });
124        }
125    }
126}
127
128impl Drop for ExternalFunctionInvokerGuard {
129    fn drop(&mut self) {
130        let previous = self.previous.take();
131        if let Some(state) = &self.state {
132            state.call.borrow_mut().external_invoker = previous;
133        } else {
134            EXTERNAL_FUNCTION_INVOKER.with(|slot| {
135                *slot.borrow_mut() = previous;
136            });
137        }
138    }
139}
140
141impl Drop for LexicalFunctionInvokerGuard {
142    fn drop(&mut self) {
143        let previous = self.previous.take();
144        if let Some(state) = &self.state {
145            state.call.borrow_mut().lexical_invoker = previous;
146        } else {
147            LEXICAL_FUNCTION_INVOKER.with(|slot| {
148                *slot.borrow_mut() = previous;
149            });
150        }
151    }
152}
153
154impl Drop for FunctionResolverGuard {
155    fn drop(&mut self) {
156        let previous = self.previous.take();
157        if let Some(state) = &self.state {
158            state.call.borrow_mut().semantic_resolver = previous;
159        } else {
160            SEMANTIC_FUNCTION_RESOLVER.with(|slot| {
161                *slot.borrow_mut() = previous;
162            });
163        }
164    }
165}
166
167impl Drop for SourceFunctionCatalogGuard {
168    fn drop(&mut self) {
169        let previous = self.previous.take();
170        if let Some(state) = &self.state {
171            state.call.borrow_mut().source_functions = previous;
172        } else {
173            SOURCE_FUNCTION_CATALOG.with(|slot| {
174                *slot.borrow_mut() = previous;
175            });
176        }
177    }
178}
179
180impl Drop for ActiveSemanticFunctionGuard {
181    fn drop(&mut self) {
182        if let Some(state) = &self.state {
183            state.call.borrow_mut().active_functions.pop();
184        } else {
185            ACTIVE_SEMANTIC_FUNCTION_STACK.with(|slot| {
186                slot.borrow_mut().pop();
187            });
188        }
189    }
190}
191
192pub fn install_semantic_function_invoker(
193    invoker: Option<Arc<FunctionInvoker>>,
194) -> FunctionInvokerGuard {
195    replace_semantic_function_invoker(invoker.map(|invoker| {
196        Rc::new(move |function, arguments: &[Value], requested_outputs| {
197            invoker(function, arguments, requested_outputs)
198        }) as Rc<FunctionInvoker>
199    }))
200}
201
202pub fn install_local_semantic_function_invoker(
203    invoker: Rc<FunctionInvoker>,
204) -> FunctionInvokerGuard {
205    replace_semantic_function_invoker(Some(invoker))
206}
207
208pub fn clear_semantic_function_invoker() -> FunctionInvokerGuard {
209    replace_semantic_function_invoker(None)
210}
211
212fn replace_semantic_function_invoker(invoker: Option<Rc<FunctionInvoker>>) -> FunctionInvokerGuard {
213    if let Some(state) = active_state() {
214        let previous = std::mem::replace(&mut state.call.borrow_mut().semantic_invoker, invoker);
215        return FunctionInvokerGuard {
216            previous,
217            state: Some(state),
218        };
219    }
220    let previous =
221        SEMANTIC_FUNCTION_INVOKER.with(|slot| std::mem::replace(&mut *slot.borrow_mut(), invoker));
222    FunctionInvokerGuard {
223        previous,
224        state: None,
225    }
226}
227
228pub fn install_external_function_invoker(
229    invoker: Option<Arc<ExternalFunctionInvoker>>,
230) -> ExternalFunctionInvokerGuard {
231    replace_external_function_invoker(
232        invoker.map(|invoker| Rc::new(move |call| invoker(call)) as Rc<ExternalFunctionInvoker>),
233    )
234}
235
236pub fn install_local_external_function_invoker(
237    invoker: Rc<ExternalFunctionInvoker>,
238) -> ExternalFunctionInvokerGuard {
239    replace_external_function_invoker(Some(invoker))
240}
241
242fn replace_external_function_invoker(
243    invoker: Option<Rc<ExternalFunctionInvoker>>,
244) -> ExternalFunctionInvokerGuard {
245    if let Some(state) = active_state() {
246        let previous = std::mem::replace(&mut state.call.borrow_mut().external_invoker, invoker);
247        return ExternalFunctionInvokerGuard {
248            previous,
249            state: Some(state),
250        };
251    }
252    let previous =
253        EXTERNAL_FUNCTION_INVOKER.with(|slot| std::mem::replace(&mut *slot.borrow_mut(), invoker));
254    ExternalFunctionInvokerGuard {
255        previous,
256        state: None,
257    }
258}
259
260pub fn install_lexical_function_invoker(
261    invoker: Option<Arc<LexicalFunctionInvoker>>,
262) -> LexicalFunctionInvokerGuard {
263    replace_lexical_function_invoker(
264        invoker.map(|invoker| Rc::new(move |call| invoker(call)) as Rc<LexicalFunctionInvoker>),
265    )
266}
267
268pub fn install_local_lexical_function_invoker(
269    invoker: Rc<LexicalFunctionInvoker>,
270) -> LexicalFunctionInvokerGuard {
271    replace_lexical_function_invoker(Some(invoker))
272}
273
274fn replace_lexical_function_invoker(
275    invoker: Option<Rc<LexicalFunctionInvoker>>,
276) -> LexicalFunctionInvokerGuard {
277    if let Some(state) = active_state() {
278        let previous = std::mem::replace(&mut state.call.borrow_mut().lexical_invoker, invoker);
279        return LexicalFunctionInvokerGuard {
280            previous,
281            state: Some(state),
282        };
283    }
284    let previous =
285        LEXICAL_FUNCTION_INVOKER.with(|slot| std::mem::replace(&mut *slot.borrow_mut(), invoker));
286    LexicalFunctionInvokerGuard {
287        previous,
288        state: None,
289    }
290}
291
292pub fn install_semantic_function_resolver(
293    resolver: Option<Arc<FunctionResolver>>,
294) -> FunctionResolverGuard {
295    if let Some(state) = active_state() {
296        let previous = std::mem::replace(&mut state.call.borrow_mut().semantic_resolver, resolver);
297        return FunctionResolverGuard {
298            previous,
299            state: Some(state),
300        };
301    }
302    let previous = SEMANTIC_FUNCTION_RESOLVER
303        .with(|slot| std::mem::replace(&mut *slot.borrow_mut(), resolver));
304    FunctionResolverGuard {
305        previous,
306        state: None,
307    }
308}
309
310pub fn install_source_function_catalog(
311    catalog: Option<Arc<Vec<SourceFunctionInfo>>>,
312) -> SourceFunctionCatalogGuard {
313    if let Some(state) = active_state() {
314        let previous = std::mem::replace(&mut state.call.borrow_mut().source_functions, catalog);
315        return SourceFunctionCatalogGuard {
316            previous,
317            state: Some(state),
318        };
319    }
320    let previous =
321        SOURCE_FUNCTION_CATALOG.with(|slot| std::mem::replace(&mut *slot.borrow_mut(), catalog));
322    SourceFunctionCatalogGuard {
323        previous,
324        state: None,
325    }
326}
327
328pub fn push_active_semantic_function(function: usize) -> ActiveSemanticFunctionGuard {
329    if let Some(state) = active_state() {
330        state.call.borrow_mut().active_functions.push(function);
331        return ActiveSemanticFunctionGuard { state: Some(state) };
332    }
333    ACTIVE_SEMANTIC_FUNCTION_STACK.with(|slot| {
334        slot.borrow_mut().push(function);
335    });
336    ActiveSemanticFunctionGuard { state: None }
337}
338
339pub fn current_semantic_function_invoker() -> Option<Rc<FunctionInvoker>> {
340    if let Some(state) = active_state() {
341        return state.call.borrow().semantic_invoker.clone();
342    }
343    SEMANTIC_FUNCTION_INVOKER.with(|slot| slot.borrow().clone())
344}
345
346pub fn current_external_function_invoker() -> Option<Rc<ExternalFunctionInvoker>> {
347    if let Some(state) = active_state() {
348        return state.call.borrow().external_invoker.clone();
349    }
350    EXTERNAL_FUNCTION_INVOKER.with(|slot| slot.borrow().clone())
351}
352
353pub fn current_lexical_function_invoker() -> Option<Rc<LexicalFunctionInvoker>> {
354    if let Some(state) = active_state() {
355        return state.call.borrow().lexical_invoker.clone();
356    }
357    LEXICAL_FUNCTION_INVOKER.with(|slot| slot.borrow().clone())
358}
359
360pub fn current_semantic_function_resolver() -> Option<Arc<FunctionResolver>> {
361    if let Some(state) = active_state() {
362        return state.call.borrow().semantic_resolver.clone();
363    }
364    SEMANTIC_FUNCTION_RESOLVER.with(|slot| slot.borrow().clone())
365}
366
367pub fn current_active_semantic_function() -> Option<usize> {
368    if let Some(state) = active_state() {
369        return state.call.borrow().active_functions.last().copied();
370    }
371    ACTIVE_SEMANTIC_FUNCTION_STACK.with(|slot| slot.borrow().last().copied())
372}
373
374/// Seed a newly-created standalone runtime context from the legacy
375/// thread-local call environment before entering its scope.
376///
377/// Embedders that already provide an explicit `RuntimeContext` must configure
378/// that context directly. This bridge exists for the standalone VM entrypoints
379/// that historically accepted resolver/invoker guards without a context.
380pub fn inherit_legacy_call_environment(context: &crate::context::RuntimeContext) {
381    let mut call = context.state().call.borrow_mut();
382    call.semantic_invoker = SEMANTIC_FUNCTION_INVOKER.with(|slot| slot.borrow().clone());
383    call.external_invoker = EXTERNAL_FUNCTION_INVOKER.with(|slot| slot.borrow().clone());
384    call.lexical_invoker = LEXICAL_FUNCTION_INVOKER.with(|slot| slot.borrow().clone());
385    call.semantic_resolver = SEMANTIC_FUNCTION_RESOLVER.with(|slot| slot.borrow().clone());
386    call.source_functions = SOURCE_FUNCTION_CATALOG.with(|slot| slot.borrow().clone());
387    call.active_functions = ACTIVE_SEMANTIC_FUNCTION_STACK.with(|slot| slot.borrow().clone());
388}
389
390pub fn source_functions_for(source_id: SourceId) -> Vec<SourceFunctionInfo> {
391    if let Some(state) = active_state() {
392        return source_functions_in_catalog(
393            state.call.borrow().source_functions.as_deref(),
394            source_id,
395        );
396    }
397    SOURCE_FUNCTION_CATALOG
398        .with(|slot| source_functions_in_catalog(slot.borrow().as_deref(), source_id))
399}
400
401pub async fn try_call_semantic_function(
402    function: usize,
403    args: &[Value],
404    requested_outputs: usize,
405) -> Option<Result<Value, RuntimeError>> {
406    let invoker = current_semantic_function_invoker();
407    let invoker = invoker?;
408    Some(invoker(function, args, requested_outputs).await)
409}
410
411pub async fn try_call_external_function(
412    call: ExternalFunctionCall,
413) -> Option<Result<Value, RuntimeError>> {
414    let invoker = current_external_function_invoker()?;
415    Some(invoker(call).await)
416}
417
418pub async fn try_call_lexical_function(
419    call: crate::call::lexical::LexicalCall,
420) -> Option<Result<crate::call::lexical::LexicalCallResult, RuntimeError>> {
421    let invoker = current_lexical_function_invoker()?;
422    Some(invoker(call).await)
423}
424
425pub async fn try_call_semantic_function_by_name(
426    name: &str,
427    args: &[Value],
428    requested_outputs: usize,
429) -> Option<Result<Value, RuntimeError>> {
430    let function = resolve_semantic_function_by_name(name)?;
431    try_call_semantic_function(function, args, requested_outputs).await
432}
433
434pub fn resolve_semantic_function_by_name(name: &str) -> Option<usize> {
435    let resolver = current_semantic_function_resolver()?;
436    resolver(name)
437}
438
439pub async fn try_load_and_call_dynamic_function(
440    name: String,
441    args: Vec<Value>,
442    requested_outputs: usize,
443) -> Option<Result<Value, RuntimeError>> {
444    let loader = crate::context::legacy::active()?
445        .state()
446        .call
447        .borrow()
448        .dynamic_loader
449        .clone()?;
450    loader(name, args, requested_outputs).await
451}
452
453fn source_functions_in_catalog(
454    catalog: Option<&Vec<SourceFunctionInfo>>,
455    source_id: SourceId,
456) -> Vec<SourceFunctionInfo> {
457    catalog
458        .map(|catalog| {
459            catalog
460                .iter()
461                .filter(|info| info.source_id == source_id)
462                .cloned()
463                .collect()
464        })
465        .unwrap_or_default()
466}
467
468fn active_state() -> Option<std::rc::Rc<crate::context::RuntimeContextState>> {
469    crate::context::legacy::active().map(|context| std::rc::Rc::clone(context.state()))
470}
471
472pub async fn try_call_semantic_descriptor(
473    request: CallableRequest,
474) -> Option<Result<Value, RuntimeError>> {
475    let CallableRequest {
476        identity,
477        fallback_policy,
478        args,
479        requested_outputs,
480    } = request;
481    if let CallableIdentity::BoundFunction(function) = identity {
482        return try_call_semantic_function(function.0, &args, requested_outputs).await;
483    }
484    if !fallback_policy.allows_semantic_name_resolution_for(&identity) {
485        return None;
486    }
487    let name = fallback_policy.resolution_name_for(&identity)?;
488    if matches!(identity, CallableIdentity::DynamicName(_))
489        && crate::class_registry::get_class(&name).is_some()
490    {
491        // Constructor calls for class names must flow through runtime constructor dispatch,
492        // not generic semantic name resolution.
493        return None;
494    }
495    if let Some(result) = try_call_semantic_function_by_name(&name, &args, requested_outputs).await
496    {
497        return Some(result);
498    }
499    if matches!(
500        identity,
501        CallableIdentity::DynamicName(_)
502            | CallableIdentity::Imported(_)
503            | CallableIdentity::ExternalName(_)
504    ) {
505        return try_load_and_call_dynamic_function(name, args, requested_outputs).await;
506    }
507    None
508}
509
510#[cfg(test)]
511mod lexical_tests {
512    use super::*;
513    use crate::call::lexical::{LexicalCall, LexicalCallResult, LexicalCapture};
514
515    #[test]
516    fn lexical_invoker_preserves_binding_identity_and_restores_scope() {
517        assert!(current_lexical_function_invoker().is_none());
518        let guard = install_lexical_function_invoker(Some(Arc::new(|mut call| {
519            Box::pin(async move {
520                assert_eq!(call.function, 7);
521                assert_eq!(call.arguments, vec![Value::Num(2.0)]);
522                call.captures[0].value = Value::Num(5.0);
523                Ok(LexicalCallResult {
524                    value: Value::Num(7.0),
525                    captures: call.captures,
526                })
527            })
528        })));
529        let result = futures::executor::block_on(try_call_lexical_function(LexicalCall {
530            function: 7,
531            captures: vec![LexicalCapture {
532                binding: runmat_types::BindingId(3),
533                value: Value::Num(1.0),
534            }],
535            arguments: vec![Value::Num(2.0)],
536            requested_outputs: 1,
537        }))
538        .expect("lexical invoker is installed")
539        .expect("lexical call succeeds");
540        assert_eq!(result.value, Value::Num(7.0));
541        assert_eq!(result.captures[0].value, Value::Num(5.0));
542        drop(guard);
543        assert!(current_lexical_function_invoker().is_none());
544    }
545
546    #[test]
547    fn external_invoker_preserves_identity_kind_and_restores_scope() {
548        assert!(current_external_function_invoker().is_none());
549        let guard = install_external_function_invoker(Some(Arc::new(|call| {
550            Box::pin(async move {
551                assert_eq!(call.function, 0);
552                assert_eq!(call.display_name, "published");
553                assert_eq!(call.arguments, vec![Value::Num(2.0)]);
554                assert_eq!(call.requested_outputs, 1);
555                Ok(Value::Num(12.0))
556            })
557        })));
558        let result =
559            futures::executor::block_on(try_call_external_function(ExternalFunctionCall {
560                function: 0,
561                display_name: "published".into(),
562                arguments: vec![Value::Num(2.0)],
563                requested_outputs: 1,
564            }))
565            .expect("external invoker is installed")
566            .expect("external call succeeds");
567        assert_eq!(result, Value::Num(12.0));
568        drop(guard);
569        assert!(current_external_function_invoker().is_none());
570    }
571}