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}