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