Skip to main content

runmat_runtime/
lib.rs

1#![allow(
2    clippy::await_holding_lock,
3    clippy::enum_variant_names,
4    clippy::get_first,
5    clippy::io_other_error,
6    clippy::needless_range_loop,
7    clippy::redundant_closure,
8    clippy::result_large_err,
9    clippy::too_many_arguments,
10    clippy::useless_conversion
11)]
12
13use crate::builtins::common::format::format_variadic;
14use runmat_builtins::Value;
15#[cfg(not(target_arch = "wasm32"))]
16use runmat_gc_api::GcPtr;
17
18pub mod dispatcher;
19
20pub mod callsite;
21pub mod console;
22pub mod data;
23pub mod interaction;
24pub mod interrupt;
25pub mod output_context;
26pub mod output_count;
27pub mod source_context;
28
29pub mod arrays;
30pub mod builtins;
31pub mod comparison;
32pub mod concatenation;
33pub mod elementwise;
34pub mod indexing;
35pub mod matrix;
36pub mod plotting_hooks;
37pub mod replay;
38pub mod runtime_error;
39pub mod user_functions;
40pub mod warning_store;
41pub mod workspace;
42
43/// Standard result type for runtime builtins.
44pub type BuiltinResult<T> = Result<T, RuntimeError>;
45
46pub use runtime_error::{
47    build_runtime_error, replay_error, replay_error_with_source, CallFrame, ErrorContext,
48    ReplayErrorKind, RuntimeError, RuntimeErrorBuilder,
49};
50
51#[cfg(feature = "blas-lapack")]
52pub mod blas;
53#[cfg(feature = "blas-lapack")]
54pub mod lapack;
55
56// Link to Apple's Accelerate framework on macOS
57#[cfg(all(feature = "blas-lapack", target_os = "macos"))]
58#[link(name = "Accelerate", kind = "framework")]
59extern "C" {}
60
61// Ensure OpenBLAS is linked on non-macOS platforms when BLAS/LAPACK is enabled
62#[cfg(all(feature = "blas-lapack", not(target_os = "macos")))]
63extern crate openblas_src;
64
65pub use dispatcher::{
66    call_builtin, call_builtin_async, call_builtin_async_with_outputs, gather_if_needed,
67    gather_if_needed_async, is_gpu_value, value_contains_gpu,
68};
69
70#[cfg(feature = "plot-core")]
71pub use builtins::plotting::{
72    export_figure_scene as runtime_plot_export_figure_scene,
73    import_figure_scene_async as runtime_plot_import_figure_scene_async,
74    import_figure_scene_from_path_async as runtime_plot_import_figure_scene_from_path_async,
75};
76pub use replay::{
77    runtime_export_workspace_state, runtime_import_workspace_state, WorkspaceReplayMode,
78};
79
80pub use runmat_macros::{register_fusion_spec, register_gpu_spec};
81
82// Pruned legacy re-exports; prefer builtins::* and explicit shims only
83// Transitional root-level shims for widely used helpers
84pub use arrays::create_range;
85pub use concatenation::create_matrix_from_values;
86pub use elementwise::{elementwise_div, elementwise_mul, elementwise_neg, elementwise_pow, power};
87pub use indexing::perform_indexing;
88// Explicitly re-export for external users (ignition VM) that build matrices from values
89// (kept above)
90// Note: constants and mathematics modules only contain #[runtime_builtin] functions
91// and don't export public items, so they don't need to be re-exported
92
93#[cfg(feature = "blas-lapack")]
94pub use blas::*;
95#[cfg(feature = "blas-lapack")]
96pub use lapack::*;
97
98pub fn make_cell_with_shape(values: Vec<Value>, shape: Vec<usize>) -> Result<Value, String> {
99    #[cfg(target_arch = "wasm32")]
100    {
101        let ca = runmat_builtins::CellArray::new_with_shape(values, shape)
102            .map_err(|e| format!("Cell creation error: {e}"))?;
103        return Ok(Value::Cell(ca));
104    }
105
106    #[cfg(not(target_arch = "wasm32"))]
107    {
108        let handles: Vec<GcPtr<Value>> = values
109            .into_iter()
110            .map(|v| runmat_gc::gc_allocate(v).map_err(|e| format!("Cell creation error: {e}")))
111            .collect::<Result<_, _>>()?;
112        let ca = runmat_builtins::CellArray::new_handles_with_shape(handles, shape)
113            .map_err(|e| format!("Cell creation error: {e}"))?;
114        Ok(Value::Cell(ca))
115    }
116}
117
118pub(crate) fn make_cell(values: Vec<Value>, rows: usize, cols: usize) -> Result<Value, String> {
119    make_cell_with_shape(values, vec![rows, cols])
120}
121
122// Internal builtin to construct a cell from a vector of values (used by ignition)
123#[runmat_macros::runtime_builtin(name = "__make_cell", builtin_path = "crate")]
124async fn make_cell_builtin(rest: Vec<Value>) -> crate::BuiltinResult<Value> {
125    let rows = 1usize;
126    let cols = rest.len();
127    make_cell(rest, rows, cols).map_err(Into::into)
128}
129
130fn to_string_scalar(v: &Value) -> Result<String, String> {
131    let s: String = v.try_into()?;
132    Ok(s)
133}
134
135fn to_string_array(v: &Value) -> Result<runmat_builtins::StringArray, String> {
136    match v {
137        Value::String(s) => runmat_builtins::StringArray::new(vec![s.clone()], vec![1, 1])
138            .map_err(|e| e.to_string()),
139        Value::StringArray(sa) => Ok(sa.clone()),
140        Value::CharArray(ca) => {
141            // Convert each row to a string; treat as column vector
142            let mut out: Vec<String> = Vec::with_capacity(ca.rows);
143            for r in 0..ca.rows {
144                let mut s = String::with_capacity(ca.cols);
145                for c in 0..ca.cols {
146                    s.push(ca.data[r * ca.cols + c]);
147                }
148                out.push(s);
149            }
150            runmat_builtins::StringArray::new(out, vec![ca.rows, 1]).map_err(|e| e.to_string())
151        }
152        other => Err(format!("cannot convert to string array: {other:?}")),
153    }
154}
155
156#[runmat_macros::runtime_builtin(name = "strtrim", builtin_path = "crate")]
157async fn strtrim_builtin(a: Value) -> crate::BuiltinResult<Value> {
158    let s = to_string_scalar(&a)?;
159    Ok(Value::String(s.trim().to_string()))
160}
161
162// Adjust strjoin semantics: join rows (row-wise)
163#[runmat_macros::runtime_builtin(name = "strjoin", builtin_path = "crate")]
164async fn strjoin_rowwise(a: Value, delim: Value) -> crate::BuiltinResult<Value> {
165    let d = to_string_scalar(&delim)?;
166    let sa = to_string_array(&a)?;
167    let rows = *sa.shape.first().unwrap_or(&sa.data.len());
168    let cols = *sa.shape.get(1).unwrap_or(&1);
169    if rows == 0 || cols == 0 {
170        return Ok(Value::StringArray(
171            runmat_builtins::StringArray::new(Vec::new(), vec![0, 0]).unwrap(),
172        ));
173    }
174    let mut out: Vec<String> = Vec::with_capacity(rows);
175    for r in 0..rows {
176        let mut s = String::new();
177        for c in 0..cols {
178            if c > 0 {
179                s.push_str(&d);
180            }
181            s.push_str(&sa.data[r + c * rows]);
182        }
183        out.push(s);
184    }
185    Ok(Value::StringArray(
186        runmat_builtins::StringArray::new(out, vec![rows, 1])
187            .map_err(|e| format!("strjoin: {e}"))?,
188    ))
189}
190
191// deal: distribute inputs to multiple outputs (via cell for expansion)
192#[runmat_macros::runtime_builtin(name = "deal", builtin_path = "crate")]
193async fn deal_builtin(rest: Vec<Value>) -> crate::BuiltinResult<Value> {
194    if let Some(out_count) = crate::output_count::current_output_count() {
195        return Ok(crate::output_count::output_list_with_padding(
196            out_count, rest,
197        ));
198    }
199    // Return cell row vector of inputs for expansion
200    let cols = rest.len();
201    make_cell(rest, 1, cols).map_err(Into::into)
202}
203
204// Object/handle utilities used by interpreter lowering for OOP/func handles
205
206#[runmat_macros::runtime_builtin(name = "rethrow", builtin_path = "crate")]
207async fn rethrow_builtin(e: Value) -> crate::BuiltinResult<Value> {
208    match e {
209        Value::MException(me) => Err(build_runtime_error(me.message)
210            .with_identifier(me.identifier)
211            .build()),
212        Value::String(s) => Err(build_runtime_error(s).build()),
213        other => Err(build_runtime_error(format!("RunMat:error: {other:?}")).build()),
214    }
215}
216
217#[runmat_macros::runtime_builtin(name = "call_method", builtin_path = "crate")]
218async fn call_method_builtin(
219    base: Value,
220    method: String,
221    rest: Vec<Value>,
222) -> crate::BuiltinResult<Value> {
223    match base {
224        Value::Object(obj) => {
225            // Simple dynamic dispatch via builtin registry: method name may be qualified as Class.method
226            let qualified = format!("{}.{}", obj.class_name, method);
227            // Prepend receiver as first arg so methods can accept it
228            let mut args = Vec::with_capacity(1 + rest.len());
229            args.push(Value::Object(obj.clone()));
230            args.extend(rest);
231            if let Ok(v) = crate::call_builtin_async(&qualified, &args).await {
232                return Ok(v);
233            }
234            // Fallback to global method name
235            Ok(crate::call_builtin_async(&method, &args).await?)
236        }
237        Value::HandleObject(h) => {
238            // Methods on handle classes dispatch to the underlying target's class namespace
239            let target = unsafe { &*h.target.as_raw() };
240            let class_name = match target {
241                Value::Object(o) => o.class_name.clone(),
242                Value::Struct(_) => h.class_name.clone(),
243                _ => h.class_name.clone(),
244            };
245            let qualified = format!("{class_name}.{method}");
246            let mut args = Vec::with_capacity(1 + rest.len());
247            args.push(Value::HandleObject(h.clone()));
248            args.extend(rest);
249            if let Ok(v) = crate::call_builtin_async(&qualified, &args).await {
250                return Ok(v);
251            }
252            Ok(crate::call_builtin_async(&method, &args).await?)
253        }
254        other => {
255            Err((format!("call_method unsupported on {other:?} for method '{method}'")).into())
256        }
257    }
258}
259
260// Global dispatch helpers for overloaded indexing (subsref/subsasgn) to support fallback resolution paths
261#[runmat_macros::runtime_builtin(name = "subsasgn", builtin_path = "crate")]
262async fn subsasgn_dispatch(
263    obj: Value,
264    kind: String,
265    payload: Value,
266    rhs: Value,
267) -> crate::BuiltinResult<Value> {
268    match &obj {
269        Value::Object(o) => {
270            let qualified = format!("{}.subsasgn", o.class_name);
271            Ok(
272                crate::call_builtin_async(&qualified, &[obj, Value::String(kind), payload, rhs])
273                    .await?,
274            )
275        }
276        Value::HandleObject(h) => {
277            let target = unsafe { &*h.target.as_raw() };
278            let class_name = match target {
279                Value::Object(o) => o.class_name.clone(),
280                _ => h.class_name.clone(),
281            };
282            let qualified = format!("{class_name}.subsasgn");
283            Ok(
284                crate::call_builtin_async(&qualified, &[obj, Value::String(kind), payload, rhs])
285                    .await?,
286            )
287        }
288        other => Err((format!("subsasgn: receiver must be object, got {other:?}")).into()),
289    }
290}
291
292#[runmat_macros::runtime_builtin(name = "subsref", builtin_path = "crate")]
293async fn subsref_dispatch(obj: Value, kind: String, payload: Value) -> crate::BuiltinResult<Value> {
294    match &obj {
295        Value::Object(o) => {
296            let qualified = format!("{}.subsref", o.class_name);
297            Ok(crate::call_builtin_async(&qualified, &[obj, Value::String(kind), payload]).await?)
298        }
299        Value::HandleObject(h) => {
300            let target = unsafe { &*h.target.as_raw() };
301            let class_name = match target {
302                Value::Object(o) => o.class_name.clone(),
303                _ => h.class_name.clone(),
304            };
305            let qualified = format!("{class_name}.subsref");
306            Ok(crate::call_builtin_async(&qualified, &[obj, Value::String(kind), payload]).await?)
307        }
308        other => Err((format!("subsref: receiver must be object, got {other:?}")).into()),
309    }
310}
311
312// -------- Handle classes & events --------
313
314#[runmat_macros::runtime_builtin(name = "new_handle_object", builtin_path = "crate")]
315async fn new_handle_object_builtin(class_name: String) -> crate::BuiltinResult<Value> {
316    // Create an underlying object instance and wrap it in a handle
317    let obj = new_object_builtin(class_name.clone()).await?;
318    let gc = runmat_gc::gc_allocate(obj).map_err(|e| format!("gc: {e}"))?;
319    Ok(Value::HandleObject(runmat_builtins::HandleRef {
320        class_name,
321        target: gc,
322        valid: true,
323    }))
324}
325
326#[runmat_macros::runtime_builtin(name = "isvalid", builtin_path = "crate")]
327async fn isvalid_builtin(v: Value) -> crate::BuiltinResult<Value> {
328    match v {
329        Value::HandleObject(h) => Ok(Value::Bool(h.valid)),
330        Value::Listener(l) => Ok(Value::Bool(l.valid && l.enabled)),
331        _ => Ok(Value::Bool(false)),
332    }
333}
334
335use std::sync::{Mutex, OnceLock};
336
337#[derive(Default)]
338struct EventRegistry {
339    next_id: u64,
340    listeners: std::collections::HashMap<(usize, String), Vec<runmat_builtins::Listener>>,
341}
342
343static EVENT_REGISTRY: OnceLock<Mutex<EventRegistry>> = OnceLock::new();
344
345fn events() -> &'static Mutex<EventRegistry> {
346    EVENT_REGISTRY.get_or_init(|| Mutex::new(EventRegistry::default()))
347}
348
349#[runmat_macros::runtime_builtin(name = "addlistener", builtin_path = "crate")]
350async fn addlistener_builtin(
351    target: Value,
352    event_name: String,
353    callback: Value,
354) -> crate::BuiltinResult<Value> {
355    let key_ptr: usize = match &target {
356        Value::HandleObject(h) => (unsafe { h.target.as_raw() }) as usize,
357        Value::Object(o) => o as *const _ as usize,
358        _ => return Err(("addlistener: target must be handle or object".to_string()).into()),
359    };
360    let mut reg = events().lock().unwrap();
361    let id = {
362        reg.next_id += 1;
363        reg.next_id
364    };
365    let tgt_gc = match target {
366        Value::HandleObject(h) => h.target,
367        Value::Object(o) => {
368            runmat_gc::gc_allocate(Value::Object(o)).map_err(|e| format!("gc: {e}"))?
369        }
370        _ => unreachable!(),
371    };
372    let cb_gc = runmat_gc::gc_allocate(callback).map_err(|e| format!("gc: {e}"))?;
373    let listener = runmat_builtins::Listener {
374        id,
375        target: tgt_gc,
376        event_name: event_name.clone(),
377        callback: cb_gc,
378        enabled: true,
379        valid: true,
380    };
381    reg.listeners
382        .entry((key_ptr, event_name))
383        .or_default()
384        .push(listener.clone());
385    Ok(Value::Listener(listener))
386}
387
388#[runmat_macros::runtime_builtin(name = "notify", builtin_path = "crate")]
389async fn notify_builtin(
390    target: Value,
391    event_name: String,
392    rest: Vec<Value>,
393) -> crate::BuiltinResult<Value> {
394    let key_ptr: usize = match &target {
395        Value::HandleObject(h) => (unsafe { h.target.as_raw() }) as usize,
396        Value::Object(o) => o as *const _ as usize,
397        _ => return Err(("notify: target must be handle or object".to_string()).into()),
398    };
399    let mut to_call: Vec<runmat_builtins::Listener> = Vec::new();
400    {
401        let reg = events().lock().unwrap();
402        if let Some(list) = reg.listeners.get(&(key_ptr, event_name.clone())) {
403            for l in list {
404                if l.valid && l.enabled {
405                    to_call.push(l.clone());
406                }
407            }
408        }
409    }
410    for l in to_call {
411        // Call callback via feval-like protocol
412        let mut args = Vec::new();
413        args.push(target.clone());
414        args.extend(rest.iter().cloned());
415        let cbv: Value = (*l.callback).clone();
416        match &cbv {
417            Value::String(s) if s.starts_with('@') => {
418                let mut a = vec![Value::String(s.clone())];
419                a.extend(args.into_iter());
420                let _ = crate::call_builtin_async("feval", &a).await?;
421            }
422            Value::FunctionHandle(name) => {
423                let mut a = vec![Value::FunctionHandle(name.clone())];
424                a.extend(args.into_iter());
425                let _ = crate::call_builtin_async("feval", &a).await?;
426            }
427            Value::Closure(_) => {
428                let mut a = vec![cbv.clone()];
429                a.extend(args.into_iter());
430                let _ = crate::call_builtin_async("feval", &a).await?;
431            }
432            _ => {}
433        }
434    }
435    Ok(Value::Num(0.0))
436}
437
438// Test-oriented dependent property handlers (global). If a class defines a Dependent
439// property named 'p', the VM will try to call get.p / set.p. We provide generic
440// implementations that read/write a conventional backing field 'p_backing'.
441#[runmat_macros::runtime_builtin(name = "get.p", builtin_path = "crate")]
442async fn get_p_builtin(obj: Value) -> crate::BuiltinResult<Value> {
443    match obj {
444        Value::Object(o) => {
445            if let Some(v) = o.properties.get("p_backing") {
446                Ok(v.clone())
447            } else {
448                Ok(Value::Num(0.0))
449            }
450        }
451        other => Err((format!("get.p requires object, got {other:?}")).into()),
452    }
453}
454
455#[runmat_macros::runtime_builtin(name = "set.p", builtin_path = "crate")]
456async fn set_p_builtin(obj: Value, val: Value) -> crate::BuiltinResult<Value> {
457    match obj {
458        Value::Object(mut o) => {
459            o.properties.insert("p_backing".to_string(), val);
460            Ok(Value::Object(o))
461        }
462        other => Err((format!("set.p requires object, got {other:?}")).into()),
463    }
464}
465
466#[runmat_macros::runtime_builtin(name = "make_handle", builtin_path = "crate")]
467async fn make_handle_builtin(name: String) -> crate::BuiltinResult<Value> {
468    Ok(Value::FunctionHandle(name))
469}
470
471#[runmat_macros::runtime_builtin(name = "make_anon", builtin_path = "crate")]
472async fn make_anon_builtin(params: String, body: String) -> crate::BuiltinResult<Value> {
473    Ok(Value::String(format!("@anon({params}) {body}")))
474}
475
476#[runmat_macros::runtime_builtin(name = "new_object", builtin_path = "crate")]
477pub(crate) async fn new_object_builtin(class_name: String) -> crate::BuiltinResult<Value> {
478    if let Some(def) = runmat_builtins::get_class(&class_name) {
479        // Collect class hierarchy from root to leaf for default initialization
480        let mut chain: Vec<runmat_builtins::ClassDef> = Vec::new();
481        // Walk up to root
482        let mut cursor: Option<String> = Some(def.name.clone());
483        while let Some(name) = cursor {
484            if let Some(cd) = runmat_builtins::get_class(&name) {
485                chain.push(cd.clone());
486                cursor = cd.parent.clone();
487            } else {
488                break;
489            }
490        }
491        // Reverse to root-first
492        chain.reverse();
493        let mut obj = runmat_builtins::ObjectInstance::new(def.name.clone());
494        // Apply defaults from root to leaf (leaf overrides effectively by later assignment)
495        for cd in chain {
496            for (k, p) in cd.properties.iter() {
497                if !p.is_static {
498                    if let Some(v) = &p.default_value {
499                        obj.properties.insert(k.clone(), v.clone());
500                    }
501                }
502            }
503        }
504        Ok(Value::Object(obj))
505    } else {
506        Ok(Value::Object(runmat_builtins::ObjectInstance::new(
507            class_name,
508        )))
509    }
510}
511
512// handle-object builtins removed for now
513
514#[runmat_macros::runtime_builtin(name = "classref", builtin_path = "crate")]
515async fn classref_builtin(class_name: String) -> crate::BuiltinResult<Value> {
516    Ok(Value::ClassRef(class_name))
517}
518
519#[runmat_macros::runtime_builtin(name = "__register_test_classes", builtin_path = "crate")]
520async fn register_test_classes_builtin() -> crate::BuiltinResult<Value> {
521    use runmat_builtins::*;
522    let mut props = std::collections::HashMap::new();
523    props.insert(
524        "x".to_string(),
525        PropertyDef {
526            name: "x".to_string(),
527            is_static: false,
528            is_dependent: false,
529            get_access: Access::Public,
530            set_access: Access::Public,
531            default_value: Some(Value::Num(0.0)),
532        },
533    );
534    props.insert(
535        "y".to_string(),
536        PropertyDef {
537            name: "y".to_string(),
538            is_static: false,
539            is_dependent: false,
540            get_access: Access::Public,
541            set_access: Access::Public,
542            default_value: Some(Value::Num(0.0)),
543        },
544    );
545    props.insert(
546        "staticValue".to_string(),
547        PropertyDef {
548            name: "staticValue".to_string(),
549            is_static: true,
550            is_dependent: false,
551            get_access: Access::Public,
552            set_access: Access::Public,
553            default_value: Some(Value::Num(42.0)),
554        },
555    );
556    props.insert(
557        "secret".to_string(),
558        PropertyDef {
559            name: "secret".to_string(),
560            is_static: false,
561            is_dependent: false,
562            get_access: Access::Private,
563            set_access: Access::Private,
564            default_value: Some(Value::Num(99.0)),
565        },
566    );
567    let mut methods = std::collections::HashMap::new();
568    methods.insert(
569        "move".to_string(),
570        MethodDef {
571            name: "move".to_string(),
572            is_static: false,
573            access: Access::Public,
574            function_name: "Point.move".to_string(),
575        },
576    );
577    methods.insert(
578        "origin".to_string(),
579        MethodDef {
580            name: "origin".to_string(),
581            is_static: true,
582            access: Access::Public,
583            function_name: "Point.origin".to_string(),
584        },
585    );
586    runmat_builtins::register_class(ClassDef {
587        name: "Point".to_string(),
588        parent: None,
589        properties: props,
590        methods,
591    });
592
593    // Namespaced class example: pkg.PointNS with same shape as Point
594    let mut ns_props = std::collections::HashMap::new();
595    ns_props.insert(
596        "x".to_string(),
597        PropertyDef {
598            name: "x".to_string(),
599            is_static: false,
600            is_dependent: false,
601            get_access: Access::Public,
602            set_access: Access::Public,
603            default_value: Some(Value::Num(1.0)),
604        },
605    );
606    ns_props.insert(
607        "y".to_string(),
608        PropertyDef {
609            name: "y".to_string(),
610            is_static: false,
611            is_dependent: false,
612            get_access: Access::Public,
613            set_access: Access::Public,
614            default_value: Some(Value::Num(2.0)),
615        },
616    );
617    let ns_methods = std::collections::HashMap::new();
618    runmat_builtins::register_class(ClassDef {
619        name: "pkg.PointNS".to_string(),
620        parent: None,
621        properties: ns_props,
622        methods: ns_methods,
623    });
624
625    // Inheritance: Shape (base) and Circle (derived)
626    let shape_props = std::collections::HashMap::new();
627    let mut shape_methods = std::collections::HashMap::new();
628    shape_methods.insert(
629        "area".to_string(),
630        MethodDef {
631            name: "area".to_string(),
632            is_static: false,
633            access: Access::Public,
634            function_name: "Shape.area".to_string(),
635        },
636    );
637    runmat_builtins::register_class(ClassDef {
638        name: "Shape".to_string(),
639        parent: None,
640        properties: shape_props,
641        methods: shape_methods,
642    });
643
644    let mut circle_props = std::collections::HashMap::new();
645    circle_props.insert(
646        "r".to_string(),
647        PropertyDef {
648            name: "r".to_string(),
649            is_static: false,
650            is_dependent: false,
651            get_access: Access::Public,
652            set_access: Access::Public,
653            default_value: Some(Value::Num(0.0)),
654        },
655    );
656    let mut circle_methods = std::collections::HashMap::new();
657    circle_methods.insert(
658        "area".to_string(),
659        MethodDef {
660            name: "area".to_string(),
661            is_static: false,
662            access: Access::Public,
663            function_name: "Circle.area".to_string(),
664        },
665    );
666    runmat_builtins::register_class(ClassDef {
667        name: "Circle".to_string(),
668        parent: Some("Shape".to_string()),
669        properties: circle_props,
670        methods: circle_methods,
671    });
672
673    // Constructor demo class: Ctor with static constructor method Ctor
674    let ctor_props = std::collections::HashMap::new();
675    let mut ctor_methods = std::collections::HashMap::new();
676    ctor_methods.insert(
677        "Ctor".to_string(),
678        MethodDef {
679            name: "Ctor".to_string(),
680            is_static: true,
681            access: Access::Public,
682            function_name: "Ctor.Ctor".to_string(),
683        },
684    );
685    runmat_builtins::register_class(ClassDef {
686        name: "Ctor".to_string(),
687        parent: None,
688        properties: ctor_props,
689        methods: ctor_methods,
690    });
691
692    // Overloaded indexing demo class: OverIdx with subsref/subsasgn
693    let overidx_props = std::collections::HashMap::new();
694    let mut overidx_methods = std::collections::HashMap::new();
695    overidx_methods.insert(
696        "subsref".to_string(),
697        MethodDef {
698            name: "subsref".to_string(),
699            is_static: false,
700            access: Access::Public,
701            function_name: "OverIdx.subsref".to_string(),
702        },
703    );
704    overidx_methods.insert(
705        "subsasgn".to_string(),
706        MethodDef {
707            name: "subsasgn".to_string(),
708            is_static: false,
709            access: Access::Public,
710            function_name: "OverIdx.subsasgn".to_string(),
711        },
712    );
713    runmat_builtins::register_class(ClassDef {
714        name: "OverIdx".to_string(),
715        parent: None,
716        properties: overidx_props,
717        methods: overidx_methods,
718    });
719    Ok(Value::Num(1.0))
720}
721
722#[cfg(feature = "test-classes")]
723pub async fn test_register_classes() {
724    let _ = register_test_classes_builtin().await;
725}
726
727// Example method implementation: Point.move(obj, dx, dy) -> updated obj
728#[runmat_macros::runtime_builtin(name = "Point.move", builtin_path = "crate")]
729async fn point_move_method(obj: Value, dx: f64, dy: f64) -> crate::BuiltinResult<Value> {
730    match obj {
731        Value::Object(mut o) => {
732            let mut x = 0.0;
733            let mut y = 0.0;
734            if let Some(Value::Num(v)) = o.properties.get("x") {
735                x = *v;
736            }
737            if let Some(Value::Num(v)) = o.properties.get("y") {
738                y = *v;
739            }
740            o.properties.insert("x".to_string(), Value::Num(x + dx));
741            o.properties.insert("y".to_string(), Value::Num(y + dy));
742            Ok(Value::Object(o))
743        }
744        other => Err((format!("Point.move requires object receiver, got {other:?}")).into()),
745    }
746}
747
748#[runmat_macros::runtime_builtin(name = "Point.origin", builtin_path = "crate")]
749async fn point_origin_method() -> crate::BuiltinResult<Value> {
750    let mut o = runmat_builtins::ObjectInstance::new("Point".to_string());
751    o.properties.insert("x".to_string(), Value::Num(0.0));
752    o.properties.insert("y".to_string(), Value::Num(0.0));
753    Ok(Value::Object(o))
754}
755
756#[runmat_macros::runtime_builtin(name = "Shape.area", builtin_path = "crate")]
757async fn shape_area_method(_obj: Value) -> crate::BuiltinResult<Value> {
758    Ok(Value::Num(0.0))
759}
760
761#[runmat_macros::runtime_builtin(name = "Circle.area", builtin_path = "crate")]
762async fn circle_area_method(obj: Value) -> crate::BuiltinResult<Value> {
763    match obj {
764        Value::Object(o) => {
765            let r = if let Some(Value::Num(v)) = o.properties.get("r") {
766                *v
767            } else {
768                0.0
769            };
770            Ok(Value::Num(std::f64::consts::PI * r * r))
771        }
772        other => Err((format!("Circle.area requires object receiver, got {other:?}")).into()),
773    }
774}
775
776// --- Test-only helpers to validate constructors and subsref/subsasgn ---
777#[runmat_macros::runtime_builtin(name = "Ctor.Ctor", builtin_path = "crate")]
778async fn ctor_ctor_method(x: f64) -> crate::BuiltinResult<Value> {
779    // Construct object with property 'x' initialized
780    let mut o = runmat_builtins::ObjectInstance::new("Ctor".to_string());
781    o.properties.insert("x".to_string(), Value::Num(x));
782    Ok(Value::Object(o))
783}
784
785// --- Test-only package functions to exercise import precedence ---
786#[runmat_macros::runtime_builtin(name = "PkgF.foo", builtin_path = "crate")]
787async fn pkgf_foo() -> crate::BuiltinResult<Value> {
788    Ok(Value::Num(10.0))
789}
790
791#[runmat_macros::runtime_builtin(name = "PkgG.foo", builtin_path = "crate")]
792async fn pkgg_foo() -> crate::BuiltinResult<Value> {
793    Ok(Value::Num(20.0))
794}
795
796#[runmat_macros::runtime_builtin(name = "OverIdx.subsref", builtin_path = "crate")]
797async fn overidx_subsref(obj: Value, kind: String, payload: Value) -> crate::BuiltinResult<Value> {
798    // Simple sentinel implementation: return different values for '.' vs '()'
799    match (obj, kind.as_str(), payload) {
800        (Value::Object(_), "()", Value::Cell(_)) => Ok(Value::Num(99.0)),
801        (Value::Object(o), "{}", Value::Cell(_)) => {
802            if let Some(v) = o.properties.get("lastCell") {
803                Ok(v.clone())
804            } else {
805                Ok(Value::Num(0.0))
806            }
807        }
808        (Value::Object(o), ".", Value::String(field)) => {
809            // If field exists, return it; otherwise sentinel 77
810            if let Some(v) = o.properties.get(&field) {
811                Ok(v.clone())
812            } else {
813                Ok(Value::Num(77.0))
814            }
815        }
816        (Value::Object(o), ".", Value::CharArray(ca)) => {
817            let field: String = ca.data.iter().collect();
818            if let Some(v) = o.properties.get(&field) {
819                Ok(v.clone())
820            } else {
821                Ok(Value::Num(77.0))
822            }
823        }
824        _ => Err(("subsref: unsupported payload".to_string()).into()),
825    }
826}
827
828#[runmat_macros::runtime_builtin(name = "OverIdx.subsasgn", builtin_path = "crate")]
829async fn overidx_subsasgn(
830    mut obj: Value,
831    kind: String,
832    payload: Value,
833    rhs: Value,
834) -> crate::BuiltinResult<Value> {
835    match (&mut obj, kind.as_str(), payload) {
836        (Value::Object(o), "()", Value::Cell(_)) => {
837            // Store into 'last' property
838            o.properties.insert("last".to_string(), rhs);
839            Ok(Value::Object(o.clone()))
840        }
841        (Value::Object(o), "{}", Value::Cell(_)) => {
842            o.properties.insert("lastCell".to_string(), rhs);
843            Ok(Value::Object(o.clone()))
844        }
845        (Value::Object(o), ".", Value::String(field)) => {
846            o.properties.insert(field, rhs);
847            Ok(Value::Object(o.clone()))
848        }
849        (Value::Object(o), ".", Value::CharArray(ca)) => {
850            let field: String = ca.data.iter().collect();
851            o.properties.insert(field, rhs);
852            Ok(Value::Object(o.clone()))
853        }
854        _ => Err(("subsasgn: unsupported payload".to_string()).into()),
855    }
856}
857
858// --- Operator overloading methods for OverIdx (test scaffolding) ---
859#[runmat_macros::runtime_builtin(name = "OverIdx.plus", builtin_path = "crate")]
860async fn overidx_plus(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
861    let o = match obj {
862        Value::Object(o) => o,
863        _ => return Err(("OverIdx.plus: receiver must be object".to_string()).into()),
864    };
865    let k = if let Some(Value::Num(v)) = o.properties.get("k") {
866        *v
867    } else {
868        0.0
869    };
870    let r: f64 = (&rhs).try_into()?;
871    Ok(Value::Num(k + r))
872}
873
874#[runmat_macros::runtime_builtin(name = "OverIdx.times", builtin_path = "crate")]
875async fn overidx_times(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
876    let o = match obj {
877        Value::Object(o) => o,
878        _ => return Err(("OverIdx.times: receiver must be object".to_string()).into()),
879    };
880    let k = if let Some(Value::Num(v)) = o.properties.get("k") {
881        *v
882    } else {
883        0.0
884    };
885    let r: f64 = (&rhs).try_into()?;
886    Ok(Value::Num(k * r))
887}
888
889#[runmat_macros::runtime_builtin(name = "OverIdx.mtimes", builtin_path = "crate")]
890async fn overidx_mtimes(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
891    let o = match obj {
892        Value::Object(o) => o,
893        _ => return Err(("OverIdx.mtimes: receiver must be object".to_string()).into()),
894    };
895    let k = if let Some(Value::Num(v)) = o.properties.get("k") {
896        *v
897    } else {
898        0.0
899    };
900    let r: f64 = (&rhs).try_into()?;
901    Ok(Value::Num(k * r))
902}
903
904#[runmat_macros::runtime_builtin(name = "OverIdx.lt", builtin_path = "crate")]
905async fn overidx_lt(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
906    let o = match obj {
907        Value::Object(o) => o,
908        _ => return Err(("OverIdx.lt: receiver must be object".to_string()).into()),
909    };
910    let k = if let Some(Value::Num(v)) = o.properties.get("k") {
911        *v
912    } else {
913        0.0
914    };
915    let r: f64 = (&rhs).try_into()?;
916    Ok(Value::Num(if k < r { 1.0 } else { 0.0 }))
917}
918
919#[runmat_macros::runtime_builtin(name = "OverIdx.gt", builtin_path = "crate")]
920async fn overidx_gt(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
921    let o = match obj {
922        Value::Object(o) => o,
923        _ => return Err(("OverIdx.gt: receiver must be object".to_string()).into()),
924    };
925    let k = if let Some(Value::Num(v)) = o.properties.get("k") {
926        *v
927    } else {
928        0.0
929    };
930    let r: f64 = (&rhs).try_into()?;
931    Ok(Value::Num(if k > r { 1.0 } else { 0.0 }))
932}
933
934#[runmat_macros::runtime_builtin(name = "OverIdx.eq", builtin_path = "crate")]
935async fn overidx_eq(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
936    let o = match obj {
937        Value::Object(o) => o,
938        _ => return Err(("OverIdx.eq: receiver must be object".to_string()).into()),
939    };
940    let k = if let Some(Value::Num(v)) = o.properties.get("k") {
941        *v
942    } else {
943        0.0
944    };
945    let r: f64 = (&rhs).try_into()?;
946    Ok(Value::Num(if (k - r).abs() < 1e-12 { 1.0 } else { 0.0 }))
947}
948
949#[runmat_macros::runtime_builtin(name = "OverIdx.uplus", builtin_path = "crate")]
950async fn overidx_uplus(obj: Value) -> crate::BuiltinResult<Value> {
951    // Identity
952    Ok(obj)
953}
954
955#[runmat_macros::runtime_builtin(name = "OverIdx.rdivide", builtin_path = "crate")]
956async fn overidx_rdivide(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
957    let o = match obj {
958        Value::Object(o) => o,
959        _ => return Err(("OverIdx.rdivide: receiver must be object".to_string()).into()),
960    };
961    let k = if let Some(Value::Num(v)) = o.properties.get("k") {
962        *v
963    } else {
964        0.0
965    };
966    let r: f64 = (&rhs).try_into()?;
967    Ok(Value::Num(k / r))
968}
969
970#[runmat_macros::runtime_builtin(name = "OverIdx.mrdivide", builtin_path = "crate")]
971async fn overidx_mrdivide(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
972    overidx_rdivide(obj, rhs).await
973}
974
975#[runmat_macros::runtime_builtin(name = "OverIdx.ldivide", builtin_path = "crate")]
976async fn overidx_ldivide(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
977    let o = match obj {
978        Value::Object(o) => o,
979        _ => return Err(("OverIdx.ldivide: receiver must be object".to_string()).into()),
980    };
981    let k = if let Some(Value::Num(v)) = o.properties.get("k") {
982        *v
983    } else {
984        0.0
985    };
986    let r: f64 = (&rhs).try_into()?;
987    Ok(Value::Num(r / k))
988}
989
990#[runmat_macros::runtime_builtin(name = "OverIdx.mldivide", builtin_path = "crate")]
991async fn overidx_mldivide(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
992    overidx_ldivide(obj, rhs).await
993}
994
995#[runmat_macros::runtime_builtin(name = "OverIdx.and", builtin_path = "crate")]
996async fn overidx_and(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
997    let o = match obj {
998        Value::Object(o) => o,
999        _ => return Err(("OverIdx.and: receiver must be object".to_string()).into()),
1000    };
1001    let k = if let Some(Value::Num(v)) = o.properties.get("k") {
1002        *v
1003    } else {
1004        0.0
1005    };
1006    let r: f64 = (&rhs).try_into()?;
1007    Ok(Value::Num(if (k != 0.0) && (r != 0.0) { 1.0 } else { 0.0 }))
1008}
1009
1010#[runmat_macros::runtime_builtin(name = "OverIdx.or", builtin_path = "crate")]
1011async fn overidx_or(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
1012    let o = match obj {
1013        Value::Object(o) => o,
1014        _ => return Err(("OverIdx.or: receiver must be object".to_string()).into()),
1015    };
1016    let k = if let Some(Value::Num(v)) = o.properties.get("k") {
1017        *v
1018    } else {
1019        0.0
1020    };
1021    let r: f64 = (&rhs).try_into()?;
1022    Ok(Value::Num(if (k != 0.0) || (r != 0.0) { 1.0 } else { 0.0 }))
1023}
1024
1025#[runmat_macros::runtime_builtin(name = "OverIdx.xor", builtin_path = "crate")]
1026async fn overidx_xor(obj: Value, rhs: Value) -> crate::BuiltinResult<Value> {
1027    let o = match obj {
1028        Value::Object(o) => o,
1029        _ => return Err(("OverIdx.xor: receiver must be object".to_string()).into()),
1030    };
1031    let k = if let Some(Value::Num(v)) = o.properties.get("k") {
1032        *v
1033    } else {
1034        0.0
1035    };
1036    let r: f64 = (&rhs).try_into()?;
1037    let a = k != 0.0;
1038    let b = r != 0.0;
1039    Ok(Value::Num(if a ^ b { 1.0 } else { 0.0 }))
1040}
1041
1042#[runmat_macros::runtime_builtin(name = "feval", builtin_path = "crate")]
1043async fn feval_builtin(f: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1044    async fn call_by_name(name: &str, args: &[Value]) -> crate::BuiltinResult<Value> {
1045        if let Some(result) = crate::user_functions::try_call_user_function(name, args).await {
1046            match result {
1047                Ok(value) => return Ok(value),
1048                Err(err) => {
1049                    let identifier = err.identifier().unwrap_or("").to_ascii_lowercase();
1050                    let message = err.message().to_ascii_lowercase();
1051                    let is_undefined = identifier.contains("undefinedfunction")
1052                        || message.contains("undefined function");
1053                    if !is_undefined {
1054                        return Err(err);
1055                    }
1056                }
1057            }
1058        }
1059        crate::call_builtin_async(name, args).await
1060    }
1061
1062    match f {
1063        // Function handle strings like "@sin"
1064        Value::String(s) => {
1065            if let Some(name) = s.strip_prefix('@') {
1066                call_by_name(name, &rest).await
1067            } else {
1068                Err(
1069                    (format!("feval: expected function handle string starting with '@', got {s}"))
1070                        .into(),
1071                )
1072            }
1073        }
1074        // Also accept character row vector handles like '@max'
1075        Value::CharArray(ca) => {
1076            if ca.rows == 1 {
1077                let s: String = ca.data.iter().collect();
1078                if let Some(name) = s.strip_prefix('@') {
1079                    call_by_name(name, &rest).await
1080                } else {
1081                    Err((format!(
1082                        "feval: expected function handle string starting with '@', got {s}"
1083                    ))
1084                    .into())
1085                }
1086            } else {
1087                Err(("feval: function handle char array must be a row vector".to_string()).into())
1088            }
1089        }
1090        Value::FunctionHandle(name) => call_by_name(&name, &rest).await,
1091        Value::Closure(c) => {
1092            let mut args = c.captures.clone();
1093            args.extend(rest);
1094            call_by_name(&c.function_name, &args).await
1095        }
1096        other => Err((format!("feval: unsupported function value {other:?}")).into()),
1097    }
1098}
1099
1100// -------- Reductions: sum/prod/mean/any/all --------
1101
1102#[allow(dead_code)]
1103fn tensor_sum_all(t: &runmat_builtins::Tensor) -> f64 {
1104    t.data.iter().sum()
1105}
1106
1107fn tensor_prod_all(t: &runmat_builtins::Tensor) -> f64 {
1108    t.data.iter().product()
1109}
1110
1111fn prod_all_or_cols(a: Value) -> Result<Value, String> {
1112    match a {
1113        Value::Tensor(t) => {
1114            let rows = t.rows();
1115            let cols = t.cols();
1116            if rows > 1 && cols > 1 {
1117                let mut out = vec![1.0f64; cols];
1118                for (c, oc) in out.iter_mut().enumerate().take(cols) {
1119                    let mut p = 1.0;
1120                    for r in 0..rows {
1121                        p *= t.data[r + c * rows];
1122                    }
1123                    *oc = p;
1124                }
1125                Ok(Value::Tensor(
1126                    runmat_builtins::Tensor::new(out, vec![1, cols])
1127                        .map_err(|e| format!("prod: {e}"))?,
1128                ))
1129            } else {
1130                Ok(Value::Num(tensor_prod_all(&t)))
1131            }
1132        }
1133        _ => Err("prod: expected tensor".to_string()),
1134    }
1135}
1136
1137fn prod_dim(a: Value, dim: f64) -> Result<Value, String> {
1138    let t = match a {
1139        Value::Tensor(t) => t,
1140        _ => return Err("prod: expected tensor".to_string()),
1141    };
1142    let dim = if dim < 1.0 { 1usize } else { dim as usize };
1143    let rows = t.rows();
1144    let cols = t.cols();
1145    if dim == 1 {
1146        let mut out = vec![1.0f64; cols];
1147        for (c, oc) in out.iter_mut().enumerate().take(cols) {
1148            let mut p = 1.0;
1149            for r in 0..rows {
1150                p *= t.data[r + c * rows];
1151            }
1152            *oc = p;
1153        }
1154        Ok(Value::Tensor(
1155            runmat_builtins::Tensor::new(out, vec![1, cols]).map_err(|e| format!("prod: {e}"))?,
1156        ))
1157    } else if dim == 2 {
1158        let mut out = vec![1.0f64; rows];
1159        for (r, orow) in out.iter_mut().enumerate().take(rows) {
1160            let mut p = 1.0;
1161            for c in 0..cols {
1162                p *= t.data[r + c * rows];
1163            }
1164            *orow = p;
1165        }
1166        Ok(Value::Tensor(
1167            runmat_builtins::Tensor::new(out, vec![rows, 1]).map_err(|e| format!("prod: {e}"))?,
1168        ))
1169    } else {
1170        Err("prod: dim out of range".to_string())
1171    }
1172}
1173
1174#[runmat_macros::runtime_builtin(name = "prod", builtin_path = "crate")]
1175async fn prod_var_builtin(a: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1176    if rest.is_empty() {
1177        return (prod_all_or_cols(a)).map_err(Into::into);
1178    }
1179    if rest.len() == 1 {
1180        match &rest[0] {
1181            Value::Num(d) => return (prod_dim(a, *d)).map_err(Into::into),
1182            Value::Int(i) => return (prod_dim(a, i.to_i64() as f64)).map_err(Into::into),
1183            _ => {}
1184        }
1185    }
1186    Err(("prod: unsupported arguments".to_string()).into())
1187}
1188
1189fn any_all_or_cols(a: Value) -> Result<Value, String> {
1190    match a {
1191        Value::Tensor(t) => {
1192            let rows = t.rows();
1193            let cols = t.cols();
1194            if rows > 1 && cols > 1 {
1195                let mut out = vec![0.0f64; cols];
1196                for (c, oc) in out.iter_mut().enumerate().take(cols) {
1197                    let mut v = 0.0;
1198                    for r in 0..rows {
1199                        if t.data[r + c * rows] != 0.0 {
1200                            v = 1.0;
1201                            break;
1202                        }
1203                    }
1204                    *oc = v;
1205                }
1206                Ok(Value::Tensor(
1207                    runmat_builtins::Tensor::new(out, vec![1, cols])
1208                        .map_err(|e| format!("any: {e}"))?,
1209                ))
1210            } else {
1211                Ok(Value::Num(if t.data.iter().any(|&x| x != 0.0) {
1212                    1.0
1213                } else {
1214                    0.0
1215                }))
1216            }
1217        }
1218        _ => Err("any: expected tensor".to_string()),
1219    }
1220}
1221
1222fn any_dim(a: Value, dim: f64) -> Result<Value, String> {
1223    let t = match a {
1224        Value::Tensor(t) => t,
1225        _ => return Err("any: expected tensor".to_string()),
1226    };
1227    let dim = if dim < 1.0 { 1usize } else { dim as usize };
1228    let rows = t.rows();
1229    let cols = t.cols();
1230    if dim == 1 {
1231        let mut out = vec![0.0f64; cols];
1232        for (c, oc) in out.iter_mut().enumerate().take(cols) {
1233            let mut v = 0.0;
1234            for r in 0..rows {
1235                if t.data[r + c * rows] != 0.0 {
1236                    v = 1.0;
1237                    break;
1238                }
1239            }
1240            *oc = v;
1241        }
1242        Ok(Value::Tensor(
1243            runmat_builtins::Tensor::new(out, vec![1, cols]).map_err(|e| format!("any: {e}"))?,
1244        ))
1245    } else if dim == 2 {
1246        let mut out = vec![0.0f64; rows];
1247        for (r, orow) in out.iter_mut().enumerate().take(rows) {
1248            let mut v = 0.0;
1249            for c in 0..cols {
1250                if t.data[r + c * rows] != 0.0 {
1251                    v = 1.0;
1252                    break;
1253                }
1254            }
1255            *orow = v;
1256        }
1257        Ok(Value::Tensor(
1258            runmat_builtins::Tensor::new(out, vec![rows, 1]).map_err(|e| format!("any: {e}"))?,
1259        ))
1260    } else {
1261        Err("any: dim out of range".to_string())
1262    }
1263}
1264
1265#[runmat_macros::runtime_builtin(name = "any", builtin_path = "crate")]
1266async fn any_var_builtin(a: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1267    if rest.is_empty() {
1268        return (any_all_or_cols(a)).map_err(Into::into);
1269    }
1270    if rest.len() == 1 {
1271        match &rest[0] {
1272            Value::Num(d) => return (any_dim(a, *d)).map_err(Into::into),
1273            Value::Int(i) => return (any_dim(a, i.to_i64() as f64)).map_err(Into::into),
1274            _ => {}
1275        }
1276    }
1277    Err(("any: unsupported arguments".to_string()).into())
1278}
1279
1280fn all_all_or_cols(a: Value) -> Result<Value, String> {
1281    match a {
1282        Value::Tensor(t) => {
1283            let rows = t.rows();
1284            let cols = t.cols();
1285            if rows > 1 && cols > 1 {
1286                let mut out = vec![0.0f64; cols];
1287                for (c, oc) in out.iter_mut().enumerate().take(cols) {
1288                    let mut v = 1.0;
1289                    for r in 0..rows {
1290                        if t.data[r + c * rows] == 0.0 {
1291                            v = 0.0;
1292                            break;
1293                        }
1294                    }
1295                    *oc = v;
1296                }
1297                Ok(Value::Tensor(
1298                    runmat_builtins::Tensor::new(out, vec![1, cols])
1299                        .map_err(|e| format!("all: {e}"))?,
1300                ))
1301            } else {
1302                Ok(Value::Num(if t.data.iter().all(|&x| x != 0.0) {
1303                    1.0
1304                } else {
1305                    0.0
1306                }))
1307            }
1308        }
1309        _ => Err("all: expected tensor".to_string()),
1310    }
1311}
1312
1313fn all_dim(a: Value, dim: f64) -> Result<Value, String> {
1314    let t = match a {
1315        Value::Tensor(t) => t,
1316        _ => return Err("all: expected tensor".to_string()),
1317    };
1318    let dim = if dim < 1.0 { 1usize } else { dim as usize };
1319    let rows = t.rows();
1320    let cols = t.cols();
1321    if dim == 1 {
1322        let mut out = vec![0.0f64; cols];
1323        for (c, oc) in out.iter_mut().enumerate().take(cols) {
1324            let mut v = 1.0;
1325            for r in 0..rows {
1326                if t.data[r + c * rows] == 0.0 {
1327                    v = 0.0;
1328                    break;
1329                }
1330            }
1331            *oc = v;
1332        }
1333        Ok(Value::Tensor(
1334            runmat_builtins::Tensor::new(out, vec![1, cols]).map_err(|e| format!("all: {e}"))?,
1335        ))
1336    } else if dim == 2 {
1337        let mut out = vec![0.0f64; rows];
1338        for (r, orow) in out.iter_mut().enumerate().take(rows) {
1339            let mut v = 1.0;
1340            for c in 0..cols {
1341                if t.data[r + c * rows] == 0.0 {
1342                    v = 0.0;
1343                    break;
1344                }
1345            }
1346            *orow = v;
1347        }
1348        Ok(Value::Tensor(
1349            runmat_builtins::Tensor::new(out, vec![rows, 1]).map_err(|e| format!("all: {e}"))?,
1350        ))
1351    } else {
1352        Err("all: dim out of range".to_string())
1353    }
1354}
1355
1356#[runmat_macros::runtime_builtin(name = "all", builtin_path = "crate")]
1357async fn all_var_builtin(a: Value, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1358    if rest.is_empty() {
1359        return (all_all_or_cols(a)).map_err(Into::into);
1360    }
1361    if rest.len() == 1 {
1362        match &rest[0] {
1363            Value::Num(d) => return (all_dim(a, *d)).map_err(Into::into),
1364            Value::Int(i) => return (all_dim(a, i.to_i64() as f64)).map_err(Into::into),
1365            _ => {}
1366        }
1367    }
1368    Err(("all: unsupported arguments".to_string()).into())
1369}
1370
1371#[runmat_macros::runtime_builtin(name = "warning", sink = true, builtin_path = "crate")]
1372async fn warning_builtin(fmt: String, rest: Vec<Value>) -> crate::BuiltinResult<Value> {
1373    let s = format_variadic(&fmt, &rest)?;
1374    tracing::warn!("Warning: {s}");
1375    Ok(Value::Num(0.0))
1376}
1377
1378#[runmat_macros::runtime_builtin(name = "getmethod", builtin_path = "crate")]
1379async fn getmethod_builtin(obj: Value, name: String) -> crate::BuiltinResult<Value> {
1380    match obj {
1381        Value::Object(o) => {
1382            // Return a closure capturing the receiver; feval will call runtime builtin call_method
1383            Ok(Value::Closure(runmat_builtins::Closure {
1384                function_name: "call_method".to_string(),
1385                captures: vec![Value::Object(o), Value::String(name)],
1386            }))
1387        }
1388        Value::ClassRef(cls) => Ok(Value::String(format!("@{cls}.{name}"))),
1389        other => Err((format!("getmethod unsupported on {other:?}")).into()),
1390    }
1391}