runmat_builtins/
lib.rs

1pub use inventory;
2use runmat_gc_api::GcPtr;
3use std::collections::HashMap;
4use std::convert::TryFrom;
5use std::fmt;
6
7#[derive(Debug, Clone, PartialEq)]
8pub enum Value {
9    Int(IntValue),
10    Num(f64),
11    /// Complex scalar value represented as (re, im)
12    Complex(f64, f64),
13    Bool(bool),
14    // Logical array (N-D of booleans). Scalars use Bool.
15    LogicalArray(LogicalArray),
16    String(String),
17    // String array (R2016b+): N-D array of string scalars
18    StringArray(StringArray),
19    // Char array (single-quoted): 2-D character array (rows x cols)
20    CharArray(CharArray),
21    Tensor(Tensor),
22    /// Complex numeric array; same column-major shape semantics as `Tensor`
23    ComplexTensor(ComplexTensor),
24    Cell(CellArray),
25    // Struct (scalar or nested). Struct arrays are represented in higher layers;
26    // this variant holds a single struct's fields.
27    Struct(StructValue),
28    // GPU-resident tensor handle (opaque; buffer managed by backend)
29    GpuTensor(runmat_accelerate_api::GpuTensorHandle),
30    // Simple object instance until full class system lands
31    Object(ObjectInstance),
32    /// Handle-object wrapper providing identity semantics and validity tracking
33    HandleObject(HandleRef),
34    /// Event listener handle for events
35    Listener(Listener),
36    // Function handle pointing to a named function (builtin or user)
37    FunctionHandle(String),
38    Closure(Closure),
39    ClassRef(String),
40    MException(MException),
41}
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum IntValue {
44    I8(i8),
45    I16(i16),
46    I32(i32),
47    I64(i64),
48    U8(u8),
49    U16(u16),
50    U32(u32),
51    U64(u64),
52}
53
54impl IntValue {
55    pub fn to_i64(&self) -> i64 {
56        match self {
57            IntValue::I8(v) => *v as i64,
58            IntValue::I16(v) => *v as i64,
59            IntValue::I32(v) => *v as i64,
60            IntValue::I64(v) => *v,
61            IntValue::U8(v) => *v as i64,
62            IntValue::U16(v) => *v as i64,
63            IntValue::U32(v) => *v as i64,
64            IntValue::U64(v) => {
65                if *v > i64::MAX as u64 {
66                    i64::MAX
67                } else {
68                    *v as i64
69                }
70            }
71        }
72    }
73    pub fn to_f64(&self) -> f64 {
74        self.to_i64() as f64
75    }
76    pub fn is_zero(&self) -> bool {
77        self.to_i64() == 0
78    }
79    pub fn class_name(&self) -> &'static str {
80        match self {
81            IntValue::I8(_) => "int8",
82            IntValue::I16(_) => "int16",
83            IntValue::I32(_) => "int32",
84            IntValue::I64(_) => "int64",
85            IntValue::U8(_) => "uint8",
86            IntValue::U16(_) => "uint16",
87            IntValue::U32(_) => "uint32",
88            IntValue::U64(_) => "uint64",
89        }
90    }
91}
92
93#[derive(Debug, Clone, PartialEq)]
94pub struct StructValue {
95    pub fields: HashMap<String, Value>,
96}
97
98impl StructValue {
99    pub fn new() -> Self {
100        Self {
101            fields: HashMap::new(),
102        }
103    }
104}
105
106impl Default for StructValue {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112#[derive(Debug, Clone, PartialEq)]
113pub struct Tensor {
114    pub data: Vec<f64>,
115    pub shape: Vec<usize>, // Column-major layout
116    pub rows: usize,       // Compatibility for 2D usage
117    pub cols: usize,       // Compatibility for 2D usage
118}
119
120#[derive(Debug, Clone, PartialEq)]
121pub struct ComplexTensor {
122    pub data: Vec<(f64, f64)>,
123    pub shape: Vec<usize>,
124    pub rows: usize,
125    pub cols: usize,
126}
127
128#[derive(Debug, Clone, PartialEq)]
129pub struct StringArray {
130    pub data: Vec<String>,
131    pub shape: Vec<usize>,
132    pub rows: usize,
133    pub cols: usize,
134}
135
136#[derive(Debug, Clone, PartialEq)]
137pub struct LogicalArray {
138    pub data: Vec<u8>, // 0 or 1 values; compact bitset can come later
139    pub shape: Vec<usize>,
140}
141
142impl LogicalArray {
143    pub fn new(data: Vec<u8>, shape: Vec<usize>) -> Result<Self, String> {
144        let expected: usize = shape.iter().product();
145        if data.len() != expected {
146            return Err(format!(
147                "LogicalArray data length {} doesn't match shape {:?} ({} elements)",
148                data.len(),
149                shape,
150                expected
151            ));
152        }
153        // Normalize to 0/1
154        let mut d = data;
155        for v in &mut d {
156            *v = if *v != 0 { 1 } else { 0 };
157        }
158        Ok(LogicalArray { data: d, shape })
159    }
160    pub fn zeros(shape: Vec<usize>) -> Self {
161        let expected: usize = shape.iter().product();
162        LogicalArray {
163            data: vec![0u8; expected],
164            shape,
165        }
166    }
167    pub fn len(&self) -> usize {
168        self.data.len()
169    }
170    pub fn is_empty(&self) -> bool {
171        self.data.is_empty()
172    }
173}
174
175#[derive(Debug, Clone, PartialEq)]
176pub struct CharArray {
177    pub data: Vec<char>,
178    pub rows: usize,
179    pub cols: usize,
180}
181
182impl CharArray {
183    pub fn new_row(s: &str) -> Self {
184        CharArray {
185            data: s.chars().collect(),
186            rows: 1,
187            cols: s.chars().count(),
188        }
189    }
190    pub fn new(data: Vec<char>, rows: usize, cols: usize) -> Result<Self, String> {
191        if rows * cols != data.len() {
192            return Err(format!(
193                "Char data length {} doesn't match dimensions {}x{}",
194                data.len(),
195                rows,
196                cols
197            ));
198        }
199        Ok(CharArray { data, rows, cols })
200    }
201}
202
203impl StringArray {
204    pub fn new(data: Vec<String>, shape: Vec<usize>) -> Result<Self, String> {
205        let expected: usize = shape.iter().product();
206        if data.len() != expected {
207            return Err(format!(
208                "StringArray data length {} doesn't match shape {:?} ({} elements)",
209                data.len(),
210                shape,
211                expected
212            ));
213        }
214        let (rows, cols) = if shape.len() >= 2 {
215            (shape[0], shape[1])
216        } else if shape.len() == 1 {
217            (1, shape[0])
218        } else {
219            (0, 0)
220        };
221        Ok(StringArray {
222            data,
223            shape,
224            rows,
225            cols,
226        })
227    }
228    pub fn new_2d(data: Vec<String>, rows: usize, cols: usize) -> Result<Self, String> {
229        Self::new(data, vec![rows, cols])
230    }
231    pub fn rows(&self) -> usize {
232        self.shape.first().copied().unwrap_or(1)
233    }
234    pub fn cols(&self) -> usize {
235        self.shape.get(1).copied().unwrap_or(1)
236    }
237}
238
239// GpuTensorHandle now lives in runmat-accel-api
240
241impl Tensor {
242    pub fn new(data: Vec<f64>, shape: Vec<usize>) -> Result<Self, String> {
243        let expected: usize = shape.iter().product();
244        if data.len() != expected {
245            return Err(format!(
246                "Tensor data length {} doesn't match shape {:?} ({} elements)",
247                data.len(),
248                shape,
249                expected
250            ));
251        }
252        let (rows, cols) = if shape.len() >= 2 {
253            (shape[0], shape[1])
254        } else if shape.len() == 1 {
255            (1, shape[0])
256        } else {
257            (0, 0)
258        };
259        Ok(Tensor {
260            data,
261            shape,
262            rows,
263            cols,
264        })
265    }
266
267    pub fn new_2d(data: Vec<f64>, rows: usize, cols: usize) -> Result<Self, String> {
268        Self::new(data, vec![rows, cols])
269    }
270
271    pub fn zeros(shape: Vec<usize>) -> Self {
272        let size: usize = shape.iter().product();
273        let (rows, cols) = if shape.len() >= 2 {
274            (shape[0], shape[1])
275        } else if shape.len() == 1 {
276            (1, shape[0])
277        } else {
278            (0, 0)
279        };
280        Tensor {
281            data: vec![0.0; size],
282            shape,
283            rows,
284            cols,
285        }
286    }
287
288    pub fn ones(shape: Vec<usize>) -> Self {
289        let size: usize = shape.iter().product();
290        let (rows, cols) = if shape.len() >= 2 {
291            (shape[0], shape[1])
292        } else if shape.len() == 1 {
293            (1, shape[0])
294        } else {
295            (0, 0)
296        };
297        Tensor {
298            data: vec![1.0; size],
299            shape,
300            rows,
301            cols,
302        }
303    }
304
305    // 2D helpers for transitional call sites
306    pub fn zeros2(rows: usize, cols: usize) -> Self {
307        Self::zeros(vec![rows, cols])
308    }
309    pub fn ones2(rows: usize, cols: usize) -> Self {
310        Self::ones(vec![rows, cols])
311    }
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    pub fn get2(&self, row: usize, col: usize) -> Result<f64, String> {
321        let rows = self.rows();
322        let cols = self.cols();
323        if row >= rows || col >= cols {
324            return Err(format!(
325                "Index ({row}, {col}) out of bounds for {rows}x{cols} tensor"
326            ));
327        }
328        // Column-major linearization: lin = row + col*rows
329        Ok(self.data[row + col * rows])
330    }
331
332    pub fn set2(&mut self, row: usize, col: usize, value: f64) -> Result<(), String> {
333        let rows = self.rows();
334        let cols = self.cols();
335        if row >= rows || col >= cols {
336            return Err(format!(
337                "Index ({row}, {col}) out of bounds for {rows}x{cols} tensor"
338            ));
339        }
340        // Column-major linearization
341        self.data[row + col * rows] = value;
342        Ok(())
343    }
344
345    pub fn scalar_to_tensor2(scalar: f64, rows: usize, cols: usize) -> Tensor {
346        Tensor {
347            data: vec![scalar; rows * cols],
348            shape: vec![rows, cols],
349            rows,
350            cols,
351        }
352    }
353    // No-compat constructors: prefer new/new_2d/zeros/zeros2/ones/ones2
354}
355
356impl ComplexTensor {
357    pub fn new(data: Vec<(f64, f64)>, shape: Vec<usize>) -> Result<Self, String> {
358        let expected: usize = shape.iter().product();
359        if data.len() != expected {
360            return Err(format!(
361                "ComplexTensor data length {} doesn't match shape {:?} ({} elements)",
362                data.len(),
363                shape,
364                expected
365            ));
366        }
367        let (rows, cols) = if shape.len() >= 2 {
368            (shape[0], shape[1])
369        } else if shape.len() == 1 {
370            (1, shape[0])
371        } else {
372            (0, 0)
373        };
374        Ok(ComplexTensor {
375            data,
376            shape,
377            rows,
378            cols,
379        })
380    }
381    pub fn new_2d(data: Vec<(f64, f64)>, rows: usize, cols: usize) -> Result<Self, String> {
382        Self::new(data, vec![rows, cols])
383    }
384    pub fn zeros(shape: Vec<usize>) -> Self {
385        let size: usize = shape.iter().product();
386        let (rows, cols) = if shape.len() >= 2 {
387            (shape[0], shape[1])
388        } else if shape.len() == 1 {
389            (1, shape[0])
390        } else {
391            (0, 0)
392        };
393        ComplexTensor {
394            data: vec![(0.0, 0.0); size],
395            shape,
396            rows,
397            cols,
398        }
399    }
400}
401
402impl fmt::Display for Tensor {
403    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404        match self.shape.len() {
405            0 | 1 => {
406                // Treat as row vector for display
407                write!(f, "[")?;
408                for (i, v) in self.data.iter().enumerate() {
409                    if i > 0 {
410                        write!(f, " ")?;
411                    }
412                    write!(f, "{}", format_number_short_g(*v))?;
413                }
414                write!(f, "]")
415            }
416            2 => {
417                let rows = self.rows();
418                let cols = self.cols();
419                write!(f, "[")?;
420                for r in 0..rows {
421                    for c in 0..cols {
422                        if c > 0 {
423                            write!(f, " ")?;
424                        }
425                        let v = self.data[r + c * rows];
426                        write!(f, "{}", format_number_short_g(v))?;
427                    }
428                    if r + 1 < rows {
429                        write!(f, "; ")?;
430                    }
431                }
432                write!(f, "]")
433            }
434            _ => write!(f, "Tensor(shape={:?})", self.shape),
435        }
436    }
437}
438
439impl fmt::Display for StringArray {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        match self.shape.len() {
442            0 | 1 => {
443                write!(f, "[")?;
444                for (i, v) in self.data.iter().enumerate() {
445                    if i > 0 {
446                        write!(f, " ")?;
447                    }
448                    let escaped = v.replace('"', "\\\"");
449                    write!(f, "\"{escaped}\"")?;
450                }
451                write!(f, "]")
452            }
453            2 => {
454                let rows = self.rows();
455                let cols = self.cols();
456                write!(f, "[")?;
457                for r in 0..rows {
458                    for c in 0..cols {
459                        if c > 0 {
460                            write!(f, " ")?;
461                        }
462                        let v = &self.data[r + c * rows];
463                        let escaped = v.replace('"', "\\\"");
464                        write!(f, "\"{escaped}\"")?;
465                    }
466                    if r + 1 < rows {
467                        write!(f, "; ")?;
468                    }
469                }
470                write!(f, "]")
471            }
472            _ => write!(f, "StringArray(shape={:?})", self.shape),
473        }
474    }
475}
476
477impl fmt::Display for LogicalArray {
478    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479        match self.shape.len() {
480            0 => write!(f, "[]"),
481            1 => {
482                write!(f, "[")?;
483                for (i, v) in self.data.iter().enumerate() {
484                    if i > 0 {
485                        write!(f, " ")?;
486                    }
487                    write!(f, "{}", if *v != 0 { 1 } else { 0 })?;
488                }
489                write!(f, "]")
490            }
491            2 => {
492                let rows = self.shape[0];
493                let cols = self.shape[1];
494                write!(f, "[")?;
495                for r in 0..rows {
496                    for c in 0..cols {
497                        if c > 0 {
498                            write!(f, " ")?;
499                        }
500                        let idx = r + c * rows;
501                        write!(f, "{}", if self.data[idx] != 0 { 1 } else { 0 })?;
502                    }
503                    if r + 1 < rows {
504                        write!(f, "; ")?;
505                    }
506                }
507                write!(f, "]")
508            }
509            _ => write!(f, "LogicalArray(shape={:?})", self.shape),
510        }
511    }
512}
513
514impl fmt::Display for CharArray {
515    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
516        // Display as single-quoted rows separated by ;
517        write!(f, "[")?;
518        for r in 0..self.rows {
519            if r > 0 {
520                write!(f, "; ")?;
521            }
522            write!(f, "'")?;
523            for c in 0..self.cols {
524                let ch = self.data[r * self.cols + c];
525                if ch == '\'' {
526                    write!(f, "''")?;
527                } else {
528                    write!(f, "{ch}")?;
529                }
530            }
531            write!(f, "'")?;
532        }
533        write!(f, "]")
534    }
535}
536
537// From implementations for Value
538impl From<i32> for Value {
539    fn from(i: i32) -> Self {
540        Value::Int(IntValue::I32(i))
541    }
542}
543impl From<i64> for Value {
544    fn from(i: i64) -> Self {
545        Value::Int(IntValue::I64(i))
546    }
547}
548impl From<u32> for Value {
549    fn from(i: u32) -> Self {
550        Value::Int(IntValue::U32(i))
551    }
552}
553impl From<u64> for Value {
554    fn from(i: u64) -> Self {
555        Value::Int(IntValue::U64(i))
556    }
557}
558impl From<i16> for Value {
559    fn from(i: i16) -> Self {
560        Value::Int(IntValue::I16(i))
561    }
562}
563impl From<i8> for Value {
564    fn from(i: i8) -> Self {
565        Value::Int(IntValue::I8(i))
566    }
567}
568impl From<u16> for Value {
569    fn from(i: u16) -> Self {
570        Value::Int(IntValue::U16(i))
571    }
572}
573impl From<u8> for Value {
574    fn from(i: u8) -> Self {
575        Value::Int(IntValue::U8(i))
576    }
577}
578
579impl From<f64> for Value {
580    fn from(f: f64) -> Self {
581        Value::Num(f)
582    }
583}
584
585impl From<bool> for Value {
586    fn from(b: bool) -> Self {
587        Value::Bool(b)
588    }
589}
590
591impl From<String> for Value {
592    fn from(s: String) -> Self {
593        Value::String(s)
594    }
595}
596
597impl From<&str> for Value {
598    fn from(s: &str) -> Self {
599        Value::String(s.to_string())
600    }
601}
602
603impl From<Tensor> for Value {
604    fn from(m: Tensor) -> Self {
605        Value::Tensor(m)
606    }
607}
608
609// Remove blanket From<Vec<Value>> to avoid losing shape information
610
611// TryFrom implementations for extracting native types
612impl TryFrom<&Value> for i32 {
613    type Error = String;
614    fn try_from(v: &Value) -> Result<Self, Self::Error> {
615        match v {
616            Value::Int(i) => Ok(i.to_i64() as i32),
617            Value::Num(n) => Ok(*n as i32),
618            _ => Err(format!("cannot convert {v:?} to i32")),
619        }
620    }
621}
622
623impl TryFrom<&Value> for f64 {
624    type Error = String;
625    fn try_from(v: &Value) -> Result<Self, Self::Error> {
626        match v {
627            Value::Num(n) => Ok(*n),
628            Value::Int(i) => Ok(i.to_f64()),
629            _ => Err(format!("cannot convert {v:?} to f64")),
630        }
631    }
632}
633
634impl TryFrom<&Value> for bool {
635    type Error = String;
636    fn try_from(v: &Value) -> Result<Self, Self::Error> {
637        match v {
638            Value::Bool(b) => Ok(*b),
639            Value::Int(i) => Ok(!i.is_zero()),
640            Value::Num(n) => Ok(*n != 0.0),
641            _ => Err(format!("cannot convert {v:?} to bool")),
642        }
643    }
644}
645
646impl TryFrom<&Value> for String {
647    type Error = String;
648    fn try_from(v: &Value) -> Result<Self, Self::Error> {
649        match v {
650            Value::String(s) => Ok(s.clone()),
651            Value::StringArray(sa) => {
652                if sa.data.len() == 1 {
653                    Ok(sa.data[0].clone())
654                } else {
655                    Err("cannot convert string array to scalar string".to_string())
656                }
657            }
658            Value::CharArray(ca) => {
659                // Convert full char array to one string if it is a single row; else error
660                if ca.rows == 1 {
661                    Ok(ca.data.iter().collect())
662                } else {
663                    Err("cannot convert multi-row char array to scalar string".to_string())
664                }
665            }
666            Value::Int(i) => Ok(i.to_i64().to_string()),
667            Value::Num(n) => Ok(n.to_string()),
668            Value::Bool(b) => Ok(b.to_string()),
669            _ => Err(format!("cannot convert {v:?} to String")),
670        }
671    }
672}
673
674impl TryFrom<&Value> for Tensor {
675    type Error = String;
676    fn try_from(v: &Value) -> Result<Self, Self::Error> {
677        match v {
678            Value::Tensor(m) => Ok(m.clone()),
679            _ => Err(format!("cannot convert {v:?} to Tensor")),
680        }
681    }
682}
683
684impl TryFrom<&Value> for Value {
685    type Error = String;
686    fn try_from(v: &Value) -> Result<Self, Self::Error> {
687        Ok(v.clone())
688    }
689}
690
691impl TryFrom<&Value> for Vec<Value> {
692    type Error = String;
693    fn try_from(v: &Value) -> Result<Self, Self::Error> {
694        match v {
695            Value::Cell(c) => Ok(c.data.iter().map(|p| (**p).clone()).collect()),
696            _ => Err(format!("cannot convert {v:?} to Vec<Value>")),
697        }
698    }
699}
700
701use serde::{Deserialize, Serialize};
702
703/// Enhanced type system used throughout RunMat for HIR and builtin functions
704/// Designed to mirror Value variants for better type inference and LSP support
705#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
706pub enum Type {
707    /// Integer number type
708    Int,
709    /// Floating-point number type  
710    Num,
711    /// Boolean type
712    Bool,
713    /// Logical array type (N-D boolean array)
714    Logical,
715    /// String type
716    String,
717    /// Tensor type with optional shape information (column-major semantics in runtime)
718    Tensor {
719        /// Optional full shape; None means unknown/dynamic; individual dims can be omitted by using None
720        shape: Option<Vec<Option<usize>>>,
721    },
722    /// Cell array type with optional element type information
723    Cell {
724        /// Optional element type (None means mixed/unknown)
725        element_type: Option<Box<Type>>,
726        /// Optional length (None means unknown/dynamic)
727        length: Option<usize>,
728    },
729    /// Function type with parameter and return types
730    Function {
731        /// Parameter types
732        params: Vec<Type>,
733        /// Return type
734        returns: Box<Type>,
735    },
736    /// Void type (no value)
737    Void,
738    /// Unknown type (for type inference)
739    Unknown,
740    /// Union type (multiple possible types)
741    Union(Vec<Type>),
742    /// Struct-like type with optional known field set (purely for inference)
743    Struct {
744        /// Optional set of known field names observed via control-flow (None = unknown fields)
745        known_fields: Option<Vec<String>>, // kept sorted unique for deterministic Eq
746    },
747}
748
749impl Type {
750    /// Create a tensor type with unknown shape
751    pub fn tensor() -> Self {
752        Type::Tensor { shape: None }
753    }
754
755    /// Create a tensor type with known shape
756    pub fn tensor_with_shape(shape: Vec<usize>) -> Self {
757        Type::Tensor {
758            shape: Some(shape.into_iter().map(Some).collect()),
759        }
760    }
761
762    /// Create a cell array type with unknown element type
763    pub fn cell() -> Self {
764        Type::Cell {
765            element_type: None,
766            length: None,
767        }
768    }
769
770    /// Create a cell array type with known element type
771    pub fn cell_of(element_type: Type) -> Self {
772        Type::Cell {
773            element_type: Some(Box::new(element_type)),
774            length: None,
775        }
776    }
777
778    /// Check if this type is compatible with another type
779    pub fn is_compatible_with(&self, other: &Type) -> bool {
780        match (self, other) {
781            (Type::Unknown, _) | (_, Type::Unknown) => true,
782            (Type::Int, Type::Num) | (Type::Num, Type::Int) => true, // Number compatibility
783            (Type::Tensor { .. }, Type::Tensor { .. }) => true, // Tensor compatibility regardless of dims for now
784            (a, b) => a == b,
785        }
786    }
787
788    /// Get the most specific common type between two types
789    pub fn unify(&self, other: &Type) -> Type {
790        match (self, other) {
791            (Type::Unknown, t) | (t, Type::Unknown) => t.clone(),
792            (Type::Int, Type::Num) | (Type::Num, Type::Int) => Type::Num,
793            (Type::Tensor { .. }, Type::Tensor { .. }) => Type::tensor(), // Lose shape info for now
794            (Type::Struct { known_fields: a }, Type::Struct { known_fields: b }) => match (a, b) {
795                (None, None) => Type::Struct { known_fields: None },
796                (Some(ka), None) | (None, Some(ka)) => Type::Struct {
797                    known_fields: Some(ka.clone()),
798                },
799                (Some(ka), Some(kb)) => {
800                    let mut set: std::collections::BTreeSet<String> = ka.iter().cloned().collect();
801                    set.extend(kb.iter().cloned());
802                    Type::Struct {
803                        known_fields: Some(set.into_iter().collect()),
804                    }
805                }
806            },
807            (a, b) if a == b => a.clone(),
808            _ => Type::Union(vec![self.clone(), other.clone()]),
809        }
810    }
811
812    /// Infer type from a Value
813    pub fn from_value(value: &Value) -> Type {
814        match value {
815            Value::Int(_) => Type::Int,
816            Value::Num(_) => Type::Num,
817            Value::Complex(_, _) => Type::Num, // treat as numeric double (complex) in type system for now
818            Value::Bool(_) => Type::Bool,
819            Value::LogicalArray(_) => Type::Logical,
820            Value::String(_) => Type::String,
821            Value::StringArray(_sa) => {
822                // Model as Cell of String for type system for now
823                Type::cell_of(Type::String)
824            }
825            Value::Tensor(t) => Type::Tensor {
826                shape: Some(t.shape.iter().map(|&d| Some(d)).collect()),
827            },
828            Value::ComplexTensor(t) => Type::Tensor {
829                shape: Some(t.shape.iter().map(|&d| Some(d)).collect()),
830            },
831            Value::Cell(cells) => {
832                if cells.data.is_empty() {
833                    Type::cell()
834                } else {
835                    // Infer element type from first element
836                    let element_type = Type::from_value(&cells.data[0]);
837                    Type::Cell {
838                        element_type: Some(Box::new(element_type)),
839                        length: Some(cells.data.len()),
840                    }
841                }
842            }
843            Value::GpuTensor(h) => Type::Tensor {
844                shape: Some(h.shape.iter().map(|&d| Some(d)).collect()),
845            },
846            Value::Object(_) => Type::Unknown,
847            Value::HandleObject(_) => Type::Unknown,
848            Value::Listener(_) => Type::Unknown,
849            Value::Struct(_) => Type::Struct { known_fields: None },
850            Value::FunctionHandle(_) => Type::Function {
851                params: vec![Type::Unknown],
852                returns: Box::new(Type::Unknown),
853            },
854            Value::Closure(_) => Type::Function {
855                params: vec![Type::Unknown],
856                returns: Box::new(Type::Unknown),
857            },
858            Value::ClassRef(_) => Type::Unknown,
859            Value::MException(_) => Type::Unknown,
860            Value::CharArray(ca) => {
861                // Treat as cell of char for type purposes; or a 2-D char matrix conceptually
862                Type::Cell {
863                    element_type: Some(Box::new(Type::String)),
864                    length: Some(ca.rows * ca.cols),
865                }
866            }
867        }
868    }
869}
870
871#[derive(Debug, Clone, PartialEq)]
872pub struct Closure {
873    pub function_name: String,
874    pub captures: Vec<Value>,
875}
876
877/// Simple builtin function definition using the unified type system
878#[derive(Debug, Clone)]
879pub struct BuiltinFunction {
880    pub name: &'static str,
881    pub description: &'static str,
882    pub category: &'static str,
883    pub doc: &'static str,
884    pub examples: &'static str,
885    pub param_types: Vec<Type>,
886    pub return_type: Type,
887    pub implementation: fn(&[Value]) -> Result<Value, String>,
888}
889
890impl BuiltinFunction {
891    #[allow(clippy::too_many_arguments)]
892    pub fn new(
893        name: &'static str,
894        description: &'static str,
895        category: &'static str,
896        doc: &'static str,
897        examples: &'static str,
898        param_types: Vec<Type>,
899        return_type: Type,
900        implementation: fn(&[Value]) -> Result<Value, String>,
901    ) -> Self {
902        Self {
903            name,
904            description,
905            category,
906            doc,
907            examples,
908            param_types,
909            return_type,
910            implementation,
911        }
912    }
913}
914
915/// A constant value that can be accessed as a variable
916#[derive(Clone)]
917pub struct Constant {
918    pub name: &'static str,
919    pub value: Value,
920}
921
922impl std::fmt::Debug for Constant {
923    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
924        write!(
925            f,
926            "Constant {{ name: {:?}, value: {:?} }}",
927            self.name, self.value
928        )
929    }
930}
931
932inventory::collect!(BuiltinFunction);
933inventory::collect!(Constant);
934
935pub fn builtin_functions() -> Vec<&'static BuiltinFunction> {
936    inventory::iter::<BuiltinFunction>().collect()
937}
938
939pub fn constants() -> Vec<&'static Constant> {
940    inventory::iter::<Constant>().collect()
941}
942
943// ----------------------
944// Builtin documentation metadata (optional, registered by macros)
945// ----------------------
946
947#[derive(Debug)]
948pub struct BuiltinDoc {
949    pub name: &'static str,
950    pub category: Option<&'static str>,
951    pub summary: Option<&'static str>,
952    pub keywords: Option<&'static str>,
953    pub errors: Option<&'static str>,
954    pub related: Option<&'static str>,
955    pub introduced: Option<&'static str>,
956    pub status: Option<&'static str>,
957    pub examples: Option<&'static str>,
958}
959
960inventory::collect!(BuiltinDoc);
961
962pub fn builtin_docs() -> Vec<&'static BuiltinDoc> {
963    inventory::iter::<BuiltinDoc>().collect()
964}
965
966// ----------------------
967// Display implementations
968// ----------------------
969
970fn format_number_short_g(value: f64) -> String {
971    if value.is_nan() {
972        return "NaN".to_string();
973    }
974    if value.is_infinite() {
975        return if value.is_sign_negative() {
976            "-Inf"
977        } else {
978            "Inf"
979        }
980        .to_string();
981    }
982    // Normalize -0.0 to 0
983    let mut v = value;
984    if v == 0.0 {
985        v = 0.0;
986    }
987
988    let abs = v.abs();
989    if abs == 0.0 {
990        return "0".to_string();
991    }
992
993    // Decide between fixed and scientific notation roughly like short g
994    let use_scientific = !(1e-4..1e6).contains(&abs);
995
996    if use_scientific {
997        // 5 significant digits in scientific notation for short g style
998        let s = format!("{v:.5e}");
999        // Trim trailing zeros in fraction part
1000        if let Some(idx) = s.find('e') {
1001            let (mut mantissa, exp) = s.split_at(idx);
1002            // mantissa like "-1.23450"
1003            if let Some(dot_idx) = mantissa.find('.') {
1004                // Trim trailing zeros
1005                let mut end = mantissa.len();
1006                while end > dot_idx + 1 && mantissa.as_bytes()[end - 1] == b'0' {
1007                    end -= 1;
1008                }
1009                if end > 0 && mantissa.as_bytes()[end - 1] == b'.' {
1010                    end -= 1;
1011                }
1012                mantissa = &mantissa[..end];
1013            }
1014            return format!("{mantissa}{exp}");
1015        }
1016        return s;
1017    }
1018
1019    // Fixed notation with up to 12 significant digits, trim trailing zeros
1020    // Compute number of decimals to retain to reach ~12 significant digits
1021    let exp10 = abs.log10().floor() as i32; // position of most significant digit
1022    let sig_digits: i32 = 12;
1023    let decimals = (sig_digits - 1 - exp10).clamp(0, 12) as usize;
1024    // Round to that many decimals
1025    let pow = 10f64.powi(decimals as i32);
1026    let rounded = (v * pow).round() / pow;
1027    let mut s = format!("{rounded:.decimals$}");
1028    if let Some(dot) = s.find('.') {
1029        // Trim trailing zeros
1030        let mut end = s.len();
1031        while end > dot + 1 && s.as_bytes()[end - 1] == b'0' {
1032            end -= 1;
1033        }
1034        if end > 0 && s.as_bytes()[end - 1] == b'.' {
1035            end -= 1;
1036        }
1037        s.truncate(end);
1038    }
1039    if s.is_empty() || s == "-0" {
1040        s = "0".to_string();
1041    }
1042    s
1043}
1044
1045// -------- Exception type --------
1046#[derive(Debug, Clone, PartialEq)]
1047pub struct MException {
1048    pub identifier: String,
1049    pub message: String,
1050    pub stack: Vec<String>,
1051}
1052
1053impl MException {
1054    pub fn new(identifier: String, message: String) -> Self {
1055        Self {
1056            identifier,
1057            message,
1058            stack: Vec::new(),
1059        }
1060    }
1061}
1062
1063/// Reference to a GC-allocated object providing language handle semantics
1064#[derive(Debug, Clone)]
1065pub struct HandleRef {
1066    pub class_name: String,
1067    pub target: GcPtr<Value>,
1068    pub valid: bool,
1069}
1070
1071impl PartialEq for HandleRef {
1072    fn eq(&self, other: &Self) -> bool {
1073        let a = unsafe { self.target.as_raw() } as usize;
1074        let b = unsafe { other.target.as_raw() } as usize;
1075        a == b
1076    }
1077}
1078
1079/// Event listener handle for events
1080#[derive(Debug, Clone, PartialEq)]
1081pub struct Listener {
1082    pub id: u64,
1083    pub target: GcPtr<Value>,
1084    pub event_name: String,
1085    pub callback: GcPtr<Value>,
1086    pub enabled: bool,
1087    pub valid: bool,
1088}
1089
1090impl Listener {
1091    pub fn class_name(&self) -> String {
1092        match unsafe { &*self.target.as_raw() } {
1093            Value::Object(o) => o.class_name.clone(),
1094            Value::HandleObject(h) => h.class_name.clone(),
1095            _ => String::new(),
1096        }
1097    }
1098}
1099
1100impl fmt::Display for Value {
1101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1102        match self {
1103            Value::Int(i) => write!(f, "{}", i.to_i64()),
1104            Value::Num(n) => write!(f, "{}", format_number_short_g(*n)),
1105            Value::Complex(re, im) => {
1106                if *im == 0.0 {
1107                    write!(f, "{}", format_number_short_g(*re))
1108                } else if *re == 0.0 {
1109                    write!(f, "{}i", format_number_short_g(*im))
1110                } else if *im < 0.0 {
1111                    write!(
1112                        f,
1113                        "{}-{}i",
1114                        format_number_short_g(*re),
1115                        format_number_short_g(im.abs())
1116                    )
1117                } else {
1118                    write!(
1119                        f,
1120                        "{}+{}i",
1121                        format_number_short_g(*re),
1122                        format_number_short_g(*im)
1123                    )
1124                }
1125            }
1126            Value::Bool(b) => write!(f, "{}", if *b { 1 } else { 0 }),
1127            Value::LogicalArray(la) => write!(f, "{la}"),
1128            Value::String(s) => write!(f, "'{s}'"),
1129            Value::StringArray(sa) => write!(f, "{sa}"),
1130            Value::CharArray(ca) => write!(f, "{ca}"),
1131            Value::Tensor(m) => write!(f, "{m}"),
1132            Value::ComplexTensor(m) => write!(f, "{m}"),
1133            Value::Cell(ca) => ca.fmt(f),
1134
1135            Value::GpuTensor(h) => write!(
1136                f,
1137                "GpuTensor(shape={:?}, device={}, buffer={})",
1138                h.shape, h.device_id, h.buffer_id
1139            ),
1140            Value::Object(obj) => write!(f, "{}(props={})", obj.class_name, obj.properties.len()),
1141            Value::HandleObject(h) => {
1142                let ptr = unsafe { h.target.as_raw() } as usize;
1143                write!(
1144                    f,
1145                    "<handle {} @0x{:x} valid={}>",
1146                    h.class_name, ptr, h.valid
1147                )
1148            }
1149            Value::Listener(l) => {
1150                let ptr = unsafe { l.target.as_raw() } as usize;
1151                write!(
1152                    f,
1153                    "<listener id={} {}@0x{:x} '{}' enabled={} valid={}>",
1154                    l.id,
1155                    l.class_name(),
1156                    ptr,
1157                    l.event_name,
1158                    l.enabled,
1159                    l.valid
1160                )
1161            }
1162            Value::Struct(st) => write!(f, "struct(fields={})", st.fields.len()),
1163            Value::FunctionHandle(name) => write!(f, "@{name}"),
1164            Value::Closure(c) => write!(
1165                f,
1166                "<closure {} captures={}>",
1167                c.function_name,
1168                c.captures.len()
1169            ),
1170            Value::ClassRef(name) => write!(f, "<class {name}>"),
1171            Value::MException(e) => write!(
1172                f,
1173                "MException(identifier='{}', message='{}')",
1174                e.identifier, e.message
1175            ),
1176        }
1177    }
1178}
1179
1180impl fmt::Display for ComplexTensor {
1181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1182        match self.shape.len() {
1183            0 | 1 => {
1184                write!(f, "[")?;
1185                for (i, (re, im)) in self.data.iter().enumerate() {
1186                    if i > 0 {
1187                        write!(f, " ")?;
1188                    }
1189                    let s = Value::Complex(*re, *im).to_string();
1190                    write!(f, "{s}")?;
1191                }
1192                write!(f, "]")
1193            }
1194            2 => {
1195                let rows = self.rows;
1196                let cols = self.cols;
1197                write!(f, "[")?;
1198                for r in 0..rows {
1199                    for c in 0..cols {
1200                        if c > 0 {
1201                            write!(f, " ")?;
1202                        }
1203                        let (re, im) = self.data[r + c * rows];
1204                        let s = Value::Complex(re, im).to_string();
1205                        write!(f, "{s}")?;
1206                    }
1207                    if r + 1 < rows {
1208                        write!(f, "; ")?;
1209                    }
1210                }
1211                write!(f, "]")
1212            }
1213            _ => write!(f, "ComplexTensor(shape={:?})", self.shape),
1214        }
1215    }
1216}
1217
1218#[derive(Debug, Clone, PartialEq)]
1219pub struct CellArray {
1220    pub data: Vec<GcPtr<Value>>,
1221    pub rows: usize,
1222    pub cols: usize,
1223}
1224
1225impl CellArray {
1226    pub fn new_handles(
1227        handles: Vec<GcPtr<Value>>,
1228        rows: usize,
1229        cols: usize,
1230    ) -> Result<Self, String> {
1231        if rows * cols != handles.len() {
1232            return Err(format!(
1233                "Cell data length {} doesn't match dimensions {}x{}",
1234                handles.len(),
1235                rows,
1236                cols
1237            ));
1238        }
1239        Ok(CellArray {
1240            data: handles,
1241            rows,
1242            cols,
1243        })
1244    }
1245    pub fn new(data: Vec<Value>, rows: usize, cols: usize) -> Result<Self, String> {
1246        if rows * cols != data.len() {
1247            return Err(format!(
1248                "Cell data length {} doesn't match dimensions {}x{}",
1249                data.len(),
1250                rows,
1251                cols
1252            ));
1253        }
1254        // Note: data will be allocated into GC handles by callers (runtime/ignition) to avoid builtins↔gc cycles
1255        let handles: Vec<GcPtr<Value>> = data
1256            .into_iter()
1257            .map(|v| unsafe { GcPtr::from_raw(Box::into_raw(Box::new(v))) })
1258            .collect();
1259        Ok(CellArray {
1260            data: handles,
1261            rows,
1262            cols,
1263        })
1264    }
1265
1266    pub fn get(&self, row: usize, col: usize) -> Result<Value, String> {
1267        if row >= self.rows || col >= self.cols {
1268            return Err(format!(
1269                "Cell index ({row}, {col}) out of bounds for {}x{} cell array",
1270                self.rows, self.cols
1271            ));
1272        }
1273        Ok((*self.data[row * self.cols + col]).clone())
1274    }
1275}
1276
1277impl fmt::Display for CellArray {
1278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1279        write!(f, "{{")?;
1280        for r in 0..self.rows {
1281            for c in 0..self.cols {
1282                if c > 0 {
1283                    write!(f, ", ")?;
1284                }
1285                let v = &self.data[r * self.cols + c];
1286                write!(f, "{}", **v)?;
1287            }
1288            if r + 1 < self.rows {
1289                write!(f, "; ")?;
1290            }
1291        }
1292        write!(f, "}}")
1293    }
1294}
1295
1296#[derive(Debug, Clone, PartialEq)]
1297pub struct ObjectInstance {
1298    pub class_name: String,
1299    pub properties: HashMap<String, Value>,
1300}
1301
1302impl ObjectInstance {
1303    pub fn new(class_name: String) -> Self {
1304        Self {
1305            class_name,
1306            properties: HashMap::new(),
1307        }
1308    }
1309}
1310
1311// -------- Class registry (scaffolding) --------
1312#[derive(Debug, Clone, PartialEq, Eq)]
1313pub enum Access {
1314    Public,
1315    Private,
1316}
1317
1318#[derive(Debug, Clone)]
1319pub struct PropertyDef {
1320    pub name: String,
1321    pub is_static: bool,
1322    pub is_dependent: bool,
1323    pub get_access: Access,
1324    pub set_access: Access,
1325    pub default_value: Option<Value>,
1326}
1327
1328#[derive(Debug, Clone)]
1329pub struct MethodDef {
1330    pub name: String,
1331    pub is_static: bool,
1332    pub access: Access,
1333    pub function_name: String, // bound runtime builtin/user func name
1334}
1335
1336#[derive(Debug, Clone)]
1337pub struct ClassDef {
1338    pub name: String, // namespaced e.g. pkg.Point
1339    pub parent: Option<String>,
1340    pub properties: HashMap<String, PropertyDef>,
1341    pub methods: HashMap<String, MethodDef>,
1342}
1343
1344use std::sync::{Mutex, OnceLock};
1345
1346static CLASS_REGISTRY: OnceLock<Mutex<HashMap<String, ClassDef>>> = OnceLock::new();
1347static STATIC_VALUES: OnceLock<Mutex<HashMap<(String, String), Value>>> = OnceLock::new();
1348
1349fn registry() -> &'static Mutex<HashMap<String, ClassDef>> {
1350    CLASS_REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
1351}
1352
1353pub fn register_class(def: ClassDef) {
1354    let mut m = registry().lock().unwrap();
1355    m.insert(def.name.clone(), def);
1356}
1357
1358pub fn get_class(name: &str) -> Option<ClassDef> {
1359    registry().lock().unwrap().get(name).cloned()
1360}
1361
1362/// Resolve a property through the inheritance chain, returning the property definition and
1363/// the name of the class where it was defined.
1364pub fn lookup_property(class_name: &str, prop: &str) -> Option<(PropertyDef, String)> {
1365    let reg = registry().lock().unwrap();
1366    let mut current = Some(class_name.to_string());
1367    let guard: Option<std::sync::MutexGuard<'_, std::collections::HashMap<String, ClassDef>>> =
1368        None;
1369    drop(guard);
1370    while let Some(name) = current {
1371        if let Some(cls) = reg.get(&name) {
1372            if let Some(p) = cls.properties.get(prop) {
1373                return Some((p.clone(), name));
1374            }
1375            current = cls.parent.clone();
1376        } else {
1377            break;
1378        }
1379    }
1380    None
1381}
1382
1383/// Resolve a method through the inheritance chain, returning the method definition and
1384/// the name of the class where it was defined.
1385pub fn lookup_method(class_name: &str, method: &str) -> Option<(MethodDef, String)> {
1386    let reg = registry().lock().unwrap();
1387    let mut current = Some(class_name.to_string());
1388    while let Some(name) = current {
1389        if let Some(cls) = reg.get(&name) {
1390            if let Some(m) = cls.methods.get(method) {
1391                return Some((m.clone(), name));
1392            }
1393            current = cls.parent.clone();
1394        } else {
1395            break;
1396        }
1397    }
1398    None
1399}
1400
1401fn static_values() -> &'static Mutex<HashMap<(String, String), Value>> {
1402    STATIC_VALUES.get_or_init(|| Mutex::new(HashMap::new()))
1403}
1404
1405pub fn get_static_property_value(class_name: &str, prop: &str) -> Option<Value> {
1406    static_values()
1407        .lock()
1408        .unwrap()
1409        .get(&(class_name.to_string(), prop.to_string()))
1410        .cloned()
1411}
1412
1413pub fn set_static_property_value(class_name: &str, prop: &str, value: Value) {
1414    static_values()
1415        .lock()
1416        .unwrap()
1417        .insert((class_name.to_string(), prop.to_string()), value);
1418}
1419
1420/// Set a static property, resolving the defining ancestor class for storage.
1421pub fn set_static_property_value_in_owner(
1422    class_name: &str,
1423    prop: &str,
1424    value: Value,
1425) -> Result<(), String> {
1426    if let Some((_p, owner)) = lookup_property(class_name, prop) {
1427        set_static_property_value(&owner, prop, value);
1428        Ok(())
1429    } else {
1430        Err(format!("Unknown static property '{class_name}.{prop}'"))
1431    }
1432}