Skip to main content

runmat_runtime/call/
descriptor.rs

1use crate::call::identity::strict_callable_display_name;
2use crate::runtime_error::semantic_error;
3use crate::RuntimeError;
4use runmat_types::{
5    BuiltinId, CallableFallbackPolicy, CallableIdentity, FunctionId, QualifiedName, SymbolName,
6};
7use runmat_value::{Closure, Value};
8
9/// Executor adapter used only to map stable source names to semantic functions.
10pub trait FunctionNameResolver {
11    fn resolve_function(&self, name: &str) -> Option<FunctionId>;
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum CallableCallKind {
16    Direct,
17    Feval,
18    EndExpr,
19}
20
21impl CallableCallKind {
22    fn label(self) -> &'static str {
23        match self {
24            CallableCallKind::Direct => "direct call",
25            CallableCallKind::Feval => "feval call",
26            CallableCallKind::EndExpr => "end-expression call",
27        }
28    }
29}
30
31#[derive(Debug, Clone)]
32pub struct CallableMetadata {
33    pub call_kind: CallableCallKind,
34    pub display_name: Option<String>,
35    pub source_id: Option<runmat_types::SourceId>,
36    pub span: Option<runmat_types::Span>,
37}
38
39impl Default for CallableMetadata {
40    fn default() -> Self {
41        Self {
42            call_kind: CallableCallKind::Direct,
43            display_name: None,
44            source_id: None,
45            span: None,
46        }
47    }
48}
49
50impl CallableMetadata {
51    fn feval(display_name: Option<String>) -> Self {
52        Self {
53            call_kind: CallableCallKind::Feval,
54            display_name,
55            source_id: None,
56            span: None,
57        }
58    }
59}
60
61#[derive(Debug, Clone)]
62pub enum CallableTarget {
63    Resolved {
64        identity: CallableIdentity,
65        fallback_policy: CallableFallbackPolicy,
66    },
67    FevalForward(Value),
68}
69
70#[derive(Debug, Clone)]
71pub struct CallableDescriptor {
72    pub target: CallableTarget,
73    pub args: Vec<Value>,
74    pub requested_outputs: usize,
75    pub metadata: CallableMetadata,
76}
77
78impl CallableDescriptor {
79    fn parse_handle_name(text: &str) -> Option<String> {
80        let handle = text.trim().strip_prefix('@').unwrap_or(text.trim()).trim();
81        if handle.is_empty() {
82            None
83        } else {
84            Some(handle.to_string())
85        }
86    }
87
88    fn is_at_prefixed_text(text: &str) -> bool {
89        text.trim().starts_with('@')
90    }
91
92    fn qualified_identity_from_name(name: &str) -> CallableIdentity {
93        if Self::is_well_formed_qualified_name(name) {
94            let segments = name
95                .split('.')
96                .map(|segment| SymbolName(segment.to_string()))
97                .collect::<Vec<_>>();
98            CallableIdentity::ExternalName(QualifiedName(segments))
99        } else {
100            // Preserve malformed dotted names as a single segment instead of silently normalizing.
101            CallableIdentity::ExternalName(QualifiedName(vec![SymbolName(name.to_string())]))
102        }
103    }
104
105    fn is_well_formed_qualified_name(name: &str) -> bool {
106        let segments = name.split('.').collect::<Vec<_>>();
107        segments.len() > 1 && segments.iter().all(|segment| !segment.is_empty())
108    }
109
110    fn function_inner(
111        function: usize,
112        name: Option<String>,
113        fallback_policy: CallableFallbackPolicy,
114        args: Vec<Value>,
115        requested_outputs: usize,
116        metadata: CallableMetadata,
117    ) -> Self {
118        let identity = CallableIdentity::BoundFunction(FunctionId(function));
119        Self::resolved_inner(
120            identity,
121            name,
122            fallback_policy,
123            args,
124            requested_outputs,
125            metadata,
126        )
127    }
128
129    fn resolved_inner(
130        identity: CallableIdentity,
131        display_name: Option<String>,
132        fallback_policy: CallableFallbackPolicy,
133        args: Vec<Value>,
134        requested_outputs: usize,
135        mut metadata: CallableMetadata,
136    ) -> Self {
137        metadata.display_name = metadata
138            .display_name
139            .or(display_name)
140            .or_else(|| strict_callable_display_name(&identity));
141        Self {
142            target: CallableTarget::Resolved {
143                identity,
144                fallback_policy,
145            },
146            args,
147            requested_outputs,
148            metadata,
149        }
150    }
151
152    fn feval_semantic(
153        function: usize,
154        name: String,
155        fallback_policy: CallableFallbackPolicy,
156        args: Vec<Value>,
157        requested_outputs: usize,
158    ) -> Self {
159        Self::function_inner(
160            function,
161            Some(name.clone()),
162            fallback_policy,
163            args,
164            requested_outputs,
165            CallableMetadata::feval(Some(name)),
166        )
167    }
168
169    fn feval_resolved_name(
170        identity: CallableIdentity,
171        name: String,
172        fallback_policy: CallableFallbackPolicy,
173        args: Vec<Value>,
174        requested_outputs: usize,
175    ) -> Self {
176        Self::resolved_inner(
177            identity,
178            Some(name.clone()),
179            fallback_policy,
180            args,
181            requested_outputs,
182            CallableMetadata::feval(Some(name)),
183        )
184    }
185
186    fn resolve_named_target(
187        name: &str,
188        function_resolver: &impl FunctionNameResolver,
189    ) -> (CallableIdentity, CallableFallbackPolicy) {
190        if let Some(function) = function_resolver.resolve_function(name) {
191            return (
192                CallableIdentity::BoundFunction(function),
193                CallableFallbackPolicy::None,
194            );
195        }
196        if runmat_builtins::builtin_name_is_known(name) {
197            return (
198                CallableIdentity::Builtin(BuiltinId(name.to_string())),
199                CallableFallbackPolicy::None,
200            );
201        }
202        if Self::is_well_formed_qualified_name(name) {
203            return (
204                Self::qualified_identity_from_name(name),
205                CallableFallbackPolicy::ExternalBoundary,
206            );
207        }
208        (
209            CallableIdentity::DynamicName(SymbolName(name.to_string())),
210            CallableFallbackPolicy::RuntimeNameResolution,
211        )
212    }
213
214    fn feval_forward(func_value: Value, args: Vec<Value>, requested_outputs: usize) -> Self {
215        Self {
216            target: CallableTarget::FevalForward(func_value),
217            args,
218            requested_outputs,
219            metadata: CallableMetadata::feval(None),
220        }
221    }
222
223    pub fn resolved(
224        identity: CallableIdentity,
225        args: Vec<Value>,
226        requested_outputs: usize,
227        fallback_policy: CallableFallbackPolicy,
228        call_kind: CallableCallKind,
229    ) -> Self {
230        Self::resolved_inner(
231            identity,
232            None,
233            fallback_policy,
234            args,
235            requested_outputs,
236            CallableMetadata {
237                call_kind,
238                ..CallableMetadata::default()
239            },
240        )
241    }
242
243    pub fn from_feval_value(
244        func_val: Value,
245        args: Vec<Value>,
246        requested_outputs: usize,
247        function_resolver: &impl FunctionNameResolver,
248    ) -> Self {
249        match func_val {
250            Value::String(text) => {
251                if Self::is_at_prefixed_text(&text) {
252                    return Self::feval_forward(Value::String(text), args, requested_outputs);
253                }
254                if let Some(name) = Self::parse_handle_name(&text) {
255                    let (identity, fallback_policy) =
256                        Self::resolve_named_target(&name, function_resolver);
257                    return Self::feval_resolved_name(
258                        identity,
259                        name,
260                        fallback_policy,
261                        args,
262                        requested_outputs,
263                    );
264                }
265                Self::feval_forward(Value::String(text), args, requested_outputs)
266            }
267            Value::CharArray(ca) if ca.rows == 1 => {
268                let text: String = ca.data.iter().collect();
269                if Self::is_at_prefixed_text(&text) {
270                    return Self::feval_forward(Value::CharArray(ca), args, requested_outputs);
271                }
272                if let Some(name) = Self::parse_handle_name(&text) {
273                    let (identity, fallback_policy) =
274                        Self::resolve_named_target(&name, function_resolver);
275                    return Self::feval_resolved_name(
276                        identity,
277                        name,
278                        fallback_policy,
279                        args,
280                        requested_outputs,
281                    );
282                }
283                Self::feval_forward(Value::CharArray(ca), args, requested_outputs)
284            }
285            Value::StringArray(sa) if sa.data.len() == 1 => {
286                if Self::is_at_prefixed_text(&sa.data[0]) {
287                    return Self::feval_forward(Value::StringArray(sa), args, requested_outputs);
288                }
289                if let Some(name) = Self::parse_handle_name(&sa.data[0]) {
290                    let (identity, fallback_policy) =
291                        Self::resolve_named_target(&name, function_resolver);
292                    return Self::feval_resolved_name(
293                        identity,
294                        name,
295                        fallback_policy,
296                        args,
297                        requested_outputs,
298                    );
299                }
300                Self::feval_forward(Value::StringArray(sa), args, requested_outputs)
301            }
302            Value::Closure(closure) => {
303                Self::from_closure(closure, args, requested_outputs, function_resolver)
304            }
305            Value::FunctionHandle(name) => {
306                let (identity, fallback_policy) =
307                    Self::resolve_named_target(&name, function_resolver);
308                Self::feval_resolved_name(identity, name, fallback_policy, args, requested_outputs)
309            }
310            Value::ExternalFunctionHandle(name) => {
311                let (identity, fallback_policy) =
312                    Self::resolve_named_target(&name, function_resolver);
313                Self::feval_resolved_name(identity, name, fallback_policy, args, requested_outputs)
314            }
315            Value::MethodFunctionHandle(name) => Self::feval_resolved_name(
316                CallableIdentity::Method(runmat_types::MethodId(name.clone())),
317                name,
318                CallableFallbackPolicy::RuntimeNameResolution,
319                args,
320                requested_outputs,
321            ),
322            Value::BoundFunctionHandle { name, function } => Self::feval_semantic(
323                function,
324                name,
325                CallableFallbackPolicy::None,
326                args,
327                requested_outputs,
328            ),
329            other => Self::feval_forward(other, args, requested_outputs),
330        }
331    }
332
333    fn from_closure(
334        closure: Closure,
335        args: Vec<Value>,
336        requested_outputs: usize,
337        function_resolver: &impl FunctionNameResolver,
338    ) -> Self {
339        let name = closure.function_name;
340        let mut call_args = closure.captures;
341        call_args.extend(args);
342        if let Some(function) = closure.bound_function {
343            return Self::feval_semantic(
344                function,
345                name,
346                CallableFallbackPolicy::None,
347                call_args,
348                requested_outputs,
349            );
350        }
351        if let Some(function) = function_resolver.resolve_function(&name) {
352            return Self::feval_semantic(
353                function.0,
354                name,
355                CallableFallbackPolicy::None,
356                call_args,
357                requested_outputs,
358            );
359        }
360        let (identity, fallback_policy) = Self::resolve_named_target(&name, function_resolver);
361        Self::feval_resolved_name(
362            identity,
363            name,
364            fallback_policy,
365            call_args,
366            requested_outputs,
367        )
368    }
369}
370
371fn function_unavailable_error(function: usize, metadata: &CallableMetadata) -> RuntimeError {
372    let display = metadata
373        .display_name
374        .as_deref()
375        .map(|name| format!(" '{name}'"))
376        .unwrap_or_default();
377    let location = match (metadata.source_id, metadata.span) {
378        (Some(source_id), Some(span)) => {
379            format!(
380                " at source {:?} span {}..{}",
381                source_id, span.start, span.end
382            )
383        }
384        (Some(source_id), None) => format!(" at source {:?}", source_id),
385        (None, Some(span)) => format!(" at span {}..{}", span.start, span.end),
386        (None, None) => String::new(),
387    };
388    semantic_error(
389        "UndefinedSemanticFunction",
390        format!(
391            "{}{} could not invoke semantic function {function}{location}",
392            metadata.call_kind.label(),
393            display,
394        ),
395    )
396}
397
398fn undefined_identity_error(
399    identity: &CallableIdentity,
400    metadata: &CallableMetadata,
401) -> RuntimeError {
402    let location = match (metadata.source_id, metadata.span) {
403        (Some(source_id), Some(span)) => {
404            format!(
405                " at source {:?} span {}..{}",
406                source_id, span.start, span.end
407            )
408        }
409        (Some(source_id), None) => format!(" at source {:?}", source_id),
410        (None, Some(span)) => format!(" at span {}..{}", span.start, span.end),
411        (None, None) => String::new(),
412    };
413    semantic_error(
414        "UndefinedFunction",
415        format!(
416            "Undefined function in {}: {identity:?}{location}",
417            metadata.call_kind.label()
418        ),
419    )
420}
421
422async fn call_builtin_with_requested_outputs(
423    name: &str,
424    args: &[Value],
425    requested_outputs: usize,
426) -> Result<Value, RuntimeError> {
427    crate::call_builtin_async_with_outputs(name, args, requested_outputs).await
428}
429
430async fn forward_named_fallback(
431    name: String,
432    args: Vec<Value>,
433    requested_outputs: usize,
434) -> Result<Value, RuntimeError> {
435    match crate::call_builtin_async_with_outputs(&name, &args, requested_outputs).await {
436        Ok(value) => Ok(value),
437        Err(err) if err.identifier() == Some("RunMat:UndefinedFunction") => {
438            crate::call_feval_async_with_outputs(
439                Value::FunctionHandle(name),
440                &args,
441                requested_outputs,
442            )
443            .await
444        }
445        Err(err) => Err(err),
446    }
447}
448
449async fn execute_resolved_callable(
450    identity: CallableIdentity,
451    args: Vec<Value>,
452    requested_outputs: usize,
453    metadata: CallableMetadata,
454    fallback_policy: CallableFallbackPolicy,
455) -> Result<Value, RuntimeError> {
456    match identity {
457        CallableIdentity::Builtin(id) => {
458            call_builtin_with_requested_outputs(&id.0, &args, requested_outputs).await
459        }
460        CallableIdentity::BoundFunction(function) => {
461            if let Some(result) = crate::user_functions::try_call_semantic_function(
462                function.0,
463                &args,
464                requested_outputs,
465            )
466            .await
467            {
468                return result;
469            }
470            Err(function_unavailable_error(function.0, &metadata))
471        }
472        CallableIdentity::ExternalFunction {
473            function,
474            display_name,
475        } => {
476            if let Some(result) = crate::user_functions::try_call_external_function(
477                crate::user_functions::ExternalFunctionCall {
478                    function: function.0,
479                    display_name,
480                    arguments: args.clone(),
481                    requested_outputs,
482                },
483            )
484            .await
485            {
486                return result;
487            }
488            if let Some(result) = crate::user_functions::try_call_semantic_function(
489                function.0,
490                &args,
491                requested_outputs,
492            )
493            .await
494            {
495                return result;
496            }
497            Err(function_unavailable_error(function.0, &metadata))
498        }
499        other => {
500            let request = crate::user_functions::CallableRequest::resolved(
501                other.clone(),
502                fallback_policy,
503                args.clone(),
504                requested_outputs,
505            );
506            if let Some(result) = crate::user_functions::try_call_semantic_descriptor(request).await
507            {
508                return result;
509            }
510            let Some(name) = fallback_policy.vm_fallback_name_for(&other) else {
511                return Err(undefined_identity_error(&other, &metadata));
512            };
513            forward_named_fallback(name, args, requested_outputs).await
514        }
515    }
516}
517
518async fn try_execute_resolved_callable(
519    identity: CallableIdentity,
520    args: Vec<Value>,
521    requested_outputs: usize,
522    fallback_policy: CallableFallbackPolicy,
523) -> Result<Option<Value>, RuntimeError> {
524    match identity {
525        CallableIdentity::Builtin(id) => {
526            call_builtin_with_requested_outputs(&id.0, &args, requested_outputs)
527                .await
528                .map(Some)
529        }
530        CallableIdentity::BoundFunction(function) => {
531            if let Some(result) = crate::user_functions::try_call_semantic_function(
532                function.0,
533                &args,
534                requested_outputs,
535            )
536            .await
537            {
538                return result.map(Some);
539            }
540            Ok(None)
541        }
542        CallableIdentity::ExternalFunction {
543            function,
544            display_name,
545        } => {
546            if let Some(result) = crate::user_functions::try_call_external_function(
547                crate::user_functions::ExternalFunctionCall {
548                    function: function.0,
549                    display_name,
550                    arguments: args.clone(),
551                    requested_outputs,
552                },
553            )
554            .await
555            {
556                return result.map(Some);
557            }
558            if let Some(result) = crate::user_functions::try_call_semantic_function(
559                function.0,
560                &args,
561                requested_outputs,
562            )
563            .await
564            {
565                return result.map(Some);
566            }
567            Ok(None)
568        }
569        other => {
570            let request = crate::user_functions::CallableRequest::resolved(
571                other.clone(),
572                fallback_policy,
573                args.clone(),
574                requested_outputs,
575            );
576            if let Some(result) = crate::user_functions::try_call_semantic_descriptor(request).await
577            {
578                return result.map(Some);
579            }
580            let Some(name) = fallback_policy.vm_fallback_name_for(&other) else {
581                return Ok(None);
582            };
583            match forward_named_fallback(name, args, requested_outputs).await {
584                Ok(value) => Ok(Some(value)),
585                Err(err) if err.identifier() == Some("RunMat:UndefinedFunction") => Ok(None),
586                Err(err) => Err(err),
587            }
588        }
589    }
590}
591
592pub async fn execute_callable_descriptor(
593    descriptor: CallableDescriptor,
594) -> Result<Value, RuntimeError> {
595    let CallableDescriptor {
596        target,
597        args,
598        requested_outputs,
599        metadata,
600    } = descriptor;
601    match target {
602        CallableTarget::Resolved {
603            identity,
604            fallback_policy,
605        } => {
606            execute_resolved_callable(identity, args, requested_outputs, metadata, fallback_policy)
607                .await
608        }
609        CallableTarget::FevalForward(func_value) => {
610            crate::call_feval_async_with_outputs(func_value, &args, requested_outputs).await
611        }
612    }
613}
614
615pub async fn try_execute_callable_descriptor(
616    descriptor: CallableDescriptor,
617) -> Result<Option<Value>, RuntimeError> {
618    let CallableDescriptor {
619        target,
620        args,
621        requested_outputs,
622        metadata: _,
623    } = descriptor;
624    match target {
625        CallableTarget::Resolved {
626            identity,
627            fallback_policy,
628        } => {
629            try_execute_resolved_callable(identity, args, requested_outputs, fallback_policy).await
630        }
631        CallableTarget::FevalForward(func_value) => {
632            crate::call_feval_async_with_outputs(func_value, &args, requested_outputs)
633                .await
634                .map(Some)
635        }
636    }
637}