1pub use inventory;
2use runmat_gc_api::GcPtr;
3use std::collections::HashMap;
4use std::convert::TryFrom;
5use std::fmt;
6use std::future::Future;
7use std::pin::Pin;
8
9use indexmap::IndexMap;
10use std::sync::OnceLock;
11
12#[cfg(target_arch = "wasm32")]
13pub mod wasm_registry {
14 use super::{BuiltinDoc, BuiltinFunction, Constant};
15 use once_cell::sync::Lazy;
16 use std::sync::Mutex;
17
18 static FUNCTIONS: Lazy<Mutex<Vec<&'static BuiltinFunction>>> =
19 Lazy::new(|| Mutex::new(Vec::new()));
20 static CONSTANTS: Lazy<Mutex<Vec<&'static Constant>>> = Lazy::new(|| Mutex::new(Vec::new()));
21 static DOCS: Lazy<Mutex<Vec<&'static BuiltinDoc>>> = Lazy::new(|| Mutex::new(Vec::new()));
22 static REGISTERED: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
23
24 fn leak<T>(value: T) -> &'static T {
25 Box::leak(Box::new(value))
26 }
27
28 pub fn submit_builtin_function(func: BuiltinFunction) {
29 let leaked = leak(func);
30 FUNCTIONS.lock().unwrap().push(leaked);
31 }
32
33 pub fn submit_constant(constant: Constant) {
34 let leaked = leak(constant);
35 CONSTANTS.lock().unwrap().push(leaked);
36 }
37
38 pub fn submit_builtin_doc(doc: BuiltinDoc) {
39 let leaked = leak(doc);
40 DOCS.lock().unwrap().push(leaked);
41 }
42
43 pub fn builtin_functions() -> Vec<&'static BuiltinFunction> {
44 FUNCTIONS.lock().unwrap().clone()
45 }
46
47 pub fn constants() -> Vec<&'static Constant> {
48 CONSTANTS.lock().unwrap().clone()
49 }
50
51 pub fn builtin_docs() -> Vec<&'static BuiltinDoc> {
52 DOCS.lock().unwrap().clone()
53 }
54
55 pub fn mark_registered() {
56 *REGISTERED.lock().unwrap() = true;
57 }
58
59 pub fn is_registered() -> bool {
60 *REGISTERED.lock().unwrap()
61 }
62}
63
64#[derive(Debug, Clone, PartialEq)]
65pub enum Value {
66 Int(IntValue),
67 Num(f64),
68 Complex(f64, f64),
70 Bool(bool),
71 LogicalArray(LogicalArray),
73 String(String),
74 StringArray(StringArray),
76 CharArray(CharArray),
78 Tensor(Tensor),
79 ComplexTensor(ComplexTensor),
81 Cell(CellArray),
82 Struct(StructValue),
85 GpuTensor(runmat_accelerate_api::GpuTensorHandle),
87 Object(ObjectInstance),
89 HandleObject(HandleRef),
91 Listener(Listener),
93 OutputList(Vec<Value>),
95 FunctionHandle(String),
97 Closure(Closure),
98 ClassRef(String),
99 MException(MException),
100}
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub enum IntValue {
103 I8(i8),
104 I16(i16),
105 I32(i32),
106 I64(i64),
107 U8(u8),
108 U16(u16),
109 U32(u32),
110 U64(u64),
111}
112
113impl IntValue {
114 pub fn to_i64(&self) -> i64 {
115 match self {
116 IntValue::I8(v) => *v as i64,
117 IntValue::I16(v) => *v as i64,
118 IntValue::I32(v) => *v as i64,
119 IntValue::I64(v) => *v,
120 IntValue::U8(v) => *v as i64,
121 IntValue::U16(v) => *v as i64,
122 IntValue::U32(v) => *v as i64,
123 IntValue::U64(v) => {
124 if *v > i64::MAX as u64 {
125 i64::MAX
126 } else {
127 *v as i64
128 }
129 }
130 }
131 }
132 pub fn to_f64(&self) -> f64 {
133 self.to_i64() as f64
134 }
135 pub fn is_zero(&self) -> bool {
136 self.to_i64() == 0
137 }
138 pub fn class_name(&self) -> &'static str {
139 match self {
140 IntValue::I8(_) => "int8",
141 IntValue::I16(_) => "int16",
142 IntValue::I32(_) => "int32",
143 IntValue::I64(_) => "int64",
144 IntValue::U8(_) => "uint8",
145 IntValue::U16(_) => "uint16",
146 IntValue::U32(_) => "uint32",
147 IntValue::U64(_) => "uint64",
148 }
149 }
150}
151
152#[derive(Debug, Clone, PartialEq)]
153pub struct StructValue {
154 pub fields: IndexMap<String, Value>,
155}
156
157impl StructValue {
158 pub fn new() -> Self {
159 Self {
160 fields: IndexMap::new(),
161 }
162 }
163
164 pub fn insert(&mut self, name: impl Into<String>, value: Value) -> Option<Value> {
166 self.fields.insert(name.into(), value)
167 }
168
169 pub fn remove(&mut self, name: &str) -> Option<Value> {
171 self.fields.shift_remove(name)
172 }
173
174 pub fn field_names(&self) -> impl Iterator<Item = &String> {
176 self.fields.keys()
177 }
178}
179
180impl Default for StructValue {
181 fn default() -> Self {
182 Self::new()
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
187pub enum NumericDType {
188 F64,
189 F32,
190}
191
192#[derive(Debug, Clone, PartialEq)]
193pub struct Tensor {
194 pub data: Vec<f64>,
195 pub shape: Vec<usize>, pub rows: usize, pub cols: usize, pub dtype: NumericDType,
200}
201
202#[derive(Debug, Clone, PartialEq)]
203pub struct ComplexTensor {
204 pub data: Vec<(f64, f64)>,
205 pub shape: Vec<usize>,
206 pub rows: usize,
207 pub cols: usize,
208}
209
210#[derive(Debug, Clone, PartialEq)]
211pub struct StringArray {
212 pub data: Vec<String>,
213 pub shape: Vec<usize>,
214 pub rows: usize,
215 pub cols: usize,
216}
217
218#[derive(Debug, Clone, PartialEq)]
219pub struct LogicalArray {
220 pub data: Vec<u8>, pub shape: Vec<usize>,
222}
223
224impl LogicalArray {
225 pub fn new(data: Vec<u8>, shape: Vec<usize>) -> Result<Self, String> {
226 let expected: usize = shape.iter().product();
227 if data.len() != expected {
228 return Err(format!(
229 "LogicalArray data length {} doesn't match shape {:?} ({} elements)",
230 data.len(),
231 shape,
232 expected
233 ));
234 }
235 let mut d = data;
237 for v in &mut d {
238 *v = if *v != 0 { 1 } else { 0 };
239 }
240 Ok(LogicalArray { data: d, shape })
241 }
242 pub fn zeros(shape: Vec<usize>) -> Self {
243 let expected: usize = shape.iter().product();
244 LogicalArray {
245 data: vec![0u8; expected],
246 shape,
247 }
248 }
249 pub fn len(&self) -> usize {
250 self.data.len()
251 }
252 pub fn is_empty(&self) -> bool {
253 self.data.is_empty()
254 }
255}
256
257#[derive(Debug, Clone, PartialEq)]
258pub struct CharArray {
259 pub data: Vec<char>,
260 pub rows: usize,
261 pub cols: usize,
262}
263
264impl CharArray {
265 pub fn new_row(s: &str) -> Self {
266 CharArray {
267 data: s.chars().collect(),
268 rows: 1,
269 cols: s.chars().count(),
270 }
271 }
272 pub fn new(data: Vec<char>, rows: usize, cols: usize) -> Result<Self, String> {
273 if rows * cols != data.len() {
274 return Err(format!(
275 "Char data length {} doesn't match dimensions {}x{}",
276 data.len(),
277 rows,
278 cols
279 ));
280 }
281 Ok(CharArray { data, rows, cols })
282 }
283}
284
285impl StringArray {
286 pub fn new(data: Vec<String>, shape: Vec<usize>) -> Result<Self, String> {
287 let expected: usize = shape.iter().product();
288 if data.len() != expected {
289 return Err(format!(
290 "StringArray data length {} doesn't match shape {:?} ({} elements)",
291 data.len(),
292 shape,
293 expected
294 ));
295 }
296 let (rows, cols) = if shape.len() >= 2 {
297 (shape[0], shape[1])
298 } else if shape.len() == 1 {
299 (1, shape[0])
300 } else {
301 (0, 0)
302 };
303 Ok(StringArray {
304 data,
305 shape,
306 rows,
307 cols,
308 })
309 }
310 pub fn new_2d(data: Vec<String>, rows: usize, cols: usize) -> Result<Self, String> {
311 Self::new(data, vec![rows, cols])
312 }
313 pub fn rows(&self) -> usize {
314 self.shape.first().copied().unwrap_or(1)
315 }
316 pub fn cols(&self) -> usize {
317 self.shape.get(1).copied().unwrap_or(1)
318 }
319}
320
321impl Tensor {
324 pub fn new(data: Vec<f64>, shape: Vec<usize>) -> Result<Self, String> {
325 let expected: usize = shape.iter().product();
326 if data.len() != expected {
327 return Err(format!(
328 "Tensor data length {} doesn't match shape {:?} ({} elements)",
329 data.len(),
330 shape,
331 expected
332 ));
333 }
334 let (rows, cols) = if shape.len() >= 2 {
335 (shape[0], shape[1])
336 } else if shape.len() == 1 {
337 (1, shape[0])
338 } else {
339 (0, 0)
340 };
341 Ok(Tensor {
342 data,
343 shape,
344 rows,
345 cols,
346 dtype: NumericDType::F64,
347 })
348 }
349
350 pub fn new_2d(data: Vec<f64>, rows: usize, cols: usize) -> Result<Self, String> {
351 Self::new(data, vec![rows, cols])
352 }
353
354 pub fn from_f32(data: Vec<f32>, shape: Vec<usize>) -> Result<Self, String> {
355 let converted: Vec<f64> = data.into_iter().map(|v| v as f64).collect();
356 Self::new_with_dtype(converted, shape, NumericDType::F32)
357 }
358
359 pub fn from_f32_slice(data: &[f32], shape: &[usize]) -> Result<Self, String> {
360 let converted: Vec<f64> = data.iter().map(|&v| v as f64).collect();
361 Self::new_with_dtype(converted, shape.to_vec(), NumericDType::F32)
362 }
363
364 pub fn new_with_dtype(
365 data: Vec<f64>,
366 shape: Vec<usize>,
367 dtype: NumericDType,
368 ) -> Result<Self, String> {
369 let mut t = Self::new(data, shape)?;
370 t.dtype = dtype;
371 Ok(t)
372 }
373
374 pub fn zeros(shape: Vec<usize>) -> Self {
375 let size: usize = shape.iter().product();
376 let (rows, cols) = if shape.len() >= 2 {
377 (shape[0], shape[1])
378 } else if shape.len() == 1 {
379 (1, shape[0])
380 } else {
381 (0, 0)
382 };
383 Tensor {
384 data: vec![0.0; size],
385 shape,
386 rows,
387 cols,
388 dtype: NumericDType::F64,
389 }
390 }
391
392 pub fn ones(shape: Vec<usize>) -> Self {
393 let size: usize = shape.iter().product();
394 let (rows, cols) = if shape.len() >= 2 {
395 (shape[0], shape[1])
396 } else if shape.len() == 1 {
397 (1, shape[0])
398 } else {
399 (0, 0)
400 };
401 Tensor {
402 data: vec![1.0; size],
403 shape,
404 rows,
405 cols,
406 dtype: NumericDType::F64,
407 }
408 }
409
410 pub fn zeros2(rows: usize, cols: usize) -> Self {
412 Self::zeros(vec![rows, cols])
413 }
414 pub fn ones2(rows: usize, cols: usize) -> Self {
415 Self::ones(vec![rows, cols])
416 }
417
418 pub fn rows(&self) -> usize {
419 self.shape.first().copied().unwrap_or(1)
420 }
421 pub fn cols(&self) -> usize {
422 self.shape.get(1).copied().unwrap_or(1)
423 }
424
425 pub fn get2(&self, row: usize, col: usize) -> Result<f64, String> {
426 let rows = self.rows();
427 let cols = self.cols();
428 if row >= rows || col >= cols {
429 return Err(format!(
430 "Index ({row}, {col}) out of bounds for {rows}x{cols} tensor"
431 ));
432 }
433 Ok(self.data[row + col * rows])
435 }
436
437 pub fn set2(&mut self, row: usize, col: usize, value: f64) -> Result<(), String> {
438 let rows = self.rows();
439 let cols = self.cols();
440 if row >= rows || col >= cols {
441 return Err(format!(
442 "Index ({row}, {col}) out of bounds for {rows}x{cols} tensor"
443 ));
444 }
445 self.data[row + col * rows] = value;
447 Ok(())
448 }
449
450 pub fn scalar_to_tensor2(scalar: f64, rows: usize, cols: usize) -> Tensor {
451 Tensor {
452 data: vec![scalar; rows * cols],
453 shape: vec![rows, cols],
454 rows,
455 cols,
456 dtype: NumericDType::F64,
457 }
458 }
459 }
461
462impl ComplexTensor {
463 pub fn new(data: Vec<(f64, f64)>, shape: Vec<usize>) -> Result<Self, String> {
464 let expected: usize = shape.iter().product();
465 if data.len() != expected {
466 return Err(format!(
467 "ComplexTensor data length {} doesn't match shape {:?} ({} elements)",
468 data.len(),
469 shape,
470 expected
471 ));
472 }
473 let (rows, cols) = if shape.len() >= 2 {
474 (shape[0], shape[1])
475 } else if shape.len() == 1 {
476 (1, shape[0])
477 } else {
478 (0, 0)
479 };
480 Ok(ComplexTensor {
481 data,
482 shape,
483 rows,
484 cols,
485 })
486 }
487 pub fn new_2d(data: Vec<(f64, f64)>, rows: usize, cols: usize) -> Result<Self, String> {
488 Self::new(data, vec![rows, cols])
489 }
490 pub fn zeros(shape: Vec<usize>) -> Self {
491 let size: usize = shape.iter().product();
492 let (rows, cols) = if shape.len() >= 2 {
493 (shape[0], shape[1])
494 } else if shape.len() == 1 {
495 (1, shape[0])
496 } else {
497 (0, 0)
498 };
499 ComplexTensor {
500 data: vec![(0.0, 0.0); size],
501 shape,
502 rows,
503 cols,
504 }
505 }
506}
507
508const MAX_ND_DISPLAY_ELEMENTS: usize = 4096;
509
510fn should_expand_nd_display(shape: &[usize]) -> bool {
511 shape.len() > 2
512 && matches!(
513 total_len(shape),
514 Some(total) if total > 0 && total <= MAX_ND_DISPLAY_ELEMENTS
515 )
516}
517
518fn column_major_strides(shape: &[usize]) -> Vec<usize> {
519 let mut strides = Vec::with_capacity(shape.len());
520 let mut stride = 1usize;
521 for &dim in shape {
522 strides.push(stride);
523 stride = stride.saturating_mul(dim);
524 }
525 strides
526}
527
528fn decode_page_coords(mut page_index: usize, page_shape: &[usize]) -> Vec<usize> {
529 let mut coords = Vec::with_capacity(page_shape.len());
530 for &dim in page_shape {
531 if dim == 0 {
532 coords.push(0);
533 } else {
534 coords.push(page_index % dim);
535 page_index /= dim;
536 }
537 }
538 coords
539}
540
541fn write_nd_pages(
542 f: &mut fmt::Formatter<'_>,
543 shape: &[usize],
544 mut write_element: impl FnMut(&mut fmt::Formatter<'_>, usize) -> fmt::Result,
545) -> fmt::Result {
546 if shape.len() <= 2 {
547 return Ok(());
548 }
549 let rows = shape[0];
550 let cols = shape[1];
551 if rows == 0 || cols == 0 {
552 return write!(f, "[]");
553 }
554 let Some(page_count) = total_len(&shape[2..]) else {
555 return write!(f, "Tensor(shape={shape:?})");
556 };
557 if page_count == 0 {
558 return write!(f, "[]");
559 }
560 let strides = column_major_strides(shape);
561 for page_index in 0..page_count {
562 if page_index > 0 {
563 write!(f, "\n\n")?;
564 }
565 let coords = decode_page_coords(page_index, &shape[2..]);
566 write!(f, "(:, :")?;
567 for &coord in &coords {
568 write!(f, ", {}", coord + 1)?;
569 }
570 write!(f, ") =")?;
571
572 let mut page_base = 0usize;
573 for (offset, &coord) in coords.iter().enumerate() {
574 page_base += coord * strides[offset + 2];
575 }
576 for r in 0..rows {
577 writeln!(f)?;
578 write!(f, " ")?;
579 for c in 0..cols {
580 if c > 0 {
581 write!(f, " ")?;
582 }
583 let linear = page_base + r + c * rows;
584 write_element(f, linear)?;
585 }
586 }
587 }
588 Ok(())
589}
590
591impl fmt::Display for Tensor {
592 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
593 match self.shape.len() {
594 0 | 1 => {
595 write!(f, "[")?;
597 for (i, v) in self.data.iter().enumerate() {
598 if i > 0 {
599 write!(f, " ")?;
600 }
601 write!(f, "{}", format_number_short_g(*v))?;
602 }
603 write!(f, "]")
604 }
605 2 => {
606 let rows = self.rows();
607 let cols = self.cols();
608 for r in 0..rows {
610 writeln!(f)?;
611 write!(f, " ")?; for c in 0..cols {
613 if c > 0 {
614 write!(f, " ")?;
615 }
616 let v = self.data[r + c * rows];
617 write!(f, "{}", format_number_short_g(v))?;
618 }
619 }
620 Ok(())
621 }
622 _ => {
623 if should_expand_nd_display(&self.shape) {
624 write_nd_pages(f, &self.shape, |f, idx| {
625 write!(f, "{}", format_number_short_g(self.data[idx]))
626 })
627 } else {
628 write!(f, "Tensor(shape={:?})", self.shape)
629 }
630 }
631 }
632 }
633}
634
635impl fmt::Display for StringArray {
636 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637 let (rows, cols) = match self.shape.len() {
638 0 => (0, 0),
639 1 => (1, self.shape[0]),
640 _ => (self.shape[0], self.shape[1]),
641 };
642 let count = self.data.len();
643 if count == 1 && rows == 1 && cols == 1 {
644 let v = &self.data[0];
645 if v == "<missing>" {
646 return write!(f, "<missing>");
647 }
648 let escaped = v.replace('"', "\\\"");
649 return write!(f, "\"{escaped}\"");
650 }
651 if self.shape.len() > 2 {
652 let dims: Vec<String> = self.shape.iter().map(|d| d.to_string()).collect();
653 return write!(f, "{} string array", dims.join("x"));
654 }
655 write!(f, "{rows}x{cols} string array")?;
656 if rows == 0 || cols == 0 {
657 return Ok(());
658 }
659 for r in 0..rows {
660 writeln!(f)?;
661 write!(f, " ")?;
662 for c in 0..cols {
663 if c > 0 {
664 write!(f, " ")?;
665 }
666 let v = &self.data[r + c * rows];
667 if v == "<missing>" {
668 write!(f, "<missing>")?;
669 } else {
670 let escaped = v.replace('"', "\\\"");
671 write!(f, "\"{escaped}\"")?;
672 }
673 }
674 }
675 Ok(())
676 }
677}
678
679impl fmt::Display for LogicalArray {
680 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
681 if self.data.len() == 1 {
682 return write!(f, "{}", if self.data[0] != 0 { 1 } else { 0 });
683 }
684 match self.shape.len() {
685 0 => write!(f, "[]"),
686 1 => {
687 write!(f, "[")?;
688 for (i, v) in self.data.iter().enumerate() {
689 if i > 0 {
690 write!(f, " ")?;
691 }
692 write!(f, "{}", if *v != 0 { 1 } else { 0 })?;
693 }
694 write!(f, "]")
695 }
696 2 => {
697 let rows = self.shape[0];
698 let cols = self.shape[1];
699 for r in 0..rows {
701 writeln!(f)?;
702 write!(f, " ")?; for c in 0..cols {
704 if c > 0 {
705 write!(f, " ")?;
706 }
707 let idx = r + c * rows;
708 write!(f, "{}", if self.data[idx] != 0 { 1 } else { 0 })?;
709 }
710 }
711 Ok(())
712 }
713 _ => {
714 if should_expand_nd_display(&self.shape) {
715 write_nd_pages(f, &self.shape, |f, idx| {
716 write!(f, "{}", if self.data[idx] != 0 { 1 } else { 0 })
717 })
718 } else {
719 let dims: Vec<String> = self.shape.iter().map(|d| d.to_string()).collect();
720 write!(f, "{} logical array", dims.join("x"))
721 }
722 }
723 }
724 }
725}
726
727impl fmt::Display for CharArray {
728 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
729 for r in 0..self.rows {
730 writeln!(f)?;
731 write!(f, " ")?; for c in 0..self.cols {
733 let ch = self.data[r * self.cols + c];
734 write!(f, "{ch}")?;
735 }
736 }
737 Ok(())
738 }
739}
740
741impl From<i32> for Value {
743 fn from(i: i32) -> Self {
744 Value::Int(IntValue::I32(i))
745 }
746}
747impl From<i64> for Value {
748 fn from(i: i64) -> Self {
749 Value::Int(IntValue::I64(i))
750 }
751}
752impl From<u32> for Value {
753 fn from(i: u32) -> Self {
754 Value::Int(IntValue::U32(i))
755 }
756}
757impl From<u64> for Value {
758 fn from(i: u64) -> Self {
759 Value::Int(IntValue::U64(i))
760 }
761}
762impl From<i16> for Value {
763 fn from(i: i16) -> Self {
764 Value::Int(IntValue::I16(i))
765 }
766}
767impl From<i8> for Value {
768 fn from(i: i8) -> Self {
769 Value::Int(IntValue::I8(i))
770 }
771}
772impl From<u16> for Value {
773 fn from(i: u16) -> Self {
774 Value::Int(IntValue::U16(i))
775 }
776}
777impl From<u8> for Value {
778 fn from(i: u8) -> Self {
779 Value::Int(IntValue::U8(i))
780 }
781}
782
783impl From<f64> for Value {
784 fn from(f: f64) -> Self {
785 Value::Num(f)
786 }
787}
788
789impl From<bool> for Value {
790 fn from(b: bool) -> Self {
791 Value::Bool(b)
792 }
793}
794
795impl From<String> for Value {
796 fn from(s: String) -> Self {
797 Value::String(s)
798 }
799}
800
801impl From<&str> for Value {
802 fn from(s: &str) -> Self {
803 Value::String(s.to_string())
804 }
805}
806
807impl From<Tensor> for Value {
808 fn from(m: Tensor) -> Self {
809 Value::Tensor(m)
810 }
811}
812
813impl TryFrom<&Value> for i32 {
817 type Error = String;
818 fn try_from(v: &Value) -> Result<Self, Self::Error> {
819 match v {
820 Value::Int(i) => Ok(i.to_i64() as i32),
821 Value::Num(n) => Ok(*n as i32),
822 _ => Err(format!("cannot convert {v:?} to i32")),
823 }
824 }
825}
826
827impl TryFrom<&Value> for f64 {
828 type Error = String;
829 fn try_from(v: &Value) -> Result<Self, Self::Error> {
830 match v {
831 Value::Num(n) => Ok(*n),
832 Value::Int(i) => Ok(i.to_f64()),
833 _ => Err(format!("cannot convert {v:?} to f64")),
834 }
835 }
836}
837
838impl TryFrom<&Value> for bool {
839 type Error = String;
840 fn try_from(v: &Value) -> Result<Self, Self::Error> {
841 match v {
842 Value::Bool(b) => Ok(*b),
843 Value::Int(i) => Ok(!i.is_zero()),
844 Value::Num(n) => Ok(*n != 0.0),
845 _ => Err(format!("cannot convert {v:?} to bool")),
846 }
847 }
848}
849
850impl TryFrom<&Value> for String {
851 type Error = String;
852 fn try_from(v: &Value) -> Result<Self, Self::Error> {
853 match v {
854 Value::String(s) => Ok(s.clone()),
855 Value::StringArray(sa) => {
856 if sa.data.len() == 1 {
857 Ok(sa.data[0].clone())
858 } else {
859 Err("cannot convert string array to scalar string".to_string())
860 }
861 }
862 Value::CharArray(ca) => {
863 if ca.rows == 1 {
865 Ok(ca.data.iter().collect())
866 } else {
867 Err("cannot convert multi-row char array to scalar string".to_string())
868 }
869 }
870 Value::Int(i) => Ok(i.to_i64().to_string()),
871 Value::Num(n) => Ok(n.to_string()),
872 Value::Bool(b) => Ok(b.to_string()),
873 _ => Err(format!("cannot convert {v:?} to String")),
874 }
875 }
876}
877
878impl TryFrom<&Value> for Tensor {
879 type Error = String;
880 fn try_from(v: &Value) -> Result<Self, Self::Error> {
881 match v {
882 Value::Tensor(m) => Ok(m.clone()),
883 _ => Err(format!("cannot convert {v:?} to Tensor")),
884 }
885 }
886}
887
888impl TryFrom<&Value> for Value {
889 type Error = String;
890 fn try_from(v: &Value) -> Result<Self, Self::Error> {
891 Ok(v.clone())
892 }
893}
894
895impl TryFrom<&Value> for Vec<Value> {
896 type Error = String;
897 fn try_from(v: &Value) -> Result<Self, Self::Error> {
898 match v {
899 Value::Cell(c) => Ok(c.data.iter().map(|p| (**p).clone()).collect()),
900 _ => Err(format!("cannot convert {v:?} to Vec<Value>")),
901 }
902 }
903}
904
905use serde::{Deserialize, Serialize};
906
907#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
910pub enum Type {
911 Int,
913 Num,
915 Bool,
917 Logical {
919 shape: Option<Vec<Option<usize>>>,
921 },
922 String,
924 Tensor {
926 shape: Option<Vec<Option<usize>>>,
928 },
929 Cell {
931 element_type: Option<Box<Type>>,
933 length: Option<usize>,
935 },
936 Function {
938 params: Vec<Type>,
940 returns: Box<Type>,
942 },
943 Void,
945 Unknown,
947 Union(Vec<Type>),
949 Struct {
951 known_fields: Option<Vec<String>>, },
954 OutputList(Vec<Type>),
956 DataDataset {
958 arrays: Option<std::collections::BTreeMap<String, DataArrayTypeInfo>>,
959 },
960 DataArray {
962 dtype: Option<String>,
963 shape: Option<Vec<Option<usize>>>,
964 chunk_shape: Option<Vec<Option<usize>>>,
965 codec: Option<String>,
966 },
967 DataTransaction,
969}
970
971#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
972pub struct DataArrayTypeInfo {
973 pub dtype: Option<String>,
974 pub shape: Option<Vec<Option<usize>>>,
975 pub chunk_shape: Option<Vec<Option<usize>>>,
976 pub codec: Option<String>,
977}
978
979impl Type {
980 pub fn tensor() -> Self {
982 Type::Tensor { shape: None }
983 }
984
985 pub fn logical() -> Self {
987 Type::Logical { shape: None }
988 }
989
990 pub fn logical_with_shape(shape: Vec<usize>) -> Self {
992 Type::Logical {
993 shape: Some(shape.into_iter().map(Some).collect()),
994 }
995 }
996
997 pub fn tensor_with_shape(shape: Vec<usize>) -> Self {
999 Type::Tensor {
1000 shape: Some(shape.into_iter().map(Some).collect()),
1001 }
1002 }
1003
1004 pub fn cell() -> Self {
1006 Type::Cell {
1007 element_type: None,
1008 length: None,
1009 }
1010 }
1011
1012 pub fn cell_of(element_type: Type) -> Self {
1014 Type::Cell {
1015 element_type: Some(Box::new(element_type)),
1016 length: None,
1017 }
1018 }
1019
1020 pub fn is_compatible_with(&self, other: &Type) -> bool {
1022 match (self, other) {
1023 (Type::Unknown, _) | (_, Type::Unknown) => true,
1024 (Type::Int, Type::Num) | (Type::Num, Type::Int) => true, (Type::Tensor { .. }, Type::Tensor { .. }) => true, (Type::OutputList(a), Type::OutputList(b)) => a.len() == b.len(),
1027 (Type::DataDataset { .. }, Type::DataDataset { .. }) => true,
1028 (Type::DataArray { .. }, Type::DataArray { .. }) => true,
1029 (Type::DataTransaction, Type::DataTransaction) => true,
1030 (a, b) => a == b,
1031 }
1032 }
1033
1034 pub fn unify(&self, other: &Type) -> Type {
1036 match (self, other) {
1037 (Type::Unknown, t) | (t, Type::Unknown) => t.clone(),
1038 (Type::Int, Type::Num) | (Type::Num, Type::Int) => Type::Num,
1039 (Type::Tensor { shape: a }, Type::Tensor { shape: b }) => {
1040 let a_norm = match a {
1041 Some(dims) if dims.is_empty() => None,
1042 _ => a.clone(),
1043 };
1044 let b_norm = match b {
1045 Some(dims) if dims.is_empty() => None,
1046 _ => b.clone(),
1047 };
1048 let a_unknown = a_norm
1049 .as_ref()
1050 .map(|dims| dims.iter().all(|d| d.is_none()))
1051 .unwrap_or(true);
1052 let b_unknown = b_norm
1053 .as_ref()
1054 .map(|dims| dims.iter().all(|d| d.is_none()))
1055 .unwrap_or(true);
1056 if a_norm == b_norm
1057 || (!a_unknown && b_unknown)
1058 || (a_norm.is_some() && b_norm.is_none())
1059 {
1060 Type::Tensor { shape: a_norm }
1061 } else if (a_unknown && !b_unknown) || (a_norm.is_none() && b_norm.is_some()) {
1062 Type::Tensor { shape: b_norm }
1063 } else {
1064 Type::tensor()
1065 }
1066 }
1067 (Type::Logical { shape: a }, Type::Logical { shape: b }) => {
1068 let a_norm = match a {
1069 Some(dims) if dims.is_empty() => None,
1070 _ => a.clone(),
1071 };
1072 let b_norm = match b {
1073 Some(dims) if dims.is_empty() => None,
1074 _ => b.clone(),
1075 };
1076 let a_unknown = a_norm
1077 .as_ref()
1078 .map(|dims| dims.iter().all(|d| d.is_none()))
1079 .unwrap_or(true);
1080 let b_unknown = b_norm
1081 .as_ref()
1082 .map(|dims| dims.iter().all(|d| d.is_none()))
1083 .unwrap_or(true);
1084 if a_norm == b_norm
1085 || (!a_unknown && b_unknown)
1086 || (a_norm.is_some() && b_norm.is_none())
1087 {
1088 Type::Logical { shape: a_norm }
1089 } else if (a_unknown && !b_unknown) || (a_norm.is_none() && b_norm.is_some()) {
1090 Type::Logical { shape: b_norm }
1091 } else {
1092 Type::logical()
1093 }
1094 }
1095 (Type::Struct { known_fields: a }, Type::Struct { known_fields: b }) => match (a, b) {
1096 (None, None) => Type::Struct { known_fields: None },
1097 (Some(ka), None) | (None, Some(ka)) => Type::Struct {
1098 known_fields: Some(ka.clone()),
1099 },
1100 (Some(ka), Some(kb)) => {
1101 let mut set: std::collections::BTreeSet<String> = ka.iter().cloned().collect();
1102 set.extend(kb.iter().cloned());
1103 Type::Struct {
1104 known_fields: Some(set.into_iter().collect()),
1105 }
1106 }
1107 },
1108 (Type::OutputList(a), Type::OutputList(b)) => {
1109 if a.len() == b.len() {
1110 let items = a
1111 .iter()
1112 .zip(b.iter())
1113 .map(|(lhs, rhs)| lhs.unify(rhs))
1114 .collect();
1115 Type::OutputList(items)
1116 } else {
1117 Type::OutputList(vec![Type::Unknown; a.len().max(b.len())])
1118 }
1119 }
1120 (Type::DataDataset { arrays: a }, Type::DataDataset { arrays: b }) => {
1121 let merged = match (a, b) {
1122 (None, None) => None,
1123 (Some(sa), None) | (None, Some(sa)) => Some(sa.clone()),
1124 (Some(sa), Some(sb)) => {
1125 let mut out = sa.clone();
1126 for (name, right) in sb {
1127 out.entry(name.clone())
1128 .and_modify(|left| {
1129 *left = unify_array_type_info(left, right);
1130 })
1131 .or_insert_with(|| right.clone());
1132 }
1133 Some(out)
1134 }
1135 };
1136 Type::DataDataset { arrays: merged }
1137 }
1138 (
1139 Type::DataArray {
1140 dtype: ad,
1141 shape: ashp,
1142 chunk_shape: ach,
1143 codec: ac,
1144 },
1145 Type::DataArray {
1146 dtype: bd,
1147 shape: bshp,
1148 chunk_shape: bch,
1149 codec: bc,
1150 },
1151 ) => Type::DataArray {
1152 dtype: ad.clone().or_else(|| bd.clone()),
1153 shape: unify_optional_dims(ashp, bshp),
1154 chunk_shape: unify_optional_dims(ach, bch),
1155 codec: ac.clone().or_else(|| bc.clone()),
1156 },
1157 (Type::DataTransaction, Type::DataTransaction) => Type::DataTransaction,
1158 (a, b) if a == b => a.clone(),
1159 _ => Type::Union(vec![self.clone(), other.clone()]),
1160 }
1161 }
1162
1163 pub fn from_value(value: &Value) -> Type {
1165 match value {
1166 Value::Int(_) => Type::Int,
1167 Value::Num(_) => Type::Num,
1168 Value::Complex(_, _) => Type::Num, Value::Bool(_) => Type::Bool,
1170 Value::LogicalArray(arr) => Type::Logical {
1171 shape: Some(arr.shape.iter().map(|&d| Some(d)).collect()),
1172 },
1173 Value::String(_) => Type::String,
1174 Value::StringArray(_sa) => {
1175 Type::cell_of(Type::String)
1177 }
1178 Value::Tensor(t) => Type::Tensor {
1179 shape: Some(t.shape.iter().map(|&d| Some(d)).collect()),
1180 },
1181 Value::ComplexTensor(t) => Type::Tensor {
1182 shape: Some(t.shape.iter().map(|&d| Some(d)).collect()),
1183 },
1184 Value::Cell(cells) => {
1185 if cells.data.is_empty() {
1186 Type::cell()
1187 } else {
1188 let element_type = Type::from_value(&cells.data[0]);
1190 Type::Cell {
1191 element_type: Some(Box::new(element_type)),
1192 length: Some(cells.data.len()),
1193 }
1194 }
1195 }
1196 Value::GpuTensor(h) => Type::Tensor {
1197 shape: Some(h.shape.iter().map(|&d| Some(d)).collect()),
1198 },
1199 Value::Object(_) => Type::Unknown,
1200 Value::HandleObject(_) => Type::Unknown,
1201 Value::Listener(_) => Type::Unknown,
1202 Value::Struct(_) => Type::Struct { known_fields: None },
1203 Value::FunctionHandle(_) => Type::Function {
1204 params: vec![Type::Unknown],
1205 returns: Box::new(Type::Unknown),
1206 },
1207 Value::Closure(_) => Type::Function {
1208 params: vec![Type::Unknown],
1209 returns: Box::new(Type::Unknown),
1210 },
1211 Value::ClassRef(_) => Type::Unknown,
1212 Value::MException(_) => Type::Unknown,
1213 Value::CharArray(ca) => {
1214 Type::Cell {
1216 element_type: Some(Box::new(Type::String)),
1217 length: Some(ca.rows * ca.cols),
1218 }
1219 }
1220 Value::OutputList(values) => {
1221 Type::OutputList(values.iter().map(Type::from_value).collect())
1222 }
1223 }
1224 }
1225}
1226
1227fn unify_optional_dims(
1228 lhs: &Option<Vec<Option<usize>>>,
1229 rhs: &Option<Vec<Option<usize>>>,
1230) -> Option<Vec<Option<usize>>> {
1231 match (lhs, rhs) {
1232 (None, None) => None,
1233 (Some(a), None) | (None, Some(a)) => Some(a.clone()),
1234 (Some(a), Some(b)) if a == b => Some(a.clone()),
1235 (Some(a), Some(b)) if a.len() == b.len() => Some(
1236 a.iter()
1237 .zip(b.iter())
1238 .map(|(x, y)| if x == y { *x } else { None })
1239 .collect(),
1240 ),
1241 (Some(_), Some(_)) => None,
1242 }
1243}
1244
1245fn unify_array_type_info(lhs: &DataArrayTypeInfo, rhs: &DataArrayTypeInfo) -> DataArrayTypeInfo {
1246 DataArrayTypeInfo {
1247 dtype: lhs.dtype.clone().or_else(|| rhs.dtype.clone()),
1248 shape: unify_optional_dims(&lhs.shape, &rhs.shape),
1249 chunk_shape: unify_optional_dims(&lhs.chunk_shape, &rhs.chunk_shape),
1250 codec: lhs.codec.clone().or_else(|| rhs.codec.clone()),
1251 }
1252}
1253
1254#[derive(Debug, Clone, PartialEq)]
1255pub struct Closure {
1256 pub function_name: String,
1257 pub captures: Vec<Value>,
1258}
1259
1260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1262pub enum AccelTag {
1263 Unary,
1264 Elementwise,
1265 Reduction,
1266 MatMul,
1267 Transpose,
1268 ArrayConstruct,
1269}
1270
1271pub type BuiltinControlFlow = runmat_async::RuntimeError;
1273
1274pub type BuiltinFuture = Pin<Box<dyn Future<Output = Result<Value, BuiltinControlFlow>> + 'static>>;
1276
1277#[derive(Clone, Debug, Default)]
1278pub struct ResolveContext {
1279 pub literal_args: Vec<LiteralValue>,
1280}
1281
1282#[derive(Clone, Debug, PartialEq)]
1283pub enum LiteralValue {
1284 Number(f64),
1285 Bool(bool),
1286 String(String),
1287 Vector(Vec<LiteralValue>),
1288 Unknown,
1289}
1290
1291impl ResolveContext {
1292 pub fn new(literal_args: Vec<LiteralValue>) -> Self {
1293 Self { literal_args }
1294 }
1295
1296 pub fn numeric_dims(&self) -> Vec<Option<usize>> {
1297 self.numeric_dims_from(0)
1298 }
1299
1300 pub fn numeric_dims_from(&self, start: usize) -> Vec<Option<usize>> {
1301 let slice = self.literal_args.get(start..).unwrap_or(&[]);
1302 if let Some(LiteralValue::Vector(values)) = slice.first() {
1303 return values
1304 .iter()
1305 .map(Self::numeric_dimension_from_literal)
1306 .collect();
1307 }
1308 slice
1309 .iter()
1310 .map(Self::numeric_dimension_from_literal)
1311 .collect()
1312 }
1313
1314 pub fn literal_string_at(&self, index: usize) -> Option<String> {
1315 match self.literal_args.get(index) {
1316 Some(LiteralValue::String(value)) => Some(value.to_ascii_lowercase()),
1317 _ => None,
1318 }
1319 }
1320
1321 pub fn literal_bool_at(&self, index: usize) -> Option<bool> {
1322 match self.literal_args.get(index) {
1323 Some(LiteralValue::Bool(value)) => Some(*value),
1324 _ => None,
1325 }
1326 }
1327
1328 pub fn literal_vector_at(&self, index: usize) -> Option<Vec<LiteralValue>> {
1329 match self.literal_args.get(index) {
1330 Some(LiteralValue::Vector(values)) => Some(values.clone()),
1331 _ => None,
1332 }
1333 }
1334
1335 pub fn numeric_vector_at(&self, index: usize) -> Option<Vec<Option<usize>>> {
1336 let values = match self.literal_args.get(index) {
1337 Some(LiteralValue::Vector(values)) => values,
1338 _ => return None,
1339 };
1340 if values
1341 .iter()
1342 .any(|value| matches!(value, LiteralValue::Vector(_)))
1343 {
1344 return None;
1345 }
1346 Some(
1347 values
1348 .iter()
1349 .map(Self::numeric_dimension_from_literal)
1350 .collect(),
1351 )
1352 }
1353
1354 fn numeric_dimension_from_literal(value: &LiteralValue) -> Option<usize> {
1355 match value {
1356 LiteralValue::Number(num) => {
1357 if num.is_finite() {
1358 let rounded = num.round();
1359 if (num - rounded).abs() <= 1e-9 && rounded >= 0.0 {
1360 return Some(rounded as usize);
1361 }
1362 }
1363 None
1364 }
1365 _ => None,
1366 }
1367 }
1368}
1369
1370#[cfg(test)]
1371mod resolve_context_tests {
1372 use super::{LiteralValue, ResolveContext};
1373
1374 #[test]
1375 fn numeric_dims_reads_vector_literal() {
1376 let ctx = ResolveContext::new(vec![LiteralValue::Vector(vec![
1377 LiteralValue::Number(2.0),
1378 LiteralValue::Number(3.0),
1379 ])]);
1380 assert_eq!(ctx.numeric_dims(), vec![Some(2), Some(3)]);
1381 }
1382
1383 #[test]
1384 fn numeric_dims_skips_non_numeric_entries() {
1385 let ctx = ResolveContext::new(vec![
1386 LiteralValue::Number(4.0),
1387 LiteralValue::String("like".to_string()),
1388 LiteralValue::Unknown,
1389 ]);
1390 assert_eq!(ctx.numeric_dims(), vec![Some(4), None, None]);
1391 }
1392
1393 #[test]
1394 fn numeric_dims_prefers_vector_even_with_trailing_args() {
1395 let ctx = ResolveContext::new(vec![
1396 LiteralValue::Vector(vec![LiteralValue::Number(1.0), LiteralValue::Number(5.0)]),
1397 LiteralValue::String("like".to_string()),
1398 ]);
1399 assert_eq!(ctx.numeric_dims(), vec![Some(1), Some(5)]);
1400 }
1401
1402 #[test]
1403 fn literal_string_is_lowercased() {
1404 let ctx = ResolveContext::new(vec![LiteralValue::String("OmItNaN".to_string())]);
1405 assert_eq!(ctx.literal_string_at(0), Some("omitnan".to_string()));
1406 }
1407
1408 #[test]
1409 fn literal_bool_is_available() {
1410 let ctx = ResolveContext::new(vec![LiteralValue::Bool(true)]);
1411 assert_eq!(ctx.literal_bool_at(0), Some(true));
1412 }
1413
1414 #[test]
1415 fn literal_vector_at_returns_clone() {
1416 let ctx = ResolveContext::new(vec![LiteralValue::Vector(vec![
1417 LiteralValue::Number(7.0),
1418 LiteralValue::Unknown,
1419 ])]);
1420 assert_eq!(
1421 ctx.literal_vector_at(0),
1422 Some(vec![LiteralValue::Number(7.0), LiteralValue::Unknown])
1423 );
1424 }
1425
1426 #[test]
1427 fn numeric_vector_at_rejects_nested_vectors() {
1428 let ctx = ResolveContext::new(vec![LiteralValue::Vector(vec![LiteralValue::Vector(
1429 vec![LiteralValue::Number(1.0)],
1430 )])]);
1431 assert_eq!(ctx.numeric_vector_at(0), None);
1432 }
1433}
1434
1435pub type TypeResolver = fn(args: &[Type]) -> Type;
1436pub type TypeResolverWithContext = fn(args: &[Type], ctx: &ResolveContext) -> Type;
1437
1438#[derive(Clone, Copy, Debug)]
1439pub enum TypeResolverKind {
1440 Legacy(TypeResolver),
1441 WithContext(TypeResolverWithContext),
1442}
1443
1444pub fn type_resolver_kind(resolver: TypeResolver) -> TypeResolverKind {
1445 TypeResolverKind::Legacy(resolver)
1446}
1447
1448pub fn type_resolver_kind_ctx(resolver: TypeResolverWithContext) -> TypeResolverKind {
1449 TypeResolverKind::WithContext(resolver)
1450}
1451
1452#[derive(Debug, Clone)]
1454pub struct BuiltinFunction {
1455 pub name: &'static str,
1456 pub description: &'static str,
1457 pub category: &'static str,
1458 pub doc: &'static str,
1459 pub examples: &'static str,
1460 pub param_types: Vec<Type>,
1461 pub return_type: Type,
1462 pub type_resolver: Option<TypeResolverKind>,
1463 pub implementation: fn(&[Value]) -> BuiltinFuture,
1464 pub accel_tags: &'static [AccelTag],
1465 pub is_sink: bool,
1466 pub suppress_auto_output: bool,
1467}
1468
1469impl BuiltinFunction {
1470 #[allow(clippy::too_many_arguments)]
1471 pub fn new(
1472 name: &'static str,
1473 description: &'static str,
1474 category: &'static str,
1475 doc: &'static str,
1476 examples: &'static str,
1477 param_types: Vec<Type>,
1478 return_type: Type,
1479 type_resolver: Option<TypeResolverKind>,
1480 implementation: fn(&[Value]) -> BuiltinFuture,
1481 accel_tags: &'static [AccelTag],
1482 is_sink: bool,
1483 suppress_auto_output: bool,
1484 ) -> Self {
1485 Self {
1486 name,
1487 description,
1488 category,
1489 doc,
1490 examples,
1491 param_types,
1492 return_type,
1493 type_resolver,
1494 implementation,
1495 accel_tags,
1496 is_sink,
1497 suppress_auto_output,
1498 }
1499 }
1500
1501 pub fn infer_return_type(&self, args: &[Type]) -> Type {
1502 self.infer_return_type_with_context(args, &ResolveContext::default())
1503 }
1504
1505 pub fn infer_return_type_with_context(&self, args: &[Type], ctx: &ResolveContext) -> Type {
1506 if let Some(resolver) = self.type_resolver {
1507 return match resolver {
1508 TypeResolverKind::Legacy(resolver) => resolver(args),
1509 TypeResolverKind::WithContext(resolver) => resolver(args, ctx),
1510 };
1511 }
1512 self.return_type.clone()
1513 }
1514}
1515
1516#[derive(Clone)]
1518pub struct Constant {
1519 pub name: &'static str,
1520 pub value: Value,
1521}
1522
1523pub mod shape_rules;
1524
1525impl std::fmt::Debug for Constant {
1526 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1527 write!(
1528 f,
1529 "Constant {{ name: {:?}, value: {:?} }}",
1530 self.name, self.value
1531 )
1532 }
1533}
1534
1535#[cfg(not(target_arch = "wasm32"))]
1536inventory::collect!(BuiltinFunction);
1537#[cfg(not(target_arch = "wasm32"))]
1538inventory::collect!(Constant);
1539
1540#[cfg(not(target_arch = "wasm32"))]
1541pub fn builtin_functions() -> Vec<&'static BuiltinFunction> {
1542 inventory::iter::<BuiltinFunction>().collect()
1543}
1544
1545#[cfg(target_arch = "wasm32")]
1546pub fn builtin_functions() -> Vec<&'static BuiltinFunction> {
1547 wasm_registry::builtin_functions()
1548}
1549
1550#[cfg(not(target_arch = "wasm32"))]
1551static BUILTIN_LOOKUP: OnceLock<HashMap<String, &'static BuiltinFunction>> = OnceLock::new();
1552
1553#[cfg(not(target_arch = "wasm32"))]
1554fn builtin_lookup_map() -> &'static HashMap<String, &'static BuiltinFunction> {
1555 BUILTIN_LOOKUP.get_or_init(|| {
1556 let mut map = HashMap::new();
1557 for func in builtin_functions() {
1558 map.insert(func.name.to_ascii_lowercase(), func);
1559 }
1560 map
1561 })
1562}
1563
1564#[cfg(not(target_arch = "wasm32"))]
1565pub fn builtin_function_by_name(name: &str) -> Option<&'static BuiltinFunction> {
1566 builtin_lookup_map()
1567 .get(&name.to_ascii_lowercase())
1568 .copied()
1569}
1570
1571#[cfg(target_arch = "wasm32")]
1572pub fn builtin_function_by_name(name: &str) -> Option<&'static BuiltinFunction> {
1573 wasm_registry::builtin_functions()
1574 .into_iter()
1575 .find(|f| f.name.eq_ignore_ascii_case(name))
1576}
1577
1578pub fn suppresses_auto_output(name: &str) -> bool {
1579 builtin_function_by_name(name)
1580 .map(|f| f.suppress_auto_output)
1581 .unwrap_or(false)
1582}
1583
1584#[cfg(not(target_arch = "wasm32"))]
1585pub fn constants() -> Vec<&'static Constant> {
1586 inventory::iter::<Constant>().collect()
1587}
1588
1589#[cfg(target_arch = "wasm32")]
1590pub fn constants() -> Vec<&'static Constant> {
1591 wasm_registry::constants()
1592}
1593
1594#[derive(Debug)]
1599pub struct BuiltinDoc {
1600 pub name: &'static str,
1601 pub category: Option<&'static str>,
1602 pub summary: Option<&'static str>,
1603 pub keywords: Option<&'static str>,
1604 pub errors: Option<&'static str>,
1605 pub related: Option<&'static str>,
1606 pub introduced: Option<&'static str>,
1607 pub status: Option<&'static str>,
1608 pub examples: Option<&'static str>,
1609}
1610
1611#[cfg(not(target_arch = "wasm32"))]
1612inventory::collect!(BuiltinDoc);
1613
1614#[cfg(not(target_arch = "wasm32"))]
1615pub fn builtin_docs() -> Vec<&'static BuiltinDoc> {
1616 inventory::iter::<BuiltinDoc>().collect()
1617}
1618
1619#[cfg(target_arch = "wasm32")]
1620pub fn builtin_docs() -> Vec<&'static BuiltinDoc> {
1621 wasm_registry::builtin_docs()
1622}
1623
1624fn format_number_short_g(value: f64) -> String {
1629 if value.is_nan() {
1630 return "NaN".to_string();
1631 }
1632 if value.is_infinite() {
1633 return if value.is_sign_negative() {
1634 "-Inf"
1635 } else {
1636 "Inf"
1637 }
1638 .to_string();
1639 }
1640 let mut v = value;
1642 if v == 0.0 {
1643 v = 0.0;
1644 }
1645
1646 let abs = v.abs();
1647 if abs == 0.0 {
1648 return "0".to_string();
1649 }
1650
1651 let use_scientific = !(1e-4..1e6).contains(&abs);
1653
1654 if use_scientific {
1655 let s = format!("{v:.4e}");
1657 if let Some(idx) = s.find('e') {
1659 let (mut mantissa, exp) = s.split_at(idx);
1660 if let Some(dot_idx) = mantissa.find('.') {
1662 let mut end = mantissa.len();
1664 while end > dot_idx + 1 && mantissa.as_bytes()[end - 1] == b'0' {
1665 end -= 1;
1666 }
1667 if end > 0 && mantissa.as_bytes()[end - 1] == b'.' {
1668 end -= 1;
1669 }
1670 mantissa = &mantissa[..end];
1671 }
1672 return format!("{mantissa}{exp}");
1673 }
1674 return s;
1675 }
1676
1677 let exp10 = abs.log10().floor() as i32; let sig_digits: i32 = 12;
1681 let decimals = (sig_digits - 1 - exp10).clamp(0, 12) as usize;
1682 let pow = 10f64.powi(decimals as i32);
1684 let rounded = (v * pow).round() / pow;
1685 let mut s = format!("{rounded:.decimals$}");
1686 if let Some(dot) = s.find('.') {
1687 let mut end = s.len();
1689 while end > dot + 1 && s.as_bytes()[end - 1] == b'0' {
1690 end -= 1;
1691 }
1692 if end > 0 && s.as_bytes()[end - 1] == b'.' {
1693 end -= 1;
1694 }
1695 s.truncate(end);
1696 }
1697 if s.is_empty() || s == "-0" {
1698 s = "0".to_string();
1699 }
1700 s
1701}
1702
1703#[derive(Debug, Clone, PartialEq)]
1705pub struct MException {
1706 pub identifier: String,
1707 pub message: String,
1708 pub stack: Vec<String>,
1709}
1710
1711impl MException {
1712 pub fn new(identifier: String, message: String) -> Self {
1713 Self {
1714 identifier,
1715 message,
1716 stack: Vec::new(),
1717 }
1718 }
1719}
1720
1721#[derive(Debug, Clone)]
1723pub struct HandleRef {
1724 pub class_name: String,
1725 pub target: GcPtr<Value>,
1726 pub valid: bool,
1727}
1728
1729impl PartialEq for HandleRef {
1730 fn eq(&self, other: &Self) -> bool {
1731 let a = unsafe { self.target.as_raw() } as usize;
1732 let b = unsafe { other.target.as_raw() } as usize;
1733 a == b
1734 }
1735}
1736
1737#[derive(Debug, Clone, PartialEq)]
1739pub struct Listener {
1740 pub id: u64,
1741 pub target: GcPtr<Value>,
1742 pub event_name: String,
1743 pub callback: GcPtr<Value>,
1744 pub enabled: bool,
1745 pub valid: bool,
1746}
1747
1748impl Listener {
1749 pub fn class_name(&self) -> String {
1750 match unsafe { &*self.target.as_raw() } {
1751 Value::Object(o) => o.class_name.clone(),
1752 Value::HandleObject(h) => h.class_name.clone(),
1753 _ => String::new(),
1754 }
1755 }
1756}
1757
1758impl fmt::Display for Value {
1759 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1760 match self {
1761 Value::Int(i) => write!(f, "{}", i.to_i64()),
1762 Value::Num(n) => write!(f, "{}", format_number_short_g(*n)),
1763 Value::Complex(re, im) => {
1764 if *im == 0.0 {
1765 write!(f, "{}", format_number_short_g(*re))
1766 } else if *re == 0.0 {
1767 write!(f, "{}i", format_number_short_g(*im))
1768 } else if *im < 0.0 {
1769 write!(
1770 f,
1771 "{}-{}i",
1772 format_number_short_g(*re),
1773 format_number_short_g(im.abs())
1774 )
1775 } else {
1776 write!(
1777 f,
1778 "{}+{}i",
1779 format_number_short_g(*re),
1780 format_number_short_g(*im)
1781 )
1782 }
1783 }
1784 Value::Bool(b) => write!(f, "{}", if *b { 1 } else { 0 }),
1785 Value::LogicalArray(la) => write!(f, "{la}"),
1786 Value::String(s) => write!(f, "'{s}'"),
1787 Value::StringArray(sa) => write!(f, "{sa}"),
1788 Value::CharArray(ca) => write!(f, "{ca}"),
1789 Value::Tensor(m) => write!(f, "{m}"),
1790 Value::ComplexTensor(m) => write!(f, "{m}"),
1791 Value::Cell(ca) => ca.fmt(f),
1792
1793 Value::GpuTensor(h) => write!(
1794 f,
1795 "GpuTensor(shape={:?}, device={}, buffer={})",
1796 h.shape, h.device_id, h.buffer_id
1797 ),
1798 Value::Object(obj) => write!(f, "{}(props={})", obj.class_name, obj.properties.len()),
1799 Value::HandleObject(h) => {
1800 let ptr = unsafe { h.target.as_raw() } as usize;
1801 write!(
1802 f,
1803 "<handle {} @0x{:x} valid={}>",
1804 h.class_name, ptr, h.valid
1805 )
1806 }
1807 Value::Listener(l) => {
1808 let ptr = unsafe { l.target.as_raw() } as usize;
1809 write!(
1810 f,
1811 "<listener id={} {}@0x{:x} '{}' enabled={} valid={}>",
1812 l.id,
1813 l.class_name(),
1814 ptr,
1815 l.event_name,
1816 l.enabled,
1817 l.valid
1818 )
1819 }
1820 Value::Struct(st) => {
1821 write!(f, "struct {{")?;
1822 for (i, (key, val)) in st.fields.iter().enumerate() {
1823 if i > 0 {
1824 write!(f, ", ")?;
1825 }
1826 write!(f, "{}: {}", key, val)?;
1827 }
1828 write!(f, "}}")
1829 }
1830 Value::OutputList(values) => {
1831 write!(f, "[")?;
1832 for (i, value) in values.iter().enumerate() {
1833 if i > 0 {
1834 write!(f, ", ")?;
1835 }
1836 write!(f, "{}", value)?;
1837 }
1838 write!(f, "]")
1839 }
1840 Value::FunctionHandle(name) => write!(f, "@{name}"),
1841 Value::Closure(c) => write!(
1842 f,
1843 "<closure {} captures={}>",
1844 c.function_name,
1845 c.captures.len()
1846 ),
1847 Value::ClassRef(name) => write!(f, "<class {name}>"),
1848 Value::MException(e) => write!(
1849 f,
1850 "MException(identifier='{}', message='{}')",
1851 e.identifier, e.message
1852 ),
1853 }
1854 }
1855}
1856
1857impl fmt::Display for ComplexTensor {
1858 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1859 match self.shape.len() {
1860 0 | 1 => {
1861 write!(f, "[")?;
1862 for (i, (re, im)) in self.data.iter().enumerate() {
1863 if i > 0 {
1864 write!(f, " ")?;
1865 }
1866 let s = Value::Complex(*re, *im).to_string();
1867 write!(f, "{s}")?;
1868 }
1869 write!(f, "]")
1870 }
1871 2 => {
1872 let rows = self.rows;
1873 let cols = self.cols;
1874 write!(f, "[")?;
1875 for r in 0..rows {
1876 for c in 0..cols {
1877 if c > 0 {
1878 write!(f, " ")?;
1879 }
1880 let (re, im) = self.data[r + c * rows];
1881 let s = Value::Complex(re, im).to_string();
1882 write!(f, "{s}")?;
1883 }
1884 if r + 1 < rows {
1885 write!(f, "; ")?;
1886 }
1887 }
1888 write!(f, "]")
1889 }
1890 _ => {
1891 if should_expand_nd_display(&self.shape) {
1892 write_nd_pages(f, &self.shape, |f, idx| {
1893 let (re, im) = self.data[idx];
1894 write!(f, "{}", Value::Complex(re, im))
1895 })
1896 } else {
1897 write!(f, "ComplexTensor(shape={:?})", self.shape)
1898 }
1899 }
1900 }
1901 }
1902}
1903
1904#[cfg(test)]
1905mod display_tests {
1906 use super::{ComplexTensor, LogicalArray, Tensor};
1907
1908 #[test]
1909 fn tensor_nd_display_uses_page_headers() {
1910 let tensor = Tensor::new(
1911 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],
1912 vec![2, 3, 2],
1913 )
1914 .expect("tensor");
1915 let rendered = tensor.to_string();
1916 assert!(rendered.contains("(:, :, 1) ="));
1917 assert!(rendered.contains("(:, :, 2) ="));
1918 assert!(rendered.contains(" 1 0 0"));
1919 }
1920
1921 #[test]
1922 fn tensor_nd_display_falls_back_for_large_arrays() {
1923 let tensor = Tensor::new(vec![0.0; 4097], vec![1, 1, 4097]).expect("tensor");
1924 assert_eq!(tensor.to_string(), "Tensor(shape=[1, 1, 4097])");
1925 }
1926
1927 #[test]
1928 fn logical_nd_display_uses_headers_and_fallback_summary() {
1929 let logical =
1930 LogicalArray::new(vec![1, 0, 0, 1, 1, 0, 0, 1], vec![2, 2, 2]).expect("logical");
1931 let rendered = logical.to_string();
1932 assert!(rendered.contains("(:, :, 1) ="));
1933 assert!(rendered.contains("(:, :, 2) ="));
1934
1935 let large = LogicalArray::new(vec![1; 4097], vec![1, 1, 4097]).expect("large logical");
1936 assert_eq!(large.to_string(), "1x1x4097 logical array");
1937 }
1938
1939 #[test]
1940 fn complex_nd_display_uses_page_headers() {
1941 let complex = ComplexTensor::new(
1942 vec![(1.0, 0.0), (0.0, 1.0), (0.0, 0.0), (1.0, 0.0)],
1943 vec![2, 1, 2],
1944 )
1945 .expect("complex");
1946 let rendered = complex.to_string();
1947 assert!(rendered.contains("(:, :, 1) ="));
1948 assert!(rendered.contains("(:, :, 2) ="));
1949 }
1950}
1951
1952#[derive(Debug, Clone, PartialEq)]
1953pub struct CellArray {
1954 pub data: Vec<GcPtr<Value>>,
1955 pub shape: Vec<usize>,
1957 pub rows: usize,
1959 pub cols: usize,
1961}
1962
1963impl CellArray {
1964 pub fn new_handles(
1965 handles: Vec<GcPtr<Value>>,
1966 rows: usize,
1967 cols: usize,
1968 ) -> Result<Self, String> {
1969 Self::new_handles_with_shape(handles, vec![rows, cols])
1970 }
1971
1972 pub fn new_handles_with_shape(
1973 handles: Vec<GcPtr<Value>>,
1974 shape: Vec<usize>,
1975 ) -> Result<Self, String> {
1976 let expected = total_len(&shape)
1977 .ok_or_else(|| "Cell data shape exceeds platform limits".to_string())?;
1978 if expected != handles.len() {
1979 return Err(format!(
1980 "Cell data length {} doesn't match shape {:?} ({} elements)",
1981 handles.len(),
1982 shape,
1983 expected
1984 ));
1985 }
1986 let (rows, cols) = shape_rows_cols(&shape);
1987 Ok(CellArray {
1988 data: handles,
1989 shape,
1990 rows,
1991 cols,
1992 })
1993 }
1994
1995 pub fn new(data: Vec<Value>, rows: usize, cols: usize) -> Result<Self, String> {
1996 Self::new_with_shape(data, vec![rows, cols])
1997 }
1998
1999 pub fn new_with_shape(data: Vec<Value>, shape: Vec<usize>) -> Result<Self, String> {
2000 let expected = total_len(&shape)
2001 .ok_or_else(|| "Cell data shape exceeds platform limits".to_string())?;
2002 if expected != data.len() {
2003 return Err(format!(
2004 "Cell data length {} doesn't match shape {:?} ({} elements)",
2005 data.len(),
2006 shape,
2007 expected
2008 ));
2009 }
2010 let handles: Vec<GcPtr<Value>> = data
2012 .into_iter()
2013 .map(|v| unsafe { GcPtr::from_raw(Box::into_raw(Box::new(v))) })
2014 .collect();
2015 Self::new_handles_with_shape(handles, shape)
2016 }
2017
2018 pub fn get(&self, row: usize, col: usize) -> Result<Value, String> {
2019 if row >= self.rows || col >= self.cols {
2020 return Err(format!(
2021 "Cell index ({row}, {col}) out of bounds for {}x{} cell array",
2022 self.rows, self.cols
2023 ));
2024 }
2025 Ok((*self.data[row * self.cols + col]).clone())
2026 }
2027}
2028
2029fn total_len(shape: &[usize]) -> Option<usize> {
2030 if shape.is_empty() {
2031 return Some(0);
2032 }
2033 shape
2034 .iter()
2035 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
2036}
2037
2038fn shape_rows_cols(shape: &[usize]) -> (usize, usize) {
2039 if shape.is_empty() {
2040 return (0, 0);
2041 }
2042 if shape.len() == 1 {
2043 return (1, shape[0]);
2044 }
2045 (shape[0], shape[1])
2046}
2047
2048impl fmt::Display for CellArray {
2049 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2050 let dims: Vec<String> = self.shape.iter().map(|d| d.to_string()).collect();
2051 if self.shape.len() > 2 {
2052 return write!(f, "{} cell array", dims.join("x"));
2053 }
2054 write!(f, "{}x{} cell array", self.rows, self.cols)?;
2055 if self.rows == 0 || self.cols == 0 {
2056 return Ok(());
2057 }
2058 for r in 0..self.rows {
2059 writeln!(f)?;
2060 write!(f, " ")?;
2061 for c in 0..self.cols {
2062 if c > 0 {
2063 write!(f, " ")?;
2064 }
2065 let value = self.get(r, c).unwrap_or_else(|_| Value::Num(f64::NAN));
2066 write!(f, "{{{value}}}")?;
2067 }
2068 }
2069 Ok(())
2070 }
2071}
2072
2073#[derive(Debug, Clone, PartialEq)]
2074pub struct ObjectInstance {
2075 pub class_name: String,
2076 pub properties: HashMap<String, Value>,
2077}
2078
2079impl ObjectInstance {
2080 pub fn new(class_name: String) -> Self {
2081 Self {
2082 class_name,
2083 properties: HashMap::new(),
2084 }
2085 }
2086}
2087
2088#[derive(Debug, Clone, PartialEq, Eq)]
2090pub enum Access {
2091 Public,
2092 Private,
2093}
2094
2095#[derive(Debug, Clone)]
2096pub struct PropertyDef {
2097 pub name: String,
2098 pub is_static: bool,
2099 pub is_dependent: bool,
2100 pub get_access: Access,
2101 pub set_access: Access,
2102 pub default_value: Option<Value>,
2103}
2104
2105#[derive(Debug, Clone)]
2106pub struct MethodDef {
2107 pub name: String,
2108 pub is_static: bool,
2109 pub access: Access,
2110 pub function_name: String, }
2112
2113#[derive(Debug, Clone)]
2114pub struct ClassDef {
2115 pub name: String, pub parent: Option<String>,
2117 pub properties: HashMap<String, PropertyDef>,
2118 pub methods: HashMap<String, MethodDef>,
2119}
2120
2121use std::sync::Mutex;
2122
2123static CLASS_REGISTRY: OnceLock<Mutex<HashMap<String, ClassDef>>> = OnceLock::new();
2124static STATIC_VALUES: OnceLock<Mutex<HashMap<(String, String), Value>>> = OnceLock::new();
2125
2126fn registry() -> &'static Mutex<HashMap<String, ClassDef>> {
2127 CLASS_REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
2128}
2129
2130pub fn register_class(def: ClassDef) {
2131 let mut m = registry().lock().unwrap();
2132 m.insert(def.name.clone(), def);
2133}
2134
2135pub fn get_class(name: &str) -> Option<ClassDef> {
2136 registry().lock().unwrap().get(name).cloned()
2137}
2138
2139pub fn lookup_property(class_name: &str, prop: &str) -> Option<(PropertyDef, String)> {
2142 let reg = registry().lock().unwrap();
2143 let mut current = Some(class_name.to_string());
2144 let guard: Option<std::sync::MutexGuard<'_, std::collections::HashMap<String, ClassDef>>> =
2145 None;
2146 drop(guard);
2147 while let Some(name) = current {
2148 if let Some(cls) = reg.get(&name) {
2149 if let Some(p) = cls.properties.get(prop) {
2150 return Some((p.clone(), name));
2151 }
2152 current = cls.parent.clone();
2153 } else {
2154 break;
2155 }
2156 }
2157 None
2158}
2159
2160pub fn lookup_method(class_name: &str, method: &str) -> Option<(MethodDef, String)> {
2163 let reg = registry().lock().unwrap();
2164 let mut current = Some(class_name.to_string());
2165 while let Some(name) = current {
2166 if let Some(cls) = reg.get(&name) {
2167 if let Some(m) = cls.methods.get(method) {
2168 return Some((m.clone(), name));
2169 }
2170 current = cls.parent.clone();
2171 } else {
2172 break;
2173 }
2174 }
2175 None
2176}
2177
2178fn static_values() -> &'static Mutex<HashMap<(String, String), Value>> {
2179 STATIC_VALUES.get_or_init(|| Mutex::new(HashMap::new()))
2180}
2181
2182pub fn get_static_property_value(class_name: &str, prop: &str) -> Option<Value> {
2183 static_values()
2184 .lock()
2185 .unwrap()
2186 .get(&(class_name.to_string(), prop.to_string()))
2187 .cloned()
2188}
2189
2190pub fn set_static_property_value(class_name: &str, prop: &str, value: Value) {
2191 static_values()
2192 .lock()
2193 .unwrap()
2194 .insert((class_name.to_string(), prop.to_string()), value);
2195}
2196
2197pub fn set_static_property_value_in_owner(
2199 class_name: &str,
2200 prop: &str,
2201 value: Value,
2202) -> Result<(), String> {
2203 if let Some((_p, owner)) = lookup_property(class_name, prop) {
2204 set_static_property_value(&owner, prop, value);
2205 Ok(())
2206 } else {
2207 Err(format!("Unknown static property '{class_name}.{prop}'"))
2208 }
2209}