1use regex::Regex;
2use runmat_builtins::{builtin_functions, Value};
3use runmat_gc_api::GcPtr;
4use runmat_macros::runtime_builtin;
5
6pub mod arrays;
7pub mod comparison;
8pub mod concatenation;
9pub mod constants;
10pub mod elementwise;
11pub mod indexing;
12pub mod introspection;
13pub mod io;
14pub mod mathematics;
15pub mod matrix;
16pub mod plotting;
17
18#[cfg(feature = "blas-lapack")]
19pub mod blas;
20#[cfg(feature = "blas-lapack")]
21pub mod lapack;
22
23#[cfg(all(feature = "blas-lapack", target_os = "macos"))]
25#[link(name = "Accelerate", kind = "framework")]
26extern "C" {}
27
28#[cfg(all(feature = "blas-lapack", not(target_os = "macos")))]
30extern crate openblas_src;
31
32pub use arrays::*;
33pub use comparison::*;
34pub use concatenation::*;
35pub use concatenation::create_matrix_from_values;
37pub use elementwise::*;
40pub use indexing::*;
41pub use matrix::*;
42
43#[cfg(feature = "blas-lapack")]
44pub use blas::*;
45#[cfg(feature = "blas-lapack")]
46pub use lapack::*;
47
48fn make_cell(values: Vec<Value>, rows: usize, cols: usize) -> Result<Value, String> {
49 let handles: Vec<GcPtr<Value>> = values
50 .into_iter()
51 .map(|v| runmat_gc::gc_allocate(v).expect("gc alloc"))
52 .collect();
53 let ca = runmat_builtins::CellArray::new_handles(handles, rows, cols)
54 .map_err(|e| format!("Cell creation error: {e}"))?;
55 Ok(Value::Cell(ca))
56}
57
58#[runmat_macros::runtime_builtin(name = "__make_cell")]
60fn make_cell_builtin(rest: Vec<Value>) -> Result<Value, String> {
61 let rows = 1usize;
62 let cols = rest.len();
63 make_cell(rest, rows, cols)
64}
65
66pub fn call_builtin(name: &str, args: &[Value]) -> Result<Value, String> {
70 let mut matching_builtins = Vec::new();
71
72 for b in builtin_functions() {
74 if b.name == name {
75 matching_builtins.push(b);
76 }
77 }
78
79 if matching_builtins.is_empty() {
80 if let Some(cls) = runmat_builtins::get_class(name) {
82 if let Some(ctor) = cls.methods.get(name) {
84 return call_builtin(&ctor.function_name, args);
86 }
87 return new_object_builtin(name.to_string());
89 }
90 return Err(format!(
91 "{}: Undefined function: {name}",
92 "MATLAB:UndefinedFunction"
93 ));
94 }
95
96 let mut last_error = String::new();
98 for builtin in matching_builtins {
99 let f = builtin.implementation;
100 match (f)(args) {
101 Ok(result) => return Ok(result),
102 Err(e) => last_error = e,
103 }
104 }
105
106 Err(format!(
108 "No matching overload for `{}` with {} args: {}",
109 name,
110 args.len(),
111 last_error
112 ))
113}
114
115#[runmat_macros::runtime_builtin(name = "cellstr")]
116fn cellstr_builtin(a: Value) -> Result<Value, String> {
117 match a {
118 Value::String(s) => make_cell(vec![Value::String(s)], 1, 1),
119 Value::StringArray(sa) => {
120 let rows = sa.rows();
121 let cols = sa.cols();
122 let mut cells: Vec<Value> = Vec::with_capacity(sa.data.len());
123 for r in 0..rows {
124 for c in 0..cols {
125 let idx = r + c * rows;
126 cells.push(Value::String(sa.data[idx].clone()));
127 }
128 }
129 make_cell(cells, rows, cols)
130 }
131 other => Err(format!(
132 "cellstr: expected string or string array, got {other:?}"
133 )),
134 }
135}
136
137#[runmat_macros::runtime_builtin(name = "char")]
138fn char_builtin(a: Value) -> Result<Value, String> {
139 match a {
140 Value::String(s) => {
141 let data: Vec<f64> = s.chars().map(|ch| ch as u32 as f64).collect();
142 Ok(Value::Tensor(
143 runmat_builtins::Tensor::new(data, vec![1, s.chars().count()])
144 .map_err(|e| format!("char: {e}"))?,
145 ))
146 }
147 Value::StringArray(sa) => {
148 let rows = sa.rows();
149 let cols = sa.cols();
150 let mut max_len = 0usize;
151 for c in 0..cols {
152 for r in 0..rows {
153 let idx = r + c * rows;
154 max_len = max_len.max(sa.data[idx].chars().count());
155 }
156 }
157 if rows == 0 || cols == 0 {
158 return Ok(Value::Tensor(
159 runmat_builtins::Tensor::new(Vec::new(), vec![0, 0]).unwrap(),
160 ));
161 }
162 let out_rows = rows;
163 let out_cols = max_len * cols;
164 let mut out = vec![32.0; out_rows * out_cols];
165 for c in 0..cols {
166 for r in 0..rows {
167 let idx = r + c * rows;
168 let s = &sa.data[idx];
169 for (j, ch) in s.chars().enumerate() {
170 let oc = c * max_len + j;
171 out[r + oc * out_rows] = ch as u32 as f64;
172 }
173 }
174 }
175 Ok(Value::Tensor(
176 runmat_builtins::Tensor::new(out, vec![out_rows, out_cols])
177 .map_err(|e| format!("char: {e}"))?,
178 ))
179 }
180 other => Err(format!(
181 "char: expected string or string array, got {other:?}"
182 )),
183 }
184}
185
186#[runmat_macros::runtime_builtin(name = "ind2sub")]
190fn ind2sub_builtin(dims_val: Value, idx_val: f64) -> Result<Value, String> {
191 let dims: Vec<usize> = match dims_val {
192 Value::Tensor(t) => {
193 if t.shape.len() == 2 && (t.shape[0] == 1 || t.shape[1] == 1) {
194 t.data.iter().map(|v| *v as usize).collect()
195 } else {
196 return Err("ind2sub: dims must be a vector".to_string());
197 }
198 }
199 Value::Cell(ca) => {
200 if ca.data.is_empty() {
201 vec![]
202 } else {
203 ca.data
204 .iter()
205 .map(|v| match &**v {
206 Value::Num(n) => *n as usize,
207 Value::Int(i) => i.to_i64() as usize,
208 _ => 1usize,
209 })
210 .collect()
211 }
212 }
213 _ => return Err("ind2sub: dims must be a vector".to_string()),
214 };
215 if dims.is_empty() {
216 return Err("ind2sub: empty dims".to_string());
217 }
218 let mut subs: Vec<usize> = vec![1; dims.len()];
219 let idx = if idx_val < 1.0 {
220 1usize
221 } else {
222 idx_val as usize
223 } - 1; let mut stride = 1usize;
225 for d in 0..dims.len() {
226 let dim_len = dims[d];
227 let val = (idx / stride) % dim_len;
228 subs[d] = val + 1; stride *= dim_len.max(1);
230 }
231 let data: Vec<f64> = subs.iter().map(|s| *s as f64).collect();
233 Ok(Value::Tensor(
234 runmat_builtins::Tensor::new(data, vec![subs.len(), 1])
235 .map_err(|e| format!("ind2sub: {e}"))?,
236 ))
237}
238
239#[runmat_macros::runtime_builtin(name = "sub2ind")]
240fn sub2ind_builtin(dims_val: Value, rest: Vec<Value>) -> Result<Value, String> {
241 let dims: Vec<usize> = match dims_val {
242 Value::Tensor(t) => {
243 if t.shape.len() == 2 && (t.shape[0] == 1 || t.shape[1] == 1) {
244 t.data.iter().map(|v| *v as usize).collect()
245 } else {
246 return Err("sub2ind: dims must be a vector".to_string());
247 }
248 }
249 Value::Cell(ca) => {
250 if ca.data.is_empty() {
251 vec![]
252 } else {
253 ca.data
254 .iter()
255 .map(|v| match &**v {
256 Value::Num(n) => *n as usize,
257 Value::Int(i) => i.to_i64() as usize,
258 _ => 1usize,
259 })
260 .collect()
261 }
262 }
263 _ => return Err("sub2ind: dims must be a vector".to_string()),
264 };
265 if dims.is_empty() {
266 return Err("sub2ind: empty dims".to_string());
267 }
268 if rest.len() != dims.len() {
269 return Err("sub2ind: expected one subscript per dimension".to_string());
270 }
271 let subs: Vec<usize> = rest
272 .iter()
273 .map(|v| match v {
274 Value::Num(n) => *n as isize,
275 Value::Int(i) => i.to_i64() as isize,
276 _ => 1isize,
277 })
278 .map(|x| if x < 1 { 1 } else { x as usize })
279 .collect();
280 let mut stride = 1usize;
282 let mut lin0 = 0usize;
283 for d in 0..dims.len() {
284 let dim_len = dims[d];
285 let s = subs[d];
286 if s == 0 || s > dim_len {
287 return Err("sub2ind: subscript out of bounds".to_string());
288 }
289 lin0 += (s - 1) * stride;
290 stride *= dim_len.max(1);
291 }
292 Ok(Value::Num((lin0 + 1) as f64))
293}
294
295#[runmat_macros::runtime_builtin(name = "strings")]
298fn strings_ctor(rest: Vec<Value>) -> Result<Value, String> {
299 let mut shape: Vec<usize> = Vec::new();
300 if rest.is_empty() {
301 shape = vec![1, 1];
302 } else {
303 for v in rest {
304 let n: f64 = (&v).try_into()?;
305 if n < 0.0 {
306 return Err("strings: dimensions must be non-negative".to_string());
307 }
308 shape.push(n as usize);
309 }
310 if shape.is_empty() {
311 shape = vec![1, 1];
312 }
313 }
314 let total: usize = shape.iter().product();
315 let data = vec![String::new(); total];
316 Ok(Value::StringArray(
317 runmat_builtins::StringArray::new(data, shape).map_err(|e| format!("strings: {e}"))?,
318 ))
319}
320
321#[runmat_macros::runtime_builtin(name = "string.empty")]
322fn string_empty_ctor(rest: Vec<Value>) -> Result<Value, String> {
323 let mut shape: Vec<usize> = Vec::new();
324 for v in rest {
325 let n: f64 = (&v).try_into()?;
326 if n < 0.0 {
327 return Err("string.empty: dimensions must be non-negative".to_string());
328 }
329 shape.push(n as usize);
330 }
331 if shape.is_empty() {
332 shape = vec![0, 0];
333 }
334 let total: usize = shape.iter().product();
335 let data = vec![String::new(); total];
336 Ok(Value::StringArray(
337 runmat_builtins::StringArray::new(data, shape).map_err(|e| format!("string.empty: {e}"))?,
338 ))
339}
340
341#[runmat_macros::runtime_builtin(name = "string")]
342fn string_conv(a: Value) -> Result<Value, String> {
343 match a {
344 Value::String(s) => Ok(Value::StringArray(
345 runmat_builtins::StringArray::new(vec![s], vec![1, 1]).unwrap(),
346 )),
347 Value::StringArray(sa) => Ok(Value::StringArray(sa)),
348 Value::CharArray(ca) => {
349 let mut out: Vec<String> = Vec::with_capacity(ca.rows);
350 for r in 0..ca.rows {
351 let mut s = String::with_capacity(ca.cols);
352 for c in 0..ca.cols {
353 s.push(ca.data[r * ca.cols + c]);
354 }
355 out.push(s);
356 }
357 Ok(Value::StringArray(
358 runmat_builtins::StringArray::new(out, vec![ca.rows, 1])
359 .map_err(|e| e.to_string())?,
360 ))
361 }
362 Value::Tensor(t) => {
363 let mut out: Vec<String> = Vec::with_capacity(t.data.len());
364 for &x in &t.data {
365 out.push(x.to_string());
366 }
367 Ok(Value::StringArray(
368 runmat_builtins::StringArray::new(out, t.shape)
369 .map_err(|e| format!("string: {e}"))?,
370 ))
371 }
372 Value::ComplexTensor(t) => {
373 let mut out: Vec<String> = Vec::with_capacity(t.data.len());
374 for &(re, im) in &t.data {
375 out.push(runmat_builtins::Value::Complex(re, im).to_string());
376 }
377 Ok(Value::StringArray(
378 runmat_builtins::StringArray::new(out, t.shape)
379 .map_err(|e| format!("string: {e}"))?,
380 ))
381 }
382 Value::LogicalArray(la) => {
383 let mut out: Vec<String> = Vec::with_capacity(la.data.len());
384 for &b in &la.data {
385 out.push((if b != 0 { 1 } else { 0 }).to_string());
386 }
387 Ok(Value::StringArray(
388 runmat_builtins::StringArray::new(out, la.shape)
389 .map_err(|e| format!("string: {e}"))?,
390 ))
391 }
392 Value::Cell(ca) => {
393 let mut out: Vec<String> = Vec::with_capacity(ca.rows * ca.cols);
394 for r in 0..ca.rows {
395 for c in 0..ca.cols {
396 let v = &ca.data[r * ca.cols + c];
397 let s: String = match &**v {
398 Value::String(s) => s.clone(),
399 Value::Num(n) => n.to_string(),
400 Value::Int(i) => i.to_i64().to_string(),
401 Value::Bool(b) => (if *b { 1 } else { 0 }).to_string(),
402 Value::LogicalArray(la) => {
403 if la.data.len() == 1 {
404 (if la.data[0] != 0 { 1 } else { 0 }).to_string()
405 } else {
406 format!("LogicalArray(shape={:?})", la.shape)
407 }
408 }
409 Value::CharArray(ch) => ch.data.iter().collect(),
410 other => format!("{other:?}"),
411 };
412 out.push(s);
413 }
414 }
415 Ok(Value::StringArray(
416 runmat_builtins::StringArray::new(out, vec![ca.rows, ca.cols])
417 .map_err(|e| e.to_string())?,
418 ))
419 }
420 Value::Num(n) => Ok(Value::StringArray(
421 runmat_builtins::StringArray::new(vec![n.to_string()], vec![1, 1]).unwrap(),
422 )),
423 Value::Complex(re, im) => {
424 let s = runmat_builtins::Value::Complex(re, im).to_string();
425 Ok(Value::StringArray(
426 runmat_builtins::StringArray::new(vec![s], vec![1, 1]).unwrap(),
427 ))
428 }
429 Value::Int(i) => Ok(Value::StringArray(
430 runmat_builtins::StringArray::new(vec![i.to_i64().to_string()], vec![1, 1]).unwrap(),
431 )),
432 Value::HandleObject(_) => {
433 Err("string: unsupported conversion from handle".to_string())
435 }
436 Value::Listener(_) => Err("string: unsupported conversion from listener".to_string()),
437 other => Err(format!("string: unsupported conversion from {other:?}")),
438 }
439}
440
441#[runmat_macros::runtime_builtin(name = "logical")]
444fn logical_ctor(a: Value) -> Result<Value, String> {
445 match a {
446 Value::Bool(b) => Ok(Value::Bool(b)),
447 Value::Num(n) => Ok(Value::Bool(n != 0.0)),
448 Value::Complex(re, im) => Ok(Value::Bool(!(re == 0.0 && im == 0.0))),
449 Value::Int(i) => Ok(Value::Bool(!i.is_zero())),
450 Value::Tensor(t) => {
451 let data: Vec<u8> = t
452 .data
453 .iter()
454 .map(|&x| if x != 0.0 { 1 } else { 0 })
455 .collect();
456 Ok(Value::LogicalArray(
457 runmat_builtins::LogicalArray::new(data, t.shape)
458 .map_err(|e| format!("logical: {e}"))?,
459 ))
460 }
461 Value::StringArray(sa) => {
462 let data: Vec<u8> = sa
463 .data
464 .iter()
465 .map(|s| if !s.is_empty() { 1 } else { 0 })
466 .collect();
467 Ok(Value::LogicalArray(
468 runmat_builtins::LogicalArray::new(data, sa.shape)
469 .map_err(|e| format!("logical: {e}"))?,
470 ))
471 }
472 Value::CharArray(ca) => {
473 let non_empty = !(ca.rows == 0 || ca.cols == 0);
474 Ok(Value::Bool(non_empty))
475 }
476 Value::LogicalArray(la) => Ok(Value::LogicalArray(la)),
477 Value::ComplexTensor(t) => {
478 let data: Vec<u8> = t
480 .data
481 .iter()
482 .map(|(re, im)| if *re != 0.0 || *im != 0.0 { 1 } else { 0 })
483 .collect();
484 Ok(Value::LogicalArray(
485 runmat_builtins::LogicalArray::new(data, t.shape)
486 .map_err(|e| format!("logical: {e}"))?,
487 ))
488 }
489 Value::Cell(_)
490 | Value::Struct(_)
491 | Value::Object(_)
492 | Value::GpuTensor(_)
493 | Value::FunctionHandle(_)
494 | Value::Closure(_)
495 | Value::ClassRef(_)
496 | Value::MException(_)
497 | Value::String(_)
498 | Value::HandleObject(_)
499 | Value::Listener(_) => Err("logical: unsupported conversion".to_string()),
500 }
501}
502
503fn to_string_scalar(v: &Value) -> Result<String, String> {
506 let s: String = v.try_into()?;
507 Ok(s)
508}
509
510fn map_string_array<F>(sa: &runmat_builtins::StringArray, mut f: F) -> runmat_builtins::Tensor
511where
512 F: FnMut(&str) -> f64,
513{
514 let mut out: Vec<f64> = Vec::with_capacity(sa.data.len());
515 for s in &sa.data {
516 out.push(f(s));
517 }
518 runmat_builtins::Tensor::new(out, sa.shape.clone()).unwrap()
519}
520
521#[runmat_macros::runtime_builtin(name = "strcmp")]
522fn strcmp_builtin(a: Value, b: Value) -> Result<Value, String> {
523 match (a, b) {
524 (Value::StringArray(sa), Value::StringArray(sb)) => {
525 if sa.shape != sb.shape {
526 return Err("strcmp: shape mismatch".to_string());
527 }
528 let data: Vec<f64> = sa
529 .data
530 .iter()
531 .zip(sb.data.iter())
532 .map(|(x, y)| if x == y { 1.0 } else { 0.0 })
533 .collect();
534 Ok(Value::Tensor(
535 runmat_builtins::Tensor::new(data, sa.shape).map_err(|e| format!("strcmp: {e}"))?,
536 ))
537 }
538 (Value::StringArray(sa), other) => {
539 let s = to_string_scalar(&other)?;
540 let t = map_string_array(&sa, |x| if x == s { 1.0 } else { 0.0 });
541 Ok(Value::Tensor(t))
542 }
543 (other, Value::StringArray(sb)) => {
544 let s = to_string_scalar(&other)?;
545 let t = map_string_array(&sb, |x| if x == s { 1.0 } else { 0.0 });
546 Ok(Value::Tensor(t))
547 }
548 (av, bv) => {
549 let as_ = to_string_scalar(&av)?;
550 let bs_ = to_string_scalar(&bv)?;
551 Ok(Value::Num(if as_ == bs_ { 1.0 } else { 0.0 }))
552 }
553 }
554}
555
556#[runmat_macros::runtime_builtin(name = "strncmp")]
557fn strncmp_builtin(a: Value, b: Value, n: f64) -> Result<Value, String> {
558 let n = if n < 0.0 { 0usize } else { n as usize };
559 let cmp = |x: &str, y: &str| -> f64 {
560 let xs = &x.chars().take(n).collect::<String>();
561 let ys = &y.chars().take(n).collect::<String>();
562 if xs == ys {
563 1.0
564 } else {
565 0.0
566 }
567 };
568 match (a, b) {
569 (Value::StringArray(sa), Value::StringArray(sb)) => {
570 if sa.shape != sb.shape {
571 return Err("strncmp: shape mismatch".to_string());
572 }
573 let data: Vec<f64> = sa
574 .data
575 .iter()
576 .zip(sb.data.iter())
577 .map(|(x, y)| cmp(x, y))
578 .collect();
579 Ok(Value::Tensor(
580 runmat_builtins::Tensor::new(data, sa.shape)
581 .map_err(|e| format!("strncmp: {e}"))?,
582 ))
583 }
584 (Value::StringArray(sa), other) => {
585 let s = to_string_scalar(&other)?;
586 let t = map_string_array(&sa, |x| cmp(x, &s));
587 Ok(Value::Tensor(t))
588 }
589 (other, Value::StringArray(sb)) => {
590 let s = to_string_scalar(&other)?;
591 let t = map_string_array(&sb, |x| cmp(&s, x));
592 Ok(Value::Tensor(t))
593 }
594 (av, bv) => {
595 let as_ = to_string_scalar(&av)?;
596 let bs_ = to_string_scalar(&bv)?;
597 Ok(Value::Num(cmp(&as_, &bs_)))
598 }
599 }
600}
601
602#[runmat_macros::runtime_builtin(name = "contains")]
603fn contains_builtin(a: Value, pat: Value) -> Result<Value, String> {
604 let p = to_string_scalar(&pat)?;
605 match a {
606 Value::String(s) => Ok(Value::Num(if s.contains(&p) { 1.0 } else { 0.0 })),
607 Value::StringArray(sa) => {
608 let data: Vec<f64> = sa
609 .data
610 .iter()
611 .map(|x| if x.contains(&p) { 1.0 } else { 0.0 })
612 .collect();
613 Ok(Value::Tensor(
614 runmat_builtins::Tensor::new(data, sa.shape)
615 .map_err(|e| format!("contains: {e}"))?,
616 ))
617 }
618 Value::CharArray(ca) => {
619 let s: String = ca.data.iter().collect();
620 Ok(Value::Num(if s.contains(&p) { 1.0 } else { 0.0 }))
621 }
622 other => Err(format!("contains: unsupported input {other:?}")),
623 }
624}
625
626#[runmat_macros::runtime_builtin(name = "strrep")]
627fn strrep_builtin(a: Value, old: Value, newv: Value) -> Result<Value, String> {
628 let old_s = to_string_scalar(&old)?;
629 let new_s = to_string_scalar(&newv)?;
630 match a {
631 Value::String(s) => Ok(Value::String(s.replace(&old_s, &new_s))),
632 Value::StringArray(sa) => {
633 let data: Vec<String> = sa.data.iter().map(|x| x.replace(&old_s, &new_s)).collect();
634 Ok(Value::StringArray(
635 runmat_builtins::StringArray::new(data, sa.shape)
636 .map_err(|e| format!("strrep: {e}"))?,
637 ))
638 }
639 Value::CharArray(ca) => {
640 let s: String = ca.data.iter().collect();
641 Ok(Value::String(s.replace(&old_s, &new_s)))
642 }
643 other => Err(format!("strrep: unsupported input {other:?}")),
644 }
645}
646
647fn to_string_array(v: &Value) -> Result<runmat_builtins::StringArray, String> {
648 match v {
649 Value::String(s) => runmat_builtins::StringArray::new(vec![s.clone()], vec![1, 1])
650 .map_err(|e| e.to_string()),
651 Value::StringArray(sa) => Ok(sa.clone()),
652 Value::CharArray(ca) => {
653 let mut out: Vec<String> = Vec::with_capacity(ca.rows);
655 for r in 0..ca.rows {
656 let mut s = String::with_capacity(ca.cols);
657 for c in 0..ca.cols {
658 s.push(ca.data[r * ca.cols + c]);
659 }
660 out.push(s);
661 }
662 runmat_builtins::StringArray::new(out, vec![ca.rows, 1]).map_err(|e| e.to_string())
663 }
664 other => Err(format!("cannot convert to string array: {other:?}")),
665 }
666}
667
668#[runmat_macros::runtime_builtin(name = "strcat")]
669fn strcat_builtin(rest: Vec<Value>) -> Result<Value, String> {
670 if rest.is_empty() {
671 return Ok(Value::String(String::new()));
672 }
673 let mut arrays: Vec<runmat_builtins::StringArray> = Vec::new();
675 for v in &rest {
676 arrays.push(to_string_array(v)?);
677 }
678 let mut shape: Vec<usize> = vec![1, 1];
680 for a in &arrays {
681 if a.shape != vec![1, 1] {
682 if shape == vec![1, 1] {
683 shape = a.shape.clone();
684 } else if shape != a.shape {
685 return Err("strcat: shape mismatch".to_string());
686 }
687 }
688 }
689 let total = shape.iter().product::<usize>().max(1);
690 let mut out: Vec<String> = vec![String::new(); total];
691 for a in &arrays {
692 if a.shape == vec![1, 1] {
693 let s = &a.data[0];
694 for item in out.iter_mut().take(total) {
695 item.push_str(s);
696 }
697 } else {
698 for (i, item) in out.iter_mut().enumerate().take(total) {
699 item.push_str(&a.data[i]);
700 }
701 }
702 }
703 Ok(Value::StringArray(
704 runmat_builtins::StringArray::new(out, shape).map_err(|e| format!("strcat: {e}"))?,
705 ))
706}
707
708#[runmat_macros::runtime_builtin(name = "join")]
711fn join_builtin(a: Value, delim: Value) -> Result<Value, String> {
712 strjoin_rowwise(a, delim)
713}
714
715#[runmat_macros::runtime_builtin(name = "split")]
716fn split_builtin(a: Value, delim: Value) -> Result<Value, String> {
717 let s = to_string_scalar(&a)?;
718 let d = to_string_scalar(&delim)?;
719 if d.is_empty() {
720 return Err("split: empty delimiter not supported".to_string());
721 }
722 let parts: Vec<String> = s.split(&d).map(|t| t.to_string()).collect();
723 let len = parts.len();
724 Ok(Value::StringArray(
725 runmat_builtins::StringArray::new(parts, vec![len, 1])
726 .map_err(|e| format!("split: {e}"))?,
727 ))
728}
729
730#[runmat_macros::runtime_builtin(name = "upper")]
731fn upper_builtin(a: Value) -> Result<Value, String> {
732 match a {
733 Value::String(s) => Ok(Value::String(s.to_uppercase())),
734 Value::StringArray(sa) => {
735 let data: Vec<String> = sa.data.iter().map(|x| x.to_uppercase()).collect();
736 Ok(Value::StringArray(
737 runmat_builtins::StringArray::new(data, sa.shape)
738 .map_err(|e| format!("upper: {e}"))?,
739 ))
740 }
741 Value::CharArray(ca) => {
742 let s: String = ca.data.iter().collect();
743 Ok(Value::String(s.to_uppercase()))
744 }
745 other => Err(format!("upper: unsupported input {other:?}")),
746 }
747}
748
749#[runmat_macros::runtime_builtin(name = "lower")]
750fn lower_builtin(a: Value) -> Result<Value, String> {
751 match a {
752 Value::String(s) => Ok(Value::String(s.to_lowercase())),
753 Value::StringArray(sa) => {
754 let data: Vec<String> = sa.data.iter().map(|x| x.to_lowercase()).collect();
755 Ok(Value::StringArray(
756 runmat_builtins::StringArray::new(data, sa.shape)
757 .map_err(|e| format!("lower: {e}"))?,
758 ))
759 }
760 Value::CharArray(ca) => {
761 let s: String = ca.data.iter().collect();
762 Ok(Value::String(s.to_lowercase()))
763 }
764 other => Err(format!("lower: unsupported input {other:?}")),
765 }
766}
767
768#[runmat_macros::runtime_builtin(name = "startsWith")]
769fn starts_with_builtin(a: Value, prefix: Value) -> Result<Value, String> {
770 let p = to_string_scalar(&prefix)?;
771 match a {
772 Value::String(s) => Ok(Value::Num(if s.starts_with(&p) { 1.0 } else { 0.0 })),
773 Value::StringArray(sa) => {
774 let data: Vec<f64> = sa
775 .data
776 .iter()
777 .map(|x| if x.starts_with(&p) { 1.0 } else { 0.0 })
778 .collect();
779 Ok(Value::Tensor(
780 runmat_builtins::Tensor::new(data, sa.shape)
781 .map_err(|e| format!("startsWith: {e}"))?,
782 ))
783 }
784 Value::CharArray(ca) => {
785 let s: String = ca.data.iter().collect();
786 Ok(Value::Num(if s.starts_with(&p) { 1.0 } else { 0.0 }))
787 }
788 other => Err(format!("startsWith: unsupported input {other:?}")),
789 }
790}
791
792#[runmat_macros::runtime_builtin(name = "endsWith")]
793fn ends_with_builtin(a: Value, suffix: Value) -> Result<Value, String> {
794 let p = to_string_scalar(&suffix)?;
795 match a {
796 Value::String(s) => Ok(Value::Num(if s.ends_with(&p) { 1.0 } else { 0.0 })),
797 Value::StringArray(sa) => {
798 let data: Vec<f64> = sa
799 .data
800 .iter()
801 .map(|x| if x.ends_with(&p) { 1.0 } else { 0.0 })
802 .collect();
803 Ok(Value::Tensor(
804 runmat_builtins::Tensor::new(data, sa.shape)
805 .map_err(|e| format!("endsWith: {e}"))?,
806 ))
807 }
808 Value::CharArray(ca) => {
809 let s: String = ca.data.iter().collect();
810 Ok(Value::Num(if s.ends_with(&p) { 1.0 } else { 0.0 }))
811 }
812 other => Err(format!("endsWith: unsupported input {other:?}")),
813 }
814}
815
816#[runmat_macros::runtime_builtin(name = "extractBetween")]
817fn extract_between_builtin(a: Value, start: Value, stop: Value) -> Result<Value, String> {
818 let s = to_string_scalar(&a)?;
819 let st = to_string_scalar(&start)?;
820 let en = to_string_scalar(&stop)?;
821 if st.is_empty() || en.is_empty() {
822 return Ok(Value::String(String::new()));
823 }
824 if let Some(i) = s.find(&st) {
825 if let Some(j) = s[i + st.len()..].find(&en) {
826 return Ok(Value::String(s[i + st.len()..i + st.len() + j].to_string()));
827 }
828 }
829 Ok(Value::String(String::new()))
830}
831
832#[runmat_macros::runtime_builtin(name = "erase")]
833fn erase_builtin(a: Value, pat: Value) -> Result<Value, String> {
834 let s = to_string_scalar(&a)?;
835 let p = to_string_scalar(&pat)?;
836 Ok(Value::String(s.replace(&p, "")))
837}
838
839#[runmat_macros::runtime_builtin(name = "eraseBetween")]
840fn erase_between_builtin(a: Value, start: Value, stop: Value) -> Result<Value, String> {
841 let s = to_string_scalar(&a)?;
842 let st = to_string_scalar(&start)?;
843 let en = to_string_scalar(&stop)?;
844 if st.is_empty() || en.is_empty() {
845 return Ok(Value::String(s));
846 }
847 if let Some(i) = s.find(&st) {
848 if let Some(j) = s[i + st.len()..].find(&en) {
849 let mut out = String::new();
850 out.push_str(&s[..i + st.len()]);
851 out.push_str(&s[i + st.len() + j..]);
852 return Ok(Value::String(out));
853 }
854 }
855 Ok(Value::String(s))
856}
857
858#[runmat_macros::runtime_builtin(name = "pad")]
859fn pad_builtin(a: Value, total_len: f64, rest: Vec<Value>) -> Result<Value, String> {
860 let s = to_string_scalar(&a)?;
862 let n = if total_len < 0.0 {
863 0usize
864 } else {
865 total_len as usize
866 };
867 let mut direction = "left".to_string();
868 let mut ch = ' ';
869 if !rest.is_empty() {
870 direction = to_string_scalar(&rest[0])?;
871 }
872 if rest.len() > 1 {
873 let t = to_string_scalar(&rest[1])?;
874 ch = t.chars().next().unwrap_or(' ');
875 }
876 if s.chars().count() >= n {
877 return Ok(Value::String(s));
878 }
879 let pad_count = n - s.chars().count();
880 let pad_str: String = std::iter::repeat_n(ch, pad_count).collect();
881 if direction == "left" {
882 Ok(Value::String(format!("{pad_str}{s}")))
883 } else {
884 Ok(Value::String(format!("{s}{pad_str}")))
885 }
886}
887
888#[runmat_macros::runtime_builtin(name = "strtrim")]
889fn strtrim_builtin(a: Value) -> Result<Value, String> {
890 let s = to_string_scalar(&a)?;
891 Ok(Value::String(s.trim().to_string()))
892}
893
894#[runmat_macros::runtime_builtin(name = "strip")]
895fn strip_builtin(a: Value) -> Result<Value, String> {
896 strtrim_builtin(a)
897}
898
899#[runmat_macros::runtime_builtin(name = "regexp")]
900fn regexp_builtin(a: Value, pat: Value) -> Result<Value, String> {
901 let s = to_string_scalar(&a)?;
902 let p = to_string_scalar(&pat)?;
903 let re = Regex::new(&p).map_err(|e| format!("regexp: {e}"))?;
904 let mut matches: Vec<Value> = Vec::new();
905 for cap in re.captures_iter(&s) {
906 let full = cap
908 .get(0)
909 .map(|m| m.as_str().to_string())
910 .unwrap_or_default();
911 let mut row: Vec<Value> = vec![Value::String(full)];
912 for i in 1..cap.len() {
913 row.push(Value::String(
914 cap.get(i)
915 .map(|m| m.as_str().to_string())
916 .unwrap_or_default(),
917 ));
918 }
919 matches.push(make_cell(row, 1, cap.len())?);
920 }
921 let len = matches.len();
922 make_cell(matches, 1, len)
923}
924
925#[runmat_macros::runtime_builtin(name = "regexpi")]
926fn regexpi_builtin(a: Value, pat: Value) -> Result<Value, String> {
927 let s = to_string_scalar(&a)?;
928 let p = to_string_scalar(&pat)?;
929 let re = Regex::new(&format!("(?i){p}")).map_err(|e| format!("regexpi: {e}"))?;
930 let mut matches: Vec<Value> = Vec::new();
931 for cap in re.captures_iter(&s) {
932 let full = cap
933 .get(0)
934 .map(|m| m.as_str().to_string())
935 .unwrap_or_default();
936 let mut row: Vec<Value> = vec![Value::String(full)];
937 for i in 1..cap.len() {
938 row.push(Value::String(
939 cap.get(i)
940 .map(|m| m.as_str().to_string())
941 .unwrap_or_default(),
942 ));
943 }
944 matches.push(make_cell(row, 1, cap.len())?);
945 }
946 let len = matches.len();
947 make_cell(matches, 1, len)
948}
949
950#[runmat_macros::runtime_builtin(name = "strjoin")]
952fn strjoin_rowwise(a: Value, delim: Value) -> Result<Value, String> {
953 let d = to_string_scalar(&delim)?;
954 let sa = to_string_array(&a)?;
955 let rows = *sa.shape.first().unwrap_or(&sa.data.len());
956 let cols = *sa.shape.get(1).unwrap_or(&1);
957 if rows == 0 || cols == 0 {
958 return Ok(Value::StringArray(
959 runmat_builtins::StringArray::new(Vec::new(), vec![0, 0]).unwrap(),
960 ));
961 }
962 let mut out: Vec<String> = Vec::with_capacity(rows);
963 for r in 0..rows {
964 let mut s = String::new();
965 for c in 0..cols {
966 if c > 0 {
967 s.push_str(&d);
968 }
969 s.push_str(&sa.data[r + c * rows]);
970 }
971 out.push(s);
972 }
973 Ok(Value::StringArray(
974 runmat_builtins::StringArray::new(out, vec![rows, 1])
975 .map_err(|e| format!("strjoin: {e}"))?,
976 ))
977}
978
979#[runmat_macros::runtime_builtin(name = "deal")]
981fn deal_builtin(rest: Vec<Value>) -> Result<Value, String> {
982 let cols = rest.len();
984 make_cell(rest, 1, cols)
985}
986
987fn do_find_all_indices(t: &runmat_builtins::Tensor) -> Result<Value, String> {
988 let mut idxs: Vec<f64> = Vec::new();
989 for (i, &v) in t.data.iter().enumerate() {
990 if v != 0.0 {
991 idxs.push((i + 1) as f64);
992 }
993 }
994 let len = idxs.len();
995 Ok(Value::Tensor(
996 runmat_builtins::Tensor::new(idxs, vec![len, 1]).map_err(|e| format!("find: {e}"))?,
997 ))
998}
999
1000fn do_find_first_k_indices(t: &runmat_builtins::Tensor, k: usize) -> Result<Value, String> {
1001 let mut idxs: Vec<f64> = Vec::new();
1002 for (i, &v) in t.data.iter().enumerate() {
1003 if v != 0.0 {
1004 idxs.push((i + 1) as f64);
1005 if idxs.len() >= k {
1006 break;
1007 }
1008 }
1009 }
1010 let len = idxs.len();
1011 Ok(Value::Tensor(
1012 runmat_builtins::Tensor::new(idxs, vec![len, 1]).map_err(|e| format!("find: {e}"))?,
1013 ))
1014}
1015
1016#[runmat_macros::runtime_builtin(name = "find")]
1017fn find_var_builtin(a: Value, rest: Vec<Value>) -> Result<Value, String> {
1018 let t = match a {
1019 Value::Tensor(t) => t,
1020 _ => return Err("find: expected tensor".to_string()),
1021 };
1022 if rest.is_empty() {
1023 return do_find_all_indices(&t);
1024 }
1025 let k = match &rest[0] {
1027 Value::Num(n) => *n as usize,
1028 Value::Int(i) => i.to_i64() as usize,
1029 _ => 0,
1030 };
1031 if k == 0 {
1032 return do_find_all_indices(&t);
1033 }
1034 do_find_first_k_indices(&t, k)
1035}
1036#[runmat_macros::runtime_builtin(name = "getfield")]
1039fn getfield_builtin(base: Value, field: String) -> Result<Value, String> {
1040 match base {
1041 Value::MException(me) => match field.as_str() {
1042 "message" => Ok(Value::String(me.message)),
1043 "identifier" => Ok(Value::String(me.identifier)),
1044 _ => Err(format!("getfield: unknown field '{field}' on MException")),
1045 },
1046 Value::Object(obj) => {
1047 if let Some((p, _owner)) = runmat_builtins::lookup_property(&obj.class_name, &field) {
1048 if p.is_static {
1049 return Err(format!(
1050 "Property '{}' is static; use classref('{}').{}",
1051 field, obj.class_name, field
1052 ));
1053 }
1054 if p.get_access == runmat_builtins::Access::Private {
1055 return Err(format!("Property '{field}' is private"));
1056 }
1057 if p.is_dependent {
1058 let getter = format!("get.{field}");
1060 if let Ok(v) = crate::call_builtin(&getter, &[Value::Object(obj.clone())]) {
1061 return Ok(v);
1062 }
1063 let backing = format!("{field}_backing");
1065 if let Some(vb) = obj.properties.get(&backing) {
1066 return Ok(vb.clone());
1067 }
1068 }
1069 }
1070 if let Some(v) = obj.properties.get(&field) {
1071 Ok(v.clone())
1072 } else {
1073 Err(format!(
1074 "Undefined property '{}' for class {}",
1075 field, obj.class_name
1076 ))
1077 }
1078 }
1079 Value::Struct(st) => st
1080 .fields
1081 .get(&field)
1082 .cloned()
1083 .ok_or_else(|| format!("getfield: unknown field '{field}'")),
1084 other => Err(format!(
1085 "getfield unsupported on this value for field '{field}': {other:?}"
1086 )),
1087 }
1088}
1089
1090#[runmat_macros::runtime_builtin(name = "error")]
1092fn error_builtin(rest: Vec<Value>) -> Result<Value, String> {
1093 if rest.is_empty() {
1096 return Err("MATLAB:error: missing message".to_string());
1097 }
1098 if rest.len() == 1 {
1099 let msg: String = (&rest[0]).try_into()?;
1100 return Err(format!("MATLAB:error: {msg}"));
1101 }
1102 let ident: String = (&rest[0]).try_into()?;
1103 let msg: String = (&rest[1]).try_into()?;
1104 let id = if ident.contains(":") {
1105 ident
1106 } else {
1107 ident.to_string()
1108 };
1109 Err(format!("{id}: {msg}"))
1110}
1111
1112#[runmat_macros::runtime_builtin(name = "rethrow")]
1113fn rethrow_builtin(e: Value) -> Result<Value, String> {
1114 match e {
1115 Value::MException(me) => Err(format!("{}: {}", me.identifier, me.message)),
1116 Value::String(s) => Err(s),
1117 other => Err(format!("MATLAB:error: {other:?}")),
1118 }
1119}
1120
1121#[runmat_macros::runtime_builtin(name = "fieldnames")]
1123fn fieldnames_builtin(s: Value) -> Result<Value, String> {
1124 match s {
1125 Value::Struct(st) => {
1126 let mut names: Vec<String> = st.fields.keys().cloned().collect();
1127 names.sort();
1128 let len = names.len();
1129 let vals: Vec<Value> = names.into_iter().map(Value::String).collect();
1130 make_cell(vals, len, 1)
1131 }
1132 other => Err(format!("fieldnames: expected struct, got {other:?}")),
1133 }
1134}
1135
1136#[runmat_macros::runtime_builtin(name = "isfield")]
1137fn isfield_builtin(s: Value, name: Value) -> Result<Value, String> {
1138 match s {
1139 Value::Struct(st) => match name {
1140 Value::String(n) => Ok(Value::Num(if st.fields.contains_key(&n) {
1141 1.0
1142 } else {
1143 0.0
1144 })),
1145 Value::StringArray(sa) => {
1146 let rows = sa.rows();
1147 let cols = sa.cols();
1148 let mut out: Vec<f64> = vec![0.0; sa.data.len()];
1149 for c in 0..cols {
1150 for r in 0..rows {
1151 let idx = r + c * rows;
1152 let n = &sa.data[idx];
1153 out[idx] = if st.fields.contains_key(n) { 1.0 } else { 0.0 };
1154 }
1155 }
1156 Ok(Value::Tensor(
1157 runmat_builtins::Tensor::new(out, vec![rows, cols])
1158 .map_err(|e| format!("isfield: {e}"))?,
1159 ))
1160 }
1161 Value::Cell(ca) => {
1162 let rows = ca.rows;
1163 let cols = ca.cols;
1164 let mut out: Vec<f64> = Vec::with_capacity(rows * cols);
1165 for c in 0..cols {
1166 for r in 0..rows {
1167 let idx = r * cols + c;
1168 let n: String = (&*ca.data[idx]).try_into()?;
1169 out.push(if st.fields.contains_key(&n) { 1.0 } else { 0.0 });
1170 }
1171 }
1172 Ok(Value::Tensor(
1173 runmat_builtins::Tensor::new(out, vec![rows, cols])
1174 .map_err(|e| format!("isfield: {e}"))?,
1175 ))
1176 }
1177 other => {
1178 let n: String = (&other).try_into()?;
1179 Ok(Value::Num(if st.fields.contains_key(&n) {
1180 1.0
1181 } else {
1182 0.0
1183 }))
1184 }
1185 },
1186 non_struct => {
1187 if let Value::Struct(st) = name {
1189 match non_struct {
1190 Value::String(n) => Ok(Value::Num(if st.fields.contains_key(&n) {
1191 1.0
1192 } else {
1193 0.0
1194 })),
1195 Value::StringArray(sa) => {
1196 let rows = sa.rows();
1197 let cols = sa.cols();
1198 let mut out: Vec<f64> = vec![0.0; sa.data.len()];
1199 for c in 0..cols {
1200 for r in 0..rows {
1201 let idx = r + c * rows;
1202 let n = &sa.data[idx];
1203 out[idx] = if st.fields.contains_key(n) { 1.0 } else { 0.0 };
1204 }
1205 }
1206 Ok(Value::Tensor(
1207 runmat_builtins::Tensor::new(out, vec![rows, cols])
1208 .map_err(|e| format!("isfield: {e}"))?,
1209 ))
1210 }
1211 Value::Cell(ca) => {
1212 let rows = ca.rows;
1213 let cols = ca.cols;
1214 let mut out: Vec<f64> = Vec::with_capacity(rows * cols);
1215 for c in 0..cols {
1216 for r in 0..rows {
1217 let idx = r * cols + c;
1218 let n: String = (&*ca.data[idx]).try_into()?;
1219 out.push(if st.fields.contains_key(&n) { 1.0 } else { 0.0 });
1220 }
1221 }
1222 Ok(Value::Tensor(
1223 runmat_builtins::Tensor::new(out, vec![rows, cols])
1224 .map_err(|e| format!("isfield: {e}"))?,
1225 ))
1226 }
1227 other => {
1228 let n: String = (&other).try_into()?;
1229 Ok(Value::Num(if st.fields.contains_key(&n) {
1230 1.0
1231 } else {
1232 0.0
1233 }))
1234 }
1235 }
1236 } else {
1237 Err(format!("isfield: expected struct, got {non_struct:?}"))
1238 }
1239 }
1240 }
1241}
1242
1243#[runmat_macros::runtime_builtin(name = "rmfield")]
1244fn rmfield_builtin(s: Value, rest: Vec<Value>) -> Result<Value, String> {
1245 let mut names: Vec<String> = Vec::new();
1246 if rest.len() == 1 {
1247 match &rest[0] {
1248 Value::Cell(ca) => {
1249 for v in &ca.data {
1250 names.push(String::try_from(&**v).map_err(|e| format!("rmfield: {e}"))?);
1251 }
1252 }
1253 other => {
1254 names.push(String::try_from(other).map_err(|e| format!("rmfield: {e}"))?);
1255 }
1256 }
1257 } else {
1258 for v in &rest {
1259 names.push(String::try_from(v).map_err(|e| format!("rmfield: {e}"))?);
1260 }
1261 }
1262 match s {
1263 Value::Struct(mut st) => {
1264 for n in names {
1265 st.fields.remove(&n);
1266 }
1267 Ok(Value::Struct(st))
1268 }
1269 other => Err(format!("rmfield: expected struct, got {other:?}")),
1270 }
1271}
1272
1273#[runmat_macros::runtime_builtin(name = "orderfields")]
1274fn orderfields_builtin(s: Value) -> Result<Value, String> {
1275 match s {
1278 Value::Struct(st) => Ok(Value::Struct(st)),
1279 other => Err(format!("orderfields: expected struct, got {other:?}")),
1280 }
1281}
1282
1283#[runmat_macros::runtime_builtin(name = "reshape")]
1284fn reshape_builtin(a: Value, rest: Vec<Value>) -> Result<Value, String> {
1285 let t = match a {
1287 Value::Tensor(t) => t,
1288 _ => return Err("reshape: expected tensor".to_string()),
1289 };
1290 let mut dims: Vec<usize> = Vec::new();
1291 for v in rest {
1292 let n: f64 = (&v).try_into()?;
1293 dims.push(n as usize);
1294 }
1295 if dims.len() < 2 || dims.len() > 3 {
1296 return Err("reshape: expected 2 or 3 dimension sizes".to_string());
1297 }
1298 let total: usize = dims.iter().product();
1299 if total != t.data.len() {
1300 return Err(format!(
1301 "reshape: element count mismatch {} vs {}",
1302 total,
1303 t.data.len()
1304 ));
1305 }
1306 let new_t =
1308 runmat_builtins::Tensor::new(t.data.clone(), dims).map_err(|e| format!("reshape: {e}"))?;
1309 Ok(Value::Tensor(new_t))
1310}
1311#[runmat_macros::runtime_builtin(name = "setfield")]
1312fn setfield_builtin(base: Value, field: String, rhs: Value) -> Result<Value, String> {
1313 match base {
1314 Value::Object(mut obj) => {
1315 if let Some((p, _owner)) = runmat_builtins::lookup_property(&obj.class_name, &field) {
1316 if p.is_static {
1317 return Err(format!(
1318 "Property '{}' is static; use classref('{}').{}",
1319 field, obj.class_name, field
1320 ));
1321 }
1322 if p.set_access == runmat_builtins::Access::Private {
1323 return Err(format!("Property '{field}' is private"));
1324 }
1325 if p.is_dependent {
1326 let setter = format!("set.{field}");
1327 if let Ok(v) =
1329 crate::call_builtin(&setter, &[Value::Object(obj.clone()), rhs.clone()])
1330 {
1331 return Ok(v);
1332 }
1333 let backing = format!("{field}_backing");
1335 obj.properties.insert(backing, rhs);
1336 return Ok(Value::Object(obj));
1337 }
1338 }
1339 obj.properties.insert(field, rhs);
1340 Ok(Value::Object(obj))
1341 }
1342 Value::Struct(mut st) => {
1343 st.fields.insert(field, rhs);
1344 Ok(Value::Struct(st))
1345 }
1346 Value::HandleObject(_) | Value::Listener(_) => Err(format!(
1347 "setfield unsupported on this value for field '{field}': handle/listener"
1348 )),
1349 other => Err(format!(
1350 "setfield unsupported on this value for field '{field}': {other:?}"
1351 )),
1352 }
1353}
1354
1355#[runmat_macros::runtime_builtin(name = "call_method")]
1356fn call_method_builtin(base: Value, method: String, rest: Vec<Value>) -> Result<Value, String> {
1357 match base {
1358 Value::Object(obj) => {
1359 let qualified = format!("{}.{}", obj.class_name, method);
1361 let mut args = Vec::with_capacity(1 + rest.len());
1363 args.push(Value::Object(obj.clone()));
1364 args.extend(rest);
1365 if let Ok(v) = crate::call_builtin(&qualified, &args) {
1366 return Ok(v);
1367 }
1368 crate::call_builtin(&method, &args)
1370 }
1371 Value::HandleObject(h) => {
1372 let target = unsafe { &*h.target.as_raw() };
1374 let class_name = match target {
1375 Value::Object(o) => o.class_name.clone(),
1376 Value::Struct(_) => h.class_name.clone(),
1377 _ => h.class_name.clone(),
1378 };
1379 let qualified = format!("{class_name}.{method}");
1380 let mut args = Vec::with_capacity(1 + rest.len());
1381 args.push(Value::HandleObject(h.clone()));
1382 args.extend(rest);
1383 if let Ok(v) = crate::call_builtin(&qualified, &args) {
1384 return Ok(v);
1385 }
1386 crate::call_builtin(&method, &args)
1387 }
1388 other => Err(format!(
1389 "call_method unsupported on {other:?} for method '{method}'"
1390 )),
1391 }
1392}
1393
1394#[runmat_macros::runtime_builtin(name = "subsasgn")]
1396fn subsasgn_dispatch(
1397 obj: Value,
1398 kind: String,
1399 payload: Value,
1400 rhs: Value,
1401) -> Result<Value, String> {
1402 match &obj {
1403 Value::Object(o) => {
1404 let qualified = format!("{}.subsasgn", o.class_name);
1405 crate::call_builtin(&qualified, &[obj, Value::String(kind), payload, rhs])
1406 }
1407 Value::HandleObject(h) => {
1408 let target = unsafe { &*h.target.as_raw() };
1409 let class_name = match target {
1410 Value::Object(o) => o.class_name.clone(),
1411 _ => h.class_name.clone(),
1412 };
1413 let qualified = format!("{class_name}.subsasgn");
1414 crate::call_builtin(&qualified, &[obj, Value::String(kind), payload, rhs])
1415 }
1416 other => Err(format!("subsasgn: receiver must be object, got {other:?}")),
1417 }
1418}
1419
1420#[runmat_macros::runtime_builtin(name = "subsref")]
1421fn subsref_dispatch(obj: Value, kind: String, payload: Value) -> Result<Value, String> {
1422 match &obj {
1423 Value::Object(o) => {
1424 let qualified = format!("{}.subsref", o.class_name);
1425 crate::call_builtin(&qualified, &[obj, Value::String(kind), payload])
1426 }
1427 Value::HandleObject(h) => {
1428 let target = unsafe { &*h.target.as_raw() };
1429 let class_name = match target {
1430 Value::Object(o) => o.class_name.clone(),
1431 _ => h.class_name.clone(),
1432 };
1433 let qualified = format!("{class_name}.subsref");
1434 crate::call_builtin(&qualified, &[obj, Value::String(kind), payload])
1435 }
1436 other => Err(format!("subsref: receiver must be object, got {other:?}")),
1437 }
1438}
1439
1440#[runmat_macros::runtime_builtin(name = "new_handle_object")]
1443fn new_handle_object_builtin(class_name: String) -> Result<Value, String> {
1444 let obj = new_object_builtin(class_name.clone())?;
1446 let gc = runmat_gc::gc_allocate(obj).map_err(|e| format!("gc: {e}"))?;
1447 Ok(Value::HandleObject(runmat_builtins::HandleRef {
1448 class_name,
1449 target: gc,
1450 valid: true,
1451 }))
1452}
1453
1454#[runmat_macros::runtime_builtin(name = "isvalid")]
1455fn isvalid_builtin(v: Value) -> Result<Value, String> {
1456 match v {
1457 Value::HandleObject(h) => Ok(Value::Bool(h.valid)),
1458 Value::Listener(l) => Ok(Value::Bool(l.valid && l.enabled)),
1459 _ => Ok(Value::Bool(false)),
1460 }
1461}
1462
1463#[runmat_macros::runtime_builtin(name = "delete")]
1464fn delete_builtin(v: Value) -> Result<Value, String> {
1465 match v {
1466 Value::HandleObject(mut h) => {
1467 h.valid = false;
1468 Ok(Value::HandleObject(h))
1469 }
1470 Value::Listener(mut l) => {
1471 l.valid = false;
1472 Ok(Value::Listener(l))
1473 }
1474 other => Err(format!("delete: unsupported value {other:?}")),
1475 }
1476}
1477
1478use std::sync::{Mutex, OnceLock};
1479
1480#[derive(Default)]
1481struct EventRegistry {
1482 next_id: u64,
1483 listeners: std::collections::HashMap<(usize, String), Vec<runmat_builtins::Listener>>,
1484}
1485
1486static EVENT_REGISTRY: OnceLock<Mutex<EventRegistry>> = OnceLock::new();
1487
1488fn events() -> &'static Mutex<EventRegistry> {
1489 EVENT_REGISTRY.get_or_init(|| Mutex::new(EventRegistry::default()))
1490}
1491
1492#[runmat_macros::runtime_builtin(name = "addlistener")]
1493fn addlistener_builtin(
1494 target: Value,
1495 event_name: String,
1496 callback: Value,
1497) -> Result<Value, String> {
1498 let key_ptr: usize = match &target {
1499 Value::HandleObject(h) => (unsafe { h.target.as_raw() }) as usize,
1500 Value::Object(o) => o as *const _ as usize,
1501 _ => return Err("addlistener: target must be handle or object".to_string()),
1502 };
1503 let mut reg = events().lock().unwrap();
1504 let id = {
1505 reg.next_id += 1;
1506 reg.next_id
1507 };
1508 let tgt_gc = match target {
1509 Value::HandleObject(h) => h.target,
1510 Value::Object(o) => {
1511 runmat_gc::gc_allocate(Value::Object(o)).map_err(|e| format!("gc: {e}"))?
1512 }
1513 _ => unreachable!(),
1514 };
1515 let cb_gc = runmat_gc::gc_allocate(callback).map_err(|e| format!("gc: {e}"))?;
1516 let listener = runmat_builtins::Listener {
1517 id,
1518 target: tgt_gc,
1519 event_name: event_name.clone(),
1520 callback: cb_gc,
1521 enabled: true,
1522 valid: true,
1523 };
1524 reg.listeners
1525 .entry((key_ptr, event_name))
1526 .or_default()
1527 .push(listener.clone());
1528 Ok(Value::Listener(listener))
1529}
1530
1531#[runmat_macros::runtime_builtin(name = "notify")]
1532fn notify_builtin(target: Value, event_name: String, rest: Vec<Value>) -> Result<Value, String> {
1533 let key_ptr: usize = match &target {
1534 Value::HandleObject(h) => (unsafe { h.target.as_raw() }) as usize,
1535 Value::Object(o) => o as *const _ as usize,
1536 _ => return Err("notify: target must be handle or object".to_string()),
1537 };
1538 let mut to_call: Vec<runmat_builtins::Listener> = Vec::new();
1539 {
1540 let reg = events().lock().unwrap();
1541 if let Some(list) = reg.listeners.get(&(key_ptr, event_name.clone())) {
1542 for l in list {
1543 if l.valid && l.enabled {
1544 to_call.push(l.clone());
1545 }
1546 }
1547 }
1548 }
1549 for l in to_call {
1550 let mut args = Vec::new();
1552 args.push(target.clone());
1553 args.extend(rest.iter().cloned());
1554 let cbv: Value = (*l.callback).clone();
1555 match &cbv {
1556 Value::String(s) if s.starts_with('@') => {
1557 let mut a = vec![Value::String(s.clone())];
1558 a.extend(args.into_iter());
1559 let _ = crate::call_builtin("feval", &a)?;
1560 }
1561 Value::FunctionHandle(name) => {
1562 let mut a = vec![Value::FunctionHandle(name.clone())];
1563 a.extend(args.into_iter());
1564 let _ = crate::call_builtin("feval", &a)?;
1565 }
1566 Value::Closure(_) => {
1567 let mut a = vec![cbv.clone()];
1568 a.extend(args.into_iter());
1569 let _ = crate::call_builtin("feval", &a)?;
1570 }
1571 _ => {}
1572 }
1573 }
1574 Ok(Value::Num(0.0))
1575}
1576
1577#[runmat_macros::runtime_builtin(name = "get.p")]
1581fn get_p_builtin(obj: Value) -> Result<Value, String> {
1582 match obj {
1583 Value::Object(o) => {
1584 if let Some(v) = o.properties.get("p_backing") {
1585 Ok(v.clone())
1586 } else {
1587 Ok(Value::Num(0.0))
1588 }
1589 }
1590 other => Err(format!("get.p requires object, got {other:?}")),
1591 }
1592}
1593
1594#[runmat_macros::runtime_builtin(name = "set.p")]
1595fn set_p_builtin(obj: Value, val: Value) -> Result<Value, String> {
1596 match obj {
1597 Value::Object(mut o) => {
1598 o.properties.insert("p_backing".to_string(), val);
1599 Ok(Value::Object(o))
1600 }
1601 other => Err(format!("set.p requires object, got {other:?}")),
1602 }
1603}
1604
1605#[runmat_macros::runtime_builtin(name = "make_handle")]
1606fn make_handle_builtin(name: String) -> Result<Value, String> {
1607 Ok(Value::String(format!("@{name}")))
1608}
1609
1610#[runmat_macros::runtime_builtin(name = "make_anon")]
1611fn make_anon_builtin(params: String, body: String) -> Result<Value, String> {
1612 Ok(Value::String(format!("@anon({params}) {body}")))
1613}
1614
1615#[runmat_macros::runtime_builtin(name = "new_object")]
1616fn new_object_builtin(class_name: String) -> Result<Value, String> {
1617 if let Some(def) = runmat_builtins::get_class(&class_name) {
1618 let mut chain: Vec<runmat_builtins::ClassDef> = Vec::new();
1620 let mut cursor: Option<String> = Some(def.name.clone());
1622 while let Some(name) = cursor {
1623 if let Some(cd) = runmat_builtins::get_class(&name) {
1624 chain.push(cd.clone());
1625 cursor = cd.parent.clone();
1626 } else {
1627 break;
1628 }
1629 }
1630 chain.reverse();
1632 let mut obj = runmat_builtins::ObjectInstance::new(def.name.clone());
1633 for cd in chain {
1635 for (k, p) in cd.properties.iter() {
1636 if !p.is_static {
1637 if let Some(v) = &p.default_value {
1638 obj.properties.insert(k.clone(), v.clone());
1639 }
1640 }
1641 }
1642 }
1643 Ok(Value::Object(obj))
1644 } else {
1645 Ok(Value::Object(runmat_builtins::ObjectInstance::new(
1646 class_name,
1647 )))
1648 }
1649}
1650
1651#[runmat_macros::runtime_builtin(name = "classref")]
1654fn classref_builtin(class_name: String) -> Result<Value, String> {
1655 Ok(Value::ClassRef(class_name))
1656}
1657
1658#[runmat_macros::runtime_builtin(name = "__register_test_classes")]
1659fn register_test_classes_builtin() -> Result<Value, String> {
1660 use runmat_builtins::*;
1661 let mut props = std::collections::HashMap::new();
1662 props.insert(
1663 "x".to_string(),
1664 PropertyDef {
1665 name: "x".to_string(),
1666 is_static: false,
1667 is_dependent: false,
1668 get_access: Access::Public,
1669 set_access: Access::Public,
1670 default_value: Some(Value::Num(0.0)),
1671 },
1672 );
1673 props.insert(
1674 "y".to_string(),
1675 PropertyDef {
1676 name: "y".to_string(),
1677 is_static: false,
1678 is_dependent: false,
1679 get_access: Access::Public,
1680 set_access: Access::Public,
1681 default_value: Some(Value::Num(0.0)),
1682 },
1683 );
1684 props.insert(
1685 "staticValue".to_string(),
1686 PropertyDef {
1687 name: "staticValue".to_string(),
1688 is_static: true,
1689 is_dependent: false,
1690 get_access: Access::Public,
1691 set_access: Access::Public,
1692 default_value: Some(Value::Num(42.0)),
1693 },
1694 );
1695 props.insert(
1696 "secret".to_string(),
1697 PropertyDef {
1698 name: "secret".to_string(),
1699 is_static: false,
1700 is_dependent: false,
1701 get_access: Access::Private,
1702 set_access: Access::Private,
1703 default_value: Some(Value::Num(99.0)),
1704 },
1705 );
1706 let mut methods = std::collections::HashMap::new();
1707 methods.insert(
1708 "move".to_string(),
1709 MethodDef {
1710 name: "move".to_string(),
1711 is_static: false,
1712 access: Access::Public,
1713 function_name: "Point.move".to_string(),
1714 },
1715 );
1716 methods.insert(
1717 "origin".to_string(),
1718 MethodDef {
1719 name: "origin".to_string(),
1720 is_static: true,
1721 access: Access::Public,
1722 function_name: "Point.origin".to_string(),
1723 },
1724 );
1725 runmat_builtins::register_class(ClassDef {
1726 name: "Point".to_string(),
1727 parent: None,
1728 properties: props,
1729 methods,
1730 });
1731
1732 let mut ns_props = std::collections::HashMap::new();
1734 ns_props.insert(
1735 "x".to_string(),
1736 PropertyDef {
1737 name: "x".to_string(),
1738 is_static: false,
1739 is_dependent: false,
1740 get_access: Access::Public,
1741 set_access: Access::Public,
1742 default_value: Some(Value::Num(1.0)),
1743 },
1744 );
1745 ns_props.insert(
1746 "y".to_string(),
1747 PropertyDef {
1748 name: "y".to_string(),
1749 is_static: false,
1750 is_dependent: false,
1751 get_access: Access::Public,
1752 set_access: Access::Public,
1753 default_value: Some(Value::Num(2.0)),
1754 },
1755 );
1756 let ns_methods = std::collections::HashMap::new();
1757 runmat_builtins::register_class(ClassDef {
1758 name: "pkg.PointNS".to_string(),
1759 parent: None,
1760 properties: ns_props,
1761 methods: ns_methods,
1762 });
1763
1764 let shape_props = std::collections::HashMap::new();
1766 let mut shape_methods = std::collections::HashMap::new();
1767 shape_methods.insert(
1768 "area".to_string(),
1769 MethodDef {
1770 name: "area".to_string(),
1771 is_static: false,
1772 access: Access::Public,
1773 function_name: "Shape.area".to_string(),
1774 },
1775 );
1776 runmat_builtins::register_class(ClassDef {
1777 name: "Shape".to_string(),
1778 parent: None,
1779 properties: shape_props,
1780 methods: shape_methods,
1781 });
1782
1783 let mut circle_props = std::collections::HashMap::new();
1784 circle_props.insert(
1785 "r".to_string(),
1786 PropertyDef {
1787 name: "r".to_string(),
1788 is_static: false,
1789 is_dependent: false,
1790 get_access: Access::Public,
1791 set_access: Access::Public,
1792 default_value: Some(Value::Num(0.0)),
1793 },
1794 );
1795 let mut circle_methods = std::collections::HashMap::new();
1796 circle_methods.insert(
1797 "area".to_string(),
1798 MethodDef {
1799 name: "area".to_string(),
1800 is_static: false,
1801 access: Access::Public,
1802 function_name: "Circle.area".to_string(),
1803 },
1804 );
1805 runmat_builtins::register_class(ClassDef {
1806 name: "Circle".to_string(),
1807 parent: Some("Shape".to_string()),
1808 properties: circle_props,
1809 methods: circle_methods,
1810 });
1811
1812 let ctor_props = std::collections::HashMap::new();
1814 let mut ctor_methods = std::collections::HashMap::new();
1815 ctor_methods.insert(
1816 "Ctor".to_string(),
1817 MethodDef {
1818 name: "Ctor".to_string(),
1819 is_static: true,
1820 access: Access::Public,
1821 function_name: "Ctor.Ctor".to_string(),
1822 },
1823 );
1824 runmat_builtins::register_class(ClassDef {
1825 name: "Ctor".to_string(),
1826 parent: None,
1827 properties: ctor_props,
1828 methods: ctor_methods,
1829 });
1830
1831 let overidx_props = std::collections::HashMap::new();
1833 let mut overidx_methods = std::collections::HashMap::new();
1834 overidx_methods.insert(
1835 "subsref".to_string(),
1836 MethodDef {
1837 name: "subsref".to_string(),
1838 is_static: false,
1839 access: Access::Public,
1840 function_name: "OverIdx.subsref".to_string(),
1841 },
1842 );
1843 overidx_methods.insert(
1844 "subsasgn".to_string(),
1845 MethodDef {
1846 name: "subsasgn".to_string(),
1847 is_static: false,
1848 access: Access::Public,
1849 function_name: "OverIdx.subsasgn".to_string(),
1850 },
1851 );
1852 runmat_builtins::register_class(ClassDef {
1853 name: "OverIdx".to_string(),
1854 parent: None,
1855 properties: overidx_props,
1856 methods: overidx_methods,
1857 });
1858 Ok(Value::Num(1.0))
1859}
1860
1861#[cfg(feature = "test-classes")]
1862pub fn test_register_classes() {
1863 let _ = register_test_classes_builtin();
1864}
1865
1866#[runmat_macros::runtime_builtin(name = "Point.move")]
1868fn point_move_method(obj: Value, dx: f64, dy: f64) -> Result<Value, String> {
1869 match obj {
1870 Value::Object(mut o) => {
1871 let mut x = 0.0;
1872 let mut y = 0.0;
1873 if let Some(Value::Num(v)) = o.properties.get("x") {
1874 x = *v;
1875 }
1876 if let Some(Value::Num(v)) = o.properties.get("y") {
1877 y = *v;
1878 }
1879 o.properties.insert("x".to_string(), Value::Num(x + dx));
1880 o.properties.insert("y".to_string(), Value::Num(y + dy));
1881 Ok(Value::Object(o))
1882 }
1883 other => Err(format!(
1884 "Point.move requires object receiver, got {other:?}"
1885 )),
1886 }
1887}
1888
1889#[runmat_macros::runtime_builtin(name = "Point.origin")]
1890fn point_origin_method() -> Result<Value, String> {
1891 let mut o = runmat_builtins::ObjectInstance::new("Point".to_string());
1892 o.properties.insert("x".to_string(), Value::Num(0.0));
1893 o.properties.insert("y".to_string(), Value::Num(0.0));
1894 Ok(Value::Object(o))
1895}
1896
1897#[runmat_macros::runtime_builtin(name = "Shape.area")]
1898fn shape_area_method(_obj: Value) -> Result<Value, String> {
1899 Ok(Value::Num(0.0))
1900}
1901
1902#[runmat_macros::runtime_builtin(name = "Circle.area")]
1903fn circle_area_method(obj: Value) -> Result<Value, String> {
1904 match obj {
1905 Value::Object(o) => {
1906 let r = if let Some(Value::Num(v)) = o.properties.get("r") {
1907 *v
1908 } else {
1909 0.0
1910 };
1911 Ok(Value::Num(std::f64::consts::PI * r * r))
1912 }
1913 other => Err(format!(
1914 "Circle.area requires object receiver, got {other:?}"
1915 )),
1916 }
1917}
1918
1919#[runmat_macros::runtime_builtin(name = "Ctor.Ctor")]
1921fn ctor_ctor_method(x: f64) -> Result<Value, String> {
1922 let mut o = runmat_builtins::ObjectInstance::new("Ctor".to_string());
1924 o.properties.insert("x".to_string(), Value::Num(x));
1925 Ok(Value::Object(o))
1926}
1927
1928#[runmat_macros::runtime_builtin(name = "PkgF.foo")]
1930fn pkgf_foo() -> Result<Value, String> {
1931 Ok(Value::Num(10.0))
1932}
1933
1934#[runmat_macros::runtime_builtin(name = "PkgG.foo")]
1935fn pkgg_foo() -> Result<Value, String> {
1936 Ok(Value::Num(20.0))
1937}
1938
1939#[runmat_macros::runtime_builtin(name = "OverIdx.subsref")]
1940fn overidx_subsref(obj: Value, kind: String, payload: Value) -> Result<Value, String> {
1941 match (obj, kind.as_str(), payload) {
1943 (Value::Object(_), "()", Value::Cell(_)) => Ok(Value::Num(99.0)),
1944 (Value::Object(o), "{}", Value::Cell(_)) => {
1945 if let Some(v) = o.properties.get("lastCell") {
1946 Ok(v.clone())
1947 } else {
1948 Ok(Value::Num(0.0))
1949 }
1950 }
1951 (Value::Object(o), ".", Value::String(field)) => {
1952 if let Some(v) = o.properties.get(&field) {
1954 Ok(v.clone())
1955 } else {
1956 Ok(Value::Num(77.0))
1957 }
1958 }
1959 (Value::Object(o), ".", Value::CharArray(ca)) => {
1960 let field: String = ca.data.iter().collect();
1961 if let Some(v) = o.properties.get(&field) {
1962 Ok(v.clone())
1963 } else {
1964 Ok(Value::Num(77.0))
1965 }
1966 }
1967 _ => Err("subsref: unsupported payload".to_string()),
1968 }
1969}
1970
1971#[runmat_macros::runtime_builtin(name = "OverIdx.subsasgn")]
1972fn overidx_subsasgn(
1973 mut obj: Value,
1974 kind: String,
1975 payload: Value,
1976 rhs: Value,
1977) -> Result<Value, String> {
1978 match (&mut obj, kind.as_str(), payload) {
1979 (Value::Object(o), "()", Value::Cell(_)) => {
1980 o.properties.insert("last".to_string(), rhs);
1982 Ok(Value::Object(o.clone()))
1983 }
1984 (Value::Object(o), "{}", Value::Cell(_)) => {
1985 o.properties.insert("lastCell".to_string(), rhs);
1986 Ok(Value::Object(o.clone()))
1987 }
1988 (Value::Object(o), ".", Value::String(field)) => {
1989 o.properties.insert(field, rhs);
1990 Ok(Value::Object(o.clone()))
1991 }
1992 (Value::Object(o), ".", Value::CharArray(ca)) => {
1993 let field: String = ca.data.iter().collect();
1994 o.properties.insert(field, rhs);
1995 Ok(Value::Object(o.clone()))
1996 }
1997 _ => Err("subsasgn: unsupported payload".to_string()),
1998 }
1999}
2000
2001#[runmat_macros::runtime_builtin(name = "OverIdx.plus")]
2003fn overidx_plus(obj: Value, rhs: Value) -> Result<Value, String> {
2004 let o = match obj {
2005 Value::Object(o) => o,
2006 _ => return Err("OverIdx.plus: receiver must be object".to_string()),
2007 };
2008 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
2009 *v
2010 } else {
2011 0.0
2012 };
2013 let r: f64 = (&rhs).try_into()?;
2014 Ok(Value::Num(k + r))
2015}
2016
2017#[runmat_macros::runtime_builtin(name = "OverIdx.times")]
2018fn overidx_times(obj: Value, rhs: Value) -> Result<Value, String> {
2019 let o = match obj {
2020 Value::Object(o) => o,
2021 _ => return Err("OverIdx.times: receiver must be object".to_string()),
2022 };
2023 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
2024 *v
2025 } else {
2026 0.0
2027 };
2028 let r: f64 = (&rhs).try_into()?;
2029 Ok(Value::Num(k * r))
2030}
2031
2032#[runmat_macros::runtime_builtin(name = "OverIdx.mtimes")]
2033fn overidx_mtimes(obj: Value, rhs: Value) -> Result<Value, String> {
2034 let o = match obj {
2035 Value::Object(o) => o,
2036 _ => return Err("OverIdx.mtimes: receiver must be object".to_string()),
2037 };
2038 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
2039 *v
2040 } else {
2041 0.0
2042 };
2043 let r: f64 = (&rhs).try_into()?;
2044 Ok(Value::Num(k * r))
2045}
2046
2047#[runmat_macros::runtime_builtin(name = "OverIdx.lt")]
2048fn overidx_lt(obj: Value, rhs: Value) -> Result<Value, String> {
2049 let o = match obj {
2050 Value::Object(o) => o,
2051 _ => return Err("OverIdx.lt: receiver must be object".to_string()),
2052 };
2053 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
2054 *v
2055 } else {
2056 0.0
2057 };
2058 let r: f64 = (&rhs).try_into()?;
2059 Ok(Value::Num(if k < r { 1.0 } else { 0.0 }))
2060}
2061
2062#[runmat_macros::runtime_builtin(name = "OverIdx.gt")]
2063fn overidx_gt(obj: Value, rhs: Value) -> Result<Value, String> {
2064 let o = match obj {
2065 Value::Object(o) => o,
2066 _ => return Err("OverIdx.gt: receiver must be object".to_string()),
2067 };
2068 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
2069 *v
2070 } else {
2071 0.0
2072 };
2073 let r: f64 = (&rhs).try_into()?;
2074 Ok(Value::Num(if k > r { 1.0 } else { 0.0 }))
2075}
2076
2077#[runmat_macros::runtime_builtin(name = "OverIdx.eq")]
2078fn overidx_eq(obj: Value, rhs: Value) -> Result<Value, String> {
2079 let o = match obj {
2080 Value::Object(o) => o,
2081 _ => return Err("OverIdx.eq: receiver must be object".to_string()),
2082 };
2083 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
2084 *v
2085 } else {
2086 0.0
2087 };
2088 let r: f64 = (&rhs).try_into()?;
2089 Ok(Value::Num(if (k - r).abs() < 1e-12 { 1.0 } else { 0.0 }))
2090}
2091
2092#[runmat_macros::runtime_builtin(name = "OverIdx.uplus")]
2093fn overidx_uplus(obj: Value) -> Result<Value, String> {
2094 Ok(obj)
2096}
2097
2098#[runmat_macros::runtime_builtin(name = "OverIdx.rdivide")]
2099fn overidx_rdivide(obj: Value, rhs: Value) -> Result<Value, String> {
2100 let o = match obj {
2101 Value::Object(o) => o,
2102 _ => return Err("OverIdx.rdivide: receiver must be object".to_string()),
2103 };
2104 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
2105 *v
2106 } else {
2107 0.0
2108 };
2109 let r: f64 = (&rhs).try_into()?;
2110 Ok(Value::Num(k / r))
2111}
2112
2113#[runmat_macros::runtime_builtin(name = "OverIdx.ldivide")]
2114fn overidx_ldivide(obj: Value, rhs: Value) -> Result<Value, String> {
2115 let o = match obj {
2116 Value::Object(o) => o,
2117 _ => return Err("OverIdx.ldivide: receiver must be object".to_string()),
2118 };
2119 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
2120 *v
2121 } else {
2122 0.0
2123 };
2124 let r: f64 = (&rhs).try_into()?;
2125 Ok(Value::Num(r / k))
2126}
2127
2128#[runmat_macros::runtime_builtin(name = "OverIdx.and")]
2129fn overidx_and(obj: Value, rhs: Value) -> Result<Value, String> {
2130 let o = match obj {
2131 Value::Object(o) => o,
2132 _ => return Err("OverIdx.and: receiver must be object".to_string()),
2133 };
2134 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
2135 *v
2136 } else {
2137 0.0
2138 };
2139 let r: f64 = (&rhs).try_into()?;
2140 Ok(Value::Num(if (k != 0.0) && (r != 0.0) { 1.0 } else { 0.0 }))
2141}
2142
2143#[runmat_macros::runtime_builtin(name = "OverIdx.or")]
2144fn overidx_or(obj: Value, rhs: Value) -> Result<Value, String> {
2145 let o = match obj {
2146 Value::Object(o) => o,
2147 _ => return Err("OverIdx.or: receiver must be object".to_string()),
2148 };
2149 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
2150 *v
2151 } else {
2152 0.0
2153 };
2154 let r: f64 = (&rhs).try_into()?;
2155 Ok(Value::Num(if (k != 0.0) || (r != 0.0) { 1.0 } else { 0.0 }))
2156}
2157
2158#[runmat_macros::runtime_builtin(name = "OverIdx.xor")]
2159fn overidx_xor(obj: Value, rhs: Value) -> Result<Value, String> {
2160 let o = match obj {
2161 Value::Object(o) => o,
2162 _ => return Err("OverIdx.xor: receiver must be object".to_string()),
2163 };
2164 let k = if let Some(Value::Num(v)) = o.properties.get("k") {
2165 *v
2166 } else {
2167 0.0
2168 };
2169 let r: f64 = (&rhs).try_into()?;
2170 let a = k != 0.0;
2171 let b = r != 0.0;
2172 Ok(Value::Num(if a ^ b { 1.0 } else { 0.0 }))
2173}
2174
2175#[runmat_macros::runtime_builtin(name = "feval")]
2176fn feval_builtin(f: Value, rest: Vec<Value>) -> Result<Value, String> {
2177 match f {
2178 Value::String(s) => {
2180 if let Some(name) = s.strip_prefix('@') {
2181 crate::call_builtin(name, &rest)
2182 } else {
2183 Err(format!(
2184 "feval: expected function handle string starting with '@', got {s}"
2185 ))
2186 }
2187 }
2188 Value::Closure(c) => {
2189 let mut args = c.captures.clone();
2190 args.extend(rest);
2191 crate::call_builtin(&c.function_name, &args)
2192 }
2193 other => Err(format!("feval: unsupported function value {other:?}")),
2195 }
2196}
2197
2198pub fn transpose(value: Value) -> Result<Value, String> {
2202 match value {
2203 Value::Tensor(ref m) => Ok(Value::Tensor(matrix_transpose(m))),
2204 Value::Complex(re, im) => Ok(Value::Complex(re, -im)),
2206 Value::ComplexTensor(ref ct) => {
2207 let mut data: Vec<(f64, f64)> = vec![(0.0, 0.0); ct.rows * ct.cols];
2208 for i in 0..ct.rows {
2210 for j in 0..ct.cols {
2211 let (re, im) = ct.data[i + j * ct.rows];
2212 data[j + i * ct.cols] = (re, -im);
2213 }
2214 }
2215 Ok(Value::ComplexTensor(
2216 runmat_builtins::ComplexTensor::new_2d(data, ct.cols, ct.rows).unwrap(),
2217 ))
2218 }
2219 Value::Num(n) => Ok(Value::Num(n)), _ => Err("transpose not supported for this type".to_string()),
2221 }
2222}
2223
2224#[runmat_macros::runtime_builtin(name = "gpuArray")]
2226fn gpu_array_builtin(x: Value) -> Result<Value, String> {
2227 match x {
2228 Value::Tensor(t) => {
2229 if let Some(p) = runmat_accelerate_api::provider() {
2231 let view = runmat_accelerate_api::HostTensorView {
2232 data: &t.data,
2233 shape: &t.shape,
2234 };
2235 let h = p
2236 .upload(&view)
2237 .map_err(|e| format!("gpuArray upload: {e}"))?;
2238 Ok(Value::GpuTensor(h))
2239 } else {
2240 Ok(Value::GpuTensor(runmat_accelerate_api::GpuTensorHandle {
2241 shape: t.shape.clone(),
2242 device_id: 0,
2243 buffer_id: 0,
2244 }))
2245 }
2246 }
2247 Value::Num(_n) => Ok(Value::GpuTensor(runmat_accelerate_api::GpuTensorHandle {
2248 shape: vec![1, 1],
2249 device_id: 0,
2250 buffer_id: 0,
2251 })),
2252 other => Err(format!("gpuArray unsupported for {other:?}")),
2253 }
2254}
2255
2256#[runmat_macros::runtime_builtin(name = "gather")]
2257fn gather_builtin(x: Value) -> Result<Value, String> {
2258 match x {
2259 Value::GpuTensor(h) => {
2260 if let Some(p) = runmat_accelerate_api::provider() {
2261 let ht = p
2262 .download(&h)
2263 .map_err(|e| format!("gather download: {e}"))?;
2264 Ok(Value::Tensor(
2265 runmat_builtins::Tensor::new(ht.data, ht.shape)
2266 .map_err(|e| format!("gather build: {e}"))?,
2267 ))
2268 } else {
2269 let total: usize = h.shape.iter().product();
2270 Ok(Value::Tensor(
2271 runmat_builtins::Tensor::new(vec![0.0; total], h.shape)
2272 .map_err(|e| format!("gather: {e}"))?,
2273 ))
2274 }
2275 }
2276 v => Ok(v),
2277 }
2278}
2279
2280fn max_scalar(a: f64, b: f64) -> f64 {
2282 a.max(b)
2283}
2284
2285fn max_vector_builtin(a: Value) -> Result<Value, String> {
2286 match a {
2287 Value::Tensor(t) => {
2288 if t.shape.len() == 2 && t.shape[1] == 1 {
2289 let mut max_val = f64::NEG_INFINITY;
2290 let mut idx = 1usize;
2291 for (i, &v) in t.data.iter().enumerate() {
2292 if v > max_val {
2293 max_val = v;
2294 idx = i + 1;
2295 }
2296 }
2297 let out = runmat_builtins::Tensor::new(vec![max_val, idx as f64], vec![2, 1])
2299 .map_err(|e| format!("max: {e}"))?;
2300 Ok(Value::Tensor(out))
2301 } else {
2302 let max_val = t.data.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
2304 Ok(Value::Num(max_val))
2305 }
2306 }
2307 Value::Num(n) => Ok(Value::Num(n)),
2308 _ => Err("max: unsupported input".to_string()),
2309 }
2310}
2311
2312fn min_vector_builtin(a: Value) -> Result<Value, String> {
2313 match a {
2314 Value::Tensor(t) => {
2315 if t.shape.len() == 2 && t.shape[1] == 1 {
2316 let mut min_val = f64::INFINITY;
2317 let mut idx = 1usize;
2318 for (i, &v) in t.data.iter().enumerate() {
2319 if v < min_val {
2320 min_val = v;
2321 idx = i + 1;
2322 }
2323 }
2324 let out = runmat_builtins::Tensor::new(vec![min_val, idx as f64], vec![2, 1])
2325 .map_err(|e| format!("min: {e}"))?;
2326 Ok(Value::Tensor(out))
2327 } else {
2328 let min_val = t.data.iter().cloned().fold(f64::INFINITY, f64::min);
2329 Ok(Value::Num(min_val))
2330 }
2331 }
2332 Value::Num(n) => Ok(Value::Num(n)),
2333 _ => Err("min: unsupported input".to_string()),
2334 }
2335}
2336
2337fn max_dim_builtin(a: Value, dim: f64) -> Result<Value, String> {
2338 let t = match a {
2339 Value::Tensor(t) => t,
2340 _ => return Err("max: expected tensor for dim variant".to_string()),
2341 };
2342 let dim = if dim < 1.0 { 1usize } else { dim as usize };
2343 if t.shape.len() < 2 {
2344 return Err("max: dim variant expects 2D tensor".to_string());
2345 }
2346 let rows = t.shape[0];
2347 let cols = t.shape[1];
2348 if dim == 1 {
2349 let mut m: Vec<f64> = vec![f64::NEG_INFINITY; cols];
2351 let mut idx: Vec<f64> = vec![1.0; cols];
2352 for c in 0..cols {
2353 for r in 0..rows {
2354 let v = t.data[r + c * rows];
2355 if v > m[c] {
2356 m[c] = v;
2357 idx[c] = (r + 1) as f64;
2358 }
2359 }
2360 }
2361 let m_t =
2362 runmat_builtins::Tensor::new(m, vec![1, cols]).map_err(|e| format!("max: {e}"))?;
2363 let i_t =
2364 runmat_builtins::Tensor::new(idx, vec![1, cols]).map_err(|e| format!("max: {e}"))?;
2365 make_cell(vec![Value::Tensor(m_t), Value::Tensor(i_t)], 1, 2)
2366 } else if dim == 2 {
2367 let mut m: Vec<f64> = vec![f64::NEG_INFINITY; rows];
2369 let mut idx: Vec<f64> = vec![1.0; rows];
2370 for r in 0..rows {
2371 for c in 0..cols {
2372 let v = t.data[r + c * rows];
2373 if v > m[r] {
2374 m[r] = v;
2375 idx[r] = (c + 1) as f64;
2376 }
2377 }
2378 }
2379 let m_t =
2380 runmat_builtins::Tensor::new(m, vec![rows, 1]).map_err(|e| format!("max: {e}"))?;
2381 let i_t =
2382 runmat_builtins::Tensor::new(idx, vec![rows, 1]).map_err(|e| format!("max: {e}"))?;
2383 make_cell(vec![Value::Tensor(m_t), Value::Tensor(i_t)], 1, 2)
2384 } else {
2385 Err("max: dim out of range".to_string())
2386 }
2387}
2388
2389fn min_dim_builtin(a: Value, dim: f64) -> Result<Value, String> {
2390 let t = match a {
2391 Value::Tensor(t) => t,
2392 _ => return Err("min: expected tensor for dim variant".to_string()),
2393 };
2394 let dim = if dim < 1.0 { 1usize } else { dim as usize };
2395 if t.shape.len() < 2 {
2396 return Err("min: dim variant expects 2D tensor".to_string());
2397 }
2398 let rows = t.shape[0];
2399 let cols = t.shape[1];
2400 if dim == 1 {
2401 let mut m: Vec<f64> = vec![f64::INFINITY; cols];
2402 let mut idx: Vec<f64> = vec![1.0; cols];
2403 for c in 0..cols {
2404 for r in 0..rows {
2405 let v = t.data[r + c * rows];
2406 if v < m[c] {
2407 m[c] = v;
2408 idx[c] = (r + 1) as f64;
2409 }
2410 }
2411 }
2412 let m_t =
2413 runmat_builtins::Tensor::new(m, vec![1, cols]).map_err(|e| format!("min: {e}"))?;
2414 let i_t =
2415 runmat_builtins::Tensor::new(idx, vec![1, cols]).map_err(|e| format!("min: {e}"))?;
2416 make_cell(vec![Value::Tensor(m_t), Value::Tensor(i_t)], 1, 2)
2417 } else if dim == 2 {
2418 let mut m: Vec<f64> = vec![f64::INFINITY; rows];
2419 let mut idx: Vec<f64> = vec![1.0; rows];
2420 for r in 0..rows {
2421 for c in 0..cols {
2422 let v = t.data[r + c * rows];
2423 if v < m[r] {
2424 m[r] = v;
2425 idx[r] = (c + 1) as f64;
2426 }
2427 }
2428 }
2429 let m_t =
2430 runmat_builtins::Tensor::new(m, vec![rows, 1]).map_err(|e| format!("min: {e}"))?;
2431 let i_t =
2432 runmat_builtins::Tensor::new(idx, vec![rows, 1]).map_err(|e| format!("min: {e}"))?;
2433 make_cell(vec![Value::Tensor(m_t), Value::Tensor(i_t)], 1, 2)
2434 } else {
2435 Err("min: dim out of range".to_string())
2436 }
2437}
2438
2439fn min_scalar(a: f64, b: f64) -> f64 {
2440 a.min(b)
2441}
2442
2443#[runmat_macros::runtime_builtin(name = "max")]
2444fn max_var_builtin(a: Value, rest: Vec<Value>) -> Result<Value, String> {
2445 if rest.is_empty() {
2446 return max_vector_builtin(a);
2447 }
2448 if rest.len() == 1 {
2449 let r0 = &rest[0];
2450 if let (Value::Num(a0), Value::Num(b0)) = (a.clone(), r0.clone()) {
2452 return Ok(Value::Num(max_scalar(a0, b0)));
2453 }
2454 if matches!(r0, Value::Num(_) | Value::Int(_)) {
2456 return max_dim_builtin(
2457 a,
2458 match r0 {
2459 Value::Num(d) => *d,
2460 Value::Int(i) => i.to_i64() as f64,
2461 _ => unreachable!(),
2462 },
2463 );
2464 }
2465 }
2466 Err("max: unsupported arguments".to_string())
2467}
2468
2469#[runmat_macros::runtime_builtin(name = "min")]
2470fn min_var_builtin(a: Value, rest: Vec<Value>) -> Result<Value, String> {
2471 if rest.is_empty() {
2472 return min_vector_builtin(a);
2473 }
2474 if rest.len() == 1 {
2475 let r0 = &rest[0];
2476 if let (Value::Num(a0), Value::Num(b0)) = (a.clone(), r0.clone()) {
2478 return Ok(Value::Num(min_scalar(a0, b0)));
2479 }
2480 match r0 {
2481 Value::Num(d) => return min_dim_builtin(a, *d),
2482 Value::Int(i) => return min_dim_builtin(a, i.to_i64() as f64),
2483 _ => {}
2484 }
2485 }
2486 Err("min: unsupported arguments".to_string())
2487}
2488
2489#[runtime_builtin(name = "sqrt")]
2490fn sqrt_builtin(x: f64) -> Result<f64, String> {
2491 if x < 0.0 {
2492 Err("MATLAB:domainError: Cannot take square root of negative number".to_string())
2493 } else {
2494 Ok(x.sqrt())
2495 }
2496}
2497
2498#[runtime_builtin(name = "tic")]
2501fn tic_builtin() -> Result<f64, String> {
2502 use std::time::{SystemTime, UNIX_EPOCH};
2503 let now = SystemTime::now()
2504 .duration_since(UNIX_EPOCH)
2505 .map_err(|e| format!("Time error: {e}"))?;
2506 Ok(now.as_secs_f64())
2507}
2508
2509#[runtime_builtin(name = "toc")]
2513fn toc_builtin() -> Result<f64, String> {
2514 Ok(0.001) }
2517
2518#[runtime_builtin(name = "exp")]
2519fn exp_builtin(x: f64) -> Result<f64, String> {
2520 Ok(x.exp())
2521}
2522
2523#[runtime_builtin(name = "log")]
2524fn log_builtin(x: f64) -> Result<f64, String> {
2525 if x <= 0.0 {
2526 Err("MATLAB:domainError: Cannot take logarithm of non-positive number".to_string())
2527 } else {
2528 Ok(x.ln())
2529 }
2530}
2531
2532fn tensor_sum_all(t: &runmat_builtins::Tensor) -> f64 {
2535 t.data.iter().sum()
2536}
2537
2538fn tensor_prod_all(t: &runmat_builtins::Tensor) -> f64 {
2539 t.data.iter().product()
2540}
2541
2542fn sum_scalar_all(a: Value) -> Result<Value, String> {
2543 match a {
2544 Value::Tensor(t) => Ok(Value::Num(tensor_sum_all(&t))),
2545 _ => Err("sum: expected tensor".to_string()),
2546 }
2547}
2548
2549fn sum_dim(a: Value, dim: f64) -> Result<Value, String> {
2550 let t = match a {
2551 Value::Tensor(t) => t,
2552 _ => return Err("sum: expected tensor".to_string()),
2553 };
2554 let dim = if dim < 1.0 { 1usize } else { dim as usize };
2555 let rows = t.rows();
2556 let cols = t.cols();
2557 if dim == 1 {
2558 let mut out = vec![0.0f64; cols];
2559 for (c, oc) in out.iter_mut().enumerate().take(cols) {
2560 let mut s = 0.0;
2561 for r in 0..rows {
2562 s += t.data[r + c * rows];
2563 }
2564 *oc = s;
2565 }
2566 Ok(Value::Tensor(
2567 runmat_builtins::Tensor::new(out, vec![1, cols]).map_err(|e| format!("sum: {e}"))?,
2568 ))
2569 } else if dim == 2 {
2570 let mut out = vec![0.0f64; rows];
2571 for (r, orow) in out.iter_mut().enumerate().take(rows) {
2572 let mut s = 0.0;
2573 for c in 0..cols {
2574 s += t.data[r + c * rows];
2575 }
2576 *orow = s;
2577 }
2578 Ok(Value::Tensor(
2579 runmat_builtins::Tensor::new(out, vec![rows, 1]).map_err(|e| format!("sum: {e}"))?,
2580 ))
2581 } else {
2582 Err("sum: dim out of range".to_string())
2583 }
2584}
2585
2586#[runmat_macros::runtime_builtin(name = "sum")]
2587fn sum_var_builtin(a: Value, rest: Vec<Value>) -> Result<Value, String> {
2588 if rest.is_empty() {
2589 return sum_scalar_all(a);
2590 }
2591 if rest.len() == 1 {
2592 match &rest[0] {
2593 Value::Num(d) => return sum_dim(a, *d),
2594 Value::Int(i) => return sum_dim(a, i.to_i64() as f64),
2595 _ => {}
2596 }
2597 }
2598 Err("sum: unsupported arguments".to_string())
2599}
2600
2601fn prod_all_or_cols(a: Value) -> Result<Value, String> {
2602 match a {
2603 Value::Tensor(t) => {
2604 let rows = t.rows();
2605 let cols = t.cols();
2606 if rows > 1 && cols > 1 {
2607 let mut out = vec![1.0f64; cols];
2608 for (c, oc) in out.iter_mut().enumerate().take(cols) {
2609 let mut p = 1.0;
2610 for r in 0..rows {
2611 p *= t.data[r + c * rows];
2612 }
2613 *oc = p;
2614 }
2615 Ok(Value::Tensor(
2616 runmat_builtins::Tensor::new(out, vec![1, cols])
2617 .map_err(|e| format!("prod: {e}"))?,
2618 ))
2619 } else {
2620 Ok(Value::Num(tensor_prod_all(&t)))
2621 }
2622 }
2623 _ => Err("prod: expected tensor".to_string()),
2624 }
2625}
2626
2627fn prod_dim(a: Value, dim: f64) -> Result<Value, String> {
2628 let t = match a {
2629 Value::Tensor(t) => t,
2630 _ => return Err("prod: expected tensor".to_string()),
2631 };
2632 let dim = if dim < 1.0 { 1usize } else { dim as usize };
2633 let rows = t.rows();
2634 let cols = t.cols();
2635 if dim == 1 {
2636 let mut out = vec![1.0f64; cols];
2637 for (c, oc) in out.iter_mut().enumerate().take(cols) {
2638 let mut p = 1.0;
2639 for r in 0..rows {
2640 p *= t.data[r + c * rows];
2641 }
2642 *oc = p;
2643 }
2644 Ok(Value::Tensor(
2645 runmat_builtins::Tensor::new(out, vec![1, cols]).map_err(|e| format!("prod: {e}"))?,
2646 ))
2647 } else if dim == 2 {
2648 let mut out = vec![1.0f64; rows];
2649 for (r, orow) in out.iter_mut().enumerate().take(rows) {
2650 let mut p = 1.0;
2651 for c in 0..cols {
2652 p *= t.data[r + c * rows];
2653 }
2654 *orow = p;
2655 }
2656 Ok(Value::Tensor(
2657 runmat_builtins::Tensor::new(out, vec![rows, 1]).map_err(|e| format!("prod: {e}"))?,
2658 ))
2659 } else {
2660 Err("prod: dim out of range".to_string())
2661 }
2662}
2663
2664#[runmat_macros::runtime_builtin(name = "prod")]
2665fn prod_var_builtin(a: Value, rest: Vec<Value>) -> Result<Value, String> {
2666 if rest.is_empty() {
2667 return prod_all_or_cols(a);
2668 }
2669 if rest.len() == 1 {
2670 match &rest[0] {
2671 Value::Num(d) => return prod_dim(a, *d),
2672 Value::Int(i) => return prod_dim(a, i.to_i64() as f64),
2673 _ => {}
2674 }
2675 }
2676 Err("prod: unsupported arguments".to_string())
2677}
2678
2679fn mean_all_or_cols(a: Value) -> Result<Value, String> {
2680 match a {
2681 Value::Tensor(t) => {
2682 let rows = t.rows();
2683 let cols = t.cols();
2684 if rows > 1 && cols > 1 {
2685 let mut out = vec![0.0f64; cols];
2686 for (c, oc) in out.iter_mut().enumerate().take(cols) {
2687 let mut s = 0.0;
2688 for r in 0..rows {
2689 s += t.data[r + c * rows];
2690 }
2691 *oc = s / (rows as f64);
2692 }
2693 Ok(Value::Tensor(
2694 runmat_builtins::Tensor::new(out, vec![1, cols])
2695 .map_err(|e| format!("mean: {e}"))?,
2696 ))
2697 } else {
2698 Ok(Value::Num(tensor_sum_all(&t) / (t.data.len() as f64)))
2699 }
2700 }
2701 _ => Err("mean: expected tensor".to_string()),
2702 }
2703}
2704
2705fn mean_dim(a: Value, dim: f64) -> Result<Value, String> {
2706 let t = match a {
2707 Value::Tensor(t) => t,
2708 _ => return Err("mean: expected tensor".to_string()),
2709 };
2710 let dim = if dim < 1.0 { 1usize } else { dim as usize };
2711 let rows = t.rows();
2712 let cols = t.cols();
2713 if dim == 1 {
2714 let mut out = vec![0.0f64; cols];
2715 for (c, oc) in out.iter_mut().enumerate().take(cols) {
2716 let mut s = 0.0;
2717 for r in 0..rows {
2718 s += t.data[r + c * rows];
2719 }
2720 *oc = s / (rows as f64);
2721 }
2722 Ok(Value::Tensor(
2723 runmat_builtins::Tensor::new(out, vec![1, cols]).map_err(|e| format!("mean: {e}"))?,
2724 ))
2725 } else if dim == 2 {
2726 let mut out = vec![0.0f64; rows];
2727 for (r, orow) in out.iter_mut().enumerate().take(rows) {
2728 let mut s = 0.0;
2729 for c in 0..cols {
2730 s += t.data[r + c * rows];
2731 }
2732 *orow = s / (cols as f64);
2733 }
2734 Ok(Value::Tensor(
2735 runmat_builtins::Tensor::new(out, vec![rows, 1]).map_err(|e| format!("mean: {e}"))?,
2736 ))
2737 } else {
2738 Err("mean: dim out of range".to_string())
2739 }
2740}
2741
2742#[runmat_macros::runtime_builtin(name = "mean")]
2743fn mean_var_builtin(a: Value, rest: Vec<Value>) -> Result<Value, String> {
2744 if rest.is_empty() {
2745 return mean_all_or_cols(a);
2746 }
2747 if rest.len() == 1 {
2748 match &rest[0] {
2749 Value::Num(d) => return mean_dim(a, *d),
2750 Value::Int(i) => return mean_dim(a, i.to_i64() as f64),
2751 _ => {}
2752 }
2753 }
2754 Err("mean: unsupported arguments".to_string())
2755}
2756
2757fn any_all_or_cols(a: Value) -> Result<Value, String> {
2758 match a {
2759 Value::Tensor(t) => {
2760 let rows = t.rows();
2761 let cols = t.cols();
2762 if rows > 1 && cols > 1 {
2763 let mut out = vec![0.0f64; cols];
2764 for (c, oc) in out.iter_mut().enumerate().take(cols) {
2765 let mut v = 0.0;
2766 for r in 0..rows {
2767 if t.data[r + c * rows] != 0.0 {
2768 v = 1.0;
2769 break;
2770 }
2771 }
2772 *oc = v;
2773 }
2774 Ok(Value::Tensor(
2775 runmat_builtins::Tensor::new(out, vec![1, cols])
2776 .map_err(|e| format!("any: {e}"))?,
2777 ))
2778 } else {
2779 Ok(Value::Num(if t.data.iter().any(|&x| x != 0.0) {
2780 1.0
2781 } else {
2782 0.0
2783 }))
2784 }
2785 }
2786 _ => Err("any: expected tensor".to_string()),
2787 }
2788}
2789
2790fn any_dim(a: Value, dim: f64) -> Result<Value, String> {
2791 let t = match a {
2792 Value::Tensor(t) => t,
2793 _ => return Err("any: expected tensor".to_string()),
2794 };
2795 let dim = if dim < 1.0 { 1usize } else { dim as usize };
2796 let rows = t.rows();
2797 let cols = t.cols();
2798 if dim == 1 {
2799 let mut out = vec![0.0f64; cols];
2800 for (c, oc) in out.iter_mut().enumerate().take(cols) {
2801 let mut v = 0.0;
2802 for r in 0..rows {
2803 if t.data[r + c * rows] != 0.0 {
2804 v = 1.0;
2805 break;
2806 }
2807 }
2808 *oc = v;
2809 }
2810 Ok(Value::Tensor(
2811 runmat_builtins::Tensor::new(out, vec![1, cols]).map_err(|e| format!("any: {e}"))?,
2812 ))
2813 } else if dim == 2 {
2814 let mut out = vec![0.0f64; rows];
2815 for (r, orow) in out.iter_mut().enumerate().take(rows) {
2816 let mut v = 0.0;
2817 for c in 0..cols {
2818 if t.data[r + c * rows] != 0.0 {
2819 v = 1.0;
2820 break;
2821 }
2822 }
2823 *orow = v;
2824 }
2825 Ok(Value::Tensor(
2826 runmat_builtins::Tensor::new(out, vec![rows, 1]).map_err(|e| format!("any: {e}"))?,
2827 ))
2828 } else {
2829 Err("any: dim out of range".to_string())
2830 }
2831}
2832
2833#[runmat_macros::runtime_builtin(name = "any")]
2834fn any_var_builtin(a: Value, rest: Vec<Value>) -> Result<Value, String> {
2835 if rest.is_empty() {
2836 return any_all_or_cols(a);
2837 }
2838 if rest.len() == 1 {
2839 match &rest[0] {
2840 Value::Num(d) => return any_dim(a, *d),
2841 Value::Int(i) => return any_dim(a, i.to_i64() as f64),
2842 _ => {}
2843 }
2844 }
2845 Err("any: unsupported arguments".to_string())
2846}
2847
2848fn all_all_or_cols(a: Value) -> Result<Value, String> {
2849 match a {
2850 Value::Tensor(t) => {
2851 let rows = t.rows();
2852 let cols = t.cols();
2853 if rows > 1 && cols > 1 {
2854 let mut out = vec![0.0f64; cols];
2855 for (c, oc) in out.iter_mut().enumerate().take(cols) {
2856 let mut v = 1.0;
2857 for r in 0..rows {
2858 if t.data[r + c * rows] == 0.0 {
2859 v = 0.0;
2860 break;
2861 }
2862 }
2863 *oc = v;
2864 }
2865 Ok(Value::Tensor(
2866 runmat_builtins::Tensor::new(out, vec![1, cols])
2867 .map_err(|e| format!("all: {e}"))?,
2868 ))
2869 } else {
2870 Ok(Value::Num(if t.data.iter().all(|&x| x != 0.0) {
2871 1.0
2872 } else {
2873 0.0
2874 }))
2875 }
2876 }
2877 _ => Err("all: expected tensor".to_string()),
2878 }
2879}
2880
2881fn all_dim(a: Value, dim: f64) -> Result<Value, String> {
2882 let t = match a {
2883 Value::Tensor(t) => t,
2884 _ => return Err("all: expected tensor".to_string()),
2885 };
2886 let dim = if dim < 1.0 { 1usize } else { dim as usize };
2887 let rows = t.rows();
2888 let cols = t.cols();
2889 if dim == 1 {
2890 let mut out = vec![0.0f64; cols];
2891 for (c, oc) in out.iter_mut().enumerate().take(cols) {
2892 let mut v = 1.0;
2893 for r in 0..rows {
2894 if t.data[r + c * rows] == 0.0 {
2895 v = 0.0;
2896 break;
2897 }
2898 }
2899 *oc = v;
2900 }
2901 Ok(Value::Tensor(
2902 runmat_builtins::Tensor::new(out, vec![1, cols]).map_err(|e| format!("all: {e}"))?,
2903 ))
2904 } else if dim == 2 {
2905 let mut out = vec![0.0f64; rows];
2906 for (r, orow) in out.iter_mut().enumerate().take(rows) {
2907 let mut v = 1.0;
2908 for c in 0..cols {
2909 if t.data[r + c * rows] == 0.0 {
2910 v = 0.0;
2911 break;
2912 }
2913 }
2914 *orow = v;
2915 }
2916 Ok(Value::Tensor(
2917 runmat_builtins::Tensor::new(out, vec![rows, 1]).map_err(|e| format!("all: {e}"))?,
2918 ))
2919 } else {
2920 Err("all: dim out of range".to_string())
2921 }
2922}
2923
2924#[runmat_macros::runtime_builtin(name = "all")]
2925fn all_var_builtin(a: Value, rest: Vec<Value>) -> Result<Value, String> {
2926 if rest.is_empty() {
2927 return all_all_or_cols(a);
2928 }
2929 if rest.len() == 1 {
2930 match &rest[0] {
2931 Value::Num(d) => return all_dim(a, *d),
2932 Value::Int(i) => return all_dim(a, i.to_i64() as f64),
2933 _ => {}
2934 }
2935 }
2936 Err("all: unsupported arguments".to_string())
2937}
2938
2939#[runmat_macros::runtime_builtin(name = "squeeze")]
2942fn squeeze_builtin(a: Value) -> Result<Value, String> {
2943 let t = match a {
2944 Value::Tensor(t) => t,
2945 Value::StringArray(_) => return Err("squeeze: not supported for string arrays".to_string()),
2946 Value::CharArray(_) => return Err("squeeze: not supported for char arrays".to_string()),
2947 _ => return Err("squeeze: expected tensor".to_string()),
2948 };
2949 let mut new_shape: Vec<usize> = t.shape.iter().copied().filter(|&d| d != 1).collect();
2950 if new_shape.is_empty() {
2951 new_shape.push(1);
2952 }
2953 Ok(Value::Tensor(
2954 runmat_builtins::Tensor::new(t.data.clone(), new_shape)
2955 .map_err(|e| format!("squeeze: {e}"))?,
2956 ))
2957}
2958
2959#[runmat_macros::runtime_builtin(name = "permute")]
2960fn permute_builtin(a: Value, order: Value) -> Result<Value, String> {
2961 let t = match a {
2962 Value::Tensor(t) => t,
2963 Value::StringArray(_) => return Err("permute: not supported for string arrays".to_string()),
2964 Value::CharArray(_) => return Err("permute: not supported for char arrays".to_string()),
2965 _ => return Err("permute: expected tensor".to_string()),
2966 };
2967 let ord = match order {
2968 Value::Tensor(idx) => idx.data.iter().map(|&v| v as usize).collect::<Vec<usize>>(),
2969 Value::Cell(c) => c
2970 .data
2971 .iter()
2972 .map(|v| match &**v {
2973 Value::Num(n) => *n as usize,
2974 Value::Int(i) => i.to_i64() as usize,
2975 _ => 0,
2976 })
2977 .collect(),
2978 Value::Num(n) => vec![n as usize],
2979 _ => return Err("permute: expected index vector".to_string()),
2980 };
2981 if ord.contains(&0) {
2982 return Err("permute: indices are 1-based".to_string());
2983 }
2984 let ord0: Vec<usize> = ord.into_iter().map(|k| k - 1).collect();
2985 let rank = t.shape.len();
2986 if ord0.len() != rank {
2987 return Err("permute: order length must match rank".to_string());
2988 }
2989 let mut new_shape = vec![0usize; rank];
2990 for (i, &src) in ord0.iter().enumerate() {
2991 new_shape[i] = *t.shape.get(src).unwrap_or(&1);
2992 }
2993 let mut src_strides = vec![0usize; rank];
2995 let mut acc = 1usize;
2996 for (d, stride) in src_strides.iter_mut().enumerate().take(rank) {
2997 *stride = acc;
2998 acc *= t.shape[d];
2999 }
3000 let mut dst_strides = vec![0usize; rank];
3001 let mut acc2 = 1usize;
3002 for (d, stride) in dst_strides.iter_mut().enumerate().take(rank) {
3003 *stride = acc2;
3004 acc2 *= new_shape[d];
3005 }
3006 let total = t.data.len();
3007 let mut out = vec![0f64; total];
3008 fn unrank(mut lin: usize, shape: &[usize]) -> Vec<usize> {
3010 let mut idx = Vec::with_capacity(shape.len());
3011 for &s in shape {
3012 idx.push(lin % s);
3013 lin /= s;
3014 }
3015 idx
3016 }
3017 for (dst_lin, item) in out.iter_mut().enumerate().take(total) {
3018 let dst_multi = unrank(dst_lin, &new_shape);
3019 let mut src_multi = vec![0usize; rank];
3021 for (dst_d, &src_d) in ord0.iter().enumerate() {
3022 src_multi[src_d] = dst_multi[dst_d];
3023 }
3024 let mut src_lin = 0usize;
3025 for d in 0..rank {
3026 src_lin += src_multi[d] * src_strides[d];
3027 }
3028 *item = t.data[src_lin];
3029 }
3030 Ok(Value::Tensor(
3031 runmat_builtins::Tensor::new(out, new_shape).map_err(|e| format!("permute: {e}"))?,
3032 ))
3033}
3034
3035#[runmat_macros::runtime_builtin(name = "diag")]
3038fn diag_builtin(a: Value) -> Result<Value, String> {
3039 match a {
3040 Value::Tensor(t) => {
3041 let rows = t.rows();
3042 let cols = t.cols();
3043 if rows == 1 || cols == 1 {
3044 let n = rows.max(cols);
3046 let mut data = vec![0.0; n * n];
3047 for (i, slot) in data.iter_mut().enumerate().step_by(n + 1).take(n) {
3048 let idx = i / (n + 1);
3050 let val = t.data[idx];
3051 *slot = val;
3052 }
3053 Ok(Value::Tensor(
3054 runmat_builtins::Tensor::new(data, vec![n, n])
3055 .map_err(|e| format!("diag: {e}"))?,
3056 ))
3057 } else {
3058 let n = rows.min(cols);
3060 let mut data = vec![0.0; n];
3061 for (i, slot) in data.iter_mut().enumerate().take(n) {
3062 *slot = t.data[i + i * rows];
3063 }
3064 Ok(Value::Tensor(
3065 runmat_builtins::Tensor::new(data, vec![n, 1])
3066 .map_err(|e| format!("diag: {e}"))?,
3067 ))
3068 }
3069 }
3070 _ => Err("diag: expected tensor".to_string()),
3071 }
3072}
3073
3074#[runmat_macros::runtime_builtin(name = "triu")]
3075fn triu_builtin(a: Value) -> Result<Value, String> {
3076 let t = match a {
3077 Value::Tensor(t) => t,
3078 _ => return Err("triu: expected tensor".to_string()),
3079 };
3080 let rows = t.rows();
3081 let cols = t.cols();
3082 let mut out = vec![0.0; rows * cols];
3083 for c in 0..cols {
3084 for r in 0..rows {
3085 if r <= c {
3086 out[r + c * rows] = t.data[r + c * rows];
3087 }
3088 }
3089 }
3090 Ok(Value::Tensor(
3091 runmat_builtins::Tensor::new(out, vec![rows, cols]).map_err(|e| format!("triu: {e}"))?,
3092 ))
3093}
3094
3095#[runmat_macros::runtime_builtin(name = "tril")]
3096fn tril_builtin(a: Value) -> Result<Value, String> {
3097 let t = match a {
3098 Value::Tensor(t) => t,
3099 _ => return Err("tril: expected tensor".to_string()),
3100 };
3101 let rows = t.rows();
3102 let cols = t.cols();
3103 let mut out = vec![0.0; rows * cols];
3104 for c in 0..cols {
3105 for r in 0..rows {
3106 if r >= c {
3107 out[r + c * rows] = t.data[r + c * rows];
3108 }
3109 }
3110 }
3111 Ok(Value::Tensor(
3112 runmat_builtins::Tensor::new(out, vec![rows, cols]).map_err(|e| format!("tril: {e}"))?,
3113 ))
3114}
3115
3116#[runmat_macros::runtime_builtin(name = "cat")]
3117fn cat_var_builtin(dim: f64, rest: Vec<Value>) -> Result<Value, String> {
3118 if rest.len() < 2 {
3119 return Err("cat: expects at least two arrays".to_string());
3120 }
3121 let d = if dim < 1.0 { 1usize } else { dim as usize } - 1; if rest
3124 .iter()
3125 .any(|v| matches!(v, Value::StringArray(_) | Value::String(_)))
3126 {
3127 let mut arrs: Vec<runmat_builtins::StringArray> = Vec::new();
3128 for v in rest {
3129 match v {
3130 Value::StringArray(sa) => arrs.push(sa),
3131 Value::String(s) => arrs.push(
3132 runmat_builtins::StringArray::new(vec![s], vec![1, 1])
3133 .map_err(|e| format!("cat: {e}"))?,
3134 ),
3135 Value::Num(n) => arrs.push(
3136 runmat_builtins::StringArray::new(vec![n.to_string()], vec![1, 1])
3137 .map_err(|e| format!("cat: {e}"))?,
3138 ),
3139 Value::Int(i) => arrs.push(
3140 runmat_builtins::StringArray::new(vec![i.to_i64().to_string()], vec![1, 1])
3141 .map_err(|e| format!("cat: {e}"))?,
3142 ),
3143 other => {
3144 return Err(format!(
3145 "cat: expected string arrays/strings or scalars, got {other:?}"
3146 ))
3147 }
3148 }
3149 }
3150 let rank = arrs
3151 .iter()
3152 .map(|a| a.shape.len())
3153 .max()
3154 .unwrap_or(2)
3155 .max(d + 1);
3156 let shapes: Vec<Vec<usize>> = arrs
3157 .iter()
3158 .map(|a| {
3159 let mut s = a.shape.clone();
3160 if s.len() < rank {
3161 s.resize(rank, 1);
3162 }
3163 s
3164 })
3165 .collect();
3166 for k in 0..rank {
3167 if k == d {
3168 continue;
3169 }
3170 let first = shapes[0][k];
3171 if !shapes.iter().all(|s| s[k] == first) {
3172 return Err("cat: dimension mismatch".to_string());
3173 }
3174 }
3175 let mut out_shape = shapes[0].clone();
3176 out_shape[d] = shapes.iter().map(|s| s[d]).sum();
3177 fn strides(shape: &[usize]) -> Vec<usize> {
3178 let mut s = vec![0; shape.len()];
3179 let mut acc = 1;
3180 for i in 0..shape.len() {
3181 s[i] = acc;
3182 acc *= shape[i];
3183 }
3184 s
3185 }
3186 let out_str = strides(&out_shape);
3187 let mut out: Vec<String> = vec![String::new(); out_shape.iter().product()];
3188 let mut offset = 0usize;
3189 for (a, s) in arrs.iter().zip(shapes.iter()) {
3190 let s_str = strides(s);
3191 let total: usize = s.iter().product();
3192 let rank = out_shape.len();
3193 for idx_lin in 0..total {
3194 let mut rem = idx_lin;
3195 let mut src_multi = vec![0usize; rank];
3196 for i in 0..rank {
3197 let si = s[i];
3198 src_multi[i] = rem % si;
3199 rem /= si;
3200 }
3201 let mut dst_multi = src_multi.clone();
3202 dst_multi[d] += offset;
3203 let mut s_lin = 0usize;
3204 for i in 0..rank {
3205 s_lin += src_multi[i] * s_str[i];
3206 }
3207 let mut d_lin = 0usize;
3208 for i in 0..rank {
3209 d_lin += dst_multi[i] * out_str[i];
3210 }
3211 out[d_lin] = a.data[s_lin].clone();
3212 }
3213 offset += s[d];
3214 }
3215 return Ok(Value::StringArray(
3216 runmat_builtins::StringArray::new(out, out_shape).map_err(|e| format!("cat: {e}"))?,
3217 ));
3218 }
3219 let tensors: Vec<runmat_builtins::Tensor> =
3221 rest.into_iter()
3222 .map(|v| match v {
3223 Value::Tensor(t) => Ok(t),
3224 Value::Num(n) => runmat_builtins::Tensor::new(vec![n], vec![1, 1])
3225 .map_err(|e| format!("cat: {e}")),
3226 Value::Int(i) => runmat_builtins::Tensor::new(vec![i.to_f64()], vec![1, 1])
3227 .map_err(|e| format!("cat: {e}")),
3228 Value::Bool(b) => {
3229 runmat_builtins::Tensor::new(vec![if b { 1.0 } else { 0.0 }], vec![1, 1])
3230 .map_err(|e| format!("cat: {e}"))
3231 }
3232 other => Err(format!("cat: expected tensors or scalars, got {other:?}")),
3233 })
3234 .collect::<Result<_, _>>()?;
3235 let mut rank = tensors.iter().map(|t| t.shape.len()).max().unwrap_or(2);
3236 if d + 1 > rank {
3237 rank = d + 1;
3238 }
3239 let shapes: Vec<Vec<usize>> = tensors
3240 .iter()
3241 .map(|t| {
3242 let mut s = t.shape.clone();
3243 s.resize(rank, 1);
3244 s
3245 })
3246 .collect();
3247 for k in 0..rank {
3248 if k == d {
3249 continue;
3250 }
3251 let first = shapes[0][k];
3252 if !shapes.iter().all(|s| s[k] == first) {
3253 return Err("cat: dimension mismatch".to_string());
3254 }
3255 }
3256 let mut out_shape = shapes[0].clone();
3257 out_shape[d] = shapes.iter().map(|s| s[d]).sum();
3258 let mut out = vec![0f64; out_shape.iter().product()];
3259 fn strides(shape: &[usize]) -> Vec<usize> {
3260 let mut s = vec![0; shape.len()];
3261 let mut acc = 1;
3262 for i in 0..shape.len() {
3263 s[i] = acc;
3264 acc *= shape[i];
3265 }
3266 s
3267 }
3268 let out_str = strides(&out_shape);
3269 let mut offset = 0usize;
3270 for (t, s) in tensors.iter().zip(shapes.iter()) {
3271 let s_str = strides(s);
3272 let rank = out_shape.len();
3273 let total: usize = s.iter().product();
3274 for idx_lin in 0..total {
3275 let mut rem = idx_lin;
3277 let mut src_multi = vec![0usize; rank];
3278 for i in 0..rank {
3279 let si = s[i];
3280 src_multi[i] = rem % si;
3281 rem /= si;
3282 }
3283 let mut dst_multi = src_multi.clone();
3284 dst_multi[d] += offset;
3285 let mut s_lin = 0usize;
3286 for i in 0..rank {
3287 s_lin += src_multi[i] * s_str[i];
3288 }
3289 let mut d_lin = 0usize;
3290 for i in 0..rank {
3291 d_lin += dst_multi[i] * out_str[i];
3292 }
3293 out[d_lin] = t.data[s_lin];
3294 }
3295 offset += s[d];
3296 }
3297 Ok(Value::Tensor(
3298 runmat_builtins::Tensor::new(out, out_shape).map_err(|e| format!("cat: {e}"))?,
3299 ))
3300}
3301
3302#[inline]
3304fn repmat_builtin(a: Value, m: f64, n: f64) -> Result<Value, String> {
3305 let t = match a {
3306 Value::Tensor(t) => t,
3307 _ => return Err("repmat: expected tensor".to_string()),
3308 };
3309 let m = if m < 1.0 { 1usize } else { m as usize };
3310 let n = if n < 1.0 { 1usize } else { n as usize };
3311 let mut shape = t.shape.clone();
3312 if shape.len() < 2 {
3313 shape.resize(2, 1);
3314 }
3315 let base_rows = shape[0];
3316 let base_cols = shape[1];
3317 let out_rows = base_rows * m;
3318 let out_cols = base_cols * n;
3319 let mut out = vec![0f64; out_rows * out_cols];
3320 for rep_c in 0..n {
3321 for rep_r in 0..m {
3322 for c in 0..base_cols {
3323 for r in 0..base_rows {
3324 let src = t.data[r + c * base_rows];
3325 let dr = r + rep_r * base_rows;
3326 let dc = c + rep_c * base_cols;
3327 out[dr + dc * out_rows] = src;
3328 }
3329 }
3330 }
3331 }
3332 Ok(Value::Tensor(
3333 runmat_builtins::Tensor::new(out, vec![out_rows, out_cols])
3334 .map_err(|e| format!("repmat: {e}"))?,
3335 ))
3336}
3337
3338#[runmat_macros::runtime_builtin(name = "repmat")]
3339fn repmat_nd_builtin(a: Value, rest: Vec<Value>) -> Result<Value, String> {
3340 let t = match a {
3341 Value::Tensor(t) => t,
3342 _ => return Err("repmat: expected tensor".to_string()),
3343 };
3344 let mut reps: Vec<usize> = Vec::new();
3346 if rest.len() == 1 {
3347 match &rest[0] {
3348 Value::Tensor(v) => {
3349 for &x in &v.data {
3350 reps.push(if x < 1.0 { 1 } else { x as usize });
3351 }
3352 }
3353 _ => return Err("repmat: expected replication vector".to_string()),
3354 }
3355 } else {
3356 for v in rest {
3357 match v {
3358 Value::Num(n) => reps.push(if n < 1.0 { 1 } else { n as usize }),
3359 Value::Int(i) => {
3360 let ii = i.to_i64();
3361 reps.push(if ii < 1 { 1 } else { ii as usize })
3362 }
3363 _ => return Err("repmat: expected numeric reps".to_string()),
3364 }
3365 }
3366 }
3367 if reps.len() == 2 {
3369 return repmat_builtin(Value::Tensor(t), reps[0] as f64, reps[1] as f64);
3370 }
3371 let rank = t.shape.len().max(reps.len());
3372 let mut base = t.shape.clone();
3373 base.resize(rank, 1);
3374 reps.resize(rank, 1);
3375 let mut out_shape = base.clone();
3376 for i in 0..rank {
3377 out_shape[i] = base[i] * reps[i];
3378 }
3379 let mut out = vec![0f64; out_shape.iter().product()];
3380 fn strides(shape: &[usize]) -> Vec<usize> {
3381 let mut s = vec![0; shape.len()];
3382 let mut acc = 1;
3383 for i in 0..shape.len() {
3384 s[i] = acc;
3385 acc *= shape[i];
3386 }
3387 s
3388 }
3389 let src_str = strides(&base);
3390 let total: usize = out_shape.iter().product();
3392 for (d_lin, item) in out.iter_mut().enumerate().take(total) {
3393 let mut rem = d_lin;
3395 let mut multi = vec![0usize; rank];
3396 for i in 0..rank {
3397 let s = out_shape[i];
3398 multi[i] = rem % s;
3399 rem /= s;
3400 }
3401 let mut src_lin = 0usize;
3403 for i in 0..rank {
3404 let coord = multi[i] % base[i];
3405 src_lin += coord * src_str[i];
3406 }
3407 *item = t.data[src_lin];
3408 }
3409 Ok(Value::Tensor(
3410 runmat_builtins::Tensor::new(out, out_shape).map_err(|e| format!("repmat: {e}"))?,
3411 ))
3412}
3413
3414#[runmat_macros::runtime_builtin(name = "sprintf")]
3419fn sprintf_builtin(fmt: String, rest: Vec<Value>) -> Result<Value, String> {
3420 let s = format_variadic(&fmt, &rest)?;
3421 Ok(Value::String(s))
3422}
3423
3424#[runmat_macros::runtime_builtin(name = "fprintf")]
3425fn fprintf_builtin(first: Value, rest: Vec<Value>) -> Result<Value, String> {
3426 let (fmt, args) = match first {
3428 Value::String(s) => (s, rest),
3429 Value::Num(_) | Value::Int(_) => {
3430 if rest.is_empty() {
3432 return Err("fprintf: missing format string".to_string());
3433 }
3434 let fmt = match &rest[0] {
3435 Value::String(s) => s.clone(),
3436 _ => return Err("fprintf: expected format string".to_string()),
3437 };
3438 (fmt, rest[1..].to_vec())
3439 }
3440 other => return Err(format!("fprintf: unsupported first argument {other:?}")),
3441 };
3442 let s = format_variadic(&fmt, &args)?;
3443 println!("{s}");
3444 Ok(Value::Num(s.len() as f64))
3445}
3446
3447#[runmat_macros::runtime_builtin(name = "warning")]
3448fn warning_builtin(fmt: String, rest: Vec<Value>) -> Result<Value, String> {
3449 let s = format_variadic(&fmt, &rest)?;
3450 eprintln!("Warning: {s}");
3451 Ok(Value::Num(0.0))
3452}
3453
3454#[runmat_macros::runtime_builtin(name = "disp")]
3455fn disp_builtin(x: Value) -> Result<Value, String> {
3456 match x {
3457 Value::String(s) => println!("{s}"),
3458 Value::Num(n) => println!("{n}"),
3459 Value::Int(i) => println!("{}", i.to_i64()),
3460 Value::Tensor(t) => println!("{:?}", t.data),
3461 other => println!("{other:?}"),
3462 }
3463 Ok(Value::Num(0.0))
3464}
3465
3466#[runmat_macros::runtime_builtin(name = "struct")]
3467fn struct_builtin(rest: Vec<Value>) -> Result<Value, String> {
3468 if !rest.len().is_multiple_of(2) {
3469 return Err("struct: expected name/value pairs".to_string());
3470 }
3471 let mut st = runmat_builtins::StructValue::new();
3472 let mut i = 0usize;
3473 while i < rest.len() {
3474 let key: String = (&rest[i]).try_into()?;
3475 let val = rest[i + 1].clone();
3476 st.fields.insert(key, val);
3477 i += 2;
3478 }
3479 Ok(Value::Struct(st))
3480}
3481
3482fn format_variadic(fmt: &str, args: &[Value]) -> Result<String, String> {
3483 let mut out = String::with_capacity(fmt.len() + args.len() * 8);
3485 let mut it = fmt.chars().peekable();
3486 let mut ai = 0usize;
3487 while let Some(c) = it.next() {
3488 if c != '%' {
3489 out.push(c);
3490 continue;
3491 }
3492 if let Some('%') = it.peek() {
3493 it.next();
3494 out.push('%');
3495 continue;
3496 }
3497 let mut precision: Option<usize> = None;
3499 while let Some(ch) = it.peek() {
3501 if ch.is_ascii_digit() {
3502 it.next();
3503 } else {
3504 break;
3505 }
3506 }
3507 if let Some('.') = it.peek() {
3509 it.next();
3510 let mut p = String::new();
3511 while let Some(ch) = it.peek() {
3512 if ch.is_ascii_digit() {
3513 p.push(*ch);
3514 it.next();
3515 } else {
3516 break;
3517 }
3518 }
3519 if !p.is_empty() {
3520 precision = p.parse::<usize>().ok();
3521 }
3522 }
3523 let ty = it.next().ok_or("sprintf: incomplete format specifier")?;
3524 let val = args.get(ai).cloned().unwrap_or(Value::Num(0.0));
3525 ai += 1;
3526 match ty {
3527 'd' | 'i' => {
3528 let v: f64 = (&val).try_into()?;
3529 out.push_str(&(v as i64).to_string());
3530 }
3531 'f' => {
3532 let v: f64 = (&val).try_into()?;
3533 if let Some(p) = precision {
3534 out.push_str(&format!("{v:.p$}"));
3535 } else {
3536 out.push_str(&format!("{v}"));
3537 }
3538 }
3539 's' => match val {
3540 Value::String(s) => out.push_str(&s),
3541 Value::Num(n) => out.push_str(&n.to_string()),
3542 Value::Int(i) => out.push_str(&i.to_i64().to_string()),
3543 Value::Tensor(t) => out.push_str(&format!("{:?}", t.data)),
3544 other => out.push_str(&format!("{other:?}")),
3545 },
3546 other => return Err(format!("sprintf: unsupported format %{other}")),
3547 }
3548 }
3549 Ok(out)
3550}
3551
3552#[runmat_macros::runtime_builtin(name = "getmethod")]
3553fn getmethod_builtin(obj: Value, name: String) -> Result<Value, String> {
3554 match obj {
3555 Value::Object(o) => {
3556 Ok(Value::Closure(runmat_builtins::Closure {
3558 function_name: "call_method".to_string(),
3559 captures: vec![Value::Object(o), Value::String(name)],
3560 }))
3561 }
3562 Value::ClassRef(cls) => Ok(Value::String(format!("@{cls}.{name}"))),
3563 other => Err(format!("getmethod unsupported on {other:?}")),
3564 }
3565}