uni_core/
value.rs

1use crate::compat::{Rc, String, Vec, fmt};
2use crate::tokenizer::SourcePos;
3
4#[cfg(feature = "datetime")]
5use chrono::{DateTime, Duration as ChronoDuration, FixedOffset};
6
7#[cfg(not(target_os = "none"))]
8use std::cell::RefCell;
9#[cfg(target_os = "none")]
10use core::cell::RefCell;
11
12use num_bigint::BigInt;
13#[cfg(feature = "complex_numbers")]
14use num_complex::Complex64;
15use num_rational::BigRational;
16use num_traits::{One, Zero};
17
18// RUST CONCEPT: Using the num ecosystem for arbitrary precision and special number types
19// BigInt: Arbitrary precision integers (unlimited size)
20// BigRational: Exact rational numbers (fractions)
21// GaussianInt: Gaussian integers (a + bi where a, b are integers)
22// Complex64: Complex numbers with f64 components
23
24#[derive(Debug, Clone)]
25pub enum Value {
26    Number(f64),                    // Floating point number (default)
27    Int32(i32),                     // 32-bit signed integer (embedded-friendly)
28    Integer(BigInt),                // Arbitrary precision integer
29    Rational(BigRational),          // Exact rational number (fraction)
30    #[cfg(feature = "complex_numbers")]
31    GaussianInt(BigInt, BigInt),    // Gaussian integer (real, imaginary) - both integers
32    #[cfg(feature = "complex_numbers")]
33    Complex(Complex64),             // Complex number (a + bi) - floating point components
34    Atom(Rc<str>),                  // Interned atoms for efficiency
35    QuotedAtom(Rc<str>),            // Quoted atoms - push without executing
36    String(Rc<str>),                // Literal strings - ref counted but not interned
37    Boolean(bool),                  // True/false boolean values
38    Null,                           // Null/undefined value (distinct from Nil empty list)
39    Pair(Rc<Value>, Rc<Value>),    // Cons cell for lists
40    Array(Rc<RefCell<Vec<Value>>>), // Mutable array/vector
41    Variable(Rc<RefCell<Value>>),   // Mutable variable (Forth-style)
42    Nil,                            // Empty list marker
43    Builtin(fn(&mut crate::interpreter::Interpreter) -> Result<(), RuntimeError>),
44    // RUST CONCEPT: Records (Scheme-style record types)
45    // Records are named product types with labeled fields
46    // type_name: The record type name (e.g., "person")
47    // fields: The field values stored in a mutable vector
48    // Uses Rc<RefCell<...>> for shared ownership with interior mutability
49    Record {
50        type_name: Rc<str>,
51        fields: Rc<RefCell<Vec<Value>>>,
52    },
53    // RUST CONCEPT: Record type descriptors
54    // Stores metadata about record types (field names, field count)
55    // Used to validate and access record instances
56    RecordType {
57        type_name: Rc<str>,
58        field_names: Rc<Vec<Rc<str>>>,
59    },
60    // RUST CONCEPT: Date/time support using chrono (only with datetime feature)
61    // DateTime stores an instant in time with timezone offset information
62    // Uses DateTime<FixedOffset> which can represent any timezone
63    // The offset is stored alongside the instant (e.g., "-05:00", "+00:00")
64    #[cfg(feature = "datetime")]
65    DateTime(DateTime<FixedOffset>),
66    // RUST CONCEPT: Duration represents a time span
67    // Can be positive (future) or negative (past)
68    // Supports days, hours, minutes, seconds, milliseconds, etc.
69    #[cfg(feature = "datetime")]
70    Duration(ChronoDuration),
71    // RUST CONCEPT: I16 buffer for audio samples and DSP
72    // Stores 16-bit signed integers (standard for digital audio)
73    // Dynamic size Vec for flexibility - can grow/shrink as needed
74    // Use with record types to add audio metadata (sample rate, channels)
75    I16Buffer(Rc<RefCell<Vec<i16>>>),
76}
77
78impl Value {
79    // RUST CONCEPT: Get the type name of a value
80    // Returns a string describing the type for display and debugging
81    pub fn type_name(&self) -> &'static str {
82        match self {
83            Value::Number(_) => "number",
84            Value::Int32(_) => "int32",
85            Value::Integer(_) => "integer",
86            Value::Rational(_) => "rational",
87            #[cfg(feature = "complex_numbers")]
88            Value::GaussianInt(_, _) => "gaussian",
89            #[cfg(feature = "complex_numbers")]
90            Value::Complex(_) => "complex",
91            Value::Atom(_) => "atom",
92            Value::QuotedAtom(_) => "quoted-atom",
93            Value::String(_) => "string",
94            Value::Boolean(_) => "boolean",
95            Value::Null => "null",
96            Value::Pair(_, _) => "list",
97            Value::Array(_) => "vector",
98            Value::Variable(_) => "variable",
99            Value::Nil => "nil",
100            Value::Builtin(_) => "builtin",
101            Value::Record { .. } => "record",
102            Value::RecordType { .. } => "record-type",
103            #[cfg(feature = "datetime")]
104            Value::DateTime(_) => "datetime",
105            #[cfg(feature = "datetime")]
106            Value::Duration(_) => "duration",
107            Value::I16Buffer(_) => "i16-buffer",
108        }
109    }
110
111    // RUST CONCEPT: Automatic numeric type demotion for cleaner results
112    // This function attempts to demote numeric types to simpler representations:
113    // - Rational with denominator 1 → Integer or Int32
114    // - Rational with numerator 0 → Int32(0)
115    // - GaussianInt with imaginary 0 → Integer or Int32
116    // - Integer that fits in i32 → Int32
117    // This keeps values in their simplest form after arithmetic operations
118    pub fn demote(self) -> Self {
119        use num_traits::ToPrimitive;
120        match &self {
121            // Check Rational: demote if denominator is 1 or numerator is 0
122            Value::Rational(r) if r.numer().is_zero() => {
123                // 0/n → Int32(0)
124                Value::Int32(0)
125            }
126            Value::Rational(r) if r.denom().is_one() => {
127                // n/1 → Integer or Int32
128                // Extract the inner BigRational and clone its numerator
129                if let Value::Rational(r) = self {
130                    let big_int = r.numer().clone();
131                    // Try to fit in i32 for embedded systems
132                    if let Some(i32_val) = big_int.to_i32() {
133                        Value::Int32(i32_val)
134                    } else {
135                        Value::Integer(big_int)
136                    }
137                } else {
138                    unreachable!()
139                }
140            }
141            // Check GaussianInt: demote if imaginary part is 0
142            #[cfg(feature = "complex_numbers")]
143            Value::GaussianInt(_re, im) if im.is_zero() => {
144                // a+0i → Integer or Int32 (move real part out)
145                if let Value::GaussianInt(re, _im) = self {
146                    // Try to fit in i32
147                    if let Some(i32_val) = re.to_i32() {
148                        Value::Int32(i32_val)
149                    } else {
150                        Value::Integer(re)
151                    }
152                } else {
153                    unreachable!()
154                }
155            }
156            // Check Integer: demote to Int32 if it fits
157            Value::Integer(i) => {
158                if let Some(i32_val) = i.to_i32() {
159                    Value::Int32(i32_val)
160                } else {
161                    self
162                }
163            }
164            // All other cases: return unchanged (no deconstruct/reconstruct)
165            _ => self,
166        }
167    }
168}
169
170#[derive(Debug)]
171pub enum RuntimeError {
172    StackUnderflow,
173    StackUnderflowAt { pos: SourcePos, context: String },
174    TypeError(String),
175    UndefinedWord(String),
176    DivisionByZero,
177    ModuloByZero,
178    DomainError(String),
179    QuitRequested, // Special error to signal clean exit from REPL/script
180}
181
182// RUST CONCEPT: Implementing traits for custom error types
183// The Display trait allows us to convert errors to strings
184impl fmt::Display for RuntimeError {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        match self {
187            RuntimeError::StackUnderflow => write!(f, "Stack underflow"),
188            RuntimeError::StackUnderflowAt { pos, context } => {
189                write!(
190                    f,
191                    "Stack underflow at line {}, column {}: {}",
192                    pos.line, pos.column, context
193                )
194            }
195            RuntimeError::TypeError(msg) => write!(f, "Type error: {}", msg),
196            RuntimeError::UndefinedWord(word) => write!(f, "Undefined word: {}", word),
197            RuntimeError::DivisionByZero => write!(f, "Division by zero"),
198            RuntimeError::ModuloByZero => write!(f, "Modulo by zero"),
199            RuntimeError::DomainError(msg) => write!(f, "Domain error: {}", msg),
200            RuntimeError::QuitRequested => write!(f, "Quit requested"),
201        }
202    }
203}
204
205// RUST CONCEPT: Implementing Display for Value types
206// This is the "data display" mode - strings WITH quotes for data structures
207impl fmt::Display for Value {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        match self {
210            Value::Number(n) => write!(f, "{}", n),
211            Value::Int32(i) => write!(f, "{}", i),
212            Value::Integer(i) => write!(f, "{}", i),
213            // RUST CONCEPT: BigRational displays as "numerator/denominator"
214            Value::Rational(r) => write!(f, "{}", r), // Shows as fraction like "3/4"
215            // RUST CONCEPT: GaussianInt displays as "a+bi" with integer parts
216            #[cfg(feature = "complex_numbers")]
217            Value::GaussianInt(re, im) => {
218                use num_traits::Zero;
219
220                // Special case: 0+1i displays as just "i"
221                if re.is_zero() && im == &BigInt::from(1) {
222                    write!(f, "i")
223                }
224                // Special case: 0-1i displays as "-i"
225                else if re.is_zero() && im == &BigInt::from(-1) {
226                    write!(f, "-i")
227                }
228                // Special case: 0+ni displays as "ni" (pure imaginary)
229                else if re.is_zero() {
230                    write!(f, "{}i", im)
231                }
232                // Special case: a+0i displays as just "a" (pure real)
233                else if im.is_zero() {
234                    write!(f, "{}", re)
235                }
236                // General case: a+bi
237                else if im >= &BigInt::from(0) {
238                    write!(f, "{}+{}i", re, im)
239                } else {
240                    write!(f, "{}{}i", re, im)
241                }
242            }
243            // RUST CONCEPT: Complex64 displays as "a+bi" format with floating point
244            #[cfg(feature = "complex_numbers")]
245            Value::Complex(c) => {
246                // Custom formatting for complex numbers
247                if c.im >= 0.0 {
248                    write!(f, "{}+{}i", c.re, c.im)
249                } else {
250                    write!(f, "{}{}i", c.re, c.im)
251                }
252            }
253            Value::Atom(a) => write!(f, "{}", a),
254            Value::QuotedAtom(a) => write!(f, "'{}", a),
255            Value::String(s) => write!(f, "\"{}\"", s), // Strings WITH quotes
256            Value::Boolean(b) => write!(f, "{}", if *b { "true" } else { "false" }),
257            Value::Null => write!(f, "null"),
258            Value::Pair(head, tail) => {
259                write!(f, "[")?;
260                write!(f, "{}", head)?;
261                let mut current = tail;
262                loop {
263                    match current.as_ref() {
264                        Value::Nil => break,
265                        Value::Pair(h, t) => {
266                            write!(f, " {}", h)?;
267                            current = t;
268                        }
269                        other => {
270                            write!(f, " | {}", other)?;
271                            break;
272                        }
273                    }
274                }
275                write!(f, "]")
276            }
277            Value::Array(elements) => {
278                let elements_ref = elements.borrow();
279                write!(f, "#[")?;
280                let mut iter = elements_ref.iter();
281                if let Some(first) = iter.next() {
282                    write!(f, "{}", first)?;
283                    for elem in iter {
284                        write!(f, " {}", elem)?;
285                    }
286                }
287                write!(f, "]")
288            }
289            Value::Variable(cell) => {
290                write!(f, "<variable:{}>", cell.borrow())
291            }
292            Value::Nil => write!(f, "[]"),
293            Value::Builtin(_) => write!(f, "<builtin>"),
294            // RUST CONCEPT: Display for record instances
295            // Shows the type name and field values
296            Value::Record { type_name, fields } => {
297                let fields_ref = fields.borrow();
298                write!(f, "#<record:{}", type_name)?;
299                for field in fields_ref.iter() {
300                    write!(f, " {}", field)?;
301                }
302                write!(f, ">")
303            }
304            // RUST CONCEPT: Display for record type descriptors
305            // Shows the type name and field names
306            Value::RecordType {
307                type_name,
308                field_names,
309            } => {
310                write!(f, "#<record-type:{}", type_name)?;
311                for field_name in field_names.iter() {
312                    write!(f, " {}", field_name)?;
313                }
314                write!(f, ">")
315            }
316            // RUST CONCEPT: Display for datetime values
317            // Uses chrono's RFC3339 format (ISO 8601 with timezone)
318            // Example: "2025-10-01T14:30:00-05:00"
319            #[cfg(feature = "datetime")]
320            Value::DateTime(dt) => write!(f, "#<datetime:{}>", dt.to_rfc3339()),
321            // RUST CONCEPT: Display for duration values
322            // Shows duration in a human-readable format
323            #[cfg(feature = "datetime")]
324            Value::Duration(d) => {
325                // Convert to total seconds for display
326                let total_secs = d.num_seconds();
327                if total_secs == 0 {
328                    write!(f, "#<duration:0s>")
329                } else {
330                    let days = total_secs / 86400;
331                    let hours = (total_secs % 86400) / 3600;
332                    let mins = (total_secs % 3600) / 60;
333                    let secs = total_secs % 60;
334
335                    write!(f, "#<duration:")?;
336                    let mut first = true;
337                    if days != 0 {
338                        write!(f, "{}d", days)?;
339                        first = false;
340                    }
341                    if hours != 0 {
342                        if !first { write!(f, " ")?; }
343                        write!(f, "{}h", hours)?;
344                        first = false;
345                    }
346                    if mins != 0 {
347                        if !first { write!(f, " ")?; }
348                        write!(f, "{}m", mins)?;
349                        first = false;
350                    }
351                    if secs != 0 || first {
352                        if !first { write!(f, " ")?; }
353                        write!(f, "{}s", secs)?;
354                    }
355                    write!(f, ">")
356                }
357            }
358            // RUST CONCEPT: Display for i16 buffers
359            // Shows buffer length and first few samples for debugging
360            Value::I16Buffer(buffer) => {
361                let buffer_ref = buffer.borrow();
362                let len = buffer_ref.len();
363                write!(f, "#<i16-buffer:{}:[", len)?;
364
365                // Show first 8 samples (or fewer if buffer is smaller)
366                let preview_count = len.min(8);
367                for i in 0..preview_count {
368                    if i > 0 {
369                        write!(f, " ")?;
370                    }
371                    write!(f, "{}", buffer_ref[i])?;
372                }
373
374                if len > preview_count {
375                    write!(f, " ...")?;
376                }
377                write!(f, "]>")
378            }
379        }
380    }
381}