1use std::collections::BTreeMap;
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq)]
15pub enum EloValue {
16 Integer(i64),
18
19 Float(f64),
21
22 String(String),
24
25 Boolean(bool),
27
28 Null,
30
31 Array(Vec<EloValue>),
33
34 Object(BTreeMap<String, EloValue>),
36}
37
38impl EloValue {
39 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 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 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 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 pub fn to_string_value(&self) -> String {
88 match self {
89 EloValue::Integer(n) => n.to_string(),
90 EloValue::Float(f) => {
91 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 pub fn to_boolean(&self) -> bool {
117 self.is_truthy()
118 }
119
120 pub fn is_numeric(&self) -> bool {
122 matches!(self, EloValue::Integer(_) | EloValue::Float(_))
123 }
124
125 pub fn is_string(&self) -> bool {
127 matches!(self, EloValue::String(_))
128 }
129
130 pub fn is_array(&self) -> bool {
132 matches!(self, EloValue::Array(_))
133 }
134
135 pub fn is_object(&self) -> bool {
137 matches!(self, EloValue::Object(_))
138 }
139
140 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 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 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 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 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 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 (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 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 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 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 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 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 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 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 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 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}