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}