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