aether/
value.rs

1// src/value.rs
2//! Runtime value types for the Aether language
3
4use crate::ast::{Expr, Stmt};
5use crate::environment::Environment;
6use num_bigint::BigInt;
7use num_rational::Ratio;
8use num_traits::Zero;
9use std::cell::RefCell;
10use std::collections::HashMap;
11use std::fmt;
12use std::rc::Rc;
13
14/// Runtime value types
15#[derive(Debug, Clone)]
16pub enum Value {
17    /// Numeric value (f64)
18    Number(f64),
19
20    /// Rational number (exact fraction)
21    Fraction(Ratio<BigInt>),
22
23    /// String value
24    String(String),
25
26    /// Boolean value
27    Boolean(bool),
28
29    /// Null value
30    Null,
31
32    /// Array of values
33    Array(Vec<Value>),
34
35    /// Dictionary (key-value map)
36    Dict(HashMap<String, Value>),
37
38    /// Function (closure)
39    Function {
40        params: Vec<String>,
41        body: Vec<Stmt>,
42        env: Rc<RefCell<Environment>>,
43    },
44
45    /// Generator (lazy iterator)
46    Generator {
47        params: Vec<String>,
48        body: Vec<Stmt>,
49        env: Rc<RefCell<Environment>>,
50        state: GeneratorState,
51    },
52
53    /// Lazy value (computed on demand)
54    Lazy {
55        expr: Expr,
56        env: Rc<RefCell<Environment>>,
57        cached: Option<Box<Value>>,
58    },
59
60    /// Built-in function
61    BuiltIn { name: String, arity: usize },
62}
63
64/// Generator execution state
65#[derive(Debug, Clone)]
66pub enum GeneratorState {
67    /// Not started yet
68    NotStarted,
69
70    /// Running with current position
71    Running { position: usize },
72
73    /// Completed
74    Done,
75}
76
77impl Value {
78    /// Check if value is truthy (for conditional evaluation)
79    pub fn is_truthy(&self) -> bool {
80        match self {
81            Value::Boolean(b) => *b,
82            Value::Null => false,
83            Value::Number(n) => *n != 0.0,
84            Value::Fraction(f) => !f.is_zero(),
85            Value::String(s) => !s.is_empty(),
86            Value::Array(arr) => !arr.is_empty(),
87            Value::Dict(dict) => !dict.is_empty(),
88            _ => true,
89        }
90    }
91
92    /// Get type name as string
93    pub fn type_name(&self) -> &'static str {
94        match self {
95            Value::Number(_) => "Number",
96            Value::Fraction(_) => "Fraction",
97            Value::String(_) => "String",
98            Value::Boolean(_) => "Boolean",
99            Value::Null => "Null",
100            Value::Array(_) => "Array",
101            Value::Dict(_) => "Dict",
102            Value::Function { .. } => "Function",
103            Value::Generator { .. } => "Generator",
104            Value::Lazy { .. } => "Lazy",
105            Value::BuiltIn { .. } => "BuiltIn",
106        }
107    }
108
109    /// Convert to number if possible
110    pub fn to_number(&self) -> Option<f64> {
111        match self {
112            Value::Number(n) => Some(*n),
113            Value::Fraction(f) => Some(
114                f.numer().to_string().parse::<f64>().ok()?
115                    / f.denom().to_string().parse::<f64>().ok()?,
116            ),
117            Value::Boolean(true) => Some(1.0),
118            Value::Boolean(false) => Some(0.0),
119            Value::String(s) => s.parse().ok(),
120            _ => None,
121        }
122    }
123
124    /// Convert to string
125    #[allow(clippy::inherent_to_string_shadow_display)]
126    pub fn to_string(&self) -> String {
127        match self {
128            Value::Number(n) => {
129                // Format number nicely (remove .0 for integers)
130                if n.fract() == 0.0 {
131                    format!("{:.0}", n)
132                } else {
133                    format!("{}", n)
134                }
135            }
136            Value::Fraction(f) => {
137                if f.is_integer() {
138                    format!("{}", f.numer())
139                } else {
140                    format!("{}/{}", f.numer(), f.denom())
141                }
142            }
143            Value::String(s) => s.clone(),
144            Value::Boolean(b) => b.to_string(),
145            Value::Null => "Null".to_string(),
146            Value::Array(arr) => {
147                let elements: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
148                format!("[{}]", elements.join(", "))
149            }
150            Value::Dict(dict) => {
151                let pairs: Vec<String> = dict
152                    .iter()
153                    .map(|(k, v)| format!("{}: {}", k, v.to_string()))
154                    .collect();
155                format!("{{{}}}", pairs.join(", "))
156            }
157            Value::Function { params, .. } => {
158                format!("<Function ({})>", params.join(", "))
159            }
160            Value::Generator { params, .. } => {
161                format!("<Generator ({})>", params.join(", "))
162            }
163            Value::Lazy { .. } => "<Lazy>".to_string(),
164            Value::BuiltIn { name, arity } => {
165                format!("<BuiltIn {} ({} args)>", name, arity)
166            }
167        }
168    }
169
170    /// Compare values for equality
171    pub fn equals(&self, other: &Value) -> bool {
172        match (self, other) {
173            (Value::Number(a), Value::Number(b)) => (a - b).abs() < f64::EPSILON,
174            (Value::Fraction(a), Value::Fraction(b)) => a == b,
175            (Value::String(a), Value::String(b)) => a == b,
176            (Value::Boolean(a), Value::Boolean(b)) => a == b,
177            (Value::Null, Value::Null) => true,
178            (Value::Array(a), Value::Array(b)) => {
179                a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x.equals(y))
180            }
181            _ => false,
182        }
183    }
184
185    /// Compare values for ordering
186    pub fn compare(&self, other: &Value) -> Option<std::cmp::Ordering> {
187        match (self, other) {
188            (Value::Number(a), Value::Number(b)) => a.partial_cmp(b),
189            (Value::Fraction(a), Value::Fraction(b)) => Some(a.cmp(b)),
190            (Value::String(a), Value::String(b)) => Some(a.cmp(b)),
191            (Value::Boolean(a), Value::Boolean(b)) => Some(a.cmp(b)),
192            _ => None,
193        }
194    }
195}
196
197impl fmt::Display for Value {
198    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199        write!(f, "{}", self.to_string())
200    }
201}
202
203impl PartialEq for Value {
204    fn eq(&self, other: &Self) -> bool {
205        self.equals(other)
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_value_truthy() {
215        assert!(Value::Boolean(true).is_truthy());
216        assert!(!Value::Boolean(false).is_truthy());
217        assert!(!Value::Null.is_truthy());
218        assert!(Value::Number(1.0).is_truthy());
219        assert!(!Value::Number(0.0).is_truthy());
220        assert!(Value::String("hello".to_string()).is_truthy());
221        assert!(!Value::String("".to_string()).is_truthy());
222    }
223
224    #[test]
225    fn test_value_type_name() {
226        assert_eq!(Value::Number(42.0).type_name(), "Number");
227        assert_eq!(Value::String("test".to_string()).type_name(), "String");
228        assert_eq!(Value::Boolean(true).type_name(), "Boolean");
229        assert_eq!(Value::Null.type_name(), "Null");
230        assert_eq!(Value::Array(vec![]).type_name(), "Array");
231    }
232
233    #[test]
234    fn test_value_to_number() {
235        assert_eq!(Value::Number(42.0).to_number(), Some(42.0));
236        assert_eq!(Value::Boolean(true).to_number(), Some(1.0));
237        assert_eq!(Value::Boolean(false).to_number(), Some(0.0));
238        assert_eq!(Value::String("123".to_string()).to_number(), Some(123.0));
239        assert_eq!(Value::String("abc".to_string()).to_number(), None);
240        assert_eq!(Value::Null.to_number(), None);
241    }
242
243    #[test]
244    fn test_value_to_string() {
245        assert_eq!(Value::Number(42.0).to_string(), "42");
246        #[allow(clippy::approx_constant)]
247        {
248            assert_eq!(Value::Number(3.14).to_string(), "3.14");
249        }
250        assert_eq!(Value::String("hello".to_string()).to_string(), "hello");
251        assert_eq!(Value::Boolean(true).to_string(), "true");
252        assert_eq!(Value::Null.to_string(), "Null");
253        assert_eq!(
254            Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]).to_string(),
255            "[1, 2]"
256        );
257    }
258
259    #[test]
260    fn test_value_equals() {
261        assert!(Value::Number(42.0).equals(&Value::Number(42.0)));
262        assert!(!Value::Number(42.0).equals(&Value::Number(43.0)));
263        assert!(Value::String("test".to_string()).equals(&Value::String("test".to_string())));
264        assert!(Value::Boolean(true).equals(&Value::Boolean(true)));
265        assert!(Value::Null.equals(&Value::Null));
266    }
267
268    #[test]
269    fn test_value_compare() {
270        use std::cmp::Ordering;
271
272        assert_eq!(
273            Value::Number(42.0).compare(&Value::Number(43.0)),
274            Some(Ordering::Less)
275        );
276        assert_eq!(
277            Value::String("a".to_string()).compare(&Value::String("b".to_string())),
278            Some(Ordering::Less)
279        );
280        assert_eq!(
281            Value::Boolean(false).compare(&Value::Boolean(true)),
282            Some(Ordering::Less)
283        );
284    }
285
286    #[test]
287    fn test_array_equality() {
288        let arr1 = Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]);
289        let arr2 = Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]);
290        let arr3 = Value::Array(vec![Value::Number(1.0), Value::Number(3.0)]);
291
292        assert!(arr1.equals(&arr2));
293        assert!(!arr1.equals(&arr3));
294    }
295}