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
9pub 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 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}