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