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