1pub use inventory;
2pub mod symbolic;
3use runmat_gc_api::{GcHandle, Trace, Tracer};
4use runmat_thread_local::runmat_thread_local;
5use std::cell::RefCell;
6use std::collections::HashMap;
7use std::collections::HashSet;
8use std::convert::TryFrom;
9use std::fmt;
10use std::future::Future;
11use std::pin::Pin;
12use std::sync::Mutex;
13use std::thread::ThreadId;
14pub use symbolic::{SymbolicExpr, SymbolicFunction};
15
16use indexmap::IndexMap;
17#[cfg(not(target_arch = "wasm32"))]
18use std::sync::OnceLock;
19
20#[cfg(target_arch = "wasm32")]
21pub mod wasm_registry {
22 use super::{BuiltinDoc, BuiltinFunction, Constant};
23 use std::cell::{Cell, RefCell};
24
25 thread_local! {
26 static FUNCTIONS: RefCell<Vec<&'static BuiltinFunction>> = const { RefCell::new(Vec::new()) };
27 static CONSTANTS: RefCell<Vec<&'static Constant>> = const { RefCell::new(Vec::new()) };
28 static DOCS: RefCell<Vec<&'static BuiltinDoc>> = const { RefCell::new(Vec::new()) };
29 static REGISTERED: Cell<bool> = const { Cell::new(false) };
30 }
31
32 fn leak<T>(value: T) -> &'static T {
33 Box::leak(Box::new(value))
34 }
35
36 pub fn submit_builtin_function(func: BuiltinFunction) {
37 let leaked = leak(func);
38 FUNCTIONS.with_borrow_mut(|functions| functions.push(leaked));
39 }
40
41 pub fn submit_constant(constant: Constant) {
42 let leaked = leak(constant);
43 CONSTANTS.with_borrow_mut(|constants| constants.push(leaked));
44 }
45
46 pub fn submit_builtin_doc(doc: BuiltinDoc) {
47 let leaked = leak(doc);
48 DOCS.with_borrow_mut(|docs| docs.push(leaked));
49 }
50
51 pub fn builtin_functions() -> Vec<&'static BuiltinFunction> {
52 FUNCTIONS.with_borrow(Clone::clone)
53 }
54
55 pub fn constants() -> Vec<&'static Constant> {
56 CONSTANTS.with_borrow(Clone::clone)
57 }
58
59 pub fn builtin_docs() -> Vec<&'static BuiltinDoc> {
60 DOCS.with_borrow(Clone::clone)
61 }
62
63 pub fn mark_registered() {
64 REGISTERED.set(true);
65 }
66
67 pub fn is_registered() -> bool {
68 REGISTERED.get()
69 }
70}
71
72#[derive(Debug, Clone, PartialEq)]
73pub enum Value {
74 Int(IntValue),
75 Num(f64),
76 Complex(f64, f64),
78 Bool(bool),
79 LogicalArray(LogicalArray),
81 String(String),
82 StringArray(StringArray),
84 CharArray(CharArray),
86 Tensor(Tensor),
87 SparseTensor(SparseTensor),
89 ComplexTensor(ComplexTensor),
91 Symbolic(SymbolicExpr),
93 Cell(CellArray),
94 Struct(StructValue),
97 GpuTensor(runmat_accelerate_api::GpuTensorHandle),
99 Object(ObjectInstance),
101 HandleObject(HandleRef),
103 Listener(Listener),
105 OutputList(Vec<Value>),
107 FunctionHandle(String),
109 ExternalFunctionHandle(String),
111 MethodFunctionHandle(String),
113 BoundFunctionHandle {
115 name: String,
116 function: usize,
117 },
118 Closure(Closure),
119 ClassRef(String),
120 MException(MException),
121}
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub enum IntValue {
124 I8(i8),
125 I16(i16),
126 I32(i32),
127 I64(i64),
128 U8(u8),
129 U16(u16),
130 U32(u32),
131 U64(u64),
132}
133
134impl IntValue {
135 pub fn to_i64(&self) -> i64 {
136 match self {
137 IntValue::I8(v) => *v as i64,
138 IntValue::I16(v) => *v as i64,
139 IntValue::I32(v) => *v as i64,
140 IntValue::I64(v) => *v,
141 IntValue::U8(v) => *v as i64,
142 IntValue::U16(v) => *v as i64,
143 IntValue::U32(v) => *v as i64,
144 IntValue::U64(v) => {
145 if *v > i64::MAX as u64 {
146 i64::MAX
147 } else {
148 *v as i64
149 }
150 }
151 }
152 }
153 pub fn to_f64(&self) -> f64 {
154 self.to_i64() as f64
155 }
156 pub fn is_zero(&self) -> bool {
157 self.to_i64() == 0
158 }
159 pub fn class_name(&self) -> &'static str {
160 match self {
161 IntValue::I8(_) => "int8",
162 IntValue::I16(_) => "int16",
163 IntValue::I32(_) => "int32",
164 IntValue::I64(_) => "int64",
165 IntValue::U8(_) => "uint8",
166 IntValue::U16(_) => "uint16",
167 IntValue::U32(_) => "uint32",
168 IntValue::U64(_) => "uint64",
169 }
170 }
171}
172
173#[derive(Debug, Clone, PartialEq)]
174pub struct StructValue {
175 pub fields: IndexMap<String, Value>,
176}
177
178impl StructValue {
179 pub fn new() -> Self {
180 Self {
181 fields: IndexMap::new(),
182 }
183 }
184
185 pub fn insert(&mut self, name: impl Into<String>, value: Value) -> Option<Value> {
187 self.fields.insert(name.into(), value)
188 }
189
190 pub fn remove(&mut self, name: &str) -> Option<Value> {
192 self.fields.shift_remove(name)
193 }
194
195 pub fn field_names(&self) -> impl Iterator<Item = &String> {
197 self.fields.keys()
198 }
199}
200
201impl Default for StructValue {
202 fn default() -> Self {
203 Self::new()
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
208pub enum NumericDType {
209 F64,
210 F32,
211 U8,
212 U16,
213}
214
215impl NumericDType {
216 pub fn class_name(self) -> &'static str {
217 match self {
218 NumericDType::F64 => "double",
219 NumericDType::F32 => "single",
220 NumericDType::U8 => "uint8",
221 NumericDType::U16 => "uint16",
222 }
223 }
224
225 pub fn byte_size(self) -> usize {
226 match self {
227 NumericDType::F64 => 8,
228 NumericDType::F32 => 4,
229 NumericDType::U8 => 1,
230 NumericDType::U16 => 2,
231 }
232 }
233}
234
235#[derive(Debug, Clone, PartialEq)]
236pub struct Tensor {
237 pub data: Vec<f64>,
238 pub shape: Vec<usize>, pub rows: usize, pub cols: usize, pub dtype: NumericDType,
243}
244
245#[derive(Debug, Clone, PartialEq)]
246pub struct SparseTensor {
247 pub rows: usize,
248 pub cols: usize,
249 pub col_ptrs: Vec<usize>,
251 pub row_indices: Vec<usize>,
253 pub values: Vec<f64>,
254}
255
256#[derive(Debug, Clone, PartialEq)]
257pub struct ComplexTensor {
258 pub data: Vec<(f64, f64)>,
259 pub shape: Vec<usize>,
260 pub rows: usize,
261 pub cols: usize,
262}
263
264#[derive(Debug, Clone, PartialEq)]
265pub struct StringArray {
266 pub data: Vec<String>,
267 pub shape: Vec<usize>,
268 pub rows: usize,
269 pub cols: usize,
270}
271
272#[derive(Debug, Clone, PartialEq)]
273pub struct LogicalArray {
274 pub data: Vec<u8>, pub shape: Vec<usize>,
276}
277
278impl LogicalArray {
279 pub fn new(data: Vec<u8>, shape: Vec<usize>) -> Result<Self, String> {
280 let expected: usize = shape.iter().product();
281 if data.len() != expected {
282 return Err(format!(
283 "LogicalArray data length {} doesn't match shape {:?} ({} elements)",
284 data.len(),
285 shape,
286 expected
287 ));
288 }
289 let mut d = data;
291 for v in &mut d {
292 *v = if *v != 0 { 1 } else { 0 };
293 }
294 Ok(LogicalArray { data: d, shape })
295 }
296 pub fn zeros(shape: Vec<usize>) -> Self {
297 let expected: usize = shape.iter().product();
298 LogicalArray {
299 data: vec![0u8; expected],
300 shape,
301 }
302 }
303 pub fn len(&self) -> usize {
304 self.data.len()
305 }
306 pub fn is_empty(&self) -> bool {
307 self.data.is_empty()
308 }
309}
310
311#[derive(Debug, Clone, PartialEq)]
312pub struct CharArray {
313 pub data: Vec<char>,
314 pub rows: usize,
315 pub cols: usize,
316}
317
318impl CharArray {
319 pub fn new_row(s: &str) -> Self {
320 CharArray {
321 data: s.chars().collect(),
322 rows: 1,
323 cols: s.chars().count(),
324 }
325 }
326 pub fn new(data: Vec<char>, rows: usize, cols: usize) -> Result<Self, String> {
327 if rows * cols != data.len() {
328 return Err(format!(
329 "Char data length {} doesn't match dimensions {}x{}",
330 data.len(),
331 rows,
332 cols
333 ));
334 }
335 Ok(CharArray { data, rows, cols })
336 }
337}
338
339impl StringArray {
340 pub fn new(data: Vec<String>, shape: Vec<usize>) -> Result<Self, String> {
341 let expected: usize = shape.iter().product();
342 if data.len() != expected {
343 return Err(format!(
344 "StringArray data length {} doesn't match shape {:?} ({} elements)",
345 data.len(),
346 shape,
347 expected
348 ));
349 }
350 let (rows, cols) = if shape.len() >= 2 {
351 (shape[0], shape[1])
352 } else if shape.len() == 1 {
353 (1, shape[0])
354 } else {
355 (0, 0)
356 };
357 Ok(StringArray {
358 data,
359 shape,
360 rows,
361 cols,
362 })
363 }
364 pub fn new_2d(data: Vec<String>, rows: usize, cols: usize) -> Result<Self, String> {
365 Self::new(data, vec![rows, cols])
366 }
367 pub fn rows(&self) -> usize {
368 self.shape.first().copied().unwrap_or(1)
369 }
370 pub fn cols(&self) -> usize {
371 self.shape.get(1).copied().unwrap_or(1)
372 }
373}
374
375impl Tensor {
378 pub fn new(data: Vec<f64>, shape: Vec<usize>) -> Result<Self, String> {
379 let expected: usize = shape.iter().product();
380 if data.len() != expected {
381 return Err(format!(
382 "Tensor data length {} doesn't match shape {:?} ({} elements)",
383 data.len(),
384 shape,
385 expected
386 ));
387 }
388 let (rows, cols) = if shape.len() >= 2 {
389 (shape[0], shape[1])
390 } else if shape.len() == 1 {
391 (1, shape[0])
392 } else {
393 (0, 0)
394 };
395 Ok(Tensor {
396 data,
397 shape,
398 rows,
399 cols,
400 dtype: NumericDType::F64,
401 })
402 }
403
404 pub fn new_2d(data: Vec<f64>, rows: usize, cols: usize) -> Result<Self, String> {
405 Self::new(data, vec![rows, cols])
406 }
407
408 pub fn from_f32(data: Vec<f32>, shape: Vec<usize>) -> Result<Self, String> {
409 let converted: Vec<f64> = data.into_iter().map(|v| v as f64).collect();
410 Self::new_with_dtype(converted, shape, NumericDType::F32)
411 }
412
413 pub fn from_f32_slice(data: &[f32], shape: &[usize]) -> Result<Self, String> {
414 let converted: Vec<f64> = data.iter().map(|&v| v as f64).collect();
415 Self::new_with_dtype(converted, shape.to_vec(), NumericDType::F32)
416 }
417
418 pub fn new_with_dtype(
419 data: Vec<f64>,
420 shape: Vec<usize>,
421 dtype: NumericDType,
422 ) -> Result<Self, String> {
423 let mut t = Self::new(data, shape)?;
424 t.dtype = dtype;
425 Ok(t)
426 }
427
428 pub fn zeros(shape: Vec<usize>) -> Self {
429 let size: usize = shape.iter().product();
430 let (rows, cols) = if shape.len() >= 2 {
431 (shape[0], shape[1])
432 } else if shape.len() == 1 {
433 (1, shape[0])
434 } else {
435 (0, 0)
436 };
437 Tensor {
438 data: vec![0.0; size],
439 shape,
440 rows,
441 cols,
442 dtype: NumericDType::F64,
443 }
444 }
445
446 pub fn ones(shape: Vec<usize>) -> Self {
447 let size: usize = shape.iter().product();
448 let (rows, cols) = if shape.len() >= 2 {
449 (shape[0], shape[1])
450 } else if shape.len() == 1 {
451 (1, shape[0])
452 } else {
453 (0, 0)
454 };
455 Tensor {
456 data: vec![1.0; size],
457 shape,
458 rows,
459 cols,
460 dtype: NumericDType::F64,
461 }
462 }
463
464 pub fn zeros2(rows: usize, cols: usize) -> Self {
466 Self::zeros(vec![rows, cols])
467 }
468 pub fn ones2(rows: usize, cols: usize) -> Self {
469 Self::ones(vec![rows, cols])
470 }
471
472 pub fn rows(&self) -> usize {
473 self.shape.first().copied().unwrap_or(1)
474 }
475 pub fn cols(&self) -> usize {
476 self.shape.get(1).copied().unwrap_or(1)
477 }
478
479 pub fn get2(&self, row: usize, col: usize) -> Result<f64, String> {
480 let rows = self.rows();
481 let cols = self.cols();
482 if row >= rows || col >= cols {
483 return Err(format!(
484 "Index ({row}, {col}) out of bounds for {rows}x{cols} tensor"
485 ));
486 }
487 Ok(self.data[row + col * rows])
489 }
490
491 pub fn set2(&mut self, row: usize, col: usize, value: f64) -> Result<(), String> {
492 let rows = self.rows();
493 let cols = self.cols();
494 if row >= rows || col >= cols {
495 return Err(format!(
496 "Index ({row}, {col}) out of bounds for {rows}x{cols} tensor"
497 ));
498 }
499 self.data[row + col * rows] = value;
501 Ok(())
502 }
503
504 pub fn scalar_to_tensor2(scalar: f64, rows: usize, cols: usize) -> Tensor {
505 Tensor {
506 data: vec![scalar; rows * cols],
507 shape: vec![rows, cols],
508 rows,
509 cols,
510 dtype: NumericDType::F64,
511 }
512 }
513 }
515
516impl SparseTensor {
517 pub fn new(
518 rows: usize,
519 cols: usize,
520 col_ptrs: Vec<usize>,
521 row_indices: Vec<usize>,
522 values: Vec<f64>,
523 ) -> Result<Self, String> {
524 if col_ptrs.len() != cols.saturating_add(1) {
525 return Err(format!(
526 "SparseTensor col_ptrs length {} doesn't match cols {}",
527 col_ptrs.len(),
528 cols
529 ));
530 }
531 if row_indices.len() != values.len() {
532 return Err(format!(
533 "SparseTensor row index length {} doesn't match value length {}",
534 row_indices.len(),
535 values.len()
536 ));
537 }
538 if col_ptrs.first().copied().unwrap_or(usize::MAX) != 0 {
539 return Err("SparseTensor col_ptrs must start at 0".to_string());
540 }
541 if col_ptrs.last().copied().unwrap_or(usize::MAX) != values.len() {
542 return Err("SparseTensor final col_ptr must equal nnz".to_string());
543 }
544 for window in col_ptrs.windows(2) {
545 if window[0] > window[1] {
546 return Err("SparseTensor col_ptrs must be nondecreasing".to_string());
547 }
548 }
549 for col in 0..cols {
550 let start = col_ptrs[col];
551 let end = col_ptrs[col + 1];
552 let mut prev: Option<usize> = None;
553 for &row in &row_indices[start..end] {
554 if row >= rows {
555 return Err(format!("SparseTensor row index {row} exceeds rows {rows}"));
556 }
557 if prev.is_some_and(|p| p >= row) {
558 return Err("SparseTensor row indices must be sorted and unique".to_string());
559 }
560 prev = Some(row);
561 }
562 }
563 Ok(Self {
564 rows,
565 cols,
566 col_ptrs,
567 row_indices,
568 values,
569 })
570 }
571
572 pub fn zeros(rows: usize, cols: usize) -> Self {
573 Self {
574 rows,
575 cols,
576 col_ptrs: vec![0; cols.saturating_add(1)],
577 row_indices: Vec::new(),
578 values: Vec::new(),
579 }
580 }
581
582 pub fn nnz(&self) -> usize {
583 self.values.len()
584 }
585
586 pub fn shape(&self) -> Vec<usize> {
587 vec![self.rows, self.cols]
588 }
589
590 pub fn to_dense(&self) -> Result<Tensor, String> {
591 let len = self
592 .rows
593 .checked_mul(self.cols)
594 .ok_or_else(|| "SparseTensor dense dimensions overflow usize".to_string())?;
595 let mut data = Vec::new();
596 data.try_reserve_exact(len)
597 .map_err(|err| format!("SparseTensor dense allocation failed: {err}"))?;
598 data.resize(len, 0.0);
599 for col in 0..self.cols {
600 for idx in self.col_ptrs[col]..self.col_ptrs[col + 1] {
601 let row = self.row_indices[idx];
602 data[row + col * self.rows] = self.values[idx];
603 }
604 }
605 Tensor::new(data, self.shape())
606 }
607
608 pub fn get(&self, row: usize, col: usize) -> Option<f64> {
609 if row >= self.rows || col >= self.cols {
610 return None;
611 }
612 let start = self.col_ptrs[col];
613 let end = self.col_ptrs[col + 1];
614 self.row_indices[start..end]
615 .binary_search(&row)
616 .ok()
617 .map(|offset| self.values[start + offset])
618 }
619}
620
621#[cfg(test)]
622mod sparse_tensor_tests {
623 use super::*;
624
625 #[test]
626 fn to_dense_rejects_overflowing_dimensions() {
627 let sparse = SparseTensor {
628 rows: usize::MAX,
629 cols: 2,
630 col_ptrs: vec![0, 0, 0],
631 row_indices: Vec::new(),
632 values: Vec::new(),
633 };
634
635 let err = sparse.to_dense().unwrap_err();
636 assert!(err.contains("overflow"));
637 }
638}
639
640impl ComplexTensor {
641 pub fn new(data: Vec<(f64, f64)>, shape: Vec<usize>) -> Result<Self, String> {
642 let expected: usize = shape.iter().product();
643 if data.len() != expected {
644 return Err(format!(
645 "ComplexTensor data length {} doesn't match shape {:?} ({} elements)",
646 data.len(),
647 shape,
648 expected
649 ));
650 }
651 let (rows, cols) = if shape.len() >= 2 {
652 (shape[0], shape[1])
653 } else if shape.len() == 1 {
654 (1, shape[0])
655 } else {
656 (0, 0)
657 };
658 Ok(ComplexTensor {
659 data,
660 shape,
661 rows,
662 cols,
663 })
664 }
665 pub fn new_2d(data: Vec<(f64, f64)>, rows: usize, cols: usize) -> Result<Self, String> {
666 Self::new(data, vec![rows, cols])
667 }
668 pub fn zeros(shape: Vec<usize>) -> Self {
669 let size: usize = shape.iter().product();
670 let (rows, cols) = if shape.len() >= 2 {
671 (shape[0], shape[1])
672 } else if shape.len() == 1 {
673 (1, shape[0])
674 } else {
675 (0, 0)
676 };
677 ComplexTensor {
678 data: vec![(0.0, 0.0); size],
679 shape,
680 rows,
681 cols,
682 }
683 }
684}
685
686const MAX_ND_DISPLAY_ELEMENTS: usize = 4096;
687
688fn should_expand_nd_display(shape: &[usize]) -> bool {
689 shape.len() > 2
690 && matches!(
691 total_len(shape),
692 Some(total) if total > 0 && total <= MAX_ND_DISPLAY_ELEMENTS
693 )
694}
695
696fn column_major_strides(shape: &[usize]) -> Vec<usize> {
697 let mut strides = Vec::with_capacity(shape.len());
698 let mut stride = 1usize;
699 for &dim in shape {
700 strides.push(stride);
701 stride = stride.saturating_mul(dim);
702 }
703 strides
704}
705
706fn decode_page_coords(mut page_index: usize, page_shape: &[usize]) -> Vec<usize> {
707 let mut coords = Vec::with_capacity(page_shape.len());
708 for &dim in page_shape {
709 if dim == 0 {
710 coords.push(0);
711 } else {
712 coords.push(page_index % dim);
713 page_index /= dim;
714 }
715 }
716 coords
717}
718
719fn write_nd_pages(
720 f: &mut fmt::Formatter<'_>,
721 shape: &[usize],
722 mut write_element: impl FnMut(&mut fmt::Formatter<'_>, usize) -> fmt::Result,
723) -> fmt::Result {
724 if shape.len() <= 2 {
725 return Ok(());
726 }
727 let rows = shape[0];
728 let cols = shape[1];
729 if rows == 0 || cols == 0 {
730 return write!(f, "[]");
731 }
732 let Some(page_count) = total_len(&shape[2..]) else {
733 return write!(f, "Tensor(shape={shape:?})");
734 };
735 if page_count == 0 {
736 return write!(f, "[]");
737 }
738 let strides = column_major_strides(shape);
739 for page_index in 0..page_count {
740 if page_index > 0 {
741 write!(f, "\n\n")?;
742 }
743 let coords = decode_page_coords(page_index, &shape[2..]);
744 write!(f, "(:, :")?;
745 for &coord in &coords {
746 write!(f, ", {}", coord + 1)?;
747 }
748 write!(f, ") =")?;
749
750 let mut page_base = 0usize;
751 for (offset, &coord) in coords.iter().enumerate() {
752 page_base += coord * strides[offset + 2];
753 }
754 for r in 0..rows {
755 writeln!(f)?;
756 write!(f, " ")?;
757 for c in 0..cols {
758 if c > 0 {
759 write!(f, " ")?;
760 }
761 let linear = page_base + r + c * rows;
762 write_element(f, linear)?;
763 }
764 }
765 }
766 Ok(())
767}
768
769impl fmt::Display for Tensor {
770 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771 match self.shape.len() {
772 0 | 1 => {
773 write!(f, "[")?;
775 for (i, v) in self.data.iter().enumerate() {
776 if i > 0 {
777 write!(f, " ")?;
778 }
779 write!(f, "{}", format_number(*v))?;
780 }
781 write!(f, "]")
782 }
783 2 => {
784 let rows = self.rows();
785 let cols = self.cols();
786 for r in 0..rows {
788 writeln!(f)?;
789 write!(f, " ")?; for c in 0..cols {
791 if c > 0 {
792 write!(f, " ")?;
793 }
794 let v = self.data[r + c * rows];
795 write!(f, "{}", format_number(v))?;
796 }
797 }
798 Ok(())
799 }
800 _ => {
801 if should_expand_nd_display(&self.shape) {
802 write_nd_pages(f, &self.shape, |f, idx| {
803 write!(f, "{}", format_number(self.data[idx]))
804 })
805 } else {
806 write!(f, "Tensor(shape={:?})", self.shape)
807 }
808 }
809 }
810 }
811}
812
813impl fmt::Display for SparseTensor {
814 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815 writeln!(
816 f,
817 "{}x{} sparse double matrix with {} nonzero entries",
818 self.rows,
819 self.cols,
820 self.nnz()
821 )?;
822 if self.nnz() == 0 {
823 return Ok(());
824 }
825 for col in 0..self.cols {
826 for idx in self.col_ptrs[col]..self.col_ptrs[col + 1] {
827 let row = self.row_indices[idx];
828 writeln!(
829 f,
830 " ({},{}) {}",
831 row + 1,
832 col + 1,
833 format_number(self.values[idx])
834 )?;
835 }
836 }
837 Ok(())
838 }
839}
840
841impl fmt::Display for StringArray {
842 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
843 let (rows, cols) = match self.shape.len() {
844 0 => (0, 0),
845 1 => (1, self.shape[0]),
846 _ => (self.shape[0], self.shape[1]),
847 };
848 let count = self.data.len();
849 if count == 1 && rows == 1 && cols == 1 {
850 let v = &self.data[0];
851 if v == "<missing>" {
852 return write!(f, "<missing>");
853 }
854 let escaped = v.replace('"', "\\\"");
855 return write!(f, "\"{escaped}\"");
856 }
857 if self.shape.len() > 2 {
858 let dims: Vec<String> = self.shape.iter().map(|d| d.to_string()).collect();
859 return write!(f, "{} string array", dims.join("x"));
860 }
861 write!(f, "{rows}x{cols} string array")?;
862 if rows == 0 || cols == 0 {
863 return Ok(());
864 }
865 for r in 0..rows {
866 writeln!(f)?;
867 write!(f, " ")?;
868 for c in 0..cols {
869 if c > 0 {
870 write!(f, " ")?;
871 }
872 let v = &self.data[r + c * rows];
873 if v == "<missing>" {
874 write!(f, "<missing>")?;
875 } else {
876 let escaped = v.replace('"', "\\\"");
877 write!(f, "\"{escaped}\"")?;
878 }
879 }
880 }
881 Ok(())
882 }
883}
884
885impl fmt::Display for LogicalArray {
886 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
887 if self.data.len() == 1 {
888 return write!(f, "{}", if self.data[0] != 0 { 1 } else { 0 });
889 }
890 match self.shape.len() {
891 0 => write!(f, "[]"),
892 1 => {
893 write!(f, "[")?;
894 for (i, v) in self.data.iter().enumerate() {
895 if i > 0 {
896 write!(f, " ")?;
897 }
898 write!(f, "{}", if *v != 0 { 1 } else { 0 })?;
899 }
900 write!(f, "]")
901 }
902 2 => {
903 let rows = self.shape[0];
904 let cols = self.shape[1];
905 for r in 0..rows {
907 writeln!(f)?;
908 write!(f, " ")?; for c in 0..cols {
910 if c > 0 {
911 write!(f, " ")?;
912 }
913 let idx = r + c * rows;
914 write!(f, "{}", if self.data[idx] != 0 { 1 } else { 0 })?;
915 }
916 }
917 Ok(())
918 }
919 _ => {
920 if should_expand_nd_display(&self.shape) {
921 write_nd_pages(f, &self.shape, |f, idx| {
922 write!(f, "{}", if self.data[idx] != 0 { 1 } else { 0 })
923 })
924 } else {
925 let dims: Vec<String> = self.shape.iter().map(|d| d.to_string()).collect();
926 write!(f, "{} logical array", dims.join("x"))
927 }
928 }
929 }
930 }
931}
932
933impl fmt::Display for CharArray {
934 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
935 for r in 0..self.rows {
936 writeln!(f)?;
937 write!(f, " ")?; for c in 0..self.cols {
939 let ch = self.data[r * self.cols + c];
940 write!(f, "{ch}")?;
941 }
942 }
943 Ok(())
944 }
945}
946
947impl From<i32> for Value {
949 fn from(i: i32) -> Self {
950 Value::Int(IntValue::I32(i))
951 }
952}
953impl From<i64> for Value {
954 fn from(i: i64) -> Self {
955 Value::Int(IntValue::I64(i))
956 }
957}
958impl From<u32> for Value {
959 fn from(i: u32) -> Self {
960 Value::Int(IntValue::U32(i))
961 }
962}
963impl From<u64> for Value {
964 fn from(i: u64) -> Self {
965 Value::Int(IntValue::U64(i))
966 }
967}
968impl From<i16> for Value {
969 fn from(i: i16) -> Self {
970 Value::Int(IntValue::I16(i))
971 }
972}
973impl From<i8> for Value {
974 fn from(i: i8) -> Self {
975 Value::Int(IntValue::I8(i))
976 }
977}
978impl From<u16> for Value {
979 fn from(i: u16) -> Self {
980 Value::Int(IntValue::U16(i))
981 }
982}
983impl From<u8> for Value {
984 fn from(i: u8) -> Self {
985 Value::Int(IntValue::U8(i))
986 }
987}
988
989impl From<f64> for Value {
990 fn from(f: f64) -> Self {
991 Value::Num(f)
992 }
993}
994
995impl From<bool> for Value {
996 fn from(b: bool) -> Self {
997 Value::Bool(b)
998 }
999}
1000
1001impl From<String> for Value {
1002 fn from(s: String) -> Self {
1003 Value::String(s)
1004 }
1005}
1006
1007impl From<&str> for Value {
1008 fn from(s: &str) -> Self {
1009 Value::String(s.to_string())
1010 }
1011}
1012
1013impl From<Tensor> for Value {
1014 fn from(m: Tensor) -> Self {
1015 Value::Tensor(m)
1016 }
1017}
1018
1019impl TryFrom<&Value> for i32 {
1023 type Error = String;
1024 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1025 match v {
1026 Value::Int(i) => Ok(i.to_i64() as i32),
1027 Value::Num(n) => Ok(*n as i32),
1028 _ => Err(format!("cannot convert {v:?} to i32")),
1029 }
1030 }
1031}
1032
1033impl TryFrom<&Value> for f64 {
1034 type Error = String;
1035 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1036 match v {
1037 Value::Num(n) => Ok(*n),
1038 Value::Int(i) => Ok(i.to_f64()),
1039 _ => Err(format!("cannot convert {v:?} to f64")),
1040 }
1041 }
1042}
1043
1044impl TryFrom<&Value> for bool {
1045 type Error = String;
1046 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1047 match v {
1048 Value::Bool(b) => Ok(*b),
1049 Value::Int(i) => Ok(!i.is_zero()),
1050 Value::Num(n) => Ok(*n != 0.0),
1051 _ => Err(format!("cannot convert {v:?} to bool")),
1052 }
1053 }
1054}
1055
1056impl TryFrom<&Value> for String {
1057 type Error = String;
1058 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1059 match v {
1060 Value::String(s) => Ok(s.clone()),
1061 Value::StringArray(sa) => {
1062 if sa.data.len() == 1 {
1063 Ok(sa.data[0].clone())
1064 } else {
1065 Err("cannot convert string array to scalar string".to_string())
1066 }
1067 }
1068 Value::CharArray(ca) => {
1069 if ca.rows == 1 {
1071 Ok(ca.data.iter().collect())
1072 } else {
1073 Err("cannot convert multi-row char array to scalar string".to_string())
1074 }
1075 }
1076 Value::Int(i) => Ok(i.to_i64().to_string()),
1077 Value::Num(n) => Ok(n.to_string()),
1078 Value::Bool(b) => Ok(b.to_string()),
1079 _ => Err(format!("cannot convert {v:?} to String")),
1080 }
1081 }
1082}
1083
1084impl TryFrom<&Value> for Tensor {
1085 type Error = String;
1086 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1087 match v {
1088 Value::Tensor(m) => Ok(m.clone()),
1089 _ => Err(format!("cannot convert {v:?} to Tensor")),
1090 }
1091 }
1092}
1093
1094impl TryFrom<&Value> for Value {
1095 type Error = String;
1096 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1097 Ok(v.clone())
1098 }
1099}
1100
1101impl TryFrom<&Value> for Vec<Value> {
1102 type Error = String;
1103 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1104 match v {
1105 Value::Cell(c) => Ok(c.data.clone()),
1106 _ => Err(format!("cannot convert {v:?} to Vec<Value>")),
1107 }
1108 }
1109}
1110
1111use serde::{Deserialize, Serialize};
1112
1113#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
1116pub enum Type {
1117 Int,
1119 Num,
1121 Bool,
1123 Logical {
1125 shape: Option<Vec<Option<usize>>>,
1127 },
1128 String,
1130 Tensor {
1132 shape: Option<Vec<Option<usize>>>,
1134 },
1135 Symbolic,
1137 Cell {
1139 element_type: Option<Box<Type>>,
1141 length: Option<usize>,
1143 },
1144 Function {
1146 params: Vec<Type>,
1148 returns: Box<Type>,
1150 },
1151 Void,
1153 Unknown,
1155 Union(Vec<Type>),
1157 Struct {
1159 known_fields: Option<Vec<String>>, },
1162 OutputList(Vec<Type>),
1164}
1165
1166impl Type {
1167 pub fn tensor() -> Self {
1169 Type::Tensor { shape: None }
1170 }
1171
1172 pub fn logical() -> Self {
1174 Type::Logical { shape: None }
1175 }
1176
1177 pub fn logical_with_shape(shape: Vec<usize>) -> Self {
1179 Type::Logical {
1180 shape: Some(shape.into_iter().map(Some).collect()),
1181 }
1182 }
1183
1184 pub fn tensor_with_shape(shape: Vec<usize>) -> Self {
1186 Type::Tensor {
1187 shape: Some(shape.into_iter().map(Some).collect()),
1188 }
1189 }
1190
1191 pub fn cell() -> Self {
1193 Type::Cell {
1194 element_type: None,
1195 length: None,
1196 }
1197 }
1198
1199 pub fn cell_of(element_type: Type) -> Self {
1201 Type::Cell {
1202 element_type: Some(Box::new(element_type)),
1203 length: None,
1204 }
1205 }
1206
1207 pub fn is_compatible_with(&self, other: &Type) -> bool {
1209 match (self, other) {
1210 (Type::Unknown, _) | (_, Type::Unknown) => true,
1211 (Type::Int, Type::Num) | (Type::Num, Type::Int) => true, (Type::Tensor { .. }, Type::Tensor { .. }) => true, (Type::OutputList(a), Type::OutputList(b)) => a.len() == b.len(),
1214 (a, b) => a == b,
1215 }
1216 }
1217
1218 pub fn unify(&self, other: &Type) -> Type {
1220 match (self, other) {
1221 (Type::Unknown, t) | (t, Type::Unknown) => t.clone(),
1222 (Type::Int, Type::Num) | (Type::Num, Type::Int) => Type::Num,
1223 (Type::Tensor { shape: a }, Type::Tensor { shape: b }) => {
1224 let a_norm = match a {
1225 Some(dims) if dims.is_empty() => None,
1226 _ => a.clone(),
1227 };
1228 let b_norm = match b {
1229 Some(dims) if dims.is_empty() => None,
1230 _ => b.clone(),
1231 };
1232 let a_unknown = a_norm
1233 .as_ref()
1234 .map(|dims| dims.iter().all(|d| d.is_none()))
1235 .unwrap_or(true);
1236 let b_unknown = b_norm
1237 .as_ref()
1238 .map(|dims| dims.iter().all(|d| d.is_none()))
1239 .unwrap_or(true);
1240 if a_norm == b_norm
1241 || (!a_unknown && b_unknown)
1242 || (a_norm.is_some() && b_norm.is_none())
1243 {
1244 Type::Tensor { shape: a_norm }
1245 } else if (a_unknown && !b_unknown) || (a_norm.is_none() && b_norm.is_some()) {
1246 Type::Tensor { shape: b_norm }
1247 } else {
1248 Type::tensor()
1249 }
1250 }
1251 (Type::Logical { shape: a }, Type::Logical { shape: b }) => {
1252 let a_norm = match a {
1253 Some(dims) if dims.is_empty() => None,
1254 _ => a.clone(),
1255 };
1256 let b_norm = match b {
1257 Some(dims) if dims.is_empty() => None,
1258 _ => b.clone(),
1259 };
1260 let a_unknown = a_norm
1261 .as_ref()
1262 .map(|dims| dims.iter().all(|d| d.is_none()))
1263 .unwrap_or(true);
1264 let b_unknown = b_norm
1265 .as_ref()
1266 .map(|dims| dims.iter().all(|d| d.is_none()))
1267 .unwrap_or(true);
1268 if a_norm == b_norm
1269 || (!a_unknown && b_unknown)
1270 || (a_norm.is_some() && b_norm.is_none())
1271 {
1272 Type::Logical { shape: a_norm }
1273 } else if (a_unknown && !b_unknown) || (a_norm.is_none() && b_norm.is_some()) {
1274 Type::Logical { shape: b_norm }
1275 } else {
1276 Type::logical()
1277 }
1278 }
1279 (Type::Struct { known_fields: a }, Type::Struct { known_fields: b }) => match (a, b) {
1280 (None, None) => Type::Struct { known_fields: None },
1281 (Some(ka), None) | (None, Some(ka)) => Type::Struct {
1282 known_fields: Some(ka.clone()),
1283 },
1284 (Some(ka), Some(kb)) => {
1285 let mut set: std::collections::BTreeSet<String> = ka.iter().cloned().collect();
1286 set.extend(kb.iter().cloned());
1287 Type::Struct {
1288 known_fields: Some(set.into_iter().collect()),
1289 }
1290 }
1291 },
1292 (Type::OutputList(a), Type::OutputList(b)) => {
1293 if a.len() == b.len() {
1294 let items = a
1295 .iter()
1296 .zip(b.iter())
1297 .map(|(lhs, rhs)| lhs.unify(rhs))
1298 .collect();
1299 Type::OutputList(items)
1300 } else {
1301 Type::OutputList(vec![Type::Unknown; a.len().max(b.len())])
1302 }
1303 }
1304 (a, b) if a == b => a.clone(),
1305 _ => Type::Union(vec![self.clone(), other.clone()]),
1306 }
1307 }
1308
1309 pub fn from_value(value: &Value) -> Type {
1311 match value {
1312 Value::Int(_) => Type::Int,
1313 Value::Num(_) => Type::Num,
1314 Value::Complex(_, _) => Type::Num, Value::Bool(_) => Type::Bool,
1316 Value::LogicalArray(arr) => Type::Logical {
1317 shape: Some(arr.shape.iter().map(|&d| Some(d)).collect()),
1318 },
1319 Value::String(_) => Type::String,
1320 Value::StringArray(_sa) => {
1321 Type::cell_of(Type::String)
1323 }
1324 Value::Tensor(t) => Type::Tensor {
1325 shape: Some(t.shape.iter().map(|&d| Some(d)).collect()),
1326 },
1327 Value::SparseTensor(t) => Type::Tensor {
1328 shape: Some(vec![Some(t.rows), Some(t.cols)]),
1329 },
1330 Value::ComplexTensor(t) => Type::Tensor {
1331 shape: Some(t.shape.iter().map(|&d| Some(d)).collect()),
1332 },
1333 Value::Symbolic(_) => Type::Symbolic,
1334 Value::Cell(cells) => {
1335 if cells.data.is_empty() {
1336 Type::cell()
1337 } else {
1338 let element_type = Type::from_value(&cells.data[0]);
1340 Type::Cell {
1341 element_type: Some(Box::new(element_type)),
1342 length: Some(cells.data.len()),
1343 }
1344 }
1345 }
1346 Value::GpuTensor(h) => Type::Tensor {
1347 shape: Some(h.shape.iter().map(|&d| Some(d)).collect()),
1348 },
1349 Value::Object(_) => Type::Unknown,
1350 Value::HandleObject(_) => Type::Unknown,
1351 Value::Listener(_) => Type::Unknown,
1352 Value::Struct(_) => Type::Struct { known_fields: None },
1353 Value::FunctionHandle(_)
1354 | Value::ExternalFunctionHandle(_)
1355 | Value::MethodFunctionHandle(_)
1356 | Value::BoundFunctionHandle { .. } => Type::Function {
1357 params: vec![Type::Unknown],
1358 returns: Box::new(Type::Unknown),
1359 },
1360 Value::Closure(_) => Type::Function {
1361 params: vec![Type::Unknown],
1362 returns: Box::new(Type::Unknown),
1363 },
1364 Value::ClassRef(_) => Type::Unknown,
1365 Value::MException(_) => Type::Unknown,
1366 Value::CharArray(ca) => {
1367 Type::Cell {
1369 element_type: Some(Box::new(Type::String)),
1370 length: Some(ca.rows * ca.cols),
1371 }
1372 }
1373 Value::OutputList(values) => {
1374 Type::OutputList(values.iter().map(Type::from_value).collect())
1375 }
1376 }
1377 }
1378}
1379
1380#[derive(Debug, Clone, PartialEq)]
1381pub struct Closure {
1382 pub function_name: String,
1383 pub bound_function: Option<usize>,
1384 pub captures: Vec<Value>,
1385}
1386
1387#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1389pub enum AccelTag {
1390 Unary,
1391 Elementwise,
1392 Reduction,
1393 MatMul,
1394 Transpose,
1395 ArrayConstruct,
1396}
1397
1398pub type BuiltinControlFlow = runmat_async::RuntimeError;
1400
1401pub type BuiltinFuture = Pin<Box<dyn Future<Output = Result<Value, BuiltinControlFlow>> + 'static>>;
1403
1404#[derive(Clone, Debug, Default)]
1405pub struct ResolveContext {
1406 pub literal_args: Vec<LiteralValue>,
1407}
1408
1409#[derive(Clone, Debug, PartialEq)]
1410pub enum LiteralValue {
1411 Number(f64),
1412 Bool(bool),
1413 String(String),
1414 Vector(Vec<LiteralValue>),
1415 Unknown,
1416}
1417
1418impl ResolveContext {
1419 pub fn new(literal_args: Vec<LiteralValue>) -> Self {
1420 Self { literal_args }
1421 }
1422
1423 pub fn numeric_dims(&self) -> Vec<Option<usize>> {
1424 self.numeric_dims_from(0)
1425 }
1426
1427 pub fn numeric_dims_from(&self, start: usize) -> Vec<Option<usize>> {
1428 let slice = self.literal_args.get(start..).unwrap_or(&[]);
1429 if let Some(LiteralValue::Vector(values)) = slice.first() {
1430 return values
1431 .iter()
1432 .map(Self::numeric_dimension_from_literal)
1433 .collect();
1434 }
1435 slice
1436 .iter()
1437 .map(Self::numeric_dimension_from_literal)
1438 .collect()
1439 }
1440
1441 pub fn literal_string_at(&self, index: usize) -> Option<String> {
1442 match self.literal_args.get(index) {
1443 Some(LiteralValue::String(value)) => Some(value.to_ascii_lowercase()),
1444 _ => None,
1445 }
1446 }
1447
1448 pub fn literal_bool_at(&self, index: usize) -> Option<bool> {
1449 match self.literal_args.get(index) {
1450 Some(LiteralValue::Bool(value)) => Some(*value),
1451 _ => None,
1452 }
1453 }
1454
1455 pub fn literal_vector_at(&self, index: usize) -> Option<Vec<LiteralValue>> {
1456 match self.literal_args.get(index) {
1457 Some(LiteralValue::Vector(values)) => Some(values.clone()),
1458 _ => None,
1459 }
1460 }
1461
1462 pub fn numeric_vector_at(&self, index: usize) -> Option<Vec<Option<usize>>> {
1463 let values = match self.literal_args.get(index) {
1464 Some(LiteralValue::Vector(values)) => values,
1465 _ => return None,
1466 };
1467 if values
1468 .iter()
1469 .any(|value| matches!(value, LiteralValue::Vector(_)))
1470 {
1471 return None;
1472 }
1473 Some(
1474 values
1475 .iter()
1476 .map(Self::numeric_dimension_from_literal)
1477 .collect(),
1478 )
1479 }
1480
1481 fn numeric_dimension_from_literal(value: &LiteralValue) -> Option<usize> {
1482 match value {
1483 LiteralValue::Number(num) => {
1484 if num.is_finite() {
1485 let rounded = num.round();
1486 if (num - rounded).abs() <= 1e-9 && rounded >= 0.0 {
1487 return Some(rounded as usize);
1488 }
1489 }
1490 None
1491 }
1492 _ => None,
1493 }
1494 }
1495}
1496
1497#[cfg(test)]
1498mod resolve_context_tests {
1499 use super::{LiteralValue, ResolveContext};
1500
1501 #[test]
1502 fn numeric_dims_reads_vector_literal() {
1503 let ctx = ResolveContext::new(vec![LiteralValue::Vector(vec![
1504 LiteralValue::Number(2.0),
1505 LiteralValue::Number(3.0),
1506 ])]);
1507 assert_eq!(ctx.numeric_dims(), vec![Some(2), Some(3)]);
1508 }
1509
1510 #[test]
1511 fn numeric_dims_skips_non_numeric_entries() {
1512 let ctx = ResolveContext::new(vec![
1513 LiteralValue::Number(4.0),
1514 LiteralValue::String("like".to_string()),
1515 LiteralValue::Unknown,
1516 ]);
1517 assert_eq!(ctx.numeric_dims(), vec![Some(4), None, None]);
1518 }
1519
1520 #[test]
1521 fn numeric_dims_prefers_vector_even_with_trailing_args() {
1522 let ctx = ResolveContext::new(vec![
1523 LiteralValue::Vector(vec![LiteralValue::Number(1.0), LiteralValue::Number(5.0)]),
1524 LiteralValue::String("like".to_string()),
1525 ]);
1526 assert_eq!(ctx.numeric_dims(), vec![Some(1), Some(5)]);
1527 }
1528
1529 #[test]
1530 fn literal_string_is_lowercased() {
1531 let ctx = ResolveContext::new(vec![LiteralValue::String("OmItNaN".to_string())]);
1532 assert_eq!(ctx.literal_string_at(0), Some("omitnan".to_string()));
1533 }
1534
1535 #[test]
1536 fn literal_bool_is_available() {
1537 let ctx = ResolveContext::new(vec![LiteralValue::Bool(true)]);
1538 assert_eq!(ctx.literal_bool_at(0), Some(true));
1539 }
1540
1541 #[test]
1542 fn literal_vector_at_returns_clone() {
1543 let ctx = ResolveContext::new(vec![LiteralValue::Vector(vec![
1544 LiteralValue::Number(7.0),
1545 LiteralValue::Unknown,
1546 ])]);
1547 assert_eq!(
1548 ctx.literal_vector_at(0),
1549 Some(vec![LiteralValue::Number(7.0), LiteralValue::Unknown])
1550 );
1551 }
1552
1553 #[test]
1554 fn numeric_vector_at_rejects_nested_vectors() {
1555 let ctx = ResolveContext::new(vec![LiteralValue::Vector(vec![LiteralValue::Vector(
1556 vec![LiteralValue::Number(1.0)],
1557 )])]);
1558 assert_eq!(ctx.numeric_vector_at(0), None);
1559 }
1560}
1561
1562pub type TypeResolver = fn(args: &[Type]) -> Type;
1563pub type TypeResolverWithContext = fn(args: &[Type], ctx: &ResolveContext) -> Type;
1564
1565#[derive(Clone, Copy, Debug)]
1566pub enum TypeResolverKind {
1567 Simple(TypeResolver),
1568 WithContext(TypeResolverWithContext),
1569}
1570
1571pub fn type_resolver_kind(resolver: TypeResolver) -> TypeResolverKind {
1572 TypeResolverKind::Simple(resolver)
1573}
1574
1575pub fn type_resolver_kind_ctx(resolver: TypeResolverWithContext) -> TypeResolverKind {
1576 TypeResolverKind::WithContext(resolver)
1577}
1578
1579#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1580pub enum BuiltinOutputMode {
1581 Fixed,
1582 ByRequestedOutputCount,
1583}
1584
1585#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1586pub enum BuiltinCompletionPolicy {
1587 Public,
1588 MethodOnly,
1589 HiddenInternal,
1590}
1591
1592#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1593pub enum BuiltinParamArity {
1594 Required,
1595 Optional,
1596 Variadic,
1597}
1598
1599#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1600pub enum BuiltinParamType {
1601 Any,
1602 NumericScalar,
1603 IntegerScalar,
1604 StringScalar,
1605 NumericArray,
1606 LogicalArray,
1607 SizeArg,
1608 LikePrototype,
1609 AxesHandle,
1610 StyleSpec,
1611 PropertyName,
1612 PropertyValue,
1613}
1614
1615#[derive(Debug, Clone, Serialize)]
1616pub struct BuiltinParamDescriptor {
1617 pub name: &'static str,
1618 pub ty: BuiltinParamType,
1619 pub arity: BuiltinParamArity,
1620 pub default: Option<&'static str>,
1621 pub description: &'static str,
1622}
1623
1624#[derive(Debug, Clone, Serialize)]
1625pub struct BuiltinSignatureDescriptor {
1626 pub label: &'static str,
1627 pub inputs: &'static [BuiltinParamDescriptor],
1628 pub outputs: &'static [BuiltinParamDescriptor],
1629}
1630
1631#[derive(Debug, Clone, Serialize)]
1632pub struct BuiltinErrorDescriptor {
1633 pub code: &'static str,
1634 pub identifier: Option<&'static str>,
1635 pub when: &'static str,
1636 pub message: &'static str,
1637}
1638
1639#[derive(Debug, Clone, Serialize)]
1640pub struct BuiltinDescriptor {
1641 pub signatures: &'static [BuiltinSignatureDescriptor],
1642 pub output_mode: BuiltinOutputMode,
1643 pub completion_policy: BuiltinCompletionPolicy,
1644 pub errors: &'static [BuiltinErrorDescriptor],
1645}
1646
1647#[derive(Debug, Clone)]
1649pub struct BuiltinFunction {
1650 pub name: &'static str,
1651 pub description: &'static str,
1652 pub category: &'static str,
1653 pub doc: &'static str,
1654 pub examples: &'static str,
1655 pub param_types: Vec<Type>,
1656 pub return_type: Type,
1657 pub type_resolver: Option<TypeResolverKind>,
1658 pub implementation: fn(&[Value]) -> BuiltinFuture,
1659 pub accel_tags: &'static [AccelTag],
1660 pub is_sink: bool,
1661 pub suppress_auto_output: bool,
1662 pub descriptor: Option<&'static BuiltinDescriptor>,
1663}
1664
1665impl BuiltinFunction {
1666 #[allow(clippy::too_many_arguments)]
1667 pub fn new(
1668 name: &'static str,
1669 description: &'static str,
1670 category: &'static str,
1671 doc: &'static str,
1672 examples: &'static str,
1673 param_types: Vec<Type>,
1674 return_type: Type,
1675 type_resolver: Option<TypeResolverKind>,
1676 implementation: fn(&[Value]) -> BuiltinFuture,
1677 accel_tags: &'static [AccelTag],
1678 is_sink: bool,
1679 suppress_auto_output: bool,
1680 ) -> Self {
1681 Self {
1682 name,
1683 description,
1684 category,
1685 doc,
1686 examples,
1687 param_types,
1688 return_type,
1689 type_resolver,
1690 implementation,
1691 accel_tags,
1692 is_sink,
1693 suppress_auto_output,
1694 descriptor: None,
1695 }
1696 }
1697
1698 pub fn with_descriptor(mut self, descriptor: &'static BuiltinDescriptor) -> Self {
1699 self.descriptor = Some(descriptor);
1700 self
1701 }
1702
1703 pub fn with_descriptor_option(
1704 mut self,
1705 descriptor: Option<&'static BuiltinDescriptor>,
1706 ) -> Self {
1707 self.descriptor = descriptor;
1708 self
1709 }
1710
1711 pub fn infer_return_type(&self, args: &[Type]) -> Type {
1712 self.infer_return_type_with_context(args, &ResolveContext::default())
1713 }
1714
1715 pub fn infer_return_type_with_context(&self, args: &[Type], ctx: &ResolveContext) -> Type {
1716 if let Some(resolver) = self.type_resolver {
1717 return match resolver {
1718 TypeResolverKind::Simple(resolver) => resolver(args),
1719 TypeResolverKind::WithContext(resolver) => resolver(args, ctx),
1720 };
1721 }
1722 self.return_type.clone()
1723 }
1724
1725 pub fn semantics(&self) -> BuiltinSemantics {
1726 semantics::builtin_semantics_for(self)
1727 }
1728}
1729
1730#[derive(Clone)]
1732pub struct Constant {
1733 pub name: &'static str,
1734 pub value: Value,
1735}
1736
1737pub mod semantics;
1738pub mod shape_rules;
1739
1740pub use semantics::{
1741 builtin_semantics_for, builtin_semantics_for_name, BuiltinAsyncBehavior, BuiltinCompatibility,
1742 BuiltinEffects, BuiltinEnvironmentEffect, BuiltinPurity, BuiltinSemanticKind, BuiltinSemantics,
1743 BuiltinWorkspaceEffect, ConcatKind, ShapeTransformKind,
1744};
1745
1746impl std::fmt::Debug for Constant {
1747 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1748 write!(
1749 f,
1750 "Constant {{ name: {:?}, value: {:?} }}",
1751 self.name, self.value
1752 )
1753 }
1754}
1755
1756#[cfg(not(target_arch = "wasm32"))]
1757inventory::collect!(BuiltinFunction);
1758#[cfg(not(target_arch = "wasm32"))]
1759inventory::collect!(Constant);
1760
1761#[cfg(not(target_arch = "wasm32"))]
1762pub fn builtin_functions() -> Vec<&'static BuiltinFunction> {
1763 inventory::iter::<BuiltinFunction>().collect()
1764}
1765
1766#[cfg(target_arch = "wasm32")]
1767pub fn builtin_functions() -> Vec<&'static BuiltinFunction> {
1768 wasm_registry::builtin_functions()
1769}
1770
1771#[cfg(not(target_arch = "wasm32"))]
1772static BUILTIN_LOOKUP: OnceLock<HashMap<String, &'static BuiltinFunction>> = OnceLock::new();
1773
1774#[cfg(not(target_arch = "wasm32"))]
1775fn builtin_lookup_map() -> &'static HashMap<String, &'static BuiltinFunction> {
1776 BUILTIN_LOOKUP.get_or_init(|| {
1777 let mut map = HashMap::new();
1778 for func in builtin_functions() {
1779 map.insert(func.name.to_ascii_lowercase(), func);
1780 }
1781 map
1782 })
1783}
1784
1785#[cfg(not(target_arch = "wasm32"))]
1786pub fn builtin_function_by_name(name: &str) -> Option<&'static BuiltinFunction> {
1787 builtin_lookup_map()
1788 .get(&name.to_ascii_lowercase())
1789 .copied()
1790}
1791
1792#[cfg(target_arch = "wasm32")]
1793pub fn builtin_function_by_name(name: &str) -> Option<&'static BuiltinFunction> {
1794 wasm_registry::builtin_functions()
1795 .into_iter()
1796 .find(|f| f.name.eq_ignore_ascii_case(name))
1797}
1798
1799pub fn suppresses_auto_output(name: &str) -> bool {
1800 builtin_function_by_name(name)
1801 .map(|f| f.suppress_auto_output)
1802 .unwrap_or(false)
1803}
1804
1805#[cfg(not(target_arch = "wasm32"))]
1806pub fn constants() -> Vec<&'static Constant> {
1807 inventory::iter::<Constant>().collect()
1808}
1809
1810#[cfg(target_arch = "wasm32")]
1811pub fn constants() -> Vec<&'static Constant> {
1812 wasm_registry::constants()
1813}
1814
1815#[derive(Debug)]
1820pub struct BuiltinDoc {
1821 pub name: &'static str,
1822 pub category: Option<&'static str>,
1823 pub summary: Option<&'static str>,
1824 pub keywords: Option<&'static str>,
1825 pub errors: Option<&'static str>,
1826 pub related: Option<&'static str>,
1827 pub introduced: Option<&'static str>,
1828 pub status: Option<&'static str>,
1829 pub examples: Option<&'static str>,
1830}
1831
1832#[cfg(not(target_arch = "wasm32"))]
1833inventory::collect!(BuiltinDoc);
1834
1835#[cfg(not(target_arch = "wasm32"))]
1836pub fn builtin_docs() -> Vec<&'static BuiltinDoc> {
1837 inventory::iter::<BuiltinDoc>().collect()
1838}
1839
1840#[cfg(target_arch = "wasm32")]
1841pub fn builtin_docs() -> Vec<&'static BuiltinDoc> {
1842 wasm_registry::builtin_docs()
1843}
1844
1845#[derive(Debug, Clone, Copy, PartialEq, Default)]
1851pub enum FormatMode {
1852 #[default]
1854 Short,
1855 Long,
1857 ShortE,
1859 LongE,
1861 ShortG,
1863 LongG,
1865 Rational,
1867 Hex,
1869}
1870
1871runmat_thread_local! {
1872 static DISPLAY_FORMAT: RefCell<FormatMode> = const { RefCell::new(FormatMode::Short) };
1873}
1874
1875pub fn set_display_format(mode: FormatMode) {
1876 DISPLAY_FORMAT.with(|c| *c.borrow_mut() = mode);
1877}
1878
1879pub fn get_display_format() -> FormatMode {
1880 DISPLAY_FORMAT.with(|c| *c.borrow())
1881}
1882
1883pub fn format_number(value: f64) -> String {
1885 if value.is_nan() {
1886 return "NaN".to_string();
1887 }
1888 if value.is_infinite() {
1889 return if value.is_sign_negative() {
1890 "-Inf"
1891 } else {
1892 "Inf"
1893 }
1894 .to_string();
1895 }
1896 let mode = get_display_format();
1897 if mode == FormatMode::Hex {
1898 return fmt_hex(value);
1899 }
1900 let v = if value == 0.0 { 0.0 } else { value };
1901 match mode {
1902 FormatMode::Short => fmt_short(v),
1903 FormatMode::Long => fmt_long(v),
1904 FormatMode::ShortE => fmt_sci(v, 4),
1905 FormatMode::LongE => fmt_sci(v, 14),
1906 FormatMode::ShortG => fmt_compact(v, 5),
1907 FormatMode::LongG => fmt_compact(v, 15),
1908 FormatMode::Rational => fmt_rational(v),
1909 FormatMode::Hex => unreachable!("hex mode handled before zero normalization"),
1910 }
1911}
1912
1913fn matlab_exp(s: &str) -> String {
1915 if let Some(e_pos) = s.find('e') {
1916 let mantissa = &s[..e_pos];
1917 let exp: i32 = s[e_pos + 1..].parse().unwrap_or(0);
1918 let sign = if exp >= 0 { '+' } else { '-' };
1919 format!("{mantissa}e{sign}{:02}", exp.unsigned_abs())
1920 } else {
1921 s.to_string()
1922 }
1923}
1924
1925fn fmt_sci(v: f64, dec: usize) -> String {
1926 if v == 0.0 {
1927 return format!("0.{:0>dec$}e+00", 0, dec = dec);
1928 }
1929 let s = format!("{v:.dec$e}");
1930 matlab_exp(&s)
1931}
1932
1933fn fmt_short(v: f64) -> String {
1934 let abs = v.abs();
1935 if abs == 0.0 {
1936 return "0".to_string();
1937 }
1938 if v.fract() == 0.0 && abs < 1e15 {
1939 return format!("{}", v as i64);
1940 }
1941 if (0.001..10000.0).contains(&abs) {
1942 format!("{:.4}", v)
1943 } else {
1944 fmt_sci(v, 4)
1945 }
1946}
1947
1948fn fmt_long(v: f64) -> String {
1949 let abs = v.abs();
1950 if abs == 0.0 {
1951 return "0".to_string();
1952 }
1953 if v.fract() == 0.0 && abs < 1e15 {
1954 return format!("{}", v as i64);
1955 }
1956 if (0.001..10000.0).contains(&abs) {
1957 format!("{:.15}", v)
1958 } else {
1959 fmt_sci(v, 14)
1960 }
1961}
1962
1963fn fmt_compact(v: f64, sig_digits: usize) -> String {
1964 let abs = v.abs();
1965 if abs == 0.0 {
1966 return "0".to_string();
1967 }
1968 let use_scientific = !(1e-4..1e6).contains(&abs);
1969 if use_scientific {
1970 let dec = sig_digits - 1;
1971 let s = format!("{v:.dec$e}");
1972 if let Some(e_pos) = s.find('e') {
1974 let exp_part = &s[e_pos..];
1975 let mut mantissa = s[..e_pos].to_string();
1976 if let Some(dot) = mantissa.find('.') {
1977 let mut end = mantissa.len();
1978 while end > dot + 1 && mantissa.as_bytes()[end - 1] == b'0' {
1979 end -= 1;
1980 }
1981 if mantissa.as_bytes()[end - 1] == b'.' {
1982 end -= 1;
1983 }
1984 mantissa.truncate(end);
1985 }
1986 return matlab_exp(&format!("{mantissa}{exp_part}"));
1987 }
1988 return matlab_exp(&s);
1989 }
1990 let exp10 = abs.log10().floor() as i32;
1991 let decimals = ((sig_digits as i32 - 1 - exp10).max(0)) as usize;
1992 let pow = 10f64.powi(decimals as i32);
1993 let rounded = (v * pow).round() / pow;
1994 let mut s = format!("{rounded:.decimals$}");
1995 if let Some(dot) = s.find('.') {
1996 let mut end = s.len();
1997 while end > dot + 1 && s.as_bytes()[end - 1] == b'0' {
1998 end -= 1;
1999 }
2000 if s.as_bytes()[end - 1] == b'.' {
2001 end -= 1;
2002 }
2003 s.truncate(end);
2004 }
2005 if s.is_empty() || s == "-0" {
2006 s = "0".to_string();
2007 }
2008 s
2009}
2010
2011fn fmt_rational(v: f64) -> String {
2012 if v == 0.0 {
2013 return "0".to_string();
2014 }
2015 let negative = v < 0.0;
2016 let abs = v.abs();
2017 if v.fract() == 0.0 && abs < 1e15 {
2018 return format!("{}", v as i64);
2019 }
2020 let tol = 5e-7 * abs;
2023 let max_d = 1_000_000i64;
2024 let mut n0: i64 = 1;
2025 let mut n1: i64 = abs.floor() as i64;
2026 let mut d0: i64 = 0;
2027 let mut d1: i64 = 1;
2028 let mut a = abs;
2029 let mut best_n = n1;
2030 let mut best_d = d1;
2031 for _ in 0..50 {
2032 if (abs - best_n as f64 / best_d as f64).abs() <= tol {
2033 break;
2034 }
2035 let f = a.fract();
2036 if f < 1e-10 {
2037 break;
2038 }
2039 a = 1.0 / f;
2040 let q = a.floor() as i64;
2041 let Some(n2) = q.checked_mul(n1).and_then(|v| v.checked_add(n0)) else {
2042 break;
2043 };
2044 let Some(d2) = q.checked_mul(d1).and_then(|v| v.checked_add(d0)) else {
2045 break;
2046 };
2047 if d2 > max_d {
2048 break;
2049 }
2050 best_n = n2;
2051 best_d = d2;
2052 n0 = n1;
2053 n1 = n2;
2054 d0 = d1;
2055 d1 = d2;
2056 }
2057 let sign = if negative { "-" } else { "" };
2058 if best_d == 1 {
2059 format!("{sign}{best_n}")
2060 } else {
2061 format!("{sign}{best_n}/{best_d}")
2062 }
2063}
2064
2065fn fmt_hex(v: f64) -> String {
2066 format!("{:016x}", v.to_bits())
2067}
2068
2069#[derive(Debug, Clone, PartialEq)]
2071pub struct MException {
2072 pub identifier: String,
2073 pub message: String,
2074 pub stack: Vec<String>,
2075}
2076
2077impl MException {
2078 pub fn new(identifier: String, message: String) -> Self {
2079 Self {
2080 identifier,
2081 message,
2082 stack: Vec::new(),
2083 }
2084 }
2085}
2086
2087#[derive(Debug, Clone)]
2089pub struct HandleRef {
2090 pub class_name: String,
2091 pub target: GcHandle,
2092 pub valid: bool,
2093}
2094
2095impl PartialEq for HandleRef {
2096 fn eq(&self, other: &Self) -> bool {
2097 self.target == other.target
2098 }
2099}
2100
2101#[derive(Debug, Clone, PartialEq)]
2103pub struct Listener {
2104 pub id: u64,
2105 pub target: GcHandle,
2106 pub target_class_name: String,
2107 pub event_name: String,
2108 pub callback: GcHandle,
2109 pub enabled: bool,
2110 pub valid: bool,
2111}
2112
2113impl Listener {
2114 pub fn class_name(&self) -> String {
2115 self.target_class_name.clone()
2116 }
2117}
2118
2119impl Trace for CellArray {
2120 fn trace(&self, tracer: &mut dyn Tracer) {
2121 for value in &self.data {
2122 value.trace(tracer);
2123 }
2124 }
2125}
2126
2127impl Trace for StructValue {
2128 fn trace(&self, tracer: &mut dyn Tracer) {
2129 for value in self.fields.values() {
2130 value.trace(tracer);
2131 }
2132 }
2133}
2134
2135impl Trace for Closure {
2136 fn trace(&self, tracer: &mut dyn Tracer) {
2137 for value in &self.captures {
2138 value.trace(tracer);
2139 }
2140 }
2141}
2142
2143impl Trace for ObjectInstance {
2144 fn trace(&self, tracer: &mut dyn Tracer) {
2145 for value in self.properties.values() {
2146 value.trace(tracer);
2147 }
2148 }
2149}
2150
2151impl Trace for HandleRef {
2152 fn trace(&self, tracer: &mut dyn Tracer) {
2153 tracer.mark(self.target);
2154 }
2155}
2156
2157impl Trace for Listener {
2158 fn trace(&self, tracer: &mut dyn Tracer) {
2159 tracer.mark(self.target);
2160 tracer.mark(self.callback);
2161 }
2162}
2163
2164impl Trace for Value {
2165 fn trace(&self, tracer: &mut dyn Tracer) {
2166 match self {
2167 Value::Cell(cells) => cells.trace(tracer),
2168 Value::Struct(struct_value) => struct_value.trace(tracer),
2169 Value::HandleObject(handle) => handle.trace(tracer),
2170 Value::Listener(listener) => listener.trace(tracer),
2171 Value::Closure(closure) => closure.trace(tracer),
2172 Value::Object(object) => object.trace(tracer),
2173 Value::OutputList(values) => {
2174 for value in values {
2175 value.trace(tracer);
2176 }
2177 }
2178 Value::Int(_)
2179 | Value::Num(_)
2180 | Value::Complex(_, _)
2181 | Value::Bool(_)
2182 | Value::LogicalArray(_)
2183 | Value::String(_)
2184 | Value::StringArray(_)
2185 | Value::CharArray(_)
2186 | Value::Tensor(_)
2187 | Value::SparseTensor(_)
2188 | Value::ComplexTensor(_)
2189 | Value::Symbolic(_)
2190 | Value::GpuTensor(_)
2191 | Value::FunctionHandle(_)
2192 | Value::ExternalFunctionHandle(_)
2193 | Value::MethodFunctionHandle(_)
2194 | Value::BoundFunctionHandle { .. }
2195 | Value::ClassRef(_)
2196 | Value::MException(_) => {}
2197 }
2198 }
2199}
2200
2201impl fmt::Display for Value {
2202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2203 match self {
2204 Value::Int(i) => write!(f, "{}", i.to_i64()),
2205 Value::Num(n) => write!(f, "{}", format_number(*n)),
2206 Value::Complex(re, im) => {
2207 if *im == 0.0 {
2208 write!(f, "{}", format_number(*re))
2209 } else if *re == 0.0 {
2210 write!(f, "{}i", format_number(*im))
2211 } else if *im < 0.0 {
2212 write!(f, "{}-{}i", format_number(*re), format_number(im.abs()))
2213 } else {
2214 write!(f, "{}+{}i", format_number(*re), format_number(*im))
2215 }
2216 }
2217 Value::Bool(b) => write!(f, "{}", if *b { 1 } else { 0 }),
2218 Value::LogicalArray(la) => write!(f, "{la}"),
2219 Value::String(s) => write!(f, "'{s}'"),
2220 Value::StringArray(sa) => write!(f, "{sa}"),
2221 Value::CharArray(ca) => write!(f, "{ca}"),
2222 Value::Tensor(m) => write!(f, "{m}"),
2223 Value::SparseTensor(m) => write!(f, "{m}"),
2224 Value::ComplexTensor(m) => write!(f, "{m}"),
2225 Value::Symbolic(expr) => write!(f, "{expr}"),
2226 Value::Cell(ca) => ca.fmt(f),
2227
2228 Value::GpuTensor(h) => write!(
2229 f,
2230 "GpuTensor(shape={:?}, device={}, buffer={})",
2231 h.shape, h.device_id, h.buffer_id
2232 ),
2233 Value::Object(obj) => write!(f, "{}(props={})", obj.class_name, obj.properties.len()),
2234 Value::HandleObject(h) => {
2235 write!(
2236 f,
2237 "<handle {} @0x{:x} valid={}>",
2238 h.class_name,
2239 h.target.addr(),
2240 h.valid
2241 )
2242 }
2243 Value::Listener(l) => {
2244 write!(
2245 f,
2246 "<listener id={} {}@0x{:x} '{}' enabled={} valid={}>",
2247 l.id,
2248 l.class_name(),
2249 l.target.addr(),
2250 l.event_name,
2251 l.enabled,
2252 l.valid
2253 )
2254 }
2255 Value::Struct(st) => {
2256 write!(f, "struct {{")?;
2257 for (i, (key, val)) in st.fields.iter().enumerate() {
2258 if i > 0 {
2259 write!(f, ", ")?;
2260 }
2261 write!(f, "{}: {}", key, val)?;
2262 }
2263 write!(f, "}}")
2264 }
2265 Value::OutputList(values) => {
2266 write!(f, "[")?;
2267 for (i, value) in values.iter().enumerate() {
2268 if i > 0 {
2269 write!(f, ", ")?;
2270 }
2271 write!(f, "{}", value)?;
2272 }
2273 write!(f, "]")
2274 }
2275 Value::FunctionHandle(name)
2276 | Value::ExternalFunctionHandle(name)
2277 | Value::MethodFunctionHandle(name) => {
2278 write!(f, "@{name}")
2279 }
2280 Value::BoundFunctionHandle { name, .. } => write!(f, "@{name}"),
2281 Value::Closure(c) => write!(
2282 f,
2283 "<closure {} captures={}>",
2284 c.function_name,
2285 c.captures.len()
2286 ),
2287 Value::ClassRef(name) => write!(f, "<class {name}>"),
2288 Value::MException(e) => write!(
2289 f,
2290 "MException(identifier='{}', message='{}')",
2291 e.identifier, e.message
2292 ),
2293 }
2294 }
2295}
2296
2297impl fmt::Display for ComplexTensor {
2298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2299 match self.shape.len() {
2300 0 | 1 => {
2301 write!(f, "[")?;
2302 for (i, (re, im)) in self.data.iter().enumerate() {
2303 if i > 0 {
2304 write!(f, " ")?;
2305 }
2306 let s = Value::Complex(*re, *im).to_string();
2307 write!(f, "{s}")?;
2308 }
2309 write!(f, "]")
2310 }
2311 2 => {
2312 let rows = self.rows;
2313 let cols = self.cols;
2314 write!(f, "[")?;
2315 for r in 0..rows {
2316 for c in 0..cols {
2317 if c > 0 {
2318 write!(f, " ")?;
2319 }
2320 let (re, im) = self.data[r + c * rows];
2321 let s = Value::Complex(re, im).to_string();
2322 write!(f, "{s}")?;
2323 }
2324 if r + 1 < rows {
2325 write!(f, "; ")?;
2326 }
2327 }
2328 write!(f, "]")
2329 }
2330 _ => {
2331 if should_expand_nd_display(&self.shape) {
2332 write_nd_pages(f, &self.shape, |f, idx| {
2333 let (re, im) = self.data[idx];
2334 write!(f, "{}", Value::Complex(re, im))
2335 })
2336 } else {
2337 write!(f, "ComplexTensor(shape={:?})", self.shape)
2338 }
2339 }
2340 }
2341 }
2342}
2343
2344#[cfg(test)]
2345mod display_tests {
2346 use super::{
2347 fmt_rational, format_number, set_display_format, ComplexTensor, FormatMode, LogicalArray,
2348 Tensor,
2349 };
2350
2351 #[test]
2352 fn fmt_rational_large_value_with_tiny_fract_does_not_overflow() {
2353 let result = std::panic::catch_unwind(|| fmt_rational(1_000_000_000_000_000.000_1));
2356 assert!(
2357 result.is_ok(),
2358 "fmt_rational panicked on large value with tiny fract"
2359 );
2360
2361 let result = std::panic::catch_unwind(|| fmt_rational(-1_000_000_000_000_000.000_1));
2363 assert!(
2364 result.is_ok(),
2365 "fmt_rational panicked on negative large value with tiny fract"
2366 );
2367 }
2368
2369 #[test]
2370 fn tensor_nd_display_uses_page_headers() {
2371 let tensor = Tensor::new(
2372 vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
2373 vec![2, 3, 2],
2374 )
2375 .expect("tensor");
2376 let rendered = tensor.to_string();
2377 assert!(rendered.contains("(:, :, 1) ="));
2378 assert!(rendered.contains("(:, :, 2) ="));
2379 assert!(rendered.contains(" 1 0 0"));
2380 }
2381
2382 #[test]
2383 fn tensor_nd_display_falls_back_for_large_arrays() {
2384 let tensor = Tensor::new(vec![0.0; 4097], vec![1, 1, 4097]).expect("tensor");
2385 assert_eq!(tensor.to_string(), "Tensor(shape=[1, 1, 4097])");
2386 }
2387
2388 #[test]
2389 fn logical_nd_display_uses_headers_and_fallback_summary() {
2390 let logical =
2391 LogicalArray::new(vec![1, 0, 0, 1, 1, 0, 0, 1], vec![2, 2, 2]).expect("logical");
2392 let rendered = logical.to_string();
2393 assert!(rendered.contains("(:, :, 1) ="));
2394 assert!(rendered.contains("(:, :, 2) ="));
2395
2396 let large = LogicalArray::new(vec![1; 4097], vec![1, 1, 4097]).expect("large logical");
2397 assert_eq!(large.to_string(), "1x1x4097 logical array");
2398 }
2399
2400 #[test]
2401 fn complex_nd_display_uses_page_headers() {
2402 let complex = ComplexTensor::new(
2403 vec![(1.0, 0.0), (0.0, 1.0), (0.0, 0.0), (1.0, 0.0)],
2404 vec![2, 1, 2],
2405 )
2406 .expect("complex");
2407 let rendered = complex.to_string();
2408 assert!(rendered.contains("(:, :, 1) ="));
2409 assert!(rendered.contains("(:, :, 2) ="));
2410 }
2411
2412 #[test]
2413 fn format_hex_preserves_negative_zero_sign_bit() {
2414 set_display_format(FormatMode::Hex);
2415 assert_eq!(format_number(-0.0), "8000000000000000");
2416 assert_eq!(format_number(0.0), "0000000000000000");
2417 set_display_format(FormatMode::Short);
2418 }
2419}
2420
2421#[derive(Debug, Clone, PartialEq)]
2422pub struct CellArray {
2423 pub data: Vec<Value>,
2424 pub shape: Vec<usize>,
2426 pub rows: usize,
2428 pub cols: usize,
2430}
2431
2432impl CellArray {
2433 pub fn new(data: Vec<Value>, rows: usize, cols: usize) -> Result<Self, String> {
2434 Self::new_with_shape(data, vec![rows, cols])
2435 }
2436
2437 pub fn new_with_shape(data: Vec<Value>, shape: Vec<usize>) -> Result<Self, String> {
2438 let expected = total_len(&shape)
2439 .ok_or_else(|| "Cell data shape exceeds platform limits".to_string())?;
2440 if expected != data.len() {
2441 return Err(format!(
2442 "Cell data length {} doesn't match shape {:?} ({} elements)",
2443 data.len(),
2444 shape,
2445 expected
2446 ));
2447 }
2448 let (rows, cols) = shape_rows_cols(&shape);
2449 Ok(CellArray {
2450 data,
2451 shape,
2452 rows,
2453 cols,
2454 })
2455 }
2456
2457 pub fn get(&self, row: usize, col: usize) -> Result<Value, String> {
2458 if row >= self.rows || col >= self.cols {
2459 return Err(format!(
2460 "Cell index ({row}, {col}) out of bounds for {}x{} cell array",
2461 self.rows, self.cols
2462 ));
2463 }
2464 Ok(self.data[row * self.cols + col].clone())
2465 }
2466}
2467
2468fn total_len(shape: &[usize]) -> Option<usize> {
2469 if shape.is_empty() {
2470 return Some(0);
2471 }
2472 shape
2473 .iter()
2474 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
2475}
2476
2477fn shape_rows_cols(shape: &[usize]) -> (usize, usize) {
2478 if shape.is_empty() {
2479 return (0, 0);
2480 }
2481 if shape.len() == 1 {
2482 return (1, shape[0]);
2483 }
2484 (shape[0], shape[1])
2485}
2486
2487impl fmt::Display for CellArray {
2488 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2489 let dims: Vec<String> = self.shape.iter().map(|d| d.to_string()).collect();
2490 if self.shape.len() > 2 {
2491 return write!(f, "{} cell array", dims.join("x"));
2492 }
2493 write!(f, "{}x{} cell array", self.rows, self.cols)?;
2494 if self.rows == 0 || self.cols == 0 {
2495 return Ok(());
2496 }
2497 for r in 0..self.rows {
2498 writeln!(f)?;
2499 write!(f, " ")?;
2500 for c in 0..self.cols {
2501 if c > 0 {
2502 write!(f, " ")?;
2503 }
2504 let value = self.get(r, c).unwrap_or_else(|_| Value::Num(f64::NAN));
2505 write!(f, "{{{value}}}")?;
2506 }
2507 }
2508 Ok(())
2509 }
2510}
2511
2512#[derive(Debug, Clone, PartialEq)]
2513pub struct ObjectInstance {
2514 pub class_name: String,
2515 pub properties: HashMap<String, Value>,
2516}
2517
2518impl ObjectInstance {
2519 pub fn new(class_name: String) -> Self {
2520 Self {
2521 class_name,
2522 properties: HashMap::new(),
2523 }
2524 }
2525
2526 pub fn is_class(&self, name: &str) -> bool {
2527 self.class_name == name
2528 }
2529}
2530
2531#[derive(Debug, Clone, PartialEq, Eq)]
2533pub enum Access {
2534 Public,
2535 Private,
2536 Protected,
2537}
2538
2539#[derive(Debug, Clone)]
2540pub struct PropertyDef {
2541 pub name: String,
2542 pub is_static: bool,
2543 pub is_constant: bool,
2544 pub is_dependent: bool,
2545 pub get_access: Access,
2546 pub set_access: Access,
2547 pub default_value: Option<Value>,
2548}
2549
2550#[derive(Debug, Clone)]
2551pub struct MethodDef {
2552 pub name: String,
2553 pub is_static: bool,
2554 pub is_abstract: bool,
2555 pub is_sealed: bool,
2556 pub access: Access,
2557 pub function_name: String, pub implicit_class_argument: Option<String>,
2559}
2560
2561#[derive(Debug, Clone)]
2562pub struct ClassDef {
2563 pub name: String, pub parent: Option<String>,
2565 pub properties: HashMap<String, PropertyDef>,
2566 pub methods: HashMap<String, MethodDef>,
2567}
2568
2569thread_local! {
2570 static CLASS_REGISTRY: RefCell<HashMap<String, ClassDef>> =
2571 RefCell::new(primitive_class_registry());
2572 static SEALED_CLASS_REGISTRY: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
2573 static ABSTRACT_CLASS_REGISTRY: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
2574 static STATIC_VALUES: RefCell<HashMap<(String, String), Value>> = RefCell::new(HashMap::new());
2575 static STATIC_VALUE_THREAD_REGISTRATION: StaticValueThreadRegistration =
2576 const { StaticValueThreadRegistration };
2577 static ENUMERATION_REGISTRY: RefCell<HashMap<String, HashSet<String>>> =
2578 RefCell::new(HashMap::new());
2579}
2580
2581static STATIC_VALUE_THREADS: once_cell::sync::Lazy<Mutex<HashSet<ThreadId>>> =
2582 once_cell::sync::Lazy::new(|| Mutex::new(HashSet::new()));
2583
2584struct StaticValueThreadRegistration;
2585
2586impl Drop for StaticValueThreadRegistration {
2587 fn drop(&mut self) {
2588 if let Ok(mut threads) = STATIC_VALUE_THREADS.lock() {
2589 threads.remove(&std::thread::current().id());
2590 }
2591 }
2592}
2593
2594fn mark_static_values_thread_active() {
2595 STATIC_VALUE_THREAD_REGISTRATION.with(|_| {});
2596 if let Ok(mut threads) = STATIC_VALUE_THREADS.lock() {
2597 threads.insert(std::thread::current().id());
2598 }
2599}
2600
2601pub fn static_property_values_exist_on_other_threads() -> bool {
2602 let current = std::thread::current().id();
2603 STATIC_VALUE_THREADS
2604 .lock()
2605 .map(|threads| threads.iter().any(|thread_id| *thread_id != current))
2606 .unwrap_or(false)
2607}
2608
2609pub fn static_property_gc_roots() -> Vec<GcHandle> {
2610 struct RootCollector {
2611 roots: Vec<GcHandle>,
2612 }
2613
2614 impl Tracer for RootCollector {
2615 fn mark(&mut self, handle: GcHandle) {
2616 self.roots.push(handle);
2617 }
2618 }
2619
2620 STATIC_VALUES.with(|values| {
2621 let values = values.borrow();
2622 let mut collector = RootCollector { roots: Vec::new() };
2623 for value in values.values() {
2624 value.trace(&mut collector);
2625 }
2626 collector.roots
2627 })
2628}
2629
2630fn primitive_class_registry() -> HashMap<String, ClassDef> {
2631 ["double", "single", "logical"]
2632 .into_iter()
2633 .map(|class_name| {
2634 let mut methods = HashMap::new();
2635 methods.insert(
2636 "zeros".to_string(),
2637 MethodDef {
2638 name: "zeros".to_string(),
2639 is_static: true,
2640 is_abstract: false,
2641 is_sealed: false,
2642 access: Access::Public,
2643 function_name: "zeros".to_string(),
2644 implicit_class_argument: Some(class_name.to_string()),
2645 },
2646 );
2647 (
2648 class_name.to_string(),
2649 ClassDef {
2650 name: class_name.to_string(),
2651 parent: None,
2652 properties: HashMap::new(),
2653 methods,
2654 },
2655 )
2656 })
2657 .collect()
2658}
2659
2660pub fn register_class(def: ClassDef) {
2661 register_class_with_modifiers(def, false, false);
2662}
2663
2664pub fn register_class_with_sealed(def: ClassDef, is_sealed: bool) {
2665 register_class_with_modifiers(def, is_sealed, false);
2666}
2667
2668pub fn register_class_with_modifiers(def: ClassDef, is_sealed: bool, is_abstract: bool) {
2669 let class_name = def.name.clone();
2670 CLASS_REGISTRY.with(|registry| {
2671 registry.borrow_mut().insert(class_name.clone(), def);
2672 });
2673 SEALED_CLASS_REGISTRY.with(|registry| {
2674 let mut registry = registry.borrow_mut();
2675 if is_sealed {
2676 registry.insert(class_name.clone());
2677 } else {
2678 registry.remove(&class_name);
2679 }
2680 });
2681 ABSTRACT_CLASS_REGISTRY.with(|registry| {
2682 let mut registry = registry.borrow_mut();
2683 if is_abstract {
2684 registry.insert(class_name.clone());
2685 } else {
2686 registry.remove(&class_name);
2687 }
2688 });
2689 ENUMERATION_REGISTRY.with(|registry| {
2690 registry.borrow_mut().entry(class_name).or_default();
2691 });
2692}
2693
2694pub fn register_class_enumerations(class_name: &str, members: impl IntoIterator<Item = String>) {
2695 ENUMERATION_REGISTRY.with(|registry| {
2696 let mut registry = registry.borrow_mut();
2697 let entry = registry.entry(class_name.to_string()).or_default();
2698 entry.clear();
2699 entry.extend(members);
2700 });
2701}
2702
2703pub fn class_has_enumeration_member(class_name: &str, member: &str) -> bool {
2704 ENUMERATION_REGISTRY.with(|registry| {
2705 registry
2706 .borrow()
2707 .get(class_name)
2708 .is_some_and(|members| members.contains(member))
2709 })
2710}
2711
2712pub fn get_class(name: &str) -> Option<ClassDef> {
2713 CLASS_REGISTRY.with(|registry| registry.borrow().get(name).cloned())
2714}
2715
2716pub fn class_names() -> Vec<String> {
2717 CLASS_REGISTRY.with(|registry| registry.borrow().keys().cloned().collect())
2718}
2719
2720pub fn is_class_sealed(name: &str) -> bool {
2721 SEALED_CLASS_REGISTRY.with(|registry| registry.borrow().contains(name))
2722}
2723
2724pub fn is_class_abstract(name: &str) -> bool {
2725 ABSTRACT_CLASS_REGISTRY.with(|registry| registry.borrow().contains(name))
2726}
2727
2728pub fn is_class_or_subclass(class_name: &str, ancestor_name: &str) -> bool {
2729 if class_name == ancestor_name {
2730 return true;
2731 }
2732 CLASS_REGISTRY.with(|registry| {
2733 let registry = registry.borrow();
2734 let mut current = Some(class_name.to_string());
2735 let mut visited = std::collections::HashSet::new();
2736 while let Some(name) = current {
2737 if !visited.insert(name.clone()) {
2738 break;
2739 }
2740 if name == ancestor_name {
2741 return true;
2742 }
2743 current = registry
2744 .get(&name)
2745 .and_then(|class_def| class_def.parent.clone());
2746 }
2747 false
2748 })
2749}
2750
2751pub fn lookup_property(class_name: &str, prop: &str) -> Option<(PropertyDef, String)> {
2754 CLASS_REGISTRY.with(|registry| {
2755 let registry = registry.borrow();
2756 let mut current = Some(class_name.to_string());
2757 let mut visited = std::collections::HashSet::new();
2758 while let Some(name) = current {
2759 if !visited.insert(name.clone()) {
2760 break;
2761 }
2762 if let Some(cls) = registry.get(&name) {
2763 if let Some(p) = cls.properties.get(prop) {
2764 return Some((p.clone(), name));
2765 }
2766 current = cls.parent.clone();
2767 } else {
2768 break;
2769 }
2770 }
2771 None
2772 })
2773}
2774
2775pub fn lookup_method(class_name: &str, method: &str) -> Option<(MethodDef, String)> {
2778 CLASS_REGISTRY.with(|registry| {
2779 let registry = registry.borrow();
2780 let mut current = Some(class_name.to_string());
2781 let mut visited = std::collections::HashSet::new();
2782 while let Some(name) = current {
2783 if !visited.insert(name.clone()) {
2784 break;
2785 }
2786 if let Some(cls) = registry.get(&name) {
2787 if let Some(m) = cls.methods.get(method) {
2788 return Some((m.clone(), name));
2789 }
2790 current = cls.parent.clone();
2791 } else {
2792 break;
2793 }
2794 }
2795 None
2796 })
2797}
2798
2799pub fn get_static_property_value(class_name: &str, prop: &str) -> Option<Value> {
2800 STATIC_VALUES.with(|values| {
2801 values
2802 .borrow()
2803 .get(&(class_name.to_string(), prop.to_string()))
2804 .cloned()
2805 })
2806}
2807
2808pub fn set_static_property_value(class_name: &str, prop: &str, value: Value) {
2809 mark_static_values_thread_active();
2810 STATIC_VALUES.with(|values| {
2811 values
2812 .borrow_mut()
2813 .insert((class_name.to_string(), prop.to_string()), value);
2814 });
2815}
2816
2817pub fn set_static_property_value_in_owner(
2819 class_name: &str,
2820 prop: &str,
2821 value: Value,
2822) -> Result<(), String> {
2823 if let Some((_p, owner)) = lookup_property(class_name, prop) {
2824 set_static_property_value(&owner, prop, value);
2825 Ok(())
2826 } else {
2827 Err(format!("Unknown static property '{class_name}.{prop}'"))
2828 }
2829}
2830
2831#[cfg(test)]
2832mod class_registry_tests {
2833 use super::{
2834 get_class, lookup_method, lookup_property, register_class, Access, ClassDef, MethodDef,
2835 PropertyDef,
2836 };
2837 use std::collections::HashMap;
2838 use std::sync::atomic::{AtomicU64, Ordering};
2839
2840 static TEST_CLASS_COUNTER: AtomicU64 = AtomicU64::new(0);
2841
2842 fn unique_class_name(prefix: &str) -> String {
2843 let id = TEST_CLASS_COUNTER.fetch_add(1, Ordering::Relaxed);
2844 format!("{}_{}", prefix, id)
2845 }
2846
2847 #[test]
2848 fn primitive_classes_expose_static_zeros_method_metadata() {
2849 for class_name in ["double", "single", "logical"] {
2850 let class_def = get_class(class_name).expect("primitive class should be registered");
2851 let method = class_def
2852 .methods
2853 .get("zeros")
2854 .expect("primitive class should expose zeros static method");
2855 assert!(method.is_static, "zeros should be static on {class_name}");
2856 assert_eq!(method.function_name, "zeros");
2857 assert_eq!(method.implicit_class_argument.as_deref(), Some(class_name));
2858
2859 let (resolved, owner) =
2860 lookup_method(class_name, "zeros").expect("lookup should find primitive zeros");
2861 assert_eq!(owner, class_name);
2862 assert_eq!(resolved.function_name, "zeros");
2863 assert_eq!(
2864 resolved.implicit_class_argument.as_deref(),
2865 Some(class_name)
2866 );
2867 }
2868 }
2869
2870 #[test]
2871 fn method_lookup_uses_parent_class_metadata_chain() {
2872 let parent_name = unique_class_name("plan6_parent");
2873 let child_name = unique_class_name("plan6_child");
2874
2875 let mut parent_methods = HashMap::new();
2876 parent_methods.insert(
2877 "parentOnly".to_string(),
2878 MethodDef {
2879 name: "parentOnly".to_string(),
2880 is_static: false,
2881 is_abstract: false,
2882 is_sealed: false,
2883 access: Access::Public,
2884 function_name: "parentOnly_impl".to_string(),
2885 implicit_class_argument: None,
2886 },
2887 );
2888 register_class(ClassDef {
2889 name: parent_name.clone(),
2890 parent: None,
2891 properties: HashMap::new(),
2892 methods: parent_methods,
2893 });
2894 register_class(ClassDef {
2895 name: child_name.clone(),
2896 parent: Some(parent_name.clone()),
2897 properties: HashMap::new(),
2898 methods: HashMap::new(),
2899 });
2900
2901 let (method, owner) = lookup_method(&child_name, "parentOnly")
2902 .expect("child lookup should resolve inherited method through parent metadata");
2903 assert_eq!(owner, parent_name);
2904 assert_eq!(method.function_name, "parentOnly_impl");
2905 }
2906
2907 #[test]
2908 fn method_lookup_handles_parent_cycle() {
2909 let class_a = unique_class_name("plan6_cycle_method_a");
2910 let class_b = unique_class_name("plan6_cycle_method_b");
2911
2912 register_class(ClassDef {
2913 name: class_a.clone(),
2914 parent: Some(class_b.clone()),
2915 properties: HashMap::new(),
2916 methods: HashMap::new(),
2917 });
2918 register_class(ClassDef {
2919 name: class_b.clone(),
2920 parent: Some(class_a.clone()),
2921 properties: HashMap::new(),
2922 methods: HashMap::new(),
2923 });
2924
2925 assert!(
2926 lookup_method(&class_a, "missing").is_none(),
2927 "cyclic parent metadata should terminate missing method lookup"
2928 );
2929 }
2930
2931 #[test]
2932 fn property_lookup_uses_parent_class_metadata_chain() {
2933 let parent_name = unique_class_name("plan6_property_parent");
2934 let child_name = unique_class_name("plan6_property_child");
2935
2936 let mut parent_properties = HashMap::new();
2937 parent_properties.insert(
2938 "parentFlag".to_string(),
2939 PropertyDef {
2940 name: "parentFlag".to_string(),
2941 is_static: false,
2942 is_constant: false,
2943 is_dependent: false,
2944 get_access: Access::Public,
2945 set_access: Access::Public,
2946 default_value: None,
2947 },
2948 );
2949 register_class(ClassDef {
2950 name: parent_name.clone(),
2951 parent: None,
2952 properties: parent_properties,
2953 methods: HashMap::new(),
2954 });
2955 register_class(ClassDef {
2956 name: child_name.clone(),
2957 parent: Some(parent_name.clone()),
2958 properties: HashMap::new(),
2959 methods: HashMap::new(),
2960 });
2961
2962 let (property, owner) = lookup_property(&child_name, "parentFlag")
2963 .expect("child property lookup should resolve inherited property through parent");
2964 assert_eq!(owner, parent_name);
2965 assert_eq!(property.name, "parentFlag");
2966 assert!(!property.is_static);
2967 }
2968
2969 #[test]
2970 fn property_lookup_handles_parent_cycle() {
2971 let class_a = unique_class_name("plan6_cycle_property_a");
2972 let class_b = unique_class_name("plan6_cycle_property_b");
2973
2974 register_class(ClassDef {
2975 name: class_a.clone(),
2976 parent: Some(class_b.clone()),
2977 properties: HashMap::new(),
2978 methods: HashMap::new(),
2979 });
2980 register_class(ClassDef {
2981 name: class_b.clone(),
2982 parent: Some(class_a.clone()),
2983 properties: HashMap::new(),
2984 methods: HashMap::new(),
2985 });
2986
2987 assert!(
2988 lookup_property(&class_a, "missing").is_none(),
2989 "cyclic parent metadata should terminate missing property lookup"
2990 );
2991 }
2992}