1use 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#[derive(Debug, Clone)]
16pub enum Value {
17 Number(f64),
19
20 Fraction(Ratio<BigInt>),
22
23 String(String),
25
26 Boolean(bool),
28
29 Null,
31
32 Array(Vec<Value>),
34
35 Dict(HashMap<String, Value>),
37
38 Function {
40 params: Vec<String>,
41 body: Vec<Stmt>,
42 env: Rc<RefCell<Environment>>,
43 },
44
45 Generator {
47 params: Vec<String>,
48 body: Vec<Stmt>,
49 env: Rc<RefCell<Environment>>,
50 state: GeneratorState,
51 },
52
53 Lazy {
55 expr: Expr,
56 env: Rc<RefCell<Environment>>,
57 cached: Option<Box<Value>>,
58 },
59
60 BuiltIn { name: String, arity: usize },
62}
63
64#[derive(Debug, Clone)]
66pub enum GeneratorState {
67 NotStarted,
69
70 Running { position: usize },
72
73 Done,
75}
76
77impl Value {
78 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 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 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 #[allow(clippy::inherent_to_string_shadow_display)]
126 pub fn to_string(&self) -> String {
127 match self {
128 Value::Number(n) => {
129 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 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 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}