use crate::ast::{Expr, Stmt};
use crate::environment::Environment;
use num_bigint::BigInt;
use num_rational::Ratio;
use num_traits::Zero;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;
#[derive(Debug, Clone)]
pub enum Value {
Number(f64),
Fraction(Ratio<BigInt>),
String(String),
Boolean(bool),
Null,
Array(Vec<Value>),
Dict(HashMap<String, Value>),
Function {
name: Option<String>,
params: Vec<String>,
body: Vec<Stmt>,
env: Rc<RefCell<Environment>>,
},
Generator {
params: Vec<String>,
body: Vec<Stmt>,
env: Rc<RefCell<Environment>>,
state: GeneratorState,
},
Lazy {
expr: Expr,
env: Rc<RefCell<Environment>>,
cached: Option<Box<Value>>,
},
BuiltIn { name: String, arity: usize },
}
#[derive(Debug, Clone)]
pub enum GeneratorState {
NotStarted,
Running { position: usize },
Done,
}
impl Value {
pub fn is_truthy(&self) -> bool {
match self {
Value::Boolean(b) => *b,
Value::Null => false,
Value::Number(n) => *n != 0.0,
Value::Fraction(f) => !f.is_zero(),
Value::String(s) => !s.is_empty(),
Value::Array(arr) => !arr.is_empty(),
Value::Dict(dict) => !dict.is_empty(),
_ => true,
}
}
pub fn type_name(&self) -> &'static str {
match self {
Value::Number(_) => "Number",
Value::Fraction(_) => "Fraction",
Value::String(_) => "String",
Value::Boolean(_) => "Boolean",
Value::Null => "Null",
Value::Array(_) => "Array",
Value::Dict(_) => "Dict",
Value::Function { .. } => "Function",
Value::Generator { .. } => "Generator",
Value::Lazy { .. } => "Lazy",
Value::BuiltIn { .. } => "BuiltIn",
}
}
pub fn to_number(&self) -> Option<f64> {
match self {
Value::Number(n) => Some(*n),
Value::Fraction(f) => Some(
f.numer().to_string().parse::<f64>().ok()?
/ f.denom().to_string().parse::<f64>().ok()?,
),
Value::Boolean(true) => Some(1.0),
Value::Boolean(false) => Some(0.0),
Value::String(s) => s.parse().ok(),
_ => None,
}
}
#[allow(clippy::inherent_to_string_shadow_display)]
pub fn to_string(&self) -> String {
match self {
Value::Number(n) => {
if n.fract() == 0.0 {
format!("{:.0}", n)
} else {
format!("{}", n)
}
}
Value::Fraction(f) => {
if f.is_integer() {
format!("{}", f.numer())
} else {
format!("{}/{}", f.numer(), f.denom())
}
}
Value::String(s) => s.clone(),
Value::Boolean(b) => b.to_string(),
Value::Null => "Null".to_string(),
Value::Array(arr) => {
let elements: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
format!("[{}]", elements.join(", "))
}
Value::Dict(dict) => {
let pairs: Vec<String> = dict
.iter()
.map(|(k, v)| format!("{}: {}", k, v.to_string()))
.collect();
format!("{{{}}}", pairs.join(", "))
}
Value::Function { name, params, .. } => {
if let Some(n) = name {
format!("<Function {} ({})>", n, params.join(", "))
} else {
format!("<Function ({})>", params.join(", "))
}
}
Value::Generator { params, .. } => {
format!("<Generator ({})>", params.join(", "))
}
Value::Lazy { .. } => "<Lazy>".to_string(),
Value::BuiltIn { name, arity } => {
format!("<BuiltIn {} ({} args)>", name, arity)
}
}
}
pub fn equals(&self, other: &Value) -> bool {
match (self, other) {
(Value::Number(a), Value::Number(b)) => (a - b).abs() < f64::EPSILON,
(Value::Fraction(a), Value::Fraction(b)) => a == b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Boolean(a), Value::Boolean(b)) => a == b,
(Value::Null, Value::Null) => true,
(Value::Array(a), Value::Array(b)) => {
a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x.equals(y))
}
_ => false,
}
}
pub fn compare(&self, other: &Value) -> Option<std::cmp::Ordering> {
match (self, other) {
(Value::Number(a), Value::Number(b)) => a.partial_cmp(b),
(Value::Fraction(a), Value::Fraction(b)) => Some(a.cmp(b)),
(Value::String(a), Value::String(b)) => Some(a.cmp(b)),
(Value::Boolean(a), Value::Boolean(b)) => Some(a.cmp(b)),
_ => None,
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_string())
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
self.equals(other)
}
}