Skip to main content

zapcode_core/
value.rs

1use indexmap::IndexMap;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use std::sync::Arc;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub enum Value {
8    Undefined,
9    Null,
10    Bool(bool),
11    Int(i64),
12    Float(f64),
13    String(Arc<str>),
14    Array(Vec<Value>),
15    Object(IndexMap<Arc<str>, Value>),
16    Function(Closure),
17    /// A generator object — calling function* creates one of these.
18    /// Generators are stateful and cannot be serialized mid-yield.
19    #[serde(skip)]
20    Generator(GeneratorObject),
21    /// Internal: a bound method on a built-in object (e.g., console.log, Math.floor).
22    /// Not visible to user code — used to dispatch builtin calls.
23    #[serde(skip)]
24    BuiltinMethod {
25        object_name: Arc<str>,
26        method_name: Arc<str>,
27    },
28}
29
30/// Identifies a function in the compiled program.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
32pub struct FunctionId(pub usize);
33
34/// A closure captures the enclosing scope's variables.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct Closure {
37    pub func_id: FunctionId,
38    pub captured: Vec<(String, Value)>,
39}
40
41/// The state of a generator object.
42#[derive(Debug, Clone)]
43pub struct GeneratorObject {
44    /// Unique ID for this generator instance (used as key in VM generator registry).
45    pub id: u64,
46    /// The function this generator was created from.
47    pub func_id: FunctionId,
48    /// Captured closure variables.
49    pub captured: Vec<(String, Value)>,
50    /// Suspended execution state. None = not yet started.
51    pub suspended: Option<SuspendedFrame>,
52    /// Whether the generator has completed.
53    pub done: bool,
54}
55
56/// Saved execution state of a suspended generator.
57#[derive(Debug, Clone)]
58pub struct SuspendedFrame {
59    pub ip: usize,
60    pub locals: Vec<Value>,
61    pub stack: Vec<Value>,
62}
63
64impl Value {
65    pub fn type_name(&self) -> &'static str {
66        match self {
67            Value::Undefined => "undefined",
68            Value::Null => "null",
69            Value::Bool(_) => "boolean",
70            Value::Int(_) | Value::Float(_) => "number",
71            Value::String(_) => "string",
72            Value::Array(_) => "object",
73            Value::Object(_) => "object",
74            Value::Function(_) | Value::BuiltinMethod { .. } => "function",
75            Value::Generator(_) => "object",
76        }
77    }
78
79    pub fn is_truthy(&self) -> bool {
80        match self {
81            Value::Undefined | Value::Null => false,
82            Value::Bool(b) => *b,
83            Value::Int(n) => *n != 0,
84            Value::Float(n) => *n != 0.0 && !n.is_nan(),
85            Value::String(s) => !s.is_empty(),
86            Value::Array(_)
87            | Value::Object(_)
88            | Value::Function(_)
89            | Value::BuiltinMethod { .. }
90            | Value::Generator(_) => true,
91        }
92    }
93
94    pub fn to_number(&self) -> f64 {
95        match self {
96            Value::Undefined => f64::NAN,
97            Value::Null => 0.0,
98            Value::Bool(true) => 1.0,
99            Value::Bool(false) => 0.0,
100            Value::Int(n) => *n as f64,
101            Value::Float(n) => *n,
102            Value::String(s) => s.parse::<f64>().unwrap_or(f64::NAN),
103            _ => f64::NAN,
104        }
105    }
106
107    pub fn to_js_string(&self) -> String {
108        match self {
109            Value::Undefined => "undefined".to_string(),
110            Value::Null => "null".to_string(),
111            Value::Bool(b) => b.to_string(),
112            Value::Int(n) => n.to_string(),
113            Value::Float(n) => {
114                if n.is_infinite() {
115                    if *n > 0.0 {
116                        "Infinity".to_string()
117                    } else {
118                        "-Infinity".to_string()
119                    }
120                } else if n.is_nan() {
121                    "NaN".to_string()
122                } else {
123                    // Remove trailing ".0" for whole numbers
124                    n.to_string()
125                }
126            }
127            Value::String(s) => s.to_string(),
128            Value::Array(arr) => {
129                let items: Vec<String> = arr.iter().map(|v| v.to_js_string()).collect();
130                items.join(",")
131            }
132            Value::Object(_) => "[object Object]".to_string(),
133            Value::Function(_) | Value::BuiltinMethod { .. } => "function".to_string(),
134            Value::Generator(_) => "[object Generator]".to_string(),
135        }
136    }
137
138    /// Strict equality (===)
139    pub fn strict_eq(&self, other: &Value) -> bool {
140        match (self, other) {
141            (Value::Undefined, Value::Undefined) | (Value::Null, Value::Null) => true,
142            (Value::Bool(a), Value::Bool(b)) => a == b,
143            (Value::Int(a), Value::Int(b)) => a == b,
144            (Value::Float(a), Value::Float(b)) => a == b,
145            (Value::Int(a), Value::Float(b)) => (*a as f64) == *b,
146            (Value::Float(a), Value::Int(b)) => *a == (*b as f64),
147            (Value::String(a), Value::String(b)) => a == b,
148            // Reference equality for arrays/objects
149            _ => false,
150        }
151    }
152}
153
154impl fmt::Display for Value {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        write!(f, "{}", self.to_js_string())
157    }
158}
159
160impl PartialEq for Value {
161    fn eq(&self, other: &Self) -> bool {
162        self.strict_eq(other)
163    }
164}