Skip to main content

runmat_vm/interpreter/dispatch/
calls.rs

1use crate::bytecode::ArgSpec;
2use crate::bytecode::Instr;
3use crate::call::builtins as call_builtins;
4use crate::call::builtins::ImportedBuiltinResolution;
5use crate::call::closures as call_closures;
6use crate::call::feval::FevalDispatch;
7use crate::call::shared::{
8    build_expanded_args_from_specs, collect_multi_outputs, expand_cell_indices,
9    lookup_user_function, prepare_user_call, subsref_brace_numeric_index_values,
10    subsref_empty_brace_cell, validate_user_function_arity, PreparedUserCall,
11};
12use crate::functions::UserFunction;
13use crate::interpreter::debug;
14use crate::interpreter::dispatch::exceptions::{redirect_exception_to_catch, ExceptionHandling};
15use crate::object::class_def as obj_class_def;
16use crate::object::resolve as obj_resolve;
17use runmat_builtins::{MException, Value};
18use runmat_runtime::RuntimeError;
19use std::future::Future;
20
21pub enum FevalHandling {
22    Completed,
23    InvokeUser {
24        name: String,
25        args: Vec<Value>,
26        functions: std::collections::HashMap<String, crate::functions::UserFunction>,
27    },
28}
29
30pub struct PreparedUserDispatch {
31    pub func: UserFunction,
32    pub var_map: std::collections::HashMap<runmat_hir::VarId, runmat_hir::VarId>,
33    pub func_program: runmat_hir::HirProgram,
34    pub func_vars: Vec<Value>,
35}
36
37pub enum BuiltinHandling {
38    Completed,
39    Caught,
40    Uncaught(Box<RuntimeError>),
41}
42
43pub enum MethodHandling {
44    Completed,
45}
46
47pub enum UserCallHandling {
48    Completed,
49    Caught,
50    Uncaught(Box<RuntimeError>),
51}
52
53pub struct ExceptionRouteContext<'a> {
54    pub try_stack: &'a mut Vec<(usize, Option<usize>)>,
55    pub vars: &'a mut Vec<Value>,
56    pub last_exception: &'a mut Option<MException>,
57    pub pc: &'a mut usize,
58}
59
60pub struct BuiltinCallContext<'a> {
61    pub stack: &'a mut Vec<Value>,
62    pub name: &'a str,
63    pub arg_count: usize,
64    pub next_instr: Option<&'a Instr>,
65    pub source_id: Option<runmat_hir::SourceId>,
66    pub call_arg_spans: Option<Vec<runmat_hir::Span>>,
67    pub imports: &'a [(Vec<String>, bool)],
68    pub call_counts: &'a [(usize, usize)],
69    pub exception: ExceptionRouteContext<'a>,
70}
71
72pub struct UserCallContext<'a> {
73    pub stack: &'a mut Vec<Value>,
74    pub name: &'a str,
75    pub out_count: usize,
76    pub bytecode_functions: &'a std::collections::HashMap<String, UserFunction>,
77    pub caller_functions: &'a mut std::collections::HashMap<String, UserFunction>,
78    pub exception: ExceptionRouteContext<'a>,
79}
80
81#[cfg(feature = "native-accel")]
82async fn accel_prepare_args(name: &str, args: &[Value]) -> Result<Vec<Value>, RuntimeError> {
83    Ok(runmat_accelerate::prepare_builtin_args(name, args)
84        .await
85        .map_err(|e| e.to_string())?)
86}
87
88#[cfg(not(feature = "native-accel"))]
89async fn accel_prepare_args(_name: &str, args: &[Value]) -> Result<Vec<Value>, RuntimeError> {
90    Ok(args.to_vec())
91}
92
93async fn call_builtin_auto(name: &str, args: &[Value]) -> Result<Value, RuntimeError> {
94    let prepared = accel_prepare_args(name, args).await?;
95    runmat_runtime::call_builtin_async(name, &prepared).await
96}
97
98fn output_hint_for_next(next_instr: Option<&Instr>) -> usize {
99    match next_instr {
100        Some(Instr::Pop) | Some(Instr::EmitStackTop { .. }) => 0,
101        _ => 1,
102    }
103}
104
105fn requested_output_count_from_next(next_instr: Option<&Instr>) -> Option<usize> {
106    match next_instr {
107        Some(Instr::Unpack(count)) => Some(*count),
108        _ => None,
109    }
110}
111
112pub fn handle_feval_dispatch(
113    dispatch: Result<FevalDispatch, RuntimeError>,
114    stack: &mut Vec<Value>,
115) -> Result<FevalHandling, RuntimeError> {
116    match dispatch? {
117        FevalDispatch::Completed(result) => {
118            stack.push(result);
119            Ok(FevalHandling::Completed)
120        }
121        FevalDispatch::InvokeUser {
122            name,
123            args,
124            functions,
125        } => Ok(FevalHandling::InvokeUser {
126            name,
127            args,
128            functions,
129        }),
130    }
131}
132
133pub fn unpack_prepared_user_call(prepared: PreparedUserCall) -> PreparedUserDispatch {
134    let PreparedUserCall {
135        func,
136        var_map,
137        func_program,
138        func_vars,
139    } = prepared;
140    PreparedUserDispatch {
141        func,
142        var_map,
143        func_program,
144        func_vars,
145    }
146}
147
148pub fn prepare_named_user_dispatch(
149    name: &str,
150    functions: &std::collections::HashMap<String, UserFunction>,
151    args: &[Value],
152    vars: &[Value],
153) -> Result<PreparedUserDispatch, RuntimeError> {
154    let func = lookup_user_function(name, functions)?;
155    validate_user_function_arity(name, &func, args.len())?;
156    let prepared = prepare_user_call(func, args, vars)?;
157    Ok(unpack_prepared_user_call(prepared))
158}
159
160pub fn push_user_call_outputs(
161    stack: &mut Vec<Value>,
162    name: &str,
163    func: &UserFunction,
164    var_map: &std::collections::HashMap<runmat_hir::VarId, runmat_hir::VarId>,
165    func_result_vars: &[Value],
166    out_count: usize,
167) -> Result<(), RuntimeError> {
168    let outputs = collect_multi_outputs(name, func, var_map, func_result_vars, out_count)?;
169    for value in outputs {
170        stack.push(value);
171    }
172    Ok(())
173}
174
175pub fn output_list_for_user_call(
176    name: &str,
177    func: &UserFunction,
178    var_map: &std::collections::HashMap<runmat_hir::VarId, runmat_hir::VarId>,
179    func_result_vars: &[Value],
180    out_count: usize,
181) -> Result<Value, RuntimeError> {
182    let outputs = collect_multi_outputs(name, func, var_map, func_result_vars, out_count)?;
183    Ok(Value::OutputList(outputs))
184}
185
186pub fn push_single_result(stack: &mut Vec<Value>, result: Value) {
187    stack.push(result);
188}
189
190pub async fn build_builtin_expand_last_args<F, Fut>(
191    stack: &mut Vec<Value>,
192    fixed_argc: usize,
193    num_indices: usize,
194    invalid_expand_msg: &'static str,
195    mut expand_object_indices: F,
196) -> Result<Vec<Value>, RuntimeError>
197where
198    F: FnMut(Value, Vec<Value>) -> Fut,
199    Fut: Future<Output = Result<Vec<Value>, RuntimeError>>,
200{
201    let mut indices = Vec::with_capacity(num_indices);
202    for _ in 0..num_indices {
203        indices.push(stack.pop().ok_or(crate::interpreter::errors::mex(
204            "StackUnderflow",
205            "stack underflow",
206        ))?);
207    }
208    indices.reverse();
209    let base = stack.pop().ok_or(crate::interpreter::errors::mex(
210        "StackUnderflow",
211        "stack underflow",
212    ))?;
213    let mut fixed = Vec::with_capacity(fixed_argc);
214    for _ in 0..fixed_argc {
215        fixed.push(stack.pop().ok_or(crate::interpreter::errors::mex(
216            "StackUnderflow",
217            "stack underflow",
218        ))?);
219    }
220    fixed.reverse();
221
222    let expanded = match (base, indices.len()) {
223        (Value::Cell(ca), 1) | (Value::Cell(ca), 2) => expand_cell_indices(&ca, &indices)?,
224        (other, _) => match other {
225            Value::Object(obj) => expand_object_indices(Value::Object(obj), indices).await?,
226            _ => {
227                return Err(crate::interpreter::errors::mex(
228                    "ExpandError",
229                    invalid_expand_msg,
230                ))
231            }
232        },
233    };
234
235    let mut args = fixed;
236    args.extend(expanded);
237    Ok(args)
238}
239
240pub async fn build_builtin_expand_at_args<F, Fut>(
241    stack: &mut Vec<Value>,
242    before_count: usize,
243    num_indices: usize,
244    after_count: usize,
245    invalid_expand_msg: &'static str,
246    mut expand_object_indices: F,
247) -> Result<Vec<Value>, RuntimeError>
248where
249    F: FnMut(Value, Vec<Value>) -> Fut,
250    Fut: Future<Output = Result<Vec<Value>, RuntimeError>>,
251{
252    let mut after = Vec::with_capacity(after_count);
253    for _ in 0..after_count {
254        after.push(stack.pop().ok_or(crate::interpreter::errors::mex(
255            "StackUnderflow",
256            "stack underflow",
257        ))?);
258    }
259    after.reverse();
260
261    let mut indices = Vec::with_capacity(num_indices);
262    for _ in 0..num_indices {
263        indices.push(stack.pop().ok_or(crate::interpreter::errors::mex(
264            "StackUnderflow",
265            "stack underflow",
266        ))?);
267    }
268    indices.reverse();
269
270    let base = stack.pop().ok_or(crate::interpreter::errors::mex(
271        "StackUnderflow",
272        "stack underflow",
273    ))?;
274
275    let mut before = Vec::with_capacity(before_count);
276    for _ in 0..before_count {
277        before.push(stack.pop().ok_or(crate::interpreter::errors::mex(
278            "StackUnderflow",
279            "stack underflow",
280        ))?);
281    }
282    before.reverse();
283
284    let expanded = match (base, indices.len()) {
285        (Value::Cell(ca), 1) | (Value::Cell(ca), 2) => expand_cell_indices(&ca, &indices)?,
286        (Value::Object(obj), _) => expand_object_indices(Value::Object(obj), indices).await?,
287        _ => {
288            return Err(crate::interpreter::errors::mex(
289                "ExpandError",
290                invalid_expand_msg,
291            ))
292        }
293    };
294
295    let mut args = before;
296    args.extend(expanded);
297    args.extend(after);
298    Ok(args)
299}
300
301pub async fn build_builtin_expand_multi_args(
302    stack: &mut Vec<Value>,
303    specs: &[ArgSpec],
304) -> Result<Vec<Value>, RuntimeError> {
305    build_expanded_args_from_specs(
306        stack,
307        specs,
308        "CallBuiltinExpandMulti requires cell or object for expand_all",
309        "CallBuiltinExpandMulti requires cell or object cell access",
310        |base| async move {
311            match base {
312                Value::Object(obj) => {
313                    let empty = subsref_empty_brace_cell()?;
314                    let args = vec![
315                        Value::Object(obj),
316                        Value::String("subsref".to_string()),
317                        Value::String("{}".to_string()),
318                        empty,
319                    ];
320                    let v = runmat_runtime::call_builtin_async("call_method", &args).await?;
321                    Ok(match v {
322                        Value::Cell(ca) => crate::call::shared::expand_all_cell(&ca),
323                        other => vec![other],
324                    })
325                }
326                _ => Err(crate::interpreter::errors::mex(
327                    "ExpandError",
328                    "CallBuiltinExpandMulti requires cell or object for expand_all",
329                )),
330            }
331        },
332        |base, indices| async move {
333            match base {
334                Value::Object(obj) => {
335                    let idx_vals = subsref_brace_numeric_index_values(&indices);
336                    let cell = runmat_runtime::call_builtin_async("__make_cell", &idx_vals).await?;
337                    let args = vec![
338                        Value::Object(obj),
339                        Value::String("subsref".to_string()),
340                        Value::String("{}".to_string()),
341                        cell,
342                    ];
343                    let v = runmat_runtime::call_builtin_async("call_method", &args).await?;
344                    Ok(vec![v])
345                }
346                _ => Err(crate::interpreter::errors::mex(
347                    "ExpandError",
348                    "CallBuiltinExpandMulti requires cell or object cell access",
349                )),
350            }
351        },
352    )
353    .await
354}
355
356pub async fn build_feval_expand_multi_args(
357    stack: &mut Vec<Value>,
358    specs: &[ArgSpec],
359) -> Result<Vec<Value>, RuntimeError> {
360    build_expanded_args_from_specs(
361        stack,
362        specs,
363        "CallFevalExpandMulti requires cell or object for expand_all",
364        "CallFevalExpandMulti requires cell or object cell access",
365        |base| async move {
366            match base {
367                Value::Object(obj) => {
368                    let empty = subsref_empty_brace_cell()?;
369                    let args = vec![
370                        Value::Object(obj),
371                        Value::String("subsref".to_string()),
372                        Value::String("{}".to_string()),
373                        empty,
374                    ];
375                    let v = runmat_runtime::call_builtin_async("call_method", &args).await?;
376                    Ok(match v {
377                        Value::Cell(ca) => crate::call::shared::expand_all_cell(&ca),
378                        other => vec![other],
379                    })
380                }
381                _ => Err(crate::interpreter::errors::mex(
382                    "InvalidExpandAllTarget",
383                    "CallFevalExpandMulti requires cell or object for expand_all",
384                )),
385            }
386        },
387        |base, indices| async move {
388            match base {
389                Value::Object(obj) => {
390                    let cell = crate::call::shared::subsref_brace_index_cell_raw(&indices)?;
391                    let args = vec![
392                        Value::Object(obj),
393                        Value::String("subsref".to_string()),
394                        Value::String("{}".to_string()),
395                        cell,
396                    ];
397                    let v = runmat_runtime::call_builtin_async("call_method", &args).await?;
398                    Ok(vec![v])
399                }
400                _ => Err(crate::interpreter::errors::mex(
401                    "ExpandError",
402                    "CallFevalExpandMulti requires cell or object cell access",
403                )),
404            }
405        },
406    )
407    .await
408}
409
410pub async fn build_user_function_expand_multi_args(
411    stack: &mut Vec<Value>,
412    specs: &[ArgSpec],
413) -> Result<Vec<Value>, RuntimeError> {
414    build_expanded_args_from_specs(
415        stack,
416        specs,
417        "CallFunctionExpandMulti requires cell or object for expand_all",
418        "CallFunctionExpandMulti requires cell or object cell access",
419        |base| async move {
420            match base {
421                Value::Cell(ca) => Ok(crate::call::shared::expand_all_cell(&ca)),
422                Value::Object(obj) => {
423                    let empty = subsref_empty_brace_cell()?;
424                    let args = vec![
425                        Value::Object(obj),
426                        Value::String("subsref".to_string()),
427                        Value::String("{}".to_string()),
428                        empty,
429                    ];
430                    let v = runmat_runtime::call_builtin_async("call_method", &args).await?;
431                    Ok(match v {
432                        Value::Cell(ca) => crate::call::shared::expand_all_cell(&ca),
433                        other => vec![other],
434                    })
435                }
436                _ => Err(crate::interpreter::errors::mex(
437                    "InvalidExpandAllTarget",
438                    "CallFunctionExpandMulti requires cell or object for expand_all",
439                )),
440            }
441        },
442        |base, indices| async move {
443            match (base, indices.len()) {
444                (Value::Cell(ca), 1) | (Value::Cell(ca), 2) => expand_cell_indices(&ca, &indices),
445                (Value::Object(obj), _) => {
446                    let cell = crate::call::shared::subsref_brace_index_cell_raw(&indices)?;
447                    let args = vec![
448                        Value::Object(obj),
449                        Value::String("subsref".to_string()),
450                        Value::String("{}".to_string()),
451                        cell,
452                    ];
453                    let v = runmat_runtime::call_builtin_async("call_method", &args).await?;
454                    Ok(vec![v])
455                }
456                _ => Err(crate::interpreter::errors::mex(
457                    "ExpandError",
458                    "CallFunctionExpandMulti requires cell or object cell access",
459                )),
460            }
461        },
462    )
463    .await
464}
465
466pub fn handle_builtin_outcome(
467    result: Result<Value, RuntimeError>,
468    imported: ImportedBuiltinResolution,
469    stack: &mut Vec<Value>,
470    ctx: ExceptionRouteContext<'_>,
471    refresh_vars: impl Fn(&[Value]),
472) -> Result<BuiltinHandling, RuntimeError> {
473    let ExceptionRouteContext {
474        try_stack,
475        vars,
476        last_exception,
477        pc,
478    } = ctx;
479    match result {
480        Ok(result) => {
481            stack.push(result);
482            Ok(BuiltinHandling::Completed)
483        }
484        Err(err) => match imported {
485            ImportedBuiltinResolution::Resolved(value) => {
486                stack.push(value);
487                Ok(BuiltinHandling::Completed)
488            }
489            ImportedBuiltinResolution::Ambiguous(message) => Err(message.into()),
490            ImportedBuiltinResolution::NotFound => Ok(
491                match redirect_exception_to_catch(
492                    err,
493                    try_stack,
494                    vars,
495                    last_exception,
496                    pc,
497                    refresh_vars,
498                ) {
499                    ExceptionHandling::Caught => BuiltinHandling::Caught,
500                    ExceptionHandling::Uncaught(err) => BuiltinHandling::Uncaught(err),
501                },
502            ),
503        },
504    }
505}
506
507pub async fn handle_builtin_call(
508    ctx: BuiltinCallContext<'_>,
509    refresh_vars: impl Fn(&[Value]),
510) -> Result<BuiltinHandling, RuntimeError> {
511    let BuiltinCallContext {
512        stack,
513        name,
514        arg_count,
515        next_instr,
516        source_id,
517        call_arg_spans,
518        imports,
519        call_counts,
520        exception,
521    } = ctx;
522    let ExceptionRouteContext {
523        try_stack: _,
524        vars: _,
525        last_exception,
526        pc,
527    } = &exception;
528    debug::trace_call_builtin(**pc, name, arg_count, stack);
529    if let Some(value) = call_builtins::special_counter_builtin(name, arg_count, call_counts)? {
530        stack.push(value);
531        return Ok(BuiltinHandling::Completed);
532    }
533    let requested_outputs = requested_output_count_from_next(next_instr);
534    let args = call_builtins::collect_call_args(stack, arg_count)?;
535
536    let _callsite_guard = runmat_runtime::callsite::push_callsite(source_id, call_arg_spans);
537    let output_hint = output_hint_for_next(next_instr);
538    let _output_guard = runmat_runtime::output_context::push_output_count(output_hint);
539
540    let prepared_primary = call_builtins::prepare_builtin_args(name, &args).await?;
541    let result = match requested_outputs {
542        Some(count) => {
543            runmat_runtime::call_builtin_async_with_outputs(name, &prepared_primary, count).await
544        }
545        None => runmat_runtime::call_builtin_async(name, &prepared_primary).await,
546    };
547    let imported = call_builtins::resolve_imported_builtin(
548        name,
549        imports,
550        &prepared_primary,
551        requested_outputs,
552    )
553    .await?;
554    if result.is_err() {
555        if let Some(err) = call_builtins::rethrow_without_explicit_exception(
556            name,
557            &args,
558            last_exception.as_ref().map(|e| e.identifier.as_str()),
559            last_exception.as_ref().map(|e| e.message.as_str()),
560        ) {
561            return Err(err);
562        }
563    }
564    handle_builtin_outcome(result, imported, stack, exception, refresh_vars)
565}
566
567pub async fn handle_method_call(
568    stack: &mut Vec<Value>,
569    name: &str,
570    arg_count: usize,
571) -> Result<MethodHandling, RuntimeError> {
572    let (base, args) = call_closures::collect_method_args(stack, arg_count)?;
573    let value = call_closures::call_method(base, name, args).await?;
574    stack.push(value);
575    Ok(MethodHandling::Completed)
576}
577
578pub async fn handle_prepared_user_function_call<BF, BFFut, IF, IFFut>(
579    ctx: UserCallContext<'_>,
580    args: Vec<Value>,
581    refresh_vars: impl Fn(&[Value]),
582    builtin_fallback: BF,
583    interpret_counts: IF,
584) -> Result<UserCallHandling, RuntimeError>
585where
586    BF: FnOnce(String, Vec<Value>, usize) -> BFFut,
587    BFFut: Future<Output = Result<Option<Value>, RuntimeError>>,
588    IF: FnOnce(crate::bytecode::Bytecode, Vec<Value>, String, usize, usize) -> IFFut,
589    IFFut: Future<Output = Result<Vec<Value>, RuntimeError>>,
590{
591    let UserCallContext {
592        stack,
593        name,
594        out_count,
595        bytecode_functions,
596        caller_functions,
597        exception,
598    } = ctx;
599    let ExceptionRouteContext {
600        try_stack,
601        vars,
602        last_exception,
603        pc,
604    } = exception;
605    let arg_count = args.len();
606    if let Some(result) = builtin_fallback(name.to_string(), args.clone(), out_count).await? {
607        stack.push(result);
608        return Ok(UserCallHandling::Completed);
609    }
610
611    let prepared = prepare_named_user_dispatch(name, bytecode_functions, &args, vars)?;
612    let PreparedUserDispatch {
613        func,
614        var_map,
615        func_program,
616        func_vars,
617    } = prepared;
618    let mut func_bytecode = crate::compile(&func_program, bytecode_functions)?;
619    func_bytecode.source_id = func.source_id;
620    for (k, v) in func_bytecode.functions.iter() {
621        caller_functions.insert(k.clone(), v.clone());
622    }
623
624    let func_result_vars = match interpret_counts(
625        func_bytecode,
626        func_vars,
627        name.to_string(),
628        out_count,
629        arg_count,
630    )
631    .await
632    {
633        Ok(v) => v,
634        Err(e) => {
635            return Ok(
636                match redirect_exception_to_catch(
637                    e,
638                    try_stack,
639                    vars,
640                    last_exception,
641                    pc,
642                    refresh_vars,
643                ) {
644                    ExceptionHandling::Caught => UserCallHandling::Caught,
645                    ExceptionHandling::Uncaught(err) => UserCallHandling::Uncaught(err),
646                },
647            )
648        }
649    };
650
651    if out_count == 1 {
652        push_user_call_outputs(stack, name, &func, &var_map, &func_result_vars, 1)?;
653    } else {
654        let output_list =
655            output_list_for_user_call(name, &func, &var_map, &func_result_vars, out_count)?;
656        push_single_result(stack, output_list);
657    }
658    Ok(UserCallHandling::Completed)
659}
660
661pub async fn handle_user_function_call<BF, BFFut, IF, IFFut>(
662    ctx: UserCallContext<'_>,
663    arg_count: usize,
664    refresh_vars: impl Fn(&[Value]),
665    builtin_fallback: BF,
666    interpret_counts: IF,
667) -> Result<UserCallHandling, RuntimeError>
668where
669    BF: FnOnce(String, Vec<Value>, usize) -> BFFut,
670    BFFut: Future<Output = Result<Option<Value>, RuntimeError>>,
671    IF: FnOnce(crate::bytecode::Bytecode, Vec<Value>, String, usize, usize) -> IFFut,
672    IFFut: Future<Output = Result<Vec<Value>, RuntimeError>>,
673{
674    let args = crate::call::builtins::collect_call_args(ctx.stack, arg_count)?;
675    handle_prepared_user_function_call(ctx, args, refresh_vars, builtin_fallback, interpret_counts)
676        .await
677}
678
679pub async fn handle_builtin_expand_last_call<F, Fut>(
680    stack: &mut Vec<Value>,
681    name: &str,
682    fixed_argc: usize,
683    num_indices: usize,
684    next_instr: Option<&Instr>,
685    expand_object_indices: F,
686) -> Result<BuiltinHandling, RuntimeError>
687where
688    F: FnMut(Value, Vec<Value>) -> Fut,
689    Fut: Future<Output = Result<Vec<Value>, RuntimeError>>,
690{
691    let args = build_builtin_expand_last_args(
692        stack,
693        fixed_argc,
694        num_indices,
695        "CallBuiltinExpandLast requires cell or object cell access",
696        expand_object_indices,
697    )
698    .await?;
699    let output_hint = output_hint_for_next(next_instr);
700    let _output_guard = runmat_runtime::output_context::push_output_count(output_hint);
701    push_single_result(stack, call_builtin_auto(name, &args).await?);
702    Ok(BuiltinHandling::Completed)
703}
704
705pub async fn handle_builtin_expand_at_call<F, Fut>(
706    stack: &mut Vec<Value>,
707    name: &str,
708    before_count: usize,
709    num_indices: usize,
710    after_count: usize,
711    next_instr: Option<&Instr>,
712    expand_object_indices: F,
713) -> Result<BuiltinHandling, RuntimeError>
714where
715    F: FnMut(Value, Vec<Value>) -> Fut,
716    Fut: Future<Output = Result<Vec<Value>, RuntimeError>>,
717{
718    let args = build_builtin_expand_at_args(
719        stack,
720        before_count,
721        num_indices,
722        after_count,
723        "CallBuiltinExpandAt requires cell or object cell access",
724        expand_object_indices,
725    )
726    .await?;
727    let output_hint = output_hint_for_next(next_instr);
728    let _output_guard = runmat_runtime::output_context::push_output_count(output_hint);
729    push_single_result(stack, call_builtin_auto(name, &args).await?);
730    Ok(BuiltinHandling::Completed)
731}
732
733pub async fn handle_builtin_expand_multi_call(
734    stack: &mut Vec<Value>,
735    name: &str,
736    specs: &[ArgSpec],
737    next_instr: Option<&Instr>,
738) -> Result<BuiltinHandling, RuntimeError> {
739    let args = build_builtin_expand_multi_args(stack, specs).await?;
740    let output_hint = output_hint_for_next(next_instr);
741    let _output_guard = runmat_runtime::output_context::push_output_count(output_hint);
742    push_single_result(stack, call_builtin_auto(name, &args).await?);
743    Ok(BuiltinHandling::Completed)
744}
745
746pub async fn handle_method_or_member_index_call(
747    stack: &mut Vec<Value>,
748    name: String,
749    arg_count: usize,
750) -> Result<MethodHandling, RuntimeError> {
751    let (base, args) = call_closures::collect_method_args(stack, arg_count)?;
752    let value = call_closures::call_method_or_member_index(base, name, args).await?;
753    stack.push(value);
754    Ok(MethodHandling::Completed)
755}
756
757pub async fn handle_static_method_call(
758    stack: &mut Vec<Value>,
759    class_name: &str,
760    method: &str,
761    arg_count: usize,
762) -> Result<MethodHandling, RuntimeError> {
763    let mut args = crate::call::builtins::collect_call_args(stack, arg_count)?;
764    match call_closures::call_static_method(class_name, method, args.clone()).await {
765        Ok(v) => {
766            stack.push(v);
767            Ok(MethodHandling::Completed)
768        }
769        Err(_) => {
770            let is_type_class = matches!(
771                class_name,
772                "gpuArray"
773                    | "logical"
774                    | "double"
775                    | "single"
776                    | "int8"
777                    | "int16"
778                    | "int32"
779                    | "int64"
780                    | "uint8"
781                    | "uint16"
782                    | "uint32"
783                    | "uint64"
784                    | "char"
785                    | "string"
786                    | "cell"
787                    | "struct"
788            );
789            if is_type_class {
790                args.push(Value::from(class_name));
791                let v = runmat_runtime::call_builtin_async(method, &args).await?;
792                stack.push(v);
793                Ok(MethodHandling::Completed)
794            } else {
795                Err(format!("Unknown static method '{}' on class {}", method, class_name).into())
796            }
797        }
798    }
799}
800
801pub fn handle_load_method(
802    stack: &mut Vec<Value>,
803    name: String,
804) -> Result<MethodHandling, RuntimeError> {
805    let base = crate::interpreter::stack::pop_value(stack)?;
806    let value = call_closures::load_method_closure(base, name)?;
807    stack.push(value);
808    Ok(MethodHandling::Completed)
809}
810
811pub fn handle_create_closure(
812    stack: &mut Vec<Value>,
813    func_name: String,
814    capture_count: usize,
815) -> Result<MethodHandling, RuntimeError> {
816    call_closures::create_closure(stack, func_name, capture_count)?;
817    Ok(MethodHandling::Completed)
818}
819
820pub fn handle_load_static_property(
821    stack: &mut Vec<Value>,
822    class_name: &str,
823    prop: &str,
824) -> Result<MethodHandling, RuntimeError> {
825    let value = obj_resolve::load_static_member(class_name, prop)?;
826    stack.push(value);
827    Ok(MethodHandling::Completed)
828}
829
830pub fn handle_register_class(
831    name: String,
832    super_class: Option<String>,
833    properties: Vec<(String, bool, String, String)>,
834    methods: Vec<(String, String, bool, String)>,
835) -> Result<MethodHandling, RuntimeError> {
836    obj_class_def::register_class(name, super_class, properties, methods)?;
837    Ok(MethodHandling::Completed)
838}