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