runmat_builtins/
lib.rs

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