use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Value {
Undefined,
Null,
Bool(bool),
Int(i64),
Float(f64),
String(Arc<str>),
Array(Vec<Value>),
Object(IndexMap<Arc<str>, Value>),
Function(Closure),
#[serde(skip)]
Generator(GeneratorObject),
#[serde(skip)]
BuiltinMethod {
object_name: Arc<str>,
method_name: Arc<str>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct FunctionId(pub usize);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Closure {
pub func_id: FunctionId,
pub captured: Vec<(String, Value)>,
}
#[derive(Debug, Clone)]
pub struct GeneratorObject {
pub id: u64,
pub func_id: FunctionId,
pub captured: Vec<(String, Value)>,
pub suspended: Option<SuspendedFrame>,
pub done: bool,
}
#[derive(Debug, Clone)]
pub struct SuspendedFrame {
pub ip: usize,
pub locals: Vec<Value>,
pub stack: Vec<Value>,
}
impl Value {
pub fn type_name(&self) -> &'static str {
match self {
Value::Undefined => "undefined",
Value::Null => "null",
Value::Bool(_) => "boolean",
Value::Int(_) | Value::Float(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "object",
Value::Object(_) => "object",
Value::Function(_) | Value::BuiltinMethod { .. } => "function",
Value::Generator(_) => "object",
}
}
pub fn is_truthy(&self) -> bool {
match self {
Value::Undefined | Value::Null => false,
Value::Bool(b) => *b,
Value::Int(n) => *n != 0,
Value::Float(n) => *n != 0.0 && !n.is_nan(),
Value::String(s) => !s.is_empty(),
Value::Array(_)
| Value::Object(_)
| Value::Function(_)
| Value::BuiltinMethod { .. }
| Value::Generator(_) => true,
}
}
pub fn to_number(&self) -> f64 {
match self {
Value::Undefined => f64::NAN,
Value::Null => 0.0,
Value::Bool(true) => 1.0,
Value::Bool(false) => 0.0,
Value::Int(n) => *n as f64,
Value::Float(n) => *n,
Value::String(s) => s.parse::<f64>().unwrap_or(f64::NAN),
_ => f64::NAN,
}
}
pub fn to_js_string(&self) -> String {
match self {
Value::Undefined => "undefined".to_string(),
Value::Null => "null".to_string(),
Value::Bool(b) => b.to_string(),
Value::Int(n) => n.to_string(),
Value::Float(n) => {
if n.is_infinite() {
if *n > 0.0 {
"Infinity".to_string()
} else {
"-Infinity".to_string()
}
} else if n.is_nan() {
"NaN".to_string()
} else {
n.to_string()
}
}
Value::String(s) => s.to_string(),
Value::Array(arr) => {
let items: Vec<String> = arr.iter().map(|v| v.to_js_string()).collect();
items.join(",")
}
Value::Object(_) => "[object Object]".to_string(),
Value::Function(_) | Value::BuiltinMethod { .. } => "function".to_string(),
Value::Generator(_) => "[object Generator]".to_string(),
}
}
pub fn strict_eq(&self, other: &Value) -> bool {
match (self, other) {
(Value::Undefined, Value::Undefined) | (Value::Null, Value::Null) => true,
(Value::Bool(a), Value::Bool(b)) => a == b,
(Value::Int(a), Value::Int(b)) => a == b,
(Value::Float(a), Value::Float(b)) => a == b,
(Value::Int(a), Value::Float(b)) => (*a as f64) == *b,
(Value::Float(a), Value::Int(b)) => *a == (*b as f64),
(Value::String(a), Value::String(b)) => a == b,
_ => false,
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_js_string())
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
self.strict_eq(other)
}
}