Skip to main content

quickjs_oxide/engine/api/core/
value.rs

1//! Values: primitives as Rust data copied out of the
2//! core, objects and symbols as handles in the core's handle table.
3
4use std::fmt;
5use std::rc::{Rc, Weak};
6
7use oxide_core::Handle;
8
9use super::RuntimeError;
10use super::runtime::Shared;
11use crate::engine::value::JsString;
12use crate::engine::value::bigint::JsBigInt;
13
14/// A JavaScript value held by the embedder.
15#[derive(Clone)]
16pub enum Value {
17    Undefined,
18    Null,
19    Bool(bool),
20    Int(i32),
21    Float(f64),
22    BigInt(JsBigInt),
23    String(JsString),
24    Symbol(SymbolRef),
25    Object(ObjectRef),
26}
27
28impl Value {
29    /// The same value; never fails (kept for the current API's callers).
30    pub fn try_clone(&self) -> Result<Self, RuntimeError> {
31        Ok(self.clone())
32    }
33
34    /// A number: an `Int` when `value` is an integer in `i32` range other
35    /// than `-0`, else a `Float`.
36    pub fn number(value: f64) -> Self {
37        if value.fract() == 0.0
38            && value >= f64::from(i32::MIN)
39            && value <= f64::from(i32::MAX)
40            && !(value == 0.0 && value.is_sign_negative())
41        {
42            Value::Int(value as i32)
43        } else {
44            Value::Float(value)
45        }
46    }
47
48    /// The numeric value of a number.
49    pub const fn as_number(&self) -> Option<f64> {
50        match self {
51            Value::Int(value) => Some(*value as f64),
52            Value::Float(value) => Some(*value),
53            _ => None,
54        }
55    }
56
57    pub fn as_object(&self) -> Option<&ObjectRef> {
58        match self {
59            Value::Object(object) => Some(object),
60            _ => None,
61        }
62    }
63
64    /// `ToBoolean`, which never runs JavaScript.
65    pub fn to_boolean(&self) -> bool {
66        match self {
67            Value::Undefined | Value::Null => false,
68            Value::Bool(value) => *value,
69            Value::Int(value) => *value != 0,
70            Value::Float(value) => *value != 0.0 && !value.is_nan(),
71            Value::BigInt(value) => !value.is_zero(),
72            Value::String(value) => !value.is_empty(),
73            // `document.all`-like objects exist only under the Test262 host,
74            // whose `IsHTMLDDA` the runtime's own `to_boolean` reports.
75            Value::Symbol(_) | Value::Object(_) => true,
76        }
77    }
78
79    /// `typeof`, for primitives and ordinary objects (a callable object is
80    /// `"object"` here; `Runtime::type_of` asks the engine).
81    pub const fn type_of(&self) -> &'static str {
82        match self {
83            Value::Undefined => "undefined",
84            Value::Null | Value::Object(_) => "object",
85            Value::Bool(_) => "boolean",
86            Value::Int(_) | Value::Float(_) => "number",
87            Value::BigInt(_) => "bigint",
88            Value::String(_) => "string",
89            Value::Symbol(_) => "symbol",
90        }
91    }
92
93    /// `IsStrictlyEqual`.
94    pub fn strict_equal(&self, other: &Self) -> bool {
95        match (self.as_number(), other.as_number()) {
96            (Some(x), Some(y)) => x == y,
97            _ => self.same_value(other),
98        }
99    }
100
101    /// `SameValue`.
102    pub fn same_value(&self, other: &Self) -> bool {
103        match (self, other) {
104            (Value::Undefined, Value::Undefined) | (Value::Null, Value::Null) => true,
105            (Value::Bool(x), Value::Bool(y)) => x == y,
106            (Value::BigInt(x), Value::BigInt(y)) => x == y,
107            (Value::String(x), Value::String(y)) => x == y,
108            (Value::Symbol(x), Value::Symbol(y)) => x == y,
109            (Value::Object(x), Value::Object(y)) => x == y,
110            _ => match (self.as_number(), other.as_number()) {
111                (Some(x), Some(y)) => {
112                    (x.is_nan() && y.is_nan())
113                        || (x == y && x.is_sign_negative() == y.is_sign_negative())
114                }
115                _ => false,
116            },
117        }
118    }
119
120    /// `SameValueZero`.
121    pub fn same_value_zero(&self, other: &Self) -> bool {
122        match (self.as_number(), other.as_number()) {
123            (Some(x), Some(y)) => (x.is_nan() && y.is_nan()) || x == y,
124            _ => self.same_value(other),
125        }
126    }
127}
128
129/// Structural equality: primitives by representation (`Int(1) != Float(1.0)`,
130/// as the current API's derived equality), references by identity.
131impl PartialEq for Value {
132    fn eq(&self, other: &Self) -> bool {
133        match (self, other) {
134            (Value::Undefined, Value::Undefined) | (Value::Null, Value::Null) => true,
135            (Value::Bool(x), Value::Bool(y)) => x == y,
136            (Value::Int(x), Value::Int(y)) => x == y,
137            (Value::Float(x), Value::Float(y)) => x == y,
138            (Value::BigInt(x), Value::BigInt(y)) => x == y,
139            (Value::String(x), Value::String(y)) => x == y,
140            (Value::Symbol(x), Value::Symbol(y)) => x == y,
141            (Value::Object(x), Value::Object(y)) => x == y,
142            _ => false,
143        }
144    }
145}
146
147impl fmt::Debug for Value {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        match self {
150            Value::Undefined => f.write_str("Undefined"),
151            Value::Null => f.write_str("Null"),
152            Value::Bool(value) => write!(f, "Bool({value})"),
153            Value::Int(value) => write!(f, "Int({value})"),
154            Value::Float(value) => write!(f, "Float({value:?})"),
155            Value::BigInt(value) => write!(f, "BigInt({value})"),
156            Value::String(value) => write!(f, "String({:?})", value.to_utf8_lossy()),
157            Value::Symbol(value) => write!(f, "Symbol({value:?})"),
158            Value::Object(value) => write!(f, "Object({value:?})"),
159        }
160    }
161}
162
163impl From<bool> for Value {
164    fn from(value: bool) -> Self {
165        Value::Bool(value)
166    }
167}
168
169impl From<i32> for Value {
170    fn from(value: i32) -> Self {
171        Value::Int(value)
172    }
173}
174
175impl From<f64> for Value {
176    fn from(value: f64) -> Self {
177        Value::number(value)
178    }
179}
180
181impl From<JsString> for Value {
182    fn from(value: JsString) -> Self {
183        Value::String(value)
184    }
185}
186
187impl From<ObjectRef> for Value {
188    fn from(value: ObjectRef) -> Self {
189        Value::Object(value)
190    }
191}
192
193impl From<CallableRef> for Value {
194    fn from(value: CallableRef) -> Self {
195        Value::Object(value.0)
196    }
197}
198
199/// One handle in the core's handle table, released when the last reference
200/// drops (deferred when the runtime is borrowed). The runtime is held weakly:
201/// a handle a host function captures lives inside the runtime it names, and
202/// a handle that outlives its runtime releases nothing.
203pub(crate) struct Slot {
204    pub(crate) shared: Weak<Shared>,
205    pub(crate) handle: Handle,
206    /// The held value; it never changes, and objects do not move.
207    pub(crate) value: oxide_core::Value,
208}
209
210impl Drop for Slot {
211    fn drop(&mut self) {
212        if let Some(shared) = self.shared.upgrade() {
213            shared.release(self.handle);
214        }
215    }
216}
217
218impl Slot {
219    /// Whether the slot is a handle of `shared`.
220    pub(crate) fn of(&self, shared: &Rc<Shared>) -> bool {
221        std::ptr::eq(self.shared.as_ptr(), Rc::as_ptr(shared))
222    }
223}
224
225/// A reference to an object of a runtime.
226#[derive(Clone)]
227pub struct ObjectRef {
228    pub(crate) slot: Rc<Slot>,
229}
230
231/// A reference to a symbol of a runtime.
232#[derive(Clone)]
233pub struct SymbolRef {
234    pub(crate) slot: Rc<Slot>,
235}
236
237/// An object reference known to be callable.
238#[derive(Clone, PartialEq, Debug)]
239pub struct CallableRef(pub(crate) ObjectRef);
240
241macro_rules! reference {
242    ($name:ident) => {
243        impl $name {
244            pub fn try_clone(&self) -> Result<Self, RuntimeError> {
245                Ok(self.clone())
246            }
247
248            /// Whether this reference belongs to `runtime`.
249            pub fn belongs_to(&self, runtime: &super::Runtime) -> bool {
250                self.slot.of(&runtime.shared)
251            }
252
253            pub(crate) fn core_value(&self) -> oxide_core::Value {
254                self.slot.value
255            }
256        }
257
258        impl PartialEq for $name {
259            fn eq(&self, other: &Self) -> bool {
260                Weak::ptr_eq(&self.slot.shared, &other.slot.shared)
261                    && self.slot.value == other.slot.value
262            }
263        }
264
265        impl Eq for $name {}
266
267        impl fmt::Debug for $name {
268            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269                write!(f, "{}({:?})", stringify!($name), self.slot.value)
270            }
271        }
272    };
273}
274
275reference!(ObjectRef);
276reference!(SymbolRef);
277
278impl CallableRef {
279    pub fn try_clone(&self) -> Result<Self, RuntimeError> {
280        Ok(self.clone())
281    }
282
283    pub fn as_object(&self) -> &ObjectRef {
284        &self.0
285    }
286
287    pub fn into_object(self) -> ObjectRef {
288        self.0
289    }
290
291    pub fn belongs_to(&self, runtime: &super::Runtime) -> bool {
292        self.0.belongs_to(runtime)
293    }
294}
295
296impl std::ops::Deref for CallableRef {
297    type Target = ObjectRef;
298
299    fn deref(&self) -> &ObjectRef {
300        &self.0
301    }
302}