Skip to main content

elo_rust/runtime/
value.rs

1//! ELO runtime value representation
2//!
3//! Provides the EloValue enum for dynamic typing and runtime value handling.
4//! This allows the compiler to track and validate types at both compile-time
5//! and runtime.
6
7use std::collections::BTreeMap;
8use std::fmt;
9
10/// Represents a runtime value in ELO
11///
12/// EloValue supports dynamic typing with support for all ELO data types:
13/// scalars (int, float, string, bool, null) and collections (arrays, objects).
14#[derive(Debug, Clone, PartialEq)]
15pub enum EloValue {
16    /// Integer value (64-bit signed)
17    Integer(i64),
18
19    /// Float value (64-bit IEEE 754)
20    Float(f64),
21
22    /// String value
23    String(String),
24
25    /// Boolean value
26    Boolean(bool),
27
28    /// Null/None value
29    Null,
30
31    /// Array of values (homogeneous or heterogeneous)
32    Array(Vec<EloValue>),
33
34    /// Object as key-value pairs (sorted by key for consistency)
35    Object(BTreeMap<String, EloValue>),
36}
37
38impl EloValue {
39    /// Get the type name as a string
40    pub fn type_name(&self) -> &'static str {
41        match self {
42            EloValue::Integer(_) => "integer",
43            EloValue::Float(_) => "float",
44            EloValue::String(_) => "string",
45            EloValue::Boolean(_) => "boolean",
46            EloValue::Null => "null",
47            EloValue::Array(_) => "array",
48            EloValue::Object(_) => "object",
49        }
50    }
51
52    /// Check if this value is truthy
53    pub fn is_truthy(&self) -> bool {
54        match self {
55            EloValue::Null => false,
56            EloValue::Boolean(b) => *b,
57            EloValue::Integer(n) => *n != 0,
58            EloValue::Float(f) => *f != 0.0,
59            EloValue::String(s) => !s.is_empty(),
60            EloValue::Array(a) => !a.is_empty(),
61            EloValue::Object(o) => !o.is_empty(),
62        }
63    }
64
65    /// Convert to integer if possible
66    pub fn to_integer(&self) -> Option<i64> {
67        match self {
68            EloValue::Integer(n) => Some(*n),
69            EloValue::Float(f) => Some(*f as i64),
70            EloValue::Boolean(b) => Some(if *b { 1 } else { 0 }),
71            EloValue::String(s) => s.parse().ok(),
72            _ => None,
73        }
74    }
75
76    /// Convert to float if possible
77    pub fn to_float(&self) -> Option<f64> {
78        match self {
79            EloValue::Integer(n) => Some(*n as f64),
80            EloValue::Float(f) => Some(*f),
81            EloValue::String(s) => s.parse().ok(),
82            _ => None,
83        }
84    }
85
86    /// Convert to string
87    pub fn to_string_value(&self) -> String {
88        match self {
89            EloValue::Integer(n) => n.to_string(),
90            EloValue::Float(f) => {
91                // Format floats nicely, avoiding trailing zeros
92                if f.fract() == 0.0 {
93                    format!("{:.1}", f)
94                } else {
95                    f.to_string()
96                }
97            }
98            EloValue::String(s) => s.clone(),
99            EloValue::Boolean(b) => b.to_string(),
100            EloValue::Null => "null".to_string(),
101            EloValue::Array(arr) => {
102                let elements: Vec<String> = arr.iter().map(|v| v.to_string_value()).collect();
103                format!("[{}]", elements.join(", "))
104            }
105            EloValue::Object(obj) => {
106                let pairs: Vec<String> = obj
107                    .iter()
108                    .map(|(k, v)| format!("{}: {}", k, v.to_string_value()))
109                    .collect();
110                format!("{{{}}}", pairs.join(", "))
111            }
112        }
113    }
114
115    /// Convert to boolean
116    pub fn to_boolean(&self) -> bool {
117        self.is_truthy()
118    }
119
120    /// Check if this value is numeric (integer or float)
121    pub fn is_numeric(&self) -> bool {
122        matches!(self, EloValue::Integer(_) | EloValue::Float(_))
123    }
124
125    /// Check if this value is a string
126    pub fn is_string(&self) -> bool {
127        matches!(self, EloValue::String(_))
128    }
129
130    /// Check if this value is an array
131    pub fn is_array(&self) -> bool {
132        matches!(self, EloValue::Array(_))
133    }
134
135    /// Check if this value is an object
136    pub fn is_object(&self) -> bool {
137        matches!(self, EloValue::Object(_))
138    }
139
140    /// Get array length if this is an array
141    pub fn array_len(&self) -> Option<usize> {
142        match self {
143            EloValue::Array(arr) => Some(arr.len()),
144            EloValue::String(s) => Some(s.len()),
145            _ => None,
146        }
147    }
148
149    /// Get value at array index
150    pub fn array_get(&self, index: usize) -> Option<EloValue> {
151        match self {
152            EloValue::Array(arr) => arr.get(index).cloned(),
153            _ => None,
154        }
155    }
156
157    /// Get object field value
158    pub fn object_get(&self, key: &str) -> Option<EloValue> {
159        match self {
160            EloValue::Object(obj) => obj.get(key).cloned(),
161            _ => None,
162        }
163    }
164
165    /// Add two values (numeric addition or string concatenation)
166    pub fn add(&self, other: &EloValue) -> Result<EloValue, String> {
167        match (self, other) {
168            (EloValue::Integer(a), EloValue::Integer(b)) => Ok(EloValue::Integer(a + b)),
169            (EloValue::Float(a), EloValue::Float(b)) => Ok(EloValue::Float(a + b)),
170            (EloValue::Integer(a), EloValue::Float(b)) => Ok(EloValue::Float(*a as f64 + b)),
171            (EloValue::Float(a), EloValue::Integer(b)) => Ok(EloValue::Float(a + *b as f64)),
172            (EloValue::String(a), EloValue::String(b)) => {
173                Ok(EloValue::String(format!("{}{}", a, b)))
174            }
175            _ => Err(format!(
176                "Cannot add {} and {}",
177                self.type_name(),
178                other.type_name()
179            )),
180        }
181    }
182
183    /// Subtract two values
184    pub fn subtract(&self, other: &EloValue) -> Result<EloValue, String> {
185        match (self, other) {
186            (EloValue::Integer(a), EloValue::Integer(b)) => Ok(EloValue::Integer(a - b)),
187            (EloValue::Float(a), EloValue::Float(b)) => Ok(EloValue::Float(a - b)),
188            (EloValue::Integer(a), EloValue::Float(b)) => Ok(EloValue::Float(*a as f64 - b)),
189            (EloValue::Float(a), EloValue::Integer(b)) => Ok(EloValue::Float(a - *b as f64)),
190            _ => Err(format!(
191                "Cannot subtract {} from {}",
192                other.type_name(),
193                self.type_name()
194            )),
195        }
196    }
197
198    /// Multiply two values
199    pub fn multiply(&self, other: &EloValue) -> Result<EloValue, String> {
200        match (self, other) {
201            (EloValue::Integer(a), EloValue::Integer(b)) => Ok(EloValue::Integer(a * b)),
202            (EloValue::Float(a), EloValue::Float(b)) => Ok(EloValue::Float(a * b)),
203            (EloValue::Integer(a), EloValue::Float(b)) => Ok(EloValue::Float(*a as f64 * b)),
204            (EloValue::Float(a), EloValue::Integer(b)) => Ok(EloValue::Float(a * *b as f64)),
205            // String repetition
206            (EloValue::String(s), EloValue::Integer(n)) => {
207                if *n < 0 {
208                    Err("Cannot repeat string negative times".to_string())
209                } else {
210                    Ok(EloValue::String(s.repeat(*n as usize)))
211                }
212            }
213            _ => Err(format!(
214                "Cannot multiply {} and {}",
215                self.type_name(),
216                other.type_name()
217            )),
218        }
219    }
220
221    /// Divide two values
222    pub fn divide(&self, other: &EloValue) -> Result<EloValue, String> {
223        match (self, other) {
224            (EloValue::Integer(a), EloValue::Integer(b)) => {
225                if *b == 0 {
226                    Err("Division by zero".to_string())
227                } else {
228                    Ok(EloValue::Integer(a / b))
229                }
230            }
231            (EloValue::Float(a), EloValue::Float(b)) => {
232                if *b == 0.0 {
233                    Err("Division by zero".to_string())
234                } else {
235                    Ok(EloValue::Float(a / b))
236                }
237            }
238            (EloValue::Integer(a), EloValue::Float(b)) => {
239                if *b == 0.0 {
240                    Err("Division by zero".to_string())
241                } else {
242                    Ok(EloValue::Float(*a as f64 / b))
243                }
244            }
245            (EloValue::Float(a), EloValue::Integer(b)) => {
246                if *b == 0 {
247                    Err("Division by zero".to_string())
248                } else {
249                    Ok(EloValue::Float(a / *b as f64))
250                }
251            }
252            _ => Err(format!(
253                "Cannot divide {} by {}",
254                self.type_name(),
255                other.type_name()
256            )),
257        }
258    }
259
260    /// Modulo operation
261    pub fn modulo(&self, other: &EloValue) -> Result<EloValue, String> {
262        match (self, other) {
263            (EloValue::Integer(a), EloValue::Integer(b)) => {
264                if *b == 0 {
265                    Err("Modulo by zero".to_string())
266                } else {
267                    Ok(EloValue::Integer(a % b))
268                }
269            }
270            _ => Err(format!(
271                "Modulo requires integers, got {} and {}",
272                self.type_name(),
273                other.type_name()
274            )),
275        }
276    }
277
278    /// Power operation
279    pub fn power(&self, other: &EloValue) -> Result<EloValue, String> {
280        match (self, other) {
281            (EloValue::Integer(a), EloValue::Integer(b)) => {
282                if *b < 0 {
283                    // Negative power returns float
284                    Ok(EloValue::Float((*a as f64).powf(*b as f64)))
285                } else {
286                    Ok(EloValue::Integer(a.pow(*b as u32)))
287                }
288            }
289            (EloValue::Float(a), EloValue::Float(b)) => Ok(EloValue::Float(a.powf(*b))),
290            (EloValue::Integer(a), EloValue::Float(b)) => Ok(EloValue::Float((*a as f64).powf(*b))),
291            (EloValue::Float(a), EloValue::Integer(b)) => Ok(EloValue::Float(a.powf(*b as f64))),
292            _ => Err(format!(
293                "Cannot raise {} to power of {}",
294                self.type_name(),
295                other.type_name()
296            )),
297        }
298    }
299
300    /// Equality comparison
301    pub fn equals(&self, other: &EloValue) -> bool {
302        match (self, other) {
303            (EloValue::Integer(a), EloValue::Integer(b)) => a == b,
304            (EloValue::Float(a), EloValue::Float(b)) => a == b,
305            (EloValue::Integer(a), EloValue::Float(b)) => (*a as f64) == *b,
306            (EloValue::Float(a), EloValue::Integer(b)) => *a == (*b as f64),
307            (EloValue::String(a), EloValue::String(b)) => a == b,
308            (EloValue::Boolean(a), EloValue::Boolean(b)) => a == b,
309            (EloValue::Null, EloValue::Null) => true,
310            _ => false,
311        }
312    }
313
314    /// Less than comparison
315    pub fn less_than(&self, other: &EloValue) -> Result<bool, String> {
316        match (self, other) {
317            (EloValue::Integer(a), EloValue::Integer(b)) => Ok(a < b),
318            (EloValue::Float(a), EloValue::Float(b)) => Ok(a < b),
319            (EloValue::Integer(a), EloValue::Float(b)) => Ok((*a as f64) < *b),
320            (EloValue::Float(a), EloValue::Integer(b)) => Ok(*a < (*b as f64)),
321            (EloValue::String(a), EloValue::String(b)) => Ok(a < b),
322            _ => Err(format!(
323                "Cannot compare {} and {}",
324                self.type_name(),
325                other.type_name()
326            )),
327        }
328    }
329
330    /// Logical AND
331    pub fn logical_and(&self, other: &EloValue) -> EloValue {
332        if self.is_truthy() {
333            other.clone()
334        } else {
335            self.clone()
336        }
337    }
338
339    /// Logical OR
340    pub fn logical_or(&self, other: &EloValue) -> EloValue {
341        if self.is_truthy() {
342            self.clone()
343        } else {
344            other.clone()
345        }
346    }
347
348    /// Logical NOT
349    pub fn logical_not(&self) -> EloValue {
350        EloValue::Boolean(!self.is_truthy())
351    }
352}
353
354impl fmt::Display for EloValue {
355    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356        write!(f, "{}", self.to_string_value())
357    }
358}
359
360#[cfg(test)]
361mod tests {
362    use super::*;
363
364    #[test]
365    fn test_integer_operations() {
366        let a = EloValue::Integer(10);
367        let b = EloValue::Integer(3);
368
369        assert_eq!(a.add(&b).unwrap(), EloValue::Integer(13));
370        assert_eq!(a.subtract(&b).unwrap(), EloValue::Integer(7));
371        assert_eq!(a.multiply(&b).unwrap(), EloValue::Integer(30));
372        assert_eq!(a.divide(&b).unwrap(), EloValue::Integer(3));
373        assert_eq!(a.modulo(&b).unwrap(), EloValue::Integer(1));
374    }
375
376    #[test]
377    fn test_float_operations() {
378        let a = EloValue::Float(10.5);
379        let b = EloValue::Float(2.5);
380
381        assert_eq!(a.add(&b).unwrap(), EloValue::Float(13.0));
382        assert_eq!(a.multiply(&b).unwrap(), EloValue::Float(26.25));
383    }
384
385    #[test]
386    fn test_mixed_numeric_operations() {
387        let i = EloValue::Integer(10);
388        let f = EloValue::Float(2.5);
389
390        assert_eq!(i.add(&f).unwrap(), EloValue::Float(12.5));
391        assert_eq!(i.multiply(&f).unwrap(), EloValue::Float(25.0));
392    }
393
394    #[test]
395    fn test_string_operations() {
396        let a = EloValue::String("hello".to_string());
397        let b = EloValue::String(" world".to_string());
398        let n = EloValue::Integer(3);
399
400        assert_eq!(
401            a.add(&b).unwrap(),
402            EloValue::String("hello world".to_string())
403        );
404        assert_eq!(
405            a.multiply(&n).unwrap(),
406            EloValue::String("hellohellohello".to_string())
407        );
408    }
409
410    #[test]
411    fn test_comparison() {
412        let a = EloValue::Integer(5);
413        let b = EloValue::Integer(10);
414
415        assert!(a.less_than(&b).unwrap());
416        assert!(!b.less_than(&a).unwrap());
417        assert!(a.equals(&EloValue::Integer(5)));
418    }
419
420    #[test]
421    fn test_boolean_logic() {
422        let t = EloValue::Boolean(true);
423        let f = EloValue::Boolean(false);
424
425        assert_eq!(t.logical_and(&f), EloValue::Boolean(false));
426        assert_eq!(t.logical_or(&f), EloValue::Boolean(true));
427        assert_eq!(t.logical_not(), EloValue::Boolean(false));
428    }
429
430    #[test]
431    fn test_truthiness() {
432        assert!(EloValue::Boolean(true).is_truthy());
433        assert!(!EloValue::Boolean(false).is_truthy());
434        assert!(EloValue::Integer(1).is_truthy());
435        assert!(!EloValue::Integer(0).is_truthy());
436        assert!(EloValue::String("x".to_string()).is_truthy());
437        assert!(!EloValue::String("".to_string()).is_truthy());
438        assert!(!EloValue::Null.is_truthy());
439    }
440
441    #[test]
442    fn test_type_conversion() {
443        let i = EloValue::Integer(42);
444        let f = EloValue::Float(3.15);
445        let s = EloValue::String("123".to_string());
446
447        assert_eq!(i.to_integer(), Some(42));
448        assert_eq!(f.to_float(), Some(3.15));
449        assert_eq!(s.to_integer(), Some(123));
450    }
451
452    #[test]
453    fn test_type_names() {
454        assert_eq!(EloValue::Integer(1).type_name(), "integer");
455        assert_eq!(EloValue::Float(1.0).type_name(), "float");
456        assert_eq!(EloValue::String("x".to_string()).type_name(), "string");
457        assert_eq!(EloValue::Boolean(true).type_name(), "boolean");
458        assert_eq!(EloValue::Null.type_name(), "null");
459        assert_eq!(EloValue::Array(vec![]).type_name(), "array");
460        assert_eq!(EloValue::Object(BTreeMap::new()).type_name(), "object");
461    }
462
463    #[test]
464    fn test_array_operations() {
465        let arr = EloValue::Array(vec![
466            EloValue::Integer(1),
467            EloValue::Integer(2),
468            EloValue::Integer(3),
469        ]);
470
471        assert_eq!(arr.array_len(), Some(3));
472        assert_eq!(arr.array_get(0), Some(EloValue::Integer(1)));
473        assert_eq!(arr.array_get(10), None);
474    }
475
476    #[test]
477    fn test_object_operations() {
478        let mut obj_map = BTreeMap::new();
479        obj_map.insert("x".to_string(), EloValue::Integer(1));
480        obj_map.insert("y".to_string(), EloValue::Integer(2));
481        let obj = EloValue::Object(obj_map);
482
483        assert_eq!(obj.object_get("x"), Some(EloValue::Integer(1)));
484        assert_eq!(obj.object_get("z"), None);
485    }
486
487    #[test]
488    fn test_division_by_zero() {
489        let a = EloValue::Integer(10);
490        let zero = EloValue::Integer(0);
491
492        assert!(a.divide(&zero).is_err());
493    }
494
495    #[test]
496    fn test_power_operation() {
497        let base = EloValue::Integer(2);
498        let exp = EloValue::Integer(3);
499
500        assert_eq!(base.power(&exp).unwrap(), EloValue::Integer(8));
501    }
502
503    #[test]
504    fn test_type_checks() {
505        let i = EloValue::Integer(1);
506        let s = EloValue::String("x".to_string());
507        let arr = EloValue::Array(vec![]);
508
509        assert!(i.is_numeric());
510        assert!(!s.is_numeric());
511        assert!(s.is_string());
512        assert!(arr.is_array());
513    }
514}