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 SymbolicArray(SymbolicArray),
95 Cell(CellArray),
96 Struct(StructValue),
99 GpuTensor(runmat_accelerate_api::GpuTensorHandle),
101 Object(ObjectInstance),
103 HandleObject(HandleRef),
105 Listener(Listener),
107 OutputList(Vec<Value>),
109 FunctionHandle(String),
111 ExternalFunctionHandle(String),
113 MethodFunctionHandle(String),
115 BoundFunctionHandle {
117 name: String,
118 function: usize,
119 },
120 Closure(Closure),
121 ClassRef(String),
122 MException(MException),
123}
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub enum IntValue {
126 I8(i8),
127 I16(i16),
128 I32(i32),
129 I64(i64),
130 U8(u8),
131 U16(u16),
132 U32(u32),
133 U64(u64),
134}
135
136impl IntValue {
137 pub fn to_i64(&self) -> i64 {
138 match self {
139 IntValue::I8(v) => *v as i64,
140 IntValue::I16(v) => *v as i64,
141 IntValue::I32(v) => *v as i64,
142 IntValue::I64(v) => *v,
143 IntValue::U8(v) => *v as i64,
144 IntValue::U16(v) => *v as i64,
145 IntValue::U32(v) => *v as i64,
146 IntValue::U64(v) => {
147 if *v > i64::MAX as u64 {
148 i64::MAX
149 } else {
150 *v as i64
151 }
152 }
153 }
154 }
155 pub fn to_f64(&self) -> f64 {
156 match self {
157 IntValue::U64(value) => *value as f64,
161 _ => self.to_i64() as f64,
162 }
163 }
164 pub fn is_zero(&self) -> bool {
165 self.to_i64() == 0
166 }
167 pub fn class_name(&self) -> &'static str {
168 match self {
169 IntValue::I8(_) => "int8",
170 IntValue::I16(_) => "int16",
171 IntValue::I32(_) => "int32",
172 IntValue::I64(_) => "int64",
173 IntValue::U8(_) => "uint8",
174 IntValue::U16(_) => "uint16",
175 IntValue::U32(_) => "uint32",
176 IntValue::U64(_) => "uint64",
177 }
178 }
179}
180
181#[cfg(test)]
182mod int_value_tests {
183 use super::IntValue;
184
185 #[test]
186 fn uint64_to_f64_does_not_clamp_through_int64() {
187 let value = IntValue::U64(u64::MAX);
188 assert_eq!(value.to_f64(), u64::MAX as f64);
189 assert!(value.to_f64() > i64::MAX as f64);
190 }
191}
192
193#[derive(Debug, Clone, PartialEq)]
194pub struct StructValue {
195 pub fields: IndexMap<String, Value>,
196}
197
198impl StructValue {
199 pub fn new() -> Self {
200 Self {
201 fields: IndexMap::new(),
202 }
203 }
204
205 pub fn insert(&mut self, name: impl Into<String>, value: Value) -> Option<Value> {
207 self.fields.insert(name.into(), value)
208 }
209
210 pub fn remove(&mut self, name: &str) -> Option<Value> {
212 self.fields.shift_remove(name)
213 }
214
215 pub fn field_names(&self) -> impl Iterator<Item = &String> {
217 self.fields.keys()
218 }
219}
220
221impl Default for StructValue {
222 fn default() -> Self {
223 Self::new()
224 }
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
228pub enum NumericDType {
229 F64,
230 F32,
231 U8,
232 U16,
233 U32,
234}
235
236#[derive(Debug, Clone, PartialEq)]
242pub enum IntegerStorage {
243 I8(Vec<i8>),
244 I16(Vec<i16>),
245 I32(Vec<i32>),
246 I64(Vec<i64>),
247 U8(Vec<u8>),
248 U16(Vec<u16>),
249 U32(Vec<u32>),
250 U64(Vec<u64>),
251}
252
253impl IntegerStorage {
254 pub fn len(&self) -> usize {
255 match self {
256 Self::I8(values) => values.len(),
257 Self::I16(values) => values.len(),
258 Self::I32(values) => values.len(),
259 Self::I64(values) => values.len(),
260 Self::U8(values) => values.len(),
261 Self::U16(values) => values.len(),
262 Self::U32(values) => values.len(),
263 Self::U64(values) => values.len(),
264 }
265 }
266
267 pub fn is_empty(&self) -> bool {
268 self.len() == 0
269 }
270
271 pub fn class_name(&self) -> &'static str {
272 match self {
273 Self::I8(_) => "int8",
274 Self::I16(_) => "int16",
275 Self::I32(_) => "int32",
276 Self::I64(_) => "int64",
277 Self::U8(_) => "uint8",
278 Self::U16(_) => "uint16",
279 Self::U32(_) => "uint32",
280 Self::U64(_) => "uint64",
281 }
282 }
283
284 pub fn to_f64_vec(&self) -> Vec<f64> {
285 match self {
286 Self::I8(values) => values.iter().map(|&value| value as f64).collect(),
287 Self::I16(values) => values.iter().map(|&value| value as f64).collect(),
288 Self::I32(values) => values.iter().map(|&value| value as f64).collect(),
289 Self::I64(values) => values.iter().map(|&value| value as f64).collect(),
290 Self::U8(values) => values.iter().map(|&value| value as f64).collect(),
291 Self::U16(values) => values.iter().map(|&value| value as f64).collect(),
292 Self::U32(values) => values.iter().map(|&value| value as f64).collect(),
293 Self::U64(values) => values.iter().map(|&value| value as f64).collect(),
294 }
295 }
296
297 pub fn value_at(&self, index: usize) -> Option<IntValue> {
299 match self {
300 Self::I8(values) => values.get(index).copied().map(IntValue::I8),
301 Self::I16(values) => values.get(index).copied().map(IntValue::I16),
302 Self::I32(values) => values.get(index).copied().map(IntValue::I32),
303 Self::I64(values) => values.get(index).copied().map(IntValue::I64),
304 Self::U8(values) => values.get(index).copied().map(IntValue::U8),
305 Self::U16(values) => values.get(index).copied().map(IntValue::U16),
306 Self::U32(values) => values.get(index).copied().map(IntValue::U32),
307 Self::U64(values) => values.get(index).copied().map(IntValue::U64),
308 }
309 }
310
311 pub fn zeros_like(&self, len: usize) -> Self {
313 match self {
314 Self::I8(_) => Self::I8(vec![0; len]),
315 Self::I16(_) => Self::I16(vec![0; len]),
316 Self::I32(_) => Self::I32(vec![0; len]),
317 Self::I64(_) => Self::I64(vec![0; len]),
318 Self::U8(_) => Self::U8(vec![0; len]),
319 Self::U16(_) => Self::U16(vec![0; len]),
320 Self::U32(_) => Self::U32(vec![0; len]),
321 Self::U64(_) => Self::U64(vec![0; len]),
322 }
323 }
324
325 pub fn set_value(&mut self, index: usize, value: IntValue) -> Result<(), String> {
327 match (self, value) {
328 (Self::I8(values), IntValue::I8(value)) => set_integer_element(values, index, value),
329 (Self::I16(values), IntValue::I16(value)) => set_integer_element(values, index, value),
330 (Self::I32(values), IntValue::I32(value)) => set_integer_element(values, index, value),
331 (Self::I64(values), IntValue::I64(value)) => set_integer_element(values, index, value),
332 (Self::U8(values), IntValue::U8(value)) => set_integer_element(values, index, value),
333 (Self::U16(values), IntValue::U16(value)) => set_integer_element(values, index, value),
334 (Self::U32(values), IntValue::U32(value)) => set_integer_element(values, index, value),
335 (Self::U64(values), IntValue::U64(value)) => set_integer_element(values, index, value),
336 (storage, value) => Err(format!(
337 "cannot store {} in {} integer storage",
338 value.class_name(),
339 storage.class_name()
340 )),
341 }
342 }
343}
344
345fn set_integer_element<T>(values: &mut [T], index: usize, value: T) -> Result<(), String> {
346 let slot = values
347 .get_mut(index)
348 .ok_or_else(|| format!("integer storage index {index} is out of bounds"))?;
349 *slot = value;
350 Ok(())
351}
352
353impl NumericDType {
354 pub fn class_name(self) -> &'static str {
355 match self {
356 NumericDType::F64 => "double",
357 NumericDType::F32 => "single",
358 NumericDType::U8 => "uint8",
359 NumericDType::U16 => "uint16",
360 NumericDType::U32 => "uint32",
361 }
362 }
363
364 pub fn byte_size(self) -> usize {
365 match self {
366 NumericDType::F64 => 8,
367 NumericDType::F32 => 4,
368 NumericDType::U8 => 1,
369 NumericDType::U16 => 2,
370 NumericDType::U32 => 4,
371 }
372 }
373}
374
375#[cfg(test)]
376mod integer_storage_tests {
377 use super::{IntegerStorage, Tensor};
378
379 #[test]
380 fn uint64_tensor_keeps_exact_backing_values() {
381 let tensor = Tensor::new_integer(IntegerStorage::U64(vec![0, u64::MAX]), vec![1, 2])
382 .expect("integer tensor");
383
384 assert_eq!(
385 tensor.integer_storage(),
386 Some(&IntegerStorage::U64(vec![0, u64::MAX]))
387 );
388 assert_eq!(tensor.data[1], u64::MAX as f64);
389 }
390
391 #[test]
392 fn integer_tensor_supports_empty_typed_arrays() {
393 let tensor = Tensor::new_integer(IntegerStorage::I64(Vec::new()), vec![0, 1])
394 .expect("empty integer tensor");
395
396 assert_eq!(
397 tensor.integer_storage().map(IntegerStorage::class_name),
398 Some("int64")
399 );
400 assert!(tensor.data.is_empty());
401 }
402
403 #[test]
404 fn integer_tensor_rejects_shape_length_mismatches() {
405 let err = Tensor::new_integer(IntegerStorage::I16(vec![1, 2]), vec![3, 1])
406 .expect_err("shape mismatch");
407 assert!(err.contains("doesn't match shape"));
408 }
409
410 #[test]
411 fn reshape_preserves_exact_integer_storage() {
412 let tensor = Tensor::new_integer(IntegerStorage::I64(vec![-1, i64::MAX]), vec![1, 2])
413 .expect("integer tensor")
414 .reshape(vec![2, 1])
415 .expect("reshape");
416
417 assert_eq!(tensor.shape, vec![2, 1]);
418 assert_eq!(
419 tensor.integer_storage(),
420 Some(&IntegerStorage::I64(vec![-1, i64::MAX]))
421 );
422 }
423}
424
425#[derive(Debug, Clone, PartialEq)]
426pub struct Tensor {
427 pub data: Vec<f64>,
428 pub integer_data: Option<IntegerStorage>,
431 pub shape: Vec<usize>, pub rows: usize, pub cols: usize, pub dtype: NumericDType,
436}
437
438#[derive(Debug, Clone, PartialEq)]
439pub struct SparseTensor {
440 pub rows: usize,
441 pub cols: usize,
442 pub col_ptrs: Vec<usize>,
444 pub row_indices: Vec<usize>,
446 pub values: Vec<f64>,
448 pub integer_data: Option<IntegerStorage>,
450}
451
452#[derive(Debug, Clone, PartialEq)]
453pub struct ComplexTensor {
454 pub data: Vec<(f64, f64)>,
455 pub shape: Vec<usize>,
456 pub rows: usize,
457 pub cols: usize,
458}
459
460#[derive(Debug, Clone, PartialEq)]
461pub struct SymbolicArray {
462 pub data: Vec<SymbolicExpr>,
463 pub shape: Vec<usize>,
464 pub rows: usize,
465 pub cols: usize,
466}
467
468#[derive(Debug, Clone, PartialEq)]
469pub struct StringArray {
470 pub data: Vec<String>,
471 pub shape: Vec<usize>,
472 pub rows: usize,
473 pub cols: usize,
474}
475
476#[derive(Debug, Clone, PartialEq)]
477pub struct LogicalArray {
478 pub data: Vec<u8>, pub shape: Vec<usize>,
480}
481
482impl LogicalArray {
483 pub fn new(data: Vec<u8>, shape: Vec<usize>) -> Result<Self, String> {
484 let expected: usize = shape.iter().product();
485 if data.len() != expected {
486 return Err(format!(
487 "LogicalArray data length {} doesn't match shape {:?} ({} elements)",
488 data.len(),
489 shape,
490 expected
491 ));
492 }
493 let mut d = data;
495 for v in &mut d {
496 *v = if *v != 0 { 1 } else { 0 };
497 }
498 Ok(LogicalArray { data: d, shape })
499 }
500 pub fn zeros(shape: Vec<usize>) -> Self {
501 let expected: usize = shape.iter().product();
502 LogicalArray {
503 data: vec![0u8; expected],
504 shape,
505 }
506 }
507 pub fn len(&self) -> usize {
508 self.data.len()
509 }
510 pub fn is_empty(&self) -> bool {
511 self.data.is_empty()
512 }
513}
514
515#[derive(Debug, Clone, PartialEq)]
516pub struct CharArray {
517 pub data: Vec<char>,
518 pub rows: usize,
519 pub cols: usize,
520}
521
522impl CharArray {
523 pub fn new_row(s: &str) -> Self {
524 CharArray {
525 data: s.chars().collect(),
526 rows: 1,
527 cols: s.chars().count(),
528 }
529 }
530 pub fn new(data: Vec<char>, rows: usize, cols: usize) -> Result<Self, String> {
531 if rows * cols != data.len() {
532 return Err(format!(
533 "Char data length {} doesn't match dimensions {}x{}",
534 data.len(),
535 rows,
536 cols
537 ));
538 }
539 Ok(CharArray { data, rows, cols })
540 }
541
542 pub fn row_string(&self) -> Option<String> {
547 (self.rows == 1).then(|| self.data.iter().collect())
548 }
549}
550
551impl StringArray {
552 pub fn new(data: Vec<String>, shape: Vec<usize>) -> Result<Self, String> {
553 let expected: usize = shape.iter().product();
554 if data.len() != expected {
555 return Err(format!(
556 "StringArray data length {} doesn't match shape {:?} ({} elements)",
557 data.len(),
558 shape,
559 expected
560 ));
561 }
562 let (rows, cols) = if shape.len() >= 2 {
563 (shape[0], shape[1])
564 } else if shape.len() == 1 {
565 (1, shape[0])
566 } else {
567 (0, 0)
568 };
569 Ok(StringArray {
570 data,
571 shape,
572 rows,
573 cols,
574 })
575 }
576 pub fn new_2d(data: Vec<String>, rows: usize, cols: usize) -> Result<Self, String> {
577 Self::new(data, vec![rows, cols])
578 }
579 pub fn rows(&self) -> usize {
580 self.shape.first().copied().unwrap_or(1)
581 }
582 pub fn cols(&self) -> usize {
583 self.shape.get(1).copied().unwrap_or(1)
584 }
585}
586
587impl SymbolicArray {
588 pub fn new(data: Vec<SymbolicExpr>, shape: Vec<usize>) -> Result<Self, String> {
589 let expected: usize = shape.iter().product();
590 if data.len() != expected {
591 return Err(format!(
592 "SymbolicArray data length {} doesn't match shape {:?} ({} elements)",
593 data.len(),
594 shape,
595 expected
596 ));
597 }
598 let rows = shape.first().copied().unwrap_or(1);
601 let cols = shape.get(1).copied().unwrap_or(1);
602 Ok(SymbolicArray {
603 data,
604 shape,
605 rows,
606 cols,
607 })
608 }
609
610 pub fn new_2d(data: Vec<SymbolicExpr>, rows: usize, cols: usize) -> Result<Self, String> {
611 Self::new(data, vec![rows, cols])
612 }
613
614 pub fn rows(&self) -> usize {
615 self.rows
616 }
617
618 pub fn cols(&self) -> usize {
619 self.cols
620 }
621}
622
623impl Tensor {
626 pub fn new(data: Vec<f64>, shape: Vec<usize>) -> Result<Self, String> {
627 let expected: usize = shape.iter().product();
628 if data.len() != expected {
629 return Err(format!(
630 "Tensor data length {} doesn't match shape {:?} ({} elements)",
631 data.len(),
632 shape,
633 expected
634 ));
635 }
636 let (rows, cols) = if shape.len() >= 2 {
637 (shape[0], shape[1])
638 } else if shape.len() == 1 {
639 (1, shape[0])
640 } else {
641 (0, 0)
642 };
643 Ok(Tensor {
644 data,
645 integer_data: None,
646 shape,
647 rows,
648 cols,
649 dtype: NumericDType::F64,
650 })
651 }
652
653 pub fn new_2d(data: Vec<f64>, rows: usize, cols: usize) -> Result<Self, String> {
654 Self::new(data, vec![rows, cols])
655 }
656
657 pub fn from_f32(data: Vec<f32>, shape: Vec<usize>) -> Result<Self, String> {
658 let converted: Vec<f64> = data.into_iter().map(|v| v as f64).collect();
659 Self::new_with_dtype(converted, shape, NumericDType::F32)
660 }
661
662 pub fn from_f32_slice(data: &[f32], shape: &[usize]) -> Result<Self, String> {
663 let converted: Vec<f64> = data.iter().map(|&v| v as f64).collect();
664 Self::new_with_dtype(converted, shape.to_vec(), NumericDType::F32)
665 }
666
667 pub fn new_with_dtype(
668 data: Vec<f64>,
669 shape: Vec<usize>,
670 dtype: NumericDType,
671 ) -> Result<Self, String> {
672 let mut t = Self::new(data, shape)?;
673 t.dtype = dtype;
674 Ok(t)
675 }
676
677 pub fn new_integer(storage: IntegerStorage, shape: Vec<usize>) -> Result<Self, String> {
682 let expected: usize = shape.iter().product();
683 if storage.len() != expected {
684 return Err(format!(
685 "integer tensor data length {} doesn't match shape {:?} ({} elements)",
686 storage.len(),
687 shape,
688 expected
689 ));
690 }
691
692 let (rows, cols) = if shape.len() >= 2 {
693 (shape[0], shape[1])
694 } else if shape.len() == 1 {
695 (1, shape[0])
696 } else {
697 (0, 0)
698 };
699 Ok(Tensor {
700 data: storage.to_f64_vec(),
701 integer_data: Some(storage),
702 shape,
703 rows,
704 cols,
705 dtype: NumericDType::F64,
708 })
709 }
710
711 pub fn integer_storage(&self) -> Option<&IntegerStorage> {
712 self.integer_data.as_ref()
713 }
714
715 pub fn reshape(mut self, shape: Vec<usize>) -> Result<Self, String> {
717 let expected: usize = shape.iter().product();
718 if self.data.len() != expected {
719 return Err(format!(
720 "Tensor data length {} doesn't match shape {:?} ({} elements)",
721 self.data.len(),
722 shape,
723 expected
724 ));
725 }
726 if let Some(storage) = &self.integer_data {
727 if storage.len() != expected {
728 return Err(format!(
729 "integer tensor data length {} doesn't match shape {:?} ({} elements)",
730 storage.len(),
731 shape,
732 expected
733 ));
734 }
735 }
736 let (rows, cols) = if shape.len() >= 2 {
737 (shape[0], shape[1])
738 } else if shape.len() == 1 {
739 (1, shape[0])
740 } else {
741 (0, 0)
742 };
743 self.shape = shape;
744 self.rows = rows;
745 self.cols = cols;
746 Ok(self)
747 }
748
749 pub fn zeros(shape: Vec<usize>) -> Self {
750 let size: usize = shape.iter().product();
751 let (rows, cols) = if shape.len() >= 2 {
752 (shape[0], shape[1])
753 } else if shape.len() == 1 {
754 (1, shape[0])
755 } else {
756 (0, 0)
757 };
758 Tensor {
759 data: vec![0.0; size],
760 integer_data: None,
761 shape,
762 rows,
763 cols,
764 dtype: NumericDType::F64,
765 }
766 }
767
768 pub fn ones(shape: Vec<usize>) -> Self {
769 let size: usize = shape.iter().product();
770 let (rows, cols) = if shape.len() >= 2 {
771 (shape[0], shape[1])
772 } else if shape.len() == 1 {
773 (1, shape[0])
774 } else {
775 (0, 0)
776 };
777 Tensor {
778 data: vec![1.0; size],
779 integer_data: None,
780 shape,
781 rows,
782 cols,
783 dtype: NumericDType::F64,
784 }
785 }
786
787 pub fn zeros2(rows: usize, cols: usize) -> Self {
789 Self::zeros(vec![rows, cols])
790 }
791 pub fn ones2(rows: usize, cols: usize) -> Self {
792 Self::ones(vec![rows, cols])
793 }
794
795 pub fn rows(&self) -> usize {
796 self.shape.first().copied().unwrap_or(1)
797 }
798 pub fn cols(&self) -> usize {
799 self.shape.get(1).copied().unwrap_or(1)
800 }
801
802 pub fn get2(&self, row: usize, col: usize) -> Result<f64, String> {
803 let rows = self.rows();
804 let cols = self.cols();
805 if row >= rows || col >= cols {
806 return Err(format!(
807 "Index ({row}, {col}) out of bounds for {rows}x{cols} tensor"
808 ));
809 }
810 Ok(self.data[row + col * rows])
812 }
813
814 pub fn set2(&mut self, row: usize, col: usize, value: f64) -> Result<(), String> {
815 let rows = self.rows();
816 let cols = self.cols();
817 if row >= rows || col >= cols {
818 return Err(format!(
819 "Index ({row}, {col}) out of bounds for {rows}x{cols} tensor"
820 ));
821 }
822 self.data[row + col * rows] = value;
824 Ok(())
825 }
826
827 pub fn scalar_to_tensor2(scalar: f64, rows: usize, cols: usize) -> Tensor {
828 Tensor {
829 data: vec![scalar; rows * cols],
830 integer_data: None,
831 shape: vec![rows, cols],
832 rows,
833 cols,
834 dtype: NumericDType::F64,
835 }
836 }
837 }
839
840impl SparseTensor {
841 pub fn new(
842 rows: usize,
843 cols: usize,
844 col_ptrs: Vec<usize>,
845 row_indices: Vec<usize>,
846 values: Vec<f64>,
847 ) -> Result<Self, String> {
848 Self::validate_structure(rows, cols, &col_ptrs, &row_indices, values.len())?;
849 Ok(Self {
850 rows,
851 cols,
852 col_ptrs,
853 row_indices,
854 values,
855 integer_data: None,
856 })
857 }
858
859 pub fn new_integer(
861 rows: usize,
862 cols: usize,
863 col_ptrs: Vec<usize>,
864 row_indices: Vec<usize>,
865 integer_data: IntegerStorage,
866 ) -> Result<Self, String> {
867 Self::validate_structure(rows, cols, &col_ptrs, &row_indices, integer_data.len())?;
868 let values = integer_data.to_f64_vec();
869 Ok(Self {
870 rows,
871 cols,
872 col_ptrs,
873 row_indices,
874 values,
875 integer_data: Some(integer_data),
876 })
877 }
878
879 fn validate_structure(
880 rows: usize,
881 cols: usize,
882 col_ptrs: &[usize],
883 row_indices: &[usize],
884 values_len: usize,
885 ) -> Result<(), String> {
886 if col_ptrs.len() != cols.saturating_add(1) {
887 return Err(format!(
888 "SparseTensor col_ptrs length {} doesn't match cols {}",
889 col_ptrs.len(),
890 cols
891 ));
892 }
893 if row_indices.len() != values_len {
894 return Err(format!(
895 "SparseTensor row index length {} doesn't match value length {}",
896 row_indices.len(),
897 values_len
898 ));
899 }
900 if col_ptrs.first().copied().unwrap_or(usize::MAX) != 0 {
901 return Err("SparseTensor col_ptrs must start at 0".to_string());
902 }
903 if col_ptrs.last().copied().unwrap_or(usize::MAX) != values_len {
904 return Err("SparseTensor final col_ptr must equal nnz".to_string());
905 }
906 for window in col_ptrs.windows(2) {
907 if window[0] > window[1] {
908 return Err("SparseTensor col_ptrs must be nondecreasing".to_string());
909 }
910 }
911 for col in 0..cols {
912 let start = col_ptrs[col];
913 let end = col_ptrs[col + 1];
914 let mut prev: Option<usize> = None;
915 for &row in &row_indices[start..end] {
916 if row >= rows {
917 return Err(format!("SparseTensor row index {row} exceeds rows {rows}"));
918 }
919 if prev.is_some_and(|p| p >= row) {
920 return Err("SparseTensor row indices must be sorted and unique".to_string());
921 }
922 prev = Some(row);
923 }
924 }
925 Ok(())
926 }
927
928 pub fn zeros(rows: usize, cols: usize) -> Self {
929 Self {
930 rows,
931 cols,
932 col_ptrs: vec![0; cols.saturating_add(1)],
933 row_indices: Vec::new(),
934 values: Vec::new(),
935 integer_data: None,
936 }
937 }
938
939 pub fn nnz(&self) -> usize {
940 self.integer_data
941 .as_ref()
942 .map_or_else(|| self.values.len(), IntegerStorage::len)
943 }
944
945 pub fn shape(&self) -> Vec<usize> {
946 vec![self.rows, self.cols]
947 }
948
949 pub fn to_dense(&self) -> Result<Tensor, String> {
950 let len = self
951 .rows
952 .checked_mul(self.cols)
953 .ok_or_else(|| "SparseTensor dense dimensions overflow usize".to_string())?;
954 if let Some(integer_data) = &self.integer_data {
955 let mut data = integer_data.zeros_like(len);
956 for col in 0..self.cols {
957 for idx in self.col_ptrs[col]..self.col_ptrs[col + 1] {
958 let row = self.row_indices[idx];
959 let value = integer_data.value_at(idx).ok_or_else(|| {
960 "SparseTensor integer storage is inconsistent".to_string()
961 })?;
962 data.set_value(row + col * self.rows, value)?;
963 }
964 }
965 return Tensor::new_integer(data, self.shape());
966 }
967 let mut data = Vec::new();
968 data.try_reserve_exact(len)
969 .map_err(|err| format!("SparseTensor dense allocation failed: {err}"))?;
970 data.resize(len, 0.0);
971 for col in 0..self.cols {
972 for idx in self.col_ptrs[col]..self.col_ptrs[col + 1] {
973 let row = self.row_indices[idx];
974 data[row + col * self.rows] = self.values[idx];
975 }
976 }
977 Tensor::new(data, self.shape())
978 }
979
980 pub fn get(&self, row: usize, col: usize) -> Option<f64> {
981 if row >= self.rows || col >= self.cols {
982 return None;
983 }
984 let start = self.col_ptrs[col];
985 let end = self.col_ptrs[col + 1];
986 self.row_indices[start..end]
987 .binary_search(&row)
988 .ok()
989 .map(|offset| self.values[start + offset])
990 }
991
992 pub fn integer_at(&self, row: usize, col: usize) -> Option<IntValue> {
994 let integer_data = self.integer_data.as_ref()?;
995 if row >= self.rows || col >= self.cols {
996 return None;
997 }
998 let start = self.col_ptrs[col];
999 let end = self.col_ptrs[col + 1];
1000 self.row_indices[start..end]
1001 .binary_search(&row)
1002 .ok()
1003 .and_then(|offset| integer_data.value_at(start + offset))
1004 }
1005
1006 pub fn integer_storage(&self) -> Option<&IntegerStorage> {
1007 self.integer_data.as_ref()
1008 }
1009
1010 pub fn class_name(&self) -> &'static str {
1011 self.integer_data
1012 .as_ref()
1013 .map_or("double", IntegerStorage::class_name)
1014 }
1015}
1016
1017#[cfg(test)]
1018mod sparse_tensor_tests {
1019 use super::*;
1020
1021 #[test]
1022 fn to_dense_rejects_overflowing_dimensions() {
1023 let sparse = SparseTensor {
1024 rows: usize::MAX,
1025 cols: 2,
1026 col_ptrs: vec![0, 0, 0],
1027 row_indices: Vec::new(),
1028 values: Vec::new(),
1029 integer_data: None,
1030 };
1031
1032 let err = sparse.to_dense().unwrap_err();
1033 assert!(err.contains("overflow"));
1034 }
1035}
1036
1037impl ComplexTensor {
1038 pub fn new(data: Vec<(f64, f64)>, shape: Vec<usize>) -> Result<Self, String> {
1039 let expected: usize = shape.iter().product();
1040 if data.len() != expected {
1041 return Err(format!(
1042 "ComplexTensor data length {} doesn't match shape {:?} ({} elements)",
1043 data.len(),
1044 shape,
1045 expected
1046 ));
1047 }
1048 let (rows, cols) = if shape.len() >= 2 {
1049 (shape[0], shape[1])
1050 } else if shape.len() == 1 {
1051 (1, shape[0])
1052 } else {
1053 (0, 0)
1054 };
1055 Ok(ComplexTensor {
1056 data,
1057 shape,
1058 rows,
1059 cols,
1060 })
1061 }
1062 pub fn new_2d(data: Vec<(f64, f64)>, rows: usize, cols: usize) -> Result<Self, String> {
1063 Self::new(data, vec![rows, cols])
1064 }
1065 pub fn zeros(shape: Vec<usize>) -> Self {
1066 let size: usize = shape.iter().product();
1067 let (rows, cols) = if shape.len() >= 2 {
1068 (shape[0], shape[1])
1069 } else if shape.len() == 1 {
1070 (1, shape[0])
1071 } else {
1072 (0, 0)
1073 };
1074 ComplexTensor {
1075 data: vec![(0.0, 0.0); size],
1076 shape,
1077 rows,
1078 cols,
1079 }
1080 }
1081}
1082
1083const MAX_ND_DISPLAY_ELEMENTS: usize = 4096;
1084
1085fn should_expand_nd_display(shape: &[usize]) -> bool {
1086 shape.len() > 2
1087 && matches!(
1088 total_len(shape),
1089 Some(total) if total > 0 && total <= MAX_ND_DISPLAY_ELEMENTS
1090 )
1091}
1092
1093fn column_major_strides(shape: &[usize]) -> Vec<usize> {
1094 let mut strides = Vec::with_capacity(shape.len());
1095 let mut stride = 1usize;
1096 for &dim in shape {
1097 strides.push(stride);
1098 stride = stride.saturating_mul(dim);
1099 }
1100 strides
1101}
1102
1103fn decode_page_coords(mut page_index: usize, page_shape: &[usize]) -> Vec<usize> {
1104 let mut coords = Vec::with_capacity(page_shape.len());
1105 for &dim in page_shape {
1106 if dim == 0 {
1107 coords.push(0);
1108 } else {
1109 coords.push(page_index % dim);
1110 page_index /= dim;
1111 }
1112 }
1113 coords
1114}
1115
1116fn write_nd_pages(
1117 f: &mut fmt::Formatter<'_>,
1118 shape: &[usize],
1119 mut write_element: impl FnMut(&mut fmt::Formatter<'_>, usize) -> fmt::Result,
1120) -> fmt::Result {
1121 if shape.len() <= 2 {
1122 return Ok(());
1123 }
1124 let rows = shape[0];
1125 let cols = shape[1];
1126 if rows == 0 || cols == 0 {
1127 return write!(f, "[]");
1128 }
1129 let Some(page_count) = total_len(&shape[2..]) else {
1130 return write!(f, "Tensor(shape={shape:?})");
1131 };
1132 if page_count == 0 {
1133 return write!(f, "[]");
1134 }
1135 let strides = column_major_strides(shape);
1136 for page_index in 0..page_count {
1137 if page_index > 0 {
1138 write!(f, "\n\n")?;
1139 }
1140 let coords = decode_page_coords(page_index, &shape[2..]);
1141 write!(f, "(:, :")?;
1142 for &coord in &coords {
1143 write!(f, ", {}", coord + 1)?;
1144 }
1145 write!(f, ") =")?;
1146
1147 let mut page_base = 0usize;
1148 for (offset, &coord) in coords.iter().enumerate() {
1149 page_base += coord * strides[offset + 2];
1150 }
1151 for r in 0..rows {
1152 writeln!(f)?;
1153 write!(f, " ")?;
1154 for c in 0..cols {
1155 if c > 0 {
1156 write!(f, " ")?;
1157 }
1158 let linear = page_base + r + c * rows;
1159 write_element(f, linear)?;
1160 }
1161 }
1162 }
1163 Ok(())
1164}
1165
1166impl fmt::Display for Tensor {
1167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1168 match self.shape.len() {
1169 0 | 1 => {
1170 write!(f, "[")?;
1172 for (i, v) in self.data.iter().enumerate() {
1173 if i > 0 {
1174 write!(f, " ")?;
1175 }
1176 write!(f, "{}", format_number(*v))?;
1177 }
1178 write!(f, "]")
1179 }
1180 2 => {
1181 let rows = self.rows();
1182 let cols = self.cols();
1183 for r in 0..rows {
1185 writeln!(f)?;
1186 write!(f, " ")?; for c in 0..cols {
1188 if c > 0 {
1189 write!(f, " ")?;
1190 }
1191 let v = self.data[r + c * rows];
1192 write!(f, "{}", format_number(v))?;
1193 }
1194 }
1195 Ok(())
1196 }
1197 _ => {
1198 if should_expand_nd_display(&self.shape) {
1199 write_nd_pages(f, &self.shape, |f, idx| {
1200 write!(f, "{}", format_number(self.data[idx]))
1201 })
1202 } else {
1203 write!(f, "Tensor(shape={:?})", self.shape)
1204 }
1205 }
1206 }
1207 }
1208}
1209
1210impl fmt::Display for SymbolicArray {
1211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1212 match self.shape.len() {
1213 0 | 1 => {
1214 write!(f, "[")?;
1215 for (i, expr) in self.data.iter().enumerate() {
1216 if i > 0 {
1217 write!(f, " ")?;
1218 }
1219 write!(f, "{expr}")?;
1220 }
1221 write!(f, "]")
1222 }
1223 2 => {
1224 let rows = self.rows();
1225 let cols = self.cols();
1226 for r in 0..rows {
1227 writeln!(f)?;
1228 write!(f, " ")?;
1229 for c in 0..cols {
1230 if c > 0 {
1231 write!(f, " ")?;
1232 }
1233 write!(f, "{}", self.data[r + c * rows])?;
1234 }
1235 }
1236 Ok(())
1237 }
1238 _ => {
1239 if should_expand_nd_display(&self.shape) {
1240 write_nd_pages(f, &self.shape, |f, idx| write!(f, "{}", self.data[idx]))
1241 } else {
1242 write!(f, "SymbolicArray(shape={:?})", self.shape)
1243 }
1244 }
1245 }
1246 }
1247}
1248
1249impl fmt::Display for SparseTensor {
1250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1251 writeln!(
1252 f,
1253 "{}x{} sparse double matrix with {} nonzero entries",
1254 self.rows,
1255 self.cols,
1256 self.nnz()
1257 )?;
1258 if self.nnz() == 0 {
1259 return Ok(());
1260 }
1261 for col in 0..self.cols {
1262 for idx in self.col_ptrs[col]..self.col_ptrs[col + 1] {
1263 let row = self.row_indices[idx];
1264 writeln!(
1265 f,
1266 " ({},{}) {}",
1267 row + 1,
1268 col + 1,
1269 format_number(self.values[idx])
1270 )?;
1271 }
1272 }
1273 Ok(())
1274 }
1275}
1276
1277impl fmt::Display for StringArray {
1278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1279 let (rows, cols) = match self.shape.len() {
1280 0 => (0, 0),
1281 1 => (1, self.shape[0]),
1282 _ => (self.shape[0], self.shape[1]),
1283 };
1284 let count = self.data.len();
1285 if count == 1 && rows == 1 && cols == 1 {
1286 let v = &self.data[0];
1287 if v == "<missing>" {
1288 return write!(f, "<missing>");
1289 }
1290 let escaped = v.replace('"', "\\\"");
1291 return write!(f, "\"{escaped}\"");
1292 }
1293 if self.shape.len() > 2 {
1294 let dims: Vec<String> = self.shape.iter().map(|d| d.to_string()).collect();
1295 return write!(f, "{} string array", dims.join("x"));
1296 }
1297 write!(f, "{rows}x{cols} string array")?;
1298 if rows == 0 || cols == 0 {
1299 return Ok(());
1300 }
1301 for r in 0..rows {
1302 writeln!(f)?;
1303 write!(f, " ")?;
1304 for c in 0..cols {
1305 if c > 0 {
1306 write!(f, " ")?;
1307 }
1308 let v = &self.data[r + c * rows];
1309 if v == "<missing>" {
1310 write!(f, "<missing>")?;
1311 } else {
1312 let escaped = v.replace('"', "\\\"");
1313 write!(f, "\"{escaped}\"")?;
1314 }
1315 }
1316 }
1317 Ok(())
1318 }
1319}
1320
1321impl fmt::Display for LogicalArray {
1322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1323 if self.data.len() == 1 {
1324 return write!(f, "{}", if self.data[0] != 0 { 1 } else { 0 });
1325 }
1326 match self.shape.len() {
1327 0 => write!(f, "[]"),
1328 1 => {
1329 write!(f, "[")?;
1330 for (i, v) in self.data.iter().enumerate() {
1331 if i > 0 {
1332 write!(f, " ")?;
1333 }
1334 write!(f, "{}", if *v != 0 { 1 } else { 0 })?;
1335 }
1336 write!(f, "]")
1337 }
1338 2 => {
1339 let rows = self.shape[0];
1340 let cols = self.shape[1];
1341 for r in 0..rows {
1343 writeln!(f)?;
1344 write!(f, " ")?; for c in 0..cols {
1346 if c > 0 {
1347 write!(f, " ")?;
1348 }
1349 let idx = r + c * rows;
1350 write!(f, "{}", if self.data[idx] != 0 { 1 } else { 0 })?;
1351 }
1352 }
1353 Ok(())
1354 }
1355 _ => {
1356 if should_expand_nd_display(&self.shape) {
1357 write_nd_pages(f, &self.shape, |f, idx| {
1358 write!(f, "{}", if self.data[idx] != 0 { 1 } else { 0 })
1359 })
1360 } else {
1361 let dims: Vec<String> = self.shape.iter().map(|d| d.to_string()).collect();
1362 write!(f, "{} logical array", dims.join("x"))
1363 }
1364 }
1365 }
1366 }
1367}
1368
1369impl fmt::Display for CharArray {
1370 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1371 for r in 0..self.rows {
1372 writeln!(f)?;
1373 write!(f, " ")?; for c in 0..self.cols {
1375 let ch = self.data[r * self.cols + c];
1376 write!(f, "{ch}")?;
1377 }
1378 }
1379 Ok(())
1380 }
1381}
1382
1383impl From<i32> for Value {
1385 fn from(i: i32) -> Self {
1386 Value::Int(IntValue::I32(i))
1387 }
1388}
1389impl From<i64> for Value {
1390 fn from(i: i64) -> Self {
1391 Value::Int(IntValue::I64(i))
1392 }
1393}
1394impl From<u32> for Value {
1395 fn from(i: u32) -> Self {
1396 Value::Int(IntValue::U32(i))
1397 }
1398}
1399impl From<u64> for Value {
1400 fn from(i: u64) -> Self {
1401 Value::Int(IntValue::U64(i))
1402 }
1403}
1404impl From<i16> for Value {
1405 fn from(i: i16) -> Self {
1406 Value::Int(IntValue::I16(i))
1407 }
1408}
1409impl From<i8> for Value {
1410 fn from(i: i8) -> Self {
1411 Value::Int(IntValue::I8(i))
1412 }
1413}
1414impl From<u16> for Value {
1415 fn from(i: u16) -> Self {
1416 Value::Int(IntValue::U16(i))
1417 }
1418}
1419impl From<u8> for Value {
1420 fn from(i: u8) -> Self {
1421 Value::Int(IntValue::U8(i))
1422 }
1423}
1424
1425impl From<f64> for Value {
1426 fn from(f: f64) -> Self {
1427 Value::Num(f)
1428 }
1429}
1430
1431impl From<bool> for Value {
1432 fn from(b: bool) -> Self {
1433 Value::Bool(b)
1434 }
1435}
1436
1437impl From<String> for Value {
1438 fn from(s: String) -> Self {
1439 Value::String(s)
1440 }
1441}
1442
1443impl From<&str> for Value {
1444 fn from(s: &str) -> Self {
1445 Value::String(s.to_string())
1446 }
1447}
1448
1449impl From<Tensor> for Value {
1450 fn from(m: Tensor) -> Self {
1451 Value::Tensor(m)
1452 }
1453}
1454
1455impl TryFrom<&Value> for i32 {
1459 type Error = String;
1460 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1461 match v {
1462 Value::Int(i) => Ok(i.to_i64() as i32),
1463 Value::Num(n) => Ok(*n as i32),
1464 _ => Err(format!("cannot convert {v:?} to i32")),
1465 }
1466 }
1467}
1468
1469impl TryFrom<&Value> for f64 {
1470 type Error = String;
1471 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1472 match v {
1473 Value::Num(n) => Ok(*n),
1474 Value::Int(i) => Ok(i.to_f64()),
1475 _ => Err(format!("cannot convert {v:?} to f64")),
1476 }
1477 }
1478}
1479
1480impl TryFrom<&Value> for bool {
1481 type Error = String;
1482 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1483 match v {
1484 Value::Bool(b) => Ok(*b),
1485 Value::Int(i) => Ok(!i.is_zero()),
1486 Value::Num(n) => Ok(*n != 0.0),
1487 _ => Err(format!("cannot convert {v:?} to bool")),
1488 }
1489 }
1490}
1491
1492impl TryFrom<&Value> for String {
1493 type Error = String;
1494 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1495 match v {
1496 Value::String(s) => Ok(s.clone()),
1497 Value::StringArray(sa) => {
1498 if sa.data.len() == 1 {
1499 Ok(sa.data[0].clone())
1500 } else {
1501 Err("cannot convert string array to scalar string".to_string())
1502 }
1503 }
1504 Value::CharArray(ca) => {
1505 if ca.rows == 1 {
1507 Ok(ca.data.iter().collect())
1508 } else {
1509 Err("cannot convert multi-row char array to scalar string".to_string())
1510 }
1511 }
1512 Value::Int(i) => Ok(i.to_i64().to_string()),
1513 Value::Num(n) => Ok(n.to_string()),
1514 Value::Bool(b) => Ok(b.to_string()),
1515 _ => Err(format!("cannot convert {v:?} to String")),
1516 }
1517 }
1518}
1519
1520impl TryFrom<&Value> for Tensor {
1521 type Error = String;
1522 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1523 match v {
1524 Value::Tensor(m) => Ok(m.clone()),
1525 _ => Err(format!("cannot convert {v:?} to Tensor")),
1526 }
1527 }
1528}
1529
1530impl TryFrom<&Value> for Value {
1531 type Error = String;
1532 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1533 Ok(v.clone())
1534 }
1535}
1536
1537impl TryFrom<&Value> for Vec<Value> {
1538 type Error = String;
1539 fn try_from(v: &Value) -> Result<Self, Self::Error> {
1540 match v {
1541 Value::Cell(c) => Ok(c.data.clone()),
1542 _ => Err(format!("cannot convert {v:?} to Vec<Value>")),
1543 }
1544 }
1545}
1546
1547use serde::{Deserialize, Serialize};
1548
1549#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
1552pub enum Type {
1553 Int,
1555 Num,
1557 Bool,
1559 Logical {
1561 shape: Option<Vec<Option<usize>>>,
1563 },
1564 String,
1566 Tensor {
1568 shape: Option<Vec<Option<usize>>>,
1570 },
1571 Symbolic,
1573 SymbolicArray {
1575 shape: Option<Vec<Option<usize>>>,
1577 },
1578 Cell {
1580 element_type: Option<Box<Type>>,
1582 length: Option<usize>,
1584 },
1585 Function {
1587 params: Vec<Type>,
1589 returns: Box<Type>,
1591 },
1592 Void,
1594 Unknown,
1596 Union(Vec<Type>),
1598 Struct {
1600 known_fields: Option<Vec<String>>, },
1603 OutputList(Vec<Type>),
1605}
1606
1607impl Type {
1608 pub fn tensor() -> Self {
1610 Type::Tensor { shape: None }
1611 }
1612
1613 pub fn logical() -> Self {
1615 Type::Logical { shape: None }
1616 }
1617
1618 pub fn logical_with_shape(shape: Vec<usize>) -> Self {
1620 Type::Logical {
1621 shape: Some(shape.into_iter().map(Some).collect()),
1622 }
1623 }
1624
1625 pub fn tensor_with_shape(shape: Vec<usize>) -> Self {
1627 Type::Tensor {
1628 shape: Some(shape.into_iter().map(Some).collect()),
1629 }
1630 }
1631
1632 pub fn cell() -> Self {
1634 Type::Cell {
1635 element_type: None,
1636 length: None,
1637 }
1638 }
1639
1640 pub fn cell_of(element_type: Type) -> Self {
1642 Type::Cell {
1643 element_type: Some(Box::new(element_type)),
1644 length: None,
1645 }
1646 }
1647
1648 pub fn is_compatible_with(&self, other: &Type) -> bool {
1650 match (self, other) {
1651 (Type::Unknown, _) | (_, Type::Unknown) => true,
1652 (Type::Int, Type::Num) | (Type::Num, Type::Int) => true, (Type::Tensor { .. }, Type::Tensor { .. }) => true, (Type::OutputList(a), Type::OutputList(b)) => a.len() == b.len(),
1655 (a, b) => a == b,
1656 }
1657 }
1658
1659 pub fn unify(&self, other: &Type) -> Type {
1661 match (self, other) {
1662 (Type::Unknown, t) | (t, Type::Unknown) => t.clone(),
1663 (Type::Int, Type::Num) | (Type::Num, Type::Int) => Type::Num,
1664 (Type::Tensor { shape: a }, Type::Tensor { shape: b }) => {
1665 let a_norm = match a {
1666 Some(dims) if dims.is_empty() => None,
1667 _ => a.clone(),
1668 };
1669 let b_norm = match b {
1670 Some(dims) if dims.is_empty() => None,
1671 _ => b.clone(),
1672 };
1673 let a_unknown = a_norm
1674 .as_ref()
1675 .map(|dims| dims.iter().all(|d| d.is_none()))
1676 .unwrap_or(true);
1677 let b_unknown = b_norm
1678 .as_ref()
1679 .map(|dims| dims.iter().all(|d| d.is_none()))
1680 .unwrap_or(true);
1681 if a_norm == b_norm
1682 || (!a_unknown && b_unknown)
1683 || (a_norm.is_some() && b_norm.is_none())
1684 {
1685 Type::Tensor { shape: a_norm }
1686 } else if (a_unknown && !b_unknown) || (a_norm.is_none() && b_norm.is_some()) {
1687 Type::Tensor { shape: b_norm }
1688 } else {
1689 Type::tensor()
1690 }
1691 }
1692 (Type::Logical { shape: a }, Type::Logical { shape: b }) => {
1693 let a_norm = match a {
1694 Some(dims) if dims.is_empty() => None,
1695 _ => a.clone(),
1696 };
1697 let b_norm = match b {
1698 Some(dims) if dims.is_empty() => None,
1699 _ => b.clone(),
1700 };
1701 let a_unknown = a_norm
1702 .as_ref()
1703 .map(|dims| dims.iter().all(|d| d.is_none()))
1704 .unwrap_or(true);
1705 let b_unknown = b_norm
1706 .as_ref()
1707 .map(|dims| dims.iter().all(|d| d.is_none()))
1708 .unwrap_or(true);
1709 if a_norm == b_norm
1710 || (!a_unknown && b_unknown)
1711 || (a_norm.is_some() && b_norm.is_none())
1712 {
1713 Type::Logical { shape: a_norm }
1714 } else if (a_unknown && !b_unknown) || (a_norm.is_none() && b_norm.is_some()) {
1715 Type::Logical { shape: b_norm }
1716 } else {
1717 Type::logical()
1718 }
1719 }
1720 (Type::Struct { known_fields: a }, Type::Struct { known_fields: b }) => match (a, b) {
1721 (None, None) => Type::Struct { known_fields: None },
1722 (Some(ka), None) | (None, Some(ka)) => Type::Struct {
1723 known_fields: Some(ka.clone()),
1724 },
1725 (Some(ka), Some(kb)) => {
1726 let mut set: std::collections::BTreeSet<String> = ka.iter().cloned().collect();
1727 set.extend(kb.iter().cloned());
1728 Type::Struct {
1729 known_fields: Some(set.into_iter().collect()),
1730 }
1731 }
1732 },
1733 (Type::OutputList(a), Type::OutputList(b)) => {
1734 if a.len() == b.len() {
1735 let items = a
1736 .iter()
1737 .zip(b.iter())
1738 .map(|(lhs, rhs)| lhs.unify(rhs))
1739 .collect();
1740 Type::OutputList(items)
1741 } else {
1742 Type::OutputList(vec![Type::Unknown; a.len().max(b.len())])
1743 }
1744 }
1745 (a, b) if a == b => a.clone(),
1746 _ => Type::Union(vec![self.clone(), other.clone()]),
1747 }
1748 }
1749
1750 pub fn from_value(value: &Value) -> Type {
1752 match value {
1753 Value::Int(_) => Type::Int,
1754 Value::Num(_) => Type::Num,
1755 Value::Complex(_, _) => Type::Num, Value::Bool(_) => Type::Bool,
1757 Value::LogicalArray(arr) => Type::Logical {
1758 shape: Some(arr.shape.iter().map(|&d| Some(d)).collect()),
1759 },
1760 Value::String(_) => Type::String,
1761 Value::StringArray(_sa) => {
1762 Type::cell_of(Type::String)
1764 }
1765 Value::Tensor(t) => Type::Tensor {
1766 shape: Some(t.shape.iter().map(|&d| Some(d)).collect()),
1767 },
1768 Value::SparseTensor(t) => Type::Tensor {
1769 shape: Some(vec![Some(t.rows), Some(t.cols)]),
1770 },
1771 Value::ComplexTensor(t) => Type::Tensor {
1772 shape: Some(t.shape.iter().map(|&d| Some(d)).collect()),
1773 },
1774 Value::Symbolic(_) => Type::Symbolic,
1775 Value::SymbolicArray(array) => Type::SymbolicArray {
1776 shape: Some(array.shape.iter().map(|&d| Some(d)).collect()),
1777 },
1778 Value::Cell(cells) => {
1779 if cells.data.is_empty() {
1780 Type::cell()
1781 } else {
1782 let element_type = Type::from_value(&cells.data[0]);
1784 Type::Cell {
1785 element_type: Some(Box::new(element_type)),
1786 length: Some(cells.data.len()),
1787 }
1788 }
1789 }
1790 Value::GpuTensor(h) => Type::Tensor {
1791 shape: Some(h.shape.iter().map(|&d| Some(d)).collect()),
1792 },
1793 Value::Object(_) => Type::Unknown,
1794 Value::HandleObject(_) => Type::Unknown,
1795 Value::Listener(_) => Type::Unknown,
1796 Value::Struct(_) => Type::Struct { known_fields: None },
1797 Value::FunctionHandle(_)
1798 | Value::ExternalFunctionHandle(_)
1799 | Value::MethodFunctionHandle(_)
1800 | Value::BoundFunctionHandle { .. } => Type::Function {
1801 params: vec![Type::Unknown],
1802 returns: Box::new(Type::Unknown),
1803 },
1804 Value::Closure(_) => Type::Function {
1805 params: vec![Type::Unknown],
1806 returns: Box::new(Type::Unknown),
1807 },
1808 Value::ClassRef(_) => Type::Unknown,
1809 Value::MException(_) => Type::Unknown,
1810 Value::CharArray(ca) => {
1811 Type::Cell {
1813 element_type: Some(Box::new(Type::String)),
1814 length: Some(ca.rows * ca.cols),
1815 }
1816 }
1817 Value::OutputList(values) => {
1818 Type::OutputList(values.iter().map(Type::from_value).collect())
1819 }
1820 }
1821 }
1822}
1823
1824#[derive(Debug, Clone, PartialEq)]
1825pub struct Closure {
1826 pub function_name: String,
1827 pub bound_function: Option<usize>,
1828 pub captures: Vec<Value>,
1829}
1830
1831#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1833pub enum AccelTag {
1834 Unary,
1835 Elementwise,
1836 Reduction,
1837 MatMul,
1838 Transpose,
1839 ArrayConstruct,
1840}
1841
1842pub type BuiltinControlFlow = runmat_async::RuntimeError;
1844
1845pub type BuiltinFuture = Pin<Box<dyn Future<Output = Result<Value, BuiltinControlFlow>> + 'static>>;
1847
1848#[derive(Clone, Debug, Default)]
1849pub struct ResolveContext {
1850 pub literal_args: Vec<LiteralValue>,
1851}
1852
1853#[derive(Clone, Debug, PartialEq)]
1854pub enum LiteralValue {
1855 Number(f64),
1856 Bool(bool),
1857 String(String),
1858 Vector(Vec<LiteralValue>),
1859 Unknown,
1860}
1861
1862impl ResolveContext {
1863 pub fn new(literal_args: Vec<LiteralValue>) -> Self {
1864 Self { literal_args }
1865 }
1866
1867 pub fn numeric_dims(&self) -> Vec<Option<usize>> {
1868 self.numeric_dims_from(0)
1869 }
1870
1871 pub fn numeric_dims_from(&self, start: usize) -> Vec<Option<usize>> {
1872 let slice = self.literal_args.get(start..).unwrap_or(&[]);
1873 if let Some(LiteralValue::Vector(values)) = slice.first() {
1874 return values
1875 .iter()
1876 .map(Self::numeric_dimension_from_literal)
1877 .collect();
1878 }
1879 slice
1880 .iter()
1881 .map(Self::numeric_dimension_from_literal)
1882 .collect()
1883 }
1884
1885 pub fn literal_string_at(&self, index: usize) -> Option<String> {
1886 match self.literal_args.get(index) {
1887 Some(LiteralValue::String(value)) => Some(value.to_ascii_lowercase()),
1888 _ => None,
1889 }
1890 }
1891
1892 pub fn literal_bool_at(&self, index: usize) -> Option<bool> {
1893 match self.literal_args.get(index) {
1894 Some(LiteralValue::Bool(value)) => Some(*value),
1895 _ => None,
1896 }
1897 }
1898
1899 pub fn literal_vector_at(&self, index: usize) -> Option<Vec<LiteralValue>> {
1900 match self.literal_args.get(index) {
1901 Some(LiteralValue::Vector(values)) => Some(values.clone()),
1902 _ => None,
1903 }
1904 }
1905
1906 pub fn numeric_vector_at(&self, index: usize) -> Option<Vec<Option<usize>>> {
1907 let values = match self.literal_args.get(index) {
1908 Some(LiteralValue::Vector(values)) => values,
1909 _ => return None,
1910 };
1911 if values
1912 .iter()
1913 .any(|value| matches!(value, LiteralValue::Vector(_)))
1914 {
1915 return None;
1916 }
1917 Some(
1918 values
1919 .iter()
1920 .map(Self::numeric_dimension_from_literal)
1921 .collect(),
1922 )
1923 }
1924
1925 fn numeric_dimension_from_literal(value: &LiteralValue) -> Option<usize> {
1926 match value {
1927 LiteralValue::Number(num) => {
1928 if num.is_finite() {
1929 let rounded = num.round();
1930 if (num - rounded).abs() <= 1e-9 && rounded >= 0.0 {
1931 return Some(rounded as usize);
1932 }
1933 }
1934 None
1935 }
1936 _ => None,
1937 }
1938 }
1939}
1940
1941#[cfg(test)]
1942mod resolve_context_tests {
1943 use super::{LiteralValue, ResolveContext};
1944
1945 #[test]
1946 fn numeric_dims_reads_vector_literal() {
1947 let ctx = ResolveContext::new(vec![LiteralValue::Vector(vec![
1948 LiteralValue::Number(2.0),
1949 LiteralValue::Number(3.0),
1950 ])]);
1951 assert_eq!(ctx.numeric_dims(), vec![Some(2), Some(3)]);
1952 }
1953
1954 #[test]
1955 fn numeric_dims_skips_non_numeric_entries() {
1956 let ctx = ResolveContext::new(vec![
1957 LiteralValue::Number(4.0),
1958 LiteralValue::String("like".to_string()),
1959 LiteralValue::Unknown,
1960 ]);
1961 assert_eq!(ctx.numeric_dims(), vec![Some(4), None, None]);
1962 }
1963
1964 #[test]
1965 fn numeric_dims_prefers_vector_even_with_trailing_args() {
1966 let ctx = ResolveContext::new(vec![
1967 LiteralValue::Vector(vec![LiteralValue::Number(1.0), LiteralValue::Number(5.0)]),
1968 LiteralValue::String("like".to_string()),
1969 ]);
1970 assert_eq!(ctx.numeric_dims(), vec![Some(1), Some(5)]);
1971 }
1972
1973 #[test]
1974 fn literal_string_is_lowercased() {
1975 let ctx = ResolveContext::new(vec![LiteralValue::String("OmItNaN".to_string())]);
1976 assert_eq!(ctx.literal_string_at(0), Some("omitnan".to_string()));
1977 }
1978
1979 #[test]
1980 fn literal_bool_is_available() {
1981 let ctx = ResolveContext::new(vec![LiteralValue::Bool(true)]);
1982 assert_eq!(ctx.literal_bool_at(0), Some(true));
1983 }
1984
1985 #[test]
1986 fn literal_vector_at_returns_clone() {
1987 let ctx = ResolveContext::new(vec![LiteralValue::Vector(vec![
1988 LiteralValue::Number(7.0),
1989 LiteralValue::Unknown,
1990 ])]);
1991 assert_eq!(
1992 ctx.literal_vector_at(0),
1993 Some(vec![LiteralValue::Number(7.0), LiteralValue::Unknown])
1994 );
1995 }
1996
1997 #[test]
1998 fn numeric_vector_at_rejects_nested_vectors() {
1999 let ctx = ResolveContext::new(vec![LiteralValue::Vector(vec![LiteralValue::Vector(
2000 vec![LiteralValue::Number(1.0)],
2001 )])]);
2002 assert_eq!(ctx.numeric_vector_at(0), None);
2003 }
2004}
2005
2006pub type TypeResolver = fn(args: &[Type]) -> Type;
2007pub type TypeResolverWithContext = fn(args: &[Type], ctx: &ResolveContext) -> Type;
2008
2009#[derive(Clone, Copy, Debug)]
2010pub enum TypeResolverKind {
2011 Simple(TypeResolver),
2012 WithContext(TypeResolverWithContext),
2013}
2014
2015pub fn type_resolver_kind(resolver: TypeResolver) -> TypeResolverKind {
2016 TypeResolverKind::Simple(resolver)
2017}
2018
2019pub fn type_resolver_kind_ctx(resolver: TypeResolverWithContext) -> TypeResolverKind {
2020 TypeResolverKind::WithContext(resolver)
2021}
2022
2023#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
2024pub enum BuiltinOutputMode {
2025 Fixed,
2026 ByRequestedOutputCount,
2027}
2028
2029#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
2030pub enum BuiltinCompletionPolicy {
2031 Public,
2032 MethodOnly,
2033 HiddenInternal,
2034}
2035
2036#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
2037pub enum BuiltinParamArity {
2038 Required,
2039 Optional,
2040 Variadic,
2041}
2042
2043#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
2044pub enum BuiltinParamType {
2045 Any,
2046 NumericScalar,
2047 IntegerScalar,
2048 StringScalar,
2049 NumericArray,
2050 LogicalArray,
2051 SizeArg,
2052 LikePrototype,
2053 AxesHandle,
2054 StyleSpec,
2055 PropertyName,
2056 PropertyValue,
2057}
2058
2059#[derive(Debug, Clone, Serialize)]
2060pub struct BuiltinParamDescriptor {
2061 pub name: &'static str,
2062 pub ty: BuiltinParamType,
2063 pub arity: BuiltinParamArity,
2064 pub default: Option<&'static str>,
2065 pub description: &'static str,
2066}
2067
2068#[derive(Debug, Clone, Serialize)]
2069pub struct BuiltinSignatureDescriptor {
2070 pub label: &'static str,
2071 pub inputs: &'static [BuiltinParamDescriptor],
2072 pub outputs: &'static [BuiltinParamDescriptor],
2073}
2074
2075#[derive(Debug, Clone, Serialize)]
2076pub struct BuiltinErrorDescriptor {
2077 pub code: &'static str,
2078 pub identifier: Option<&'static str>,
2079 pub when: &'static str,
2080 pub message: &'static str,
2081}
2082
2083#[derive(Debug, Clone, Serialize)]
2084pub struct BuiltinDescriptor {
2085 pub signatures: &'static [BuiltinSignatureDescriptor],
2086 pub output_mode: BuiltinOutputMode,
2087 pub completion_policy: BuiltinCompletionPolicy,
2088 pub errors: &'static [BuiltinErrorDescriptor],
2089}
2090
2091#[derive(Debug, Clone)]
2093pub struct BuiltinFunction {
2094 pub name: &'static str,
2095 pub description: &'static str,
2096 pub category: &'static str,
2097 pub doc: &'static str,
2098 pub examples: &'static str,
2099 pub param_types: Vec<Type>,
2100 pub return_type: Type,
2101 pub type_resolver: Option<TypeResolverKind>,
2102 pub implementation: fn(&[Value]) -> BuiltinFuture,
2103 pub accel_tags: &'static [AccelTag],
2104 pub is_sink: bool,
2105 pub suppress_auto_output: bool,
2106 pub descriptor: Option<&'static BuiltinDescriptor>,
2107}
2108
2109impl BuiltinFunction {
2110 #[allow(clippy::too_many_arguments)]
2111 pub fn new(
2112 name: &'static str,
2113 description: &'static str,
2114 category: &'static str,
2115 doc: &'static str,
2116 examples: &'static str,
2117 param_types: Vec<Type>,
2118 return_type: Type,
2119 type_resolver: Option<TypeResolverKind>,
2120 implementation: fn(&[Value]) -> BuiltinFuture,
2121 accel_tags: &'static [AccelTag],
2122 is_sink: bool,
2123 suppress_auto_output: bool,
2124 ) -> Self {
2125 Self {
2126 name,
2127 description,
2128 category,
2129 doc,
2130 examples,
2131 param_types,
2132 return_type,
2133 type_resolver,
2134 implementation,
2135 accel_tags,
2136 is_sink,
2137 suppress_auto_output,
2138 descriptor: None,
2139 }
2140 }
2141
2142 pub fn with_descriptor(mut self, descriptor: &'static BuiltinDescriptor) -> Self {
2143 self.descriptor = Some(descriptor);
2144 self
2145 }
2146
2147 pub fn with_descriptor_option(
2148 mut self,
2149 descriptor: Option<&'static BuiltinDescriptor>,
2150 ) -> Self {
2151 self.descriptor = descriptor;
2152 self
2153 }
2154
2155 pub fn infer_return_type(&self, args: &[Type]) -> Type {
2156 self.infer_return_type_with_context(args, &ResolveContext::default())
2157 }
2158
2159 pub fn infer_return_type_with_context(&self, args: &[Type], ctx: &ResolveContext) -> Type {
2160 if let Some(resolver) = self.type_resolver {
2161 return match resolver {
2162 TypeResolverKind::Simple(resolver) => resolver(args),
2163 TypeResolverKind::WithContext(resolver) => resolver(args, ctx),
2164 };
2165 }
2166 self.return_type.clone()
2167 }
2168
2169 pub fn semantics(&self) -> BuiltinSemantics {
2170 semantics::builtin_semantics_for(self)
2171 }
2172}
2173
2174#[derive(Clone)]
2176pub struct Constant {
2177 pub name: &'static str,
2178 pub value: Value,
2179}
2180
2181pub mod semantics;
2182pub mod shape_rules;
2183
2184pub use semantics::{
2185 builtin_semantics_for, builtin_semantics_for_name, BuiltinAsyncBehavior, BuiltinCompatibility,
2186 BuiltinEffects, BuiltinEnvironmentEffect, BuiltinPurity, BuiltinSemanticKind, BuiltinSemantics,
2187 BuiltinWorkspaceEffect, ConcatKind, ShapeTransformKind,
2188};
2189
2190impl std::fmt::Debug for Constant {
2191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2192 write!(
2193 f,
2194 "Constant {{ name: {:?}, value: {:?} }}",
2195 self.name, self.value
2196 )
2197 }
2198}
2199
2200#[cfg(not(target_arch = "wasm32"))]
2201inventory::collect!(BuiltinFunction);
2202#[cfg(not(target_arch = "wasm32"))]
2203inventory::collect!(Constant);
2204
2205#[cfg(not(target_arch = "wasm32"))]
2206pub fn builtin_functions() -> Vec<&'static BuiltinFunction> {
2207 inventory::iter::<BuiltinFunction>().collect()
2208}
2209
2210#[cfg(target_arch = "wasm32")]
2211pub fn builtin_functions() -> Vec<&'static BuiltinFunction> {
2212 wasm_registry::builtin_functions()
2213}
2214
2215#[cfg(not(target_arch = "wasm32"))]
2216static BUILTIN_LOOKUP: OnceLock<HashMap<String, &'static BuiltinFunction>> = OnceLock::new();
2217
2218#[cfg(not(target_arch = "wasm32"))]
2219fn builtin_lookup_map() -> &'static HashMap<String, &'static BuiltinFunction> {
2220 BUILTIN_LOOKUP.get_or_init(|| {
2221 let mut map = HashMap::new();
2222 for func in builtin_functions() {
2223 map.insert(func.name.to_ascii_lowercase(), func);
2224 }
2225 map
2226 })
2227}
2228
2229#[cfg(not(target_arch = "wasm32"))]
2230pub fn builtin_function_by_name(name: &str) -> Option<&'static BuiltinFunction> {
2231 builtin_lookup_map()
2232 .get(&name.to_ascii_lowercase())
2233 .copied()
2234}
2235
2236#[cfg(target_arch = "wasm32")]
2237pub fn builtin_function_by_name(name: &str) -> Option<&'static BuiltinFunction> {
2238 wasm_registry::builtin_functions()
2239 .into_iter()
2240 .find(|f| f.name.eq_ignore_ascii_case(name))
2241}
2242
2243pub fn suppresses_auto_output(name: &str) -> bool {
2244 builtin_function_by_name(name)
2245 .map(|f| f.suppress_auto_output)
2246 .unwrap_or(false)
2247}
2248
2249#[cfg(not(target_arch = "wasm32"))]
2250pub fn constants() -> Vec<&'static Constant> {
2251 inventory::iter::<Constant>().collect()
2252}
2253
2254#[cfg(target_arch = "wasm32")]
2255pub fn constants() -> Vec<&'static Constant> {
2256 wasm_registry::constants()
2257}
2258
2259#[derive(Debug)]
2264pub struct BuiltinDoc {
2265 pub name: &'static str,
2266 pub category: Option<&'static str>,
2267 pub summary: Option<&'static str>,
2268 pub keywords: Option<&'static str>,
2269 pub errors: Option<&'static str>,
2270 pub related: Option<&'static str>,
2271 pub introduced: Option<&'static str>,
2272 pub status: Option<&'static str>,
2273 pub examples: Option<&'static str>,
2274}
2275
2276#[cfg(not(target_arch = "wasm32"))]
2277inventory::collect!(BuiltinDoc);
2278
2279#[cfg(not(target_arch = "wasm32"))]
2280pub fn builtin_docs() -> Vec<&'static BuiltinDoc> {
2281 inventory::iter::<BuiltinDoc>().collect()
2282}
2283
2284#[cfg(target_arch = "wasm32")]
2285pub fn builtin_docs() -> Vec<&'static BuiltinDoc> {
2286 wasm_registry::builtin_docs()
2287}
2288
2289#[derive(Debug, Clone, Copy, PartialEq, Default)]
2295pub enum FormatMode {
2296 #[default]
2298 Short,
2299 Long,
2301 ShortE,
2303 LongE,
2305 ShortG,
2307 LongG,
2309 Rational,
2311 Hex,
2313}
2314
2315runmat_thread_local! {
2316 static DISPLAY_FORMAT: RefCell<FormatMode> = const { RefCell::new(FormatMode::Short) };
2317}
2318
2319pub fn set_display_format(mode: FormatMode) {
2320 DISPLAY_FORMAT.with(|c| *c.borrow_mut() = mode);
2321}
2322
2323pub fn get_display_format() -> FormatMode {
2324 DISPLAY_FORMAT.with(|c| *c.borrow())
2325}
2326
2327pub fn format_number(value: f64) -> String {
2329 if value.is_nan() {
2330 return "NaN".to_string();
2331 }
2332 if value.is_infinite() {
2333 return if value.is_sign_negative() {
2334 "-Inf"
2335 } else {
2336 "Inf"
2337 }
2338 .to_string();
2339 }
2340 let mode = get_display_format();
2341 if mode == FormatMode::Hex {
2342 return fmt_hex(value);
2343 }
2344 let v = if value == 0.0 { 0.0 } else { value };
2345 match mode {
2346 FormatMode::Short => fmt_short(v),
2347 FormatMode::Long => fmt_long(v),
2348 FormatMode::ShortE => fmt_sci(v, 4),
2349 FormatMode::LongE => fmt_sci(v, 14),
2350 FormatMode::ShortG => fmt_compact(v, 5),
2351 FormatMode::LongG => fmt_compact(v, 15),
2352 FormatMode::Rational => fmt_rational(v),
2353 FormatMode::Hex => unreachable!("hex mode handled before zero normalization"),
2354 }
2355}
2356
2357fn matlab_exp(s: &str) -> String {
2359 if let Some(e_pos) = s.find('e') {
2360 let mantissa = &s[..e_pos];
2361 let exp: i32 = s[e_pos + 1..].parse().unwrap_or(0);
2362 let sign = if exp >= 0 { '+' } else { '-' };
2363 format!("{mantissa}e{sign}{:02}", exp.unsigned_abs())
2364 } else {
2365 s.to_string()
2366 }
2367}
2368
2369fn fmt_sci(v: f64, dec: usize) -> String {
2370 if v == 0.0 {
2371 return format!("0.{:0>dec$}e+00", 0, dec = dec);
2372 }
2373 let s = format!("{v:.dec$e}");
2374 matlab_exp(&s)
2375}
2376
2377fn fmt_short(v: f64) -> String {
2378 let abs = v.abs();
2379 if abs == 0.0 {
2380 return "0".to_string();
2381 }
2382 if v.fract() == 0.0 && abs < 1e15 {
2383 return format!("{}", v as i64);
2384 }
2385 if (0.001..10000.0).contains(&abs) {
2386 format!("{:.4}", v)
2387 } else {
2388 fmt_sci(v, 4)
2389 }
2390}
2391
2392fn fmt_long(v: f64) -> String {
2393 let abs = v.abs();
2394 if abs == 0.0 {
2395 return "0".to_string();
2396 }
2397 if v.fract() == 0.0 && abs < 1e15 {
2398 return format!("{}", v as i64);
2399 }
2400 if (0.001..10000.0).contains(&abs) {
2401 format!("{:.15}", v)
2402 } else {
2403 fmt_sci(v, 14)
2404 }
2405}
2406
2407fn fmt_compact(v: f64, sig_digits: usize) -> String {
2408 let abs = v.abs();
2409 if abs == 0.0 {
2410 return "0".to_string();
2411 }
2412 let use_scientific = !(1e-4..1e6).contains(&abs);
2413 if use_scientific {
2414 let dec = sig_digits - 1;
2415 let s = format!("{v:.dec$e}");
2416 if let Some(e_pos) = s.find('e') {
2418 let exp_part = &s[e_pos..];
2419 let mut mantissa = s[..e_pos].to_string();
2420 if let Some(dot) = mantissa.find('.') {
2421 let mut end = mantissa.len();
2422 while end > dot + 1 && mantissa.as_bytes()[end - 1] == b'0' {
2423 end -= 1;
2424 }
2425 if mantissa.as_bytes()[end - 1] == b'.' {
2426 end -= 1;
2427 }
2428 mantissa.truncate(end);
2429 }
2430 return matlab_exp(&format!("{mantissa}{exp_part}"));
2431 }
2432 return matlab_exp(&s);
2433 }
2434 let exp10 = abs.log10().floor() as i32;
2435 let decimals = ((sig_digits as i32 - 1 - exp10).max(0)) as usize;
2436 let pow = 10f64.powi(decimals as i32);
2437 let rounded = (v * pow).round() / pow;
2438 let mut s = format!("{rounded:.decimals$}");
2439 if let Some(dot) = s.find('.') {
2440 let mut end = s.len();
2441 while end > dot + 1 && s.as_bytes()[end - 1] == b'0' {
2442 end -= 1;
2443 }
2444 if s.as_bytes()[end - 1] == b'.' {
2445 end -= 1;
2446 }
2447 s.truncate(end);
2448 }
2449 if s.is_empty() || s == "-0" {
2450 s = "0".to_string();
2451 }
2452 s
2453}
2454
2455fn fmt_rational(v: f64) -> String {
2456 if v == 0.0 {
2457 return "0".to_string();
2458 }
2459 let negative = v < 0.0;
2460 let abs = v.abs();
2461 if v.fract() == 0.0 && abs < 1e15 {
2462 return format!("{}", v as i64);
2463 }
2464 let tol = 5e-7 * abs;
2467 let max_d = 1_000_000i64;
2468 let mut n0: i64 = 1;
2469 let mut n1: i64 = abs.floor() as i64;
2470 let mut d0: i64 = 0;
2471 let mut d1: i64 = 1;
2472 let mut a = abs;
2473 let mut best_n = n1;
2474 let mut best_d = d1;
2475 for _ in 0..50 {
2476 if (abs - best_n as f64 / best_d as f64).abs() <= tol {
2477 break;
2478 }
2479 let f = a.fract();
2480 if f < 1e-10 {
2481 break;
2482 }
2483 a = 1.0 / f;
2484 let q = a.floor() as i64;
2485 let Some(n2) = q.checked_mul(n1).and_then(|v| v.checked_add(n0)) else {
2486 break;
2487 };
2488 let Some(d2) = q.checked_mul(d1).and_then(|v| v.checked_add(d0)) else {
2489 break;
2490 };
2491 if d2 > max_d {
2492 break;
2493 }
2494 best_n = n2;
2495 best_d = d2;
2496 n0 = n1;
2497 n1 = n2;
2498 d0 = d1;
2499 d1 = d2;
2500 }
2501 let sign = if negative { "-" } else { "" };
2502 if best_d == 1 {
2503 format!("{sign}{best_n}")
2504 } else {
2505 format!("{sign}{best_n}/{best_d}")
2506 }
2507}
2508
2509fn fmt_hex(v: f64) -> String {
2510 format!("{:016x}", v.to_bits())
2511}
2512
2513#[derive(Debug, Clone, PartialEq)]
2515pub struct MException {
2516 pub identifier: String,
2517 pub message: String,
2518 pub stack: Vec<String>,
2519}
2520
2521impl MException {
2522 pub fn new(identifier: String, message: String) -> Self {
2523 Self {
2524 identifier,
2525 message,
2526 stack: Vec::new(),
2527 }
2528 }
2529}
2530
2531#[derive(Debug, Clone)]
2533pub struct HandleRef {
2534 pub class_name: String,
2535 pub target: GcHandle,
2536 pub valid: bool,
2537}
2538
2539impl PartialEq for HandleRef {
2540 fn eq(&self, other: &Self) -> bool {
2541 self.target == other.target
2542 }
2543}
2544
2545#[derive(Debug, Clone, PartialEq)]
2547pub struct Listener {
2548 pub id: u64,
2549 pub target: GcHandle,
2550 pub target_class_name: String,
2551 pub event_name: String,
2552 pub callback: GcHandle,
2553 pub enabled: bool,
2554 pub valid: bool,
2555}
2556
2557impl Listener {
2558 pub fn class_name(&self) -> String {
2559 self.target_class_name.clone()
2560 }
2561}
2562
2563impl Trace for CellArray {
2564 fn trace(&self, tracer: &mut dyn Tracer) {
2565 for value in &self.data {
2566 value.trace(tracer);
2567 }
2568 }
2569}
2570
2571impl Trace for StructValue {
2572 fn trace(&self, tracer: &mut dyn Tracer) {
2573 for value in self.fields.values() {
2574 value.trace(tracer);
2575 }
2576 }
2577}
2578
2579impl Trace for Closure {
2580 fn trace(&self, tracer: &mut dyn Tracer) {
2581 for value in &self.captures {
2582 value.trace(tracer);
2583 }
2584 }
2585}
2586
2587impl Trace for ObjectInstance {
2588 fn trace(&self, tracer: &mut dyn Tracer) {
2589 for value in self.properties.values() {
2590 value.trace(tracer);
2591 }
2592 if let Some(dynamic_properties) = &self.dynamic_properties {
2593 for property in dynamic_properties.values() {
2594 if let Some(metadata_handle) = property.metadata_handle {
2595 tracer.mark(metadata_handle);
2596 }
2597 }
2598 }
2599 }
2600}
2601
2602impl Trace for HandleRef {
2603 fn trace(&self, tracer: &mut dyn Tracer) {
2604 tracer.mark(self.target);
2605 }
2606}
2607
2608impl Trace for Listener {
2609 fn trace(&self, tracer: &mut dyn Tracer) {
2610 tracer.mark(self.target);
2611 tracer.mark(self.callback);
2612 }
2613}
2614
2615impl Trace for Value {
2616 fn trace(&self, tracer: &mut dyn Tracer) {
2617 match self {
2618 Value::Cell(cells) => cells.trace(tracer),
2619 Value::Struct(struct_value) => struct_value.trace(tracer),
2620 Value::HandleObject(handle) => handle.trace(tracer),
2621 Value::Listener(listener) => listener.trace(tracer),
2622 Value::Closure(closure) => closure.trace(tracer),
2623 Value::Object(object) => object.trace(tracer),
2624 Value::OutputList(values) => {
2625 for value in values {
2626 value.trace(tracer);
2627 }
2628 }
2629 Value::Int(_)
2630 | Value::Num(_)
2631 | Value::Complex(_, _)
2632 | Value::Bool(_)
2633 | Value::LogicalArray(_)
2634 | Value::String(_)
2635 | Value::StringArray(_)
2636 | Value::CharArray(_)
2637 | Value::Tensor(_)
2638 | Value::SparseTensor(_)
2639 | Value::ComplexTensor(_)
2640 | Value::Symbolic(_)
2641 | Value::SymbolicArray(_)
2642 | Value::GpuTensor(_)
2643 | Value::FunctionHandle(_)
2644 | Value::ExternalFunctionHandle(_)
2645 | Value::MethodFunctionHandle(_)
2646 | Value::BoundFunctionHandle { .. }
2647 | Value::ClassRef(_)
2648 | Value::MException(_) => {}
2649 }
2650 }
2651}
2652
2653impl fmt::Display for Value {
2654 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2655 match self {
2656 Value::Int(i) => write!(f, "{}", i.to_i64()),
2657 Value::Num(n) => write!(f, "{}", format_number(*n)),
2658 Value::Complex(re, im) => {
2659 if *im == 0.0 {
2660 write!(f, "{}", format_number(*re))
2661 } else if *re == 0.0 {
2662 write!(f, "{}i", format_number(*im))
2663 } else if *im < 0.0 {
2664 write!(f, "{}-{}i", format_number(*re), format_number(im.abs()))
2665 } else {
2666 write!(f, "{}+{}i", format_number(*re), format_number(*im))
2667 }
2668 }
2669 Value::Bool(b) => write!(f, "{}", if *b { 1 } else { 0 }),
2670 Value::LogicalArray(la) => write!(f, "{la}"),
2671 Value::String(s) => write!(f, "'{s}'"),
2672 Value::StringArray(sa) => write!(f, "{sa}"),
2673 Value::CharArray(ca) => write!(f, "{ca}"),
2674 Value::Tensor(m) => write!(f, "{m}"),
2675 Value::SparseTensor(m) => write!(f, "{m}"),
2676 Value::ComplexTensor(m) => write!(f, "{m}"),
2677 Value::Symbolic(expr) => write!(f, "{expr}"),
2678 Value::SymbolicArray(array) => write!(f, "{array}"),
2679 Value::Cell(ca) => ca.fmt(f),
2680
2681 Value::GpuTensor(h) => write!(
2682 f,
2683 "GpuTensor(shape={:?}, device={}, buffer={})",
2684 h.shape, h.device_id, h.buffer_id
2685 ),
2686 Value::Object(obj) => write!(f, "{}(props={})", obj.class_name, obj.properties.len()),
2687 Value::HandleObject(h) => {
2688 write!(
2689 f,
2690 "<handle {} @0x{:x} valid={}>",
2691 h.class_name,
2692 h.target.addr(),
2693 h.valid
2694 )
2695 }
2696 Value::Listener(l) => {
2697 write!(
2698 f,
2699 "<listener id={} {}@0x{:x} '{}' enabled={} valid={}>",
2700 l.id,
2701 l.class_name(),
2702 l.target.addr(),
2703 l.event_name,
2704 l.enabled,
2705 l.valid
2706 )
2707 }
2708 Value::Struct(st) => {
2709 write!(f, "struct {{")?;
2710 for (i, (key, val)) in st.fields.iter().enumerate() {
2711 if i > 0 {
2712 write!(f, ", ")?;
2713 }
2714 write!(f, "{}: {}", key, val)?;
2715 }
2716 write!(f, "}}")
2717 }
2718 Value::OutputList(values) => {
2719 write!(f, "[")?;
2720 for (i, value) in values.iter().enumerate() {
2721 if i > 0 {
2722 write!(f, ", ")?;
2723 }
2724 write!(f, "{}", value)?;
2725 }
2726 write!(f, "]")
2727 }
2728 Value::FunctionHandle(name)
2729 | Value::ExternalFunctionHandle(name)
2730 | Value::MethodFunctionHandle(name) => {
2731 write!(f, "@{name}")
2732 }
2733 Value::BoundFunctionHandle { name, .. } => write!(f, "@{name}"),
2734 Value::Closure(c) => write!(
2735 f,
2736 "<closure {} captures={}>",
2737 c.function_name,
2738 c.captures.len()
2739 ),
2740 Value::ClassRef(name) => write!(f, "<class {name}>"),
2741 Value::MException(e) => write!(
2742 f,
2743 "MException(identifier='{}', message='{}')",
2744 e.identifier, e.message
2745 ),
2746 }
2747 }
2748}
2749
2750impl fmt::Display for ComplexTensor {
2751 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2752 match self.shape.len() {
2753 0 | 1 => {
2754 write!(f, "[")?;
2755 for (i, (re, im)) in self.data.iter().enumerate() {
2756 if i > 0 {
2757 write!(f, " ")?;
2758 }
2759 let s = Value::Complex(*re, *im).to_string();
2760 write!(f, "{s}")?;
2761 }
2762 write!(f, "]")
2763 }
2764 2 => {
2765 let rows = self.rows;
2766 let cols = self.cols;
2767 write!(f, "[")?;
2768 for r in 0..rows {
2769 for c in 0..cols {
2770 if c > 0 {
2771 write!(f, " ")?;
2772 }
2773 let (re, im) = self.data[r + c * rows];
2774 let s = Value::Complex(re, im).to_string();
2775 write!(f, "{s}")?;
2776 }
2777 if r + 1 < rows {
2778 write!(f, "; ")?;
2779 }
2780 }
2781 write!(f, "]")
2782 }
2783 _ => {
2784 if should_expand_nd_display(&self.shape) {
2785 write_nd_pages(f, &self.shape, |f, idx| {
2786 let (re, im) = self.data[idx];
2787 write!(f, "{}", Value::Complex(re, im))
2788 })
2789 } else {
2790 write!(f, "ComplexTensor(shape={:?})", self.shape)
2791 }
2792 }
2793 }
2794 }
2795}
2796
2797#[cfg(test)]
2798mod display_tests {
2799 use super::{
2800 fmt_rational, format_number, set_display_format, ComplexTensor, FormatMode, LogicalArray,
2801 Tensor,
2802 };
2803
2804 #[test]
2805 fn fmt_rational_large_value_with_tiny_fract_does_not_overflow() {
2806 let result = std::panic::catch_unwind(|| fmt_rational(1_000_000_000_000_000.000_1));
2809 assert!(
2810 result.is_ok(),
2811 "fmt_rational panicked on large value with tiny fract"
2812 );
2813
2814 let result = std::panic::catch_unwind(|| fmt_rational(-1_000_000_000_000_000.000_1));
2816 assert!(
2817 result.is_ok(),
2818 "fmt_rational panicked on negative large value with tiny fract"
2819 );
2820 }
2821
2822 #[test]
2823 fn tensor_nd_display_uses_page_headers() {
2824 let tensor = Tensor::new(
2825 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],
2826 vec![2, 3, 2],
2827 )
2828 .expect("tensor");
2829 let rendered = tensor.to_string();
2830 assert!(rendered.contains("(:, :, 1) ="));
2831 assert!(rendered.contains("(:, :, 2) ="));
2832 assert!(rendered.contains(" 1 0 0"));
2833 }
2834
2835 #[test]
2836 fn tensor_nd_display_falls_back_for_large_arrays() {
2837 let tensor = Tensor::new(vec![0.0; 4097], vec![1, 1, 4097]).expect("tensor");
2838 assert_eq!(tensor.to_string(), "Tensor(shape=[1, 1, 4097])");
2839 }
2840
2841 #[test]
2842 fn logical_nd_display_uses_headers_and_fallback_summary() {
2843 let logical =
2844 LogicalArray::new(vec![1, 0, 0, 1, 1, 0, 0, 1], vec![2, 2, 2]).expect("logical");
2845 let rendered = logical.to_string();
2846 assert!(rendered.contains("(:, :, 1) ="));
2847 assert!(rendered.contains("(:, :, 2) ="));
2848
2849 let large = LogicalArray::new(vec![1; 4097], vec![1, 1, 4097]).expect("large logical");
2850 assert_eq!(large.to_string(), "1x1x4097 logical array");
2851 }
2852
2853 #[test]
2854 fn complex_nd_display_uses_page_headers() {
2855 let complex = ComplexTensor::new(
2856 vec![(1.0, 0.0), (0.0, 1.0), (0.0, 0.0), (1.0, 0.0)],
2857 vec![2, 1, 2],
2858 )
2859 .expect("complex");
2860 let rendered = complex.to_string();
2861 assert!(rendered.contains("(:, :, 1) ="));
2862 assert!(rendered.contains("(:, :, 2) ="));
2863 }
2864
2865 #[test]
2866 fn format_hex_preserves_negative_zero_sign_bit() {
2867 set_display_format(FormatMode::Hex);
2868 assert_eq!(format_number(-0.0), "8000000000000000");
2869 assert_eq!(format_number(0.0), "0000000000000000");
2870 set_display_format(FormatMode::Short);
2871 }
2872}
2873
2874#[derive(Debug, Clone, PartialEq)]
2875pub struct CellArray {
2876 pub data: Vec<Value>,
2877 pub shape: Vec<usize>,
2879 pub rows: usize,
2881 pub cols: usize,
2883}
2884
2885impl CellArray {
2886 pub fn new(data: Vec<Value>, rows: usize, cols: usize) -> Result<Self, String> {
2887 Self::new_with_shape(data, vec![rows, cols])
2888 }
2889
2890 pub fn new_with_shape(data: Vec<Value>, shape: Vec<usize>) -> Result<Self, String> {
2891 let expected = total_len(&shape)
2892 .ok_or_else(|| "Cell data shape exceeds platform limits".to_string())?;
2893 if expected != data.len() {
2894 return Err(format!(
2895 "Cell data length {} doesn't match shape {:?} ({} elements)",
2896 data.len(),
2897 shape,
2898 expected
2899 ));
2900 }
2901 let (rows, cols) = shape_rows_cols(&shape);
2902 Ok(CellArray {
2903 data,
2904 shape,
2905 rows,
2906 cols,
2907 })
2908 }
2909
2910 pub fn get(&self, row: usize, col: usize) -> Result<Value, String> {
2911 if row >= self.rows || col >= self.cols {
2912 return Err(format!(
2913 "Cell index ({row}, {col}) out of bounds for {}x{} cell array",
2914 self.rows, self.cols
2915 ));
2916 }
2917 Ok(self.data[row * self.cols + col].clone())
2918 }
2919}
2920
2921fn total_len(shape: &[usize]) -> Option<usize> {
2922 if shape.is_empty() {
2923 return Some(0);
2924 }
2925 shape
2926 .iter()
2927 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
2928}
2929
2930fn shape_rows_cols(shape: &[usize]) -> (usize, usize) {
2931 if shape.is_empty() {
2932 return (0, 0);
2933 }
2934 if shape.len() == 1 {
2935 return (1, shape[0]);
2936 }
2937 (shape[0], shape[1])
2938}
2939
2940impl fmt::Display for CellArray {
2941 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2942 let dims: Vec<String> = self.shape.iter().map(|d| d.to_string()).collect();
2943 if self.shape.len() > 2 {
2944 return write!(f, "{} cell array", dims.join("x"));
2945 }
2946 write!(f, "{}x{} cell array", self.rows, self.cols)?;
2947 if self.rows == 0 || self.cols == 0 {
2948 return Ok(());
2949 }
2950 for r in 0..self.rows {
2951 writeln!(f)?;
2952 write!(f, " ")?;
2953 for c in 0..self.cols {
2954 if c > 0 {
2955 write!(f, " ")?;
2956 }
2957 let value = self.get(r, c).unwrap_or_else(|_| Value::Num(f64::NAN));
2958 write!(f, "{{{value}}}")?;
2959 }
2960 }
2961 Ok(())
2962 }
2963}
2964
2965#[derive(Debug, Clone, PartialEq)]
2966pub struct ObjectInstance {
2967 pub class_name: String,
2968 pub properties: HashMap<String, Value>,
2969 pub dynamic_properties: Option<Box<HashMap<String, DynamicPropertyDef>>>,
2970}
2971
2972impl ObjectInstance {
2973 pub fn new(class_name: String) -> Self {
2974 Self {
2975 class_name,
2976 properties: HashMap::new(),
2977 dynamic_properties: None,
2978 }
2979 }
2980
2981 pub fn is_class(&self, name: &str) -> bool {
2982 self.class_name == name
2983 }
2984
2985 pub fn dynamic_property(&self, name: &str) -> Option<&DynamicPropertyDef> {
2986 self.dynamic_properties
2987 .as_ref()
2988 .and_then(|properties| properties.get(name))
2989 }
2990
2991 pub fn dynamic_property_mut(&mut self, name: &str) -> Option<&mut DynamicPropertyDef> {
2992 self.dynamic_properties
2993 .as_mut()
2994 .and_then(|properties| properties.get_mut(name))
2995 }
2996
2997 pub fn has_dynamic_property(&self, name: &str) -> bool {
2998 self.dynamic_property(name).is_some()
2999 }
3000
3001 pub fn insert_dynamic_property(
3002 &mut self,
3003 name: String,
3004 property: DynamicPropertyDef,
3005 ) -> Option<DynamicPropertyDef> {
3006 self.dynamic_properties
3007 .get_or_insert_with(|| Box::new(HashMap::new()))
3008 .insert(name, property)
3009 }
3010
3011 pub fn remove_dynamic_property(&mut self, name: &str) -> Option<DynamicPropertyDef> {
3012 let properties = self.dynamic_properties.as_mut()?;
3013 let removed = properties.remove(name);
3014 if properties.is_empty() {
3015 self.dynamic_properties = None;
3016 }
3017 removed
3018 }
3019
3020 pub fn dynamic_property_names(&self) -> Vec<String> {
3021 self.dynamic_properties
3022 .as_ref()
3023 .map(|properties| properties.keys().cloned().collect())
3024 .unwrap_or_default()
3025 }
3026}
3027
3028#[derive(Debug, Clone, PartialEq, Eq)]
3030pub enum Access {
3031 Public,
3032 Private,
3033 Protected,
3034}
3035
3036#[derive(Debug, Clone)]
3037pub struct PropertyDef {
3038 pub name: String,
3039 pub is_static: bool,
3040 pub is_constant: bool,
3041 pub is_dependent: bool,
3042 pub get_access: Access,
3043 pub set_access: Access,
3044 pub default_value: Option<Value>,
3045}
3046
3047#[derive(Debug, Clone, PartialEq, Eq)]
3048pub struct DynamicPropertyDef {
3049 pub name: String,
3050 pub defining_class: String,
3051 pub metadata_handle: Option<GcHandle>,
3052 pub get_access: Access,
3053 pub set_access: Access,
3054 pub dependent: bool,
3055 pub hidden: bool,
3056 pub transient: bool,
3057 pub non_copyable: bool,
3058 pub abort_set: bool,
3059 pub set_observable: bool,
3060 pub get_observable: bool,
3061 pub description: String,
3062}
3063
3064impl DynamicPropertyDef {
3065 pub fn new(name: String, defining_class: String) -> Self {
3066 Self {
3067 name,
3068 defining_class,
3069 metadata_handle: None,
3070 get_access: Access::Public,
3071 set_access: Access::Public,
3072 dependent: false,
3073 hidden: false,
3074 transient: false,
3075 non_copyable: false,
3076 abort_set: false,
3077 set_observable: false,
3078 get_observable: false,
3079 description: String::new(),
3080 }
3081 }
3082}
3083
3084#[derive(Debug, Clone)]
3085pub struct MethodDef {
3086 pub name: String,
3087 pub is_static: bool,
3088 pub is_abstract: bool,
3089 pub is_sealed: bool,
3090 pub access: Access,
3091 pub function_name: String, pub implicit_class_argument: Option<String>,
3093}
3094
3095#[derive(Debug, Clone)]
3096pub struct ClassDef {
3097 pub name: String, pub parent: Option<String>,
3099 pub properties: HashMap<String, PropertyDef>,
3100 pub methods: HashMap<String, MethodDef>,
3101}
3102
3103thread_local! {
3104 static CLASS_REGISTRY: RefCell<HashMap<String, ClassDef>> =
3105 RefCell::new(primitive_class_registry());
3106 static SEALED_CLASS_REGISTRY: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
3107 static ABSTRACT_CLASS_REGISTRY: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
3108 static STATIC_VALUES: RefCell<HashMap<(String, String), Value>> = RefCell::new(HashMap::new());
3109 static STATIC_VALUE_THREAD_REGISTRATION: StaticValueThreadRegistration =
3110 const { StaticValueThreadRegistration };
3111 static ENUMERATION_REGISTRY: RefCell<HashMap<String, HashSet<String>>> =
3112 RefCell::new(HashMap::new());
3113}
3114
3115static STATIC_VALUE_THREADS: once_cell::sync::Lazy<Mutex<HashSet<ThreadId>>> =
3116 once_cell::sync::Lazy::new(|| Mutex::new(HashSet::new()));
3117
3118struct StaticValueThreadRegistration;
3119
3120impl Drop for StaticValueThreadRegistration {
3121 fn drop(&mut self) {
3122 if let Ok(mut threads) = STATIC_VALUE_THREADS.lock() {
3123 threads.remove(&std::thread::current().id());
3124 }
3125 }
3126}
3127
3128fn mark_static_values_thread_active() {
3129 STATIC_VALUE_THREAD_REGISTRATION.with(|_| {});
3130 if let Ok(mut threads) = STATIC_VALUE_THREADS.lock() {
3131 threads.insert(std::thread::current().id());
3132 }
3133}
3134
3135pub fn static_property_values_exist_on_other_threads() -> bool {
3136 let current = std::thread::current().id();
3137 STATIC_VALUE_THREADS
3138 .lock()
3139 .map(|threads| threads.iter().any(|thread_id| *thread_id != current))
3140 .unwrap_or(false)
3141}
3142
3143pub fn static_property_gc_roots() -> Vec<GcHandle> {
3144 struct RootCollector {
3145 roots: Vec<GcHandle>,
3146 }
3147
3148 impl Tracer for RootCollector {
3149 fn mark(&mut self, handle: GcHandle) {
3150 self.roots.push(handle);
3151 }
3152 }
3153
3154 STATIC_VALUES.with(|values| {
3155 let values = values.borrow();
3156 let mut collector = RootCollector { roots: Vec::new() };
3157 for value in values.values() {
3158 value.trace(&mut collector);
3159 }
3160 collector.roots
3161 })
3162}
3163
3164fn primitive_class_registry() -> HashMap<String, ClassDef> {
3165 let mut registry: HashMap<String, ClassDef> = ["double", "single", "logical"]
3166 .into_iter()
3167 .map(|class_name| {
3168 let mut methods = HashMap::new();
3169 methods.insert(
3170 "zeros".to_string(),
3171 MethodDef {
3172 name: "zeros".to_string(),
3173 is_static: true,
3174 is_abstract: false,
3175 is_sealed: false,
3176 access: Access::Public,
3177 function_name: "zeros".to_string(),
3178 implicit_class_argument: Some(class_name.to_string()),
3179 },
3180 );
3181 (
3182 class_name.to_string(),
3183 ClassDef {
3184 name: class_name.to_string(),
3185 parent: None,
3186 properties: HashMap::new(),
3187 methods,
3188 },
3189 )
3190 })
3191 .collect();
3192
3193 registry.insert(
3194 "handle".to_string(),
3195 ClassDef {
3196 name: "handle".to_string(),
3197 parent: None,
3198 properties: HashMap::new(),
3199 methods: HashMap::new(),
3200 },
3201 );
3202 registry.insert(
3203 "dynamicprops".to_string(),
3204 ClassDef {
3205 name: "dynamicprops".to_string(),
3206 parent: Some("handle".to_string()),
3207 properties: HashMap::new(),
3208 methods: HashMap::new(),
3209 },
3210 );
3211 registry.insert(
3212 "matlab.metadata.Property".to_string(),
3213 ClassDef {
3214 name: "matlab.metadata.Property".to_string(),
3215 parent: None,
3216 properties: HashMap::new(),
3217 methods: HashMap::new(),
3218 },
3219 );
3220 let mut dynamic_property_methods = HashMap::new();
3221 dynamic_property_methods.insert(
3222 "delete".to_string(),
3223 MethodDef {
3224 name: "delete".to_string(),
3225 is_static: false,
3226 is_abstract: false,
3227 is_sealed: false,
3228 access: Access::Public,
3229 function_name: "matlab.metadata.DynamicProperty.delete".to_string(),
3230 implicit_class_argument: None,
3231 },
3232 );
3233 registry.insert(
3234 "matlab.metadata.DynamicProperty".to_string(),
3235 ClassDef {
3236 name: "matlab.metadata.DynamicProperty".to_string(),
3237 parent: Some("handle".to_string()),
3238 properties: HashMap::new(),
3239 methods: dynamic_property_methods,
3240 },
3241 );
3242
3243 registry
3244}
3245
3246pub fn register_class(def: ClassDef) {
3247 register_class_with_modifiers(def, false, false);
3248}
3249
3250pub fn register_class_with_sealed(def: ClassDef, is_sealed: bool) {
3251 register_class_with_modifiers(def, is_sealed, false);
3252}
3253
3254pub fn register_class_with_modifiers(def: ClassDef, is_sealed: bool, is_abstract: bool) {
3255 let class_name = def.name.clone();
3256 CLASS_REGISTRY.with(|registry| {
3257 registry.borrow_mut().insert(class_name.clone(), def);
3258 });
3259 SEALED_CLASS_REGISTRY.with(|registry| {
3260 let mut registry = registry.borrow_mut();
3261 if is_sealed {
3262 registry.insert(class_name.clone());
3263 } else {
3264 registry.remove(&class_name);
3265 }
3266 });
3267 ABSTRACT_CLASS_REGISTRY.with(|registry| {
3268 let mut registry = registry.borrow_mut();
3269 if is_abstract {
3270 registry.insert(class_name.clone());
3271 } else {
3272 registry.remove(&class_name);
3273 }
3274 });
3275 ENUMERATION_REGISTRY.with(|registry| {
3276 registry.borrow_mut().entry(class_name).or_default();
3277 });
3278}
3279
3280pub fn register_class_enumerations(class_name: &str, members: impl IntoIterator<Item = String>) {
3281 ENUMERATION_REGISTRY.with(|registry| {
3282 let mut registry = registry.borrow_mut();
3283 let entry = registry.entry(class_name.to_string()).or_default();
3284 entry.clear();
3285 entry.extend(members);
3286 });
3287}
3288
3289pub fn class_has_enumeration_member(class_name: &str, member: &str) -> bool {
3290 ENUMERATION_REGISTRY.with(|registry| {
3291 registry
3292 .borrow()
3293 .get(class_name)
3294 .is_some_and(|members| members.contains(member))
3295 })
3296}
3297
3298pub fn get_class(name: &str) -> Option<ClassDef> {
3299 CLASS_REGISTRY.with(|registry| registry.borrow().get(name).cloned())
3300}
3301
3302pub fn class_names() -> Vec<String> {
3303 CLASS_REGISTRY.with(|registry| registry.borrow().keys().cloned().collect())
3304}
3305
3306pub fn is_class_sealed(name: &str) -> bool {
3307 SEALED_CLASS_REGISTRY.with(|registry| registry.borrow().contains(name))
3308}
3309
3310pub fn is_class_abstract(name: &str) -> bool {
3311 ABSTRACT_CLASS_REGISTRY.with(|registry| registry.borrow().contains(name))
3312}
3313
3314pub fn is_class_or_subclass(class_name: &str, ancestor_name: &str) -> bool {
3315 if class_name == ancestor_name {
3316 return true;
3317 }
3318 CLASS_REGISTRY.with(|registry| {
3319 let registry = registry.borrow();
3320 let mut current = Some(class_name.to_string());
3321 let mut visited = std::collections::HashSet::new();
3322 while let Some(name) = current {
3323 if !visited.insert(name.clone()) {
3324 break;
3325 }
3326 if name == ancestor_name {
3327 return true;
3328 }
3329 current = registry
3330 .get(&name)
3331 .and_then(|class_def| class_def.parent.clone());
3332 }
3333 false
3334 })
3335}
3336
3337pub fn superclass_chain(class_name: &str) -> Option<Vec<String>> {
3338 CLASS_REGISTRY.with(|registry| {
3339 let registry = registry.borrow();
3340 let mut current = registry
3341 .get(class_name)
3342 .and_then(|class_def| class_def.parent.clone());
3343 let mut visited = std::collections::HashSet::new();
3344 visited.insert(class_name.to_string());
3345 let mut supers = Vec::new();
3346 while let Some(name) = current {
3347 if !visited.insert(name.clone()) {
3348 break;
3349 }
3350 supers.push(name.clone());
3351 current = registry
3352 .get(&name)
3353 .and_then(|class_def| class_def.parent.clone());
3354 }
3355 if registry.contains_key(class_name) {
3356 Some(supers)
3357 } else {
3358 None
3359 }
3360 })
3361}
3362
3363pub fn lookup_property(class_name: &str, prop: &str) -> Option<(PropertyDef, String)> {
3366 CLASS_REGISTRY.with(|registry| {
3367 let registry = registry.borrow();
3368 let mut current = Some(class_name.to_string());
3369 let mut visited = std::collections::HashSet::new();
3370 while let Some(name) = current {
3371 if !visited.insert(name.clone()) {
3372 break;
3373 }
3374 if let Some(cls) = registry.get(&name) {
3375 if let Some(p) = cls.properties.get(prop) {
3376 return Some((p.clone(), name));
3377 }
3378 current = cls.parent.clone();
3379 } else {
3380 break;
3381 }
3382 }
3383 None
3384 })
3385}
3386
3387pub fn lookup_method(class_name: &str, method: &str) -> Option<(MethodDef, String)> {
3390 CLASS_REGISTRY.with(|registry| {
3391 let registry = registry.borrow();
3392 let mut current = Some(class_name.to_string());
3393 let mut visited = std::collections::HashSet::new();
3394 while let Some(name) = current {
3395 if !visited.insert(name.clone()) {
3396 break;
3397 }
3398 if let Some(cls) = registry.get(&name) {
3399 if let Some(m) = cls.methods.get(method) {
3400 return Some((m.clone(), name));
3401 }
3402 current = cls.parent.clone();
3403 } else {
3404 break;
3405 }
3406 }
3407 None
3408 })
3409}
3410
3411pub fn get_static_property_value(class_name: &str, prop: &str) -> Option<Value> {
3412 STATIC_VALUES.with(|values| {
3413 values
3414 .borrow()
3415 .get(&(class_name.to_string(), prop.to_string()))
3416 .cloned()
3417 })
3418}
3419
3420pub fn set_static_property_value(class_name: &str, prop: &str, value: Value) {
3421 mark_static_values_thread_active();
3422 STATIC_VALUES.with(|values| {
3423 values
3424 .borrow_mut()
3425 .insert((class_name.to_string(), prop.to_string()), value);
3426 });
3427}
3428
3429pub fn set_static_property_value_in_owner(
3431 class_name: &str,
3432 prop: &str,
3433 value: Value,
3434) -> Result<(), String> {
3435 if let Some((_p, owner)) = lookup_property(class_name, prop) {
3436 set_static_property_value(&owner, prop, value);
3437 Ok(())
3438 } else {
3439 Err(format!("Unknown static property '{class_name}.{prop}'"))
3440 }
3441}
3442
3443#[cfg(test)]
3444mod class_registry_tests {
3445 use super::{
3446 get_class, lookup_method, lookup_property, register_class, superclass_chain, Access,
3447 ClassDef, MethodDef, PropertyDef,
3448 };
3449 use std::collections::HashMap;
3450 use std::sync::atomic::{AtomicU64, Ordering};
3451
3452 static TEST_CLASS_COUNTER: AtomicU64 = AtomicU64::new(0);
3453
3454 fn unique_class_name(prefix: &str) -> String {
3455 let id = TEST_CLASS_COUNTER.fetch_add(1, Ordering::Relaxed);
3456 format!("{}_{}", prefix, id)
3457 }
3458
3459 #[test]
3460 fn primitive_classes_expose_static_zeros_method_metadata() {
3461 for class_name in ["double", "single", "logical"] {
3462 let class_def = get_class(class_name).expect("primitive class should be registered");
3463 let method = class_def
3464 .methods
3465 .get("zeros")
3466 .expect("primitive class should expose zeros static method");
3467 assert!(method.is_static, "zeros should be static on {class_name}");
3468 assert_eq!(method.function_name, "zeros");
3469 assert_eq!(method.implicit_class_argument.as_deref(), Some(class_name));
3470
3471 let (resolved, owner) =
3472 lookup_method(class_name, "zeros").expect("lookup should find primitive zeros");
3473 assert_eq!(owner, class_name);
3474 assert_eq!(resolved.function_name, "zeros");
3475 assert_eq!(
3476 resolved.implicit_class_argument.as_deref(),
3477 Some(class_name)
3478 );
3479 }
3480 }
3481
3482 #[test]
3483 fn superclass_chain_reports_nearest_to_root_order() {
3484 let grand = unique_class_name("super_chain_grand");
3485 let parent = unique_class_name("super_chain_parent");
3486 let child = unique_class_name("super_chain_child");
3487
3488 register_class(ClassDef {
3489 name: grand.clone(),
3490 parent: None,
3491 properties: HashMap::new(),
3492 methods: HashMap::new(),
3493 });
3494 register_class(ClassDef {
3495 name: parent.clone(),
3496 parent: Some(grand.clone()),
3497 properties: HashMap::new(),
3498 methods: HashMap::new(),
3499 });
3500 register_class(ClassDef {
3501 name: child.clone(),
3502 parent: Some(parent.clone()),
3503 properties: HashMap::new(),
3504 methods: HashMap::new(),
3505 });
3506
3507 assert_eq!(
3508 superclass_chain(&child),
3509 Some(vec![parent.clone(), grand.clone()])
3510 );
3511 assert_eq!(superclass_chain(&grand), Some(Vec::new()));
3512 assert_eq!(superclass_chain("MissingSuperclassChainClass"), None);
3513 }
3514
3515 #[test]
3516 fn superclass_chain_reports_recorded_parent_when_parent_metadata_is_missing() {
3517 let child = unique_class_name("super_chain_missing_parent_child");
3518 let parent = unique_class_name("super_chain_missing_parent_parent");
3519
3520 register_class(ClassDef {
3521 name: child.clone(),
3522 parent: Some(parent.clone()),
3523 properties: HashMap::new(),
3524 methods: HashMap::new(),
3525 });
3526
3527 assert_eq!(superclass_chain(&child), Some(vec![parent]));
3528 }
3529
3530 #[test]
3531 fn superclass_chain_stops_before_repeating_cycle_start() {
3532 let first = unique_class_name("super_chain_cycle_first");
3533 let second = unique_class_name("super_chain_cycle_second");
3534
3535 register_class(ClassDef {
3536 name: first.clone(),
3537 parent: Some(second.clone()),
3538 properties: HashMap::new(),
3539 methods: HashMap::new(),
3540 });
3541 register_class(ClassDef {
3542 name: second.clone(),
3543 parent: Some(first.clone()),
3544 properties: HashMap::new(),
3545 methods: HashMap::new(),
3546 });
3547
3548 assert_eq!(superclass_chain(&first), Some(vec![second]));
3549 }
3550
3551 #[test]
3552 fn method_lookup_uses_parent_class_metadata_chain() {
3553 let parent_name = unique_class_name("plan6_parent");
3554 let child_name = unique_class_name("plan6_child");
3555
3556 let mut parent_methods = HashMap::new();
3557 parent_methods.insert(
3558 "parentOnly".to_string(),
3559 MethodDef {
3560 name: "parentOnly".to_string(),
3561 is_static: false,
3562 is_abstract: false,
3563 is_sealed: false,
3564 access: Access::Public,
3565 function_name: "parentOnly_impl".to_string(),
3566 implicit_class_argument: None,
3567 },
3568 );
3569 register_class(ClassDef {
3570 name: parent_name.clone(),
3571 parent: None,
3572 properties: HashMap::new(),
3573 methods: parent_methods,
3574 });
3575 register_class(ClassDef {
3576 name: child_name.clone(),
3577 parent: Some(parent_name.clone()),
3578 properties: HashMap::new(),
3579 methods: HashMap::new(),
3580 });
3581
3582 let (method, owner) = lookup_method(&child_name, "parentOnly")
3583 .expect("child lookup should resolve inherited method through parent metadata");
3584 assert_eq!(owner, parent_name);
3585 assert_eq!(method.function_name, "parentOnly_impl");
3586 }
3587
3588 #[test]
3589 fn method_lookup_handles_parent_cycle() {
3590 let class_a = unique_class_name("plan6_cycle_method_a");
3591 let class_b = unique_class_name("plan6_cycle_method_b");
3592
3593 register_class(ClassDef {
3594 name: class_a.clone(),
3595 parent: Some(class_b.clone()),
3596 properties: HashMap::new(),
3597 methods: HashMap::new(),
3598 });
3599 register_class(ClassDef {
3600 name: class_b.clone(),
3601 parent: Some(class_a.clone()),
3602 properties: HashMap::new(),
3603 methods: HashMap::new(),
3604 });
3605
3606 assert!(
3607 lookup_method(&class_a, "missing").is_none(),
3608 "cyclic parent metadata should terminate missing method lookup"
3609 );
3610 }
3611
3612 #[test]
3613 fn property_lookup_uses_parent_class_metadata_chain() {
3614 let parent_name = unique_class_name("plan6_property_parent");
3615 let child_name = unique_class_name("plan6_property_child");
3616
3617 let mut parent_properties = HashMap::new();
3618 parent_properties.insert(
3619 "parentFlag".to_string(),
3620 PropertyDef {
3621 name: "parentFlag".to_string(),
3622 is_static: false,
3623 is_constant: false,
3624 is_dependent: false,
3625 get_access: Access::Public,
3626 set_access: Access::Public,
3627 default_value: None,
3628 },
3629 );
3630 register_class(ClassDef {
3631 name: parent_name.clone(),
3632 parent: None,
3633 properties: parent_properties,
3634 methods: HashMap::new(),
3635 });
3636 register_class(ClassDef {
3637 name: child_name.clone(),
3638 parent: Some(parent_name.clone()),
3639 properties: HashMap::new(),
3640 methods: HashMap::new(),
3641 });
3642
3643 let (property, owner) = lookup_property(&child_name, "parentFlag")
3644 .expect("child property lookup should resolve inherited property through parent");
3645 assert_eq!(owner, parent_name);
3646 assert_eq!(property.name, "parentFlag");
3647 assert!(!property.is_static);
3648 }
3649
3650 #[test]
3651 fn property_lookup_handles_parent_cycle() {
3652 let class_a = unique_class_name("plan6_cycle_property_a");
3653 let class_b = unique_class_name("plan6_cycle_property_b");
3654
3655 register_class(ClassDef {
3656 name: class_a.clone(),
3657 parent: Some(class_b.clone()),
3658 properties: HashMap::new(),
3659 methods: HashMap::new(),
3660 });
3661 register_class(ClassDef {
3662 name: class_b.clone(),
3663 parent: Some(class_a.clone()),
3664 properties: HashMap::new(),
3665 methods: HashMap::new(),
3666 });
3667
3668 assert!(
3669 lookup_property(&class_a, "missing").is_none(),
3670 "cyclic parent metadata should terminate missing property lookup"
3671 );
3672 }
3673}