mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1238 lines
41 KiB
1238 lines
41 KiB
//! This module implements the JavaScript Value. |
|
//! |
|
//! Javascript values, utility methods and conversion between Javascript values and Rust values. |
|
|
|
#[cfg(test)] |
|
mod tests; |
|
|
|
use crate::builtins::{ |
|
function::{Function, NativeFunction, NativeFunctionData}, |
|
object::{ |
|
internal_methods_trait::ObjectInternalMethods, InternalState, InternalStateCell, Object, |
|
ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE, |
|
}, |
|
property::Property, |
|
}; |
|
use gc::{Gc, GcCell}; |
|
use gc_derive::{Finalize, Trace}; |
|
use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue}; |
|
use std::{ |
|
any::Any, |
|
collections::HashSet, |
|
f64::NAN, |
|
fmt::{self, Display}, |
|
ops::{Add, BitAnd, BitOr, BitXor, Deref, DerefMut, Div, Mul, Not, Rem, Shl, Shr, Sub}, |
|
str::FromStr, |
|
}; |
|
|
|
/// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) |
|
#[must_use] |
|
pub type ResultValue = Result<Value, Value>; |
|
|
|
/// A Garbage-collected Javascript value as represented in the interpreter. |
|
pub type Value = Gc<ValueData>; |
|
|
|
pub fn undefined() -> Value { |
|
Gc::new(ValueData::Undefined) |
|
} |
|
|
|
/// A Javascript value |
|
#[derive(Trace, Finalize, Debug, Clone)] |
|
pub enum ValueData { |
|
/// `null` - A null value, for when a value doesn't exist |
|
Null, |
|
/// `undefined` - An undefined value, for when a field or index doesn't exist |
|
Undefined, |
|
/// `boolean` - A `true` / `false` value, for if a certain criteria is met |
|
Boolean(bool), |
|
/// `String` - A UTF-8 string, such as `"Hello, world"` |
|
String(String), |
|
/// `Number` - A 64-bit floating point number, such as `3.1415` |
|
Number(f64), |
|
/// `Number` - A 32-bit integer, such as `42` |
|
Integer(i32), |
|
/// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values |
|
Object(GcCell<Object>), |
|
/// `Function` - A runnable block of code, such as `Math.sqrt`, which can take some variables and return a useful value or act upon an object |
|
Function(Box<GcCell<Function>>), |
|
/// `Symbol` - A Symbol Type - Internally Symbols are similar to objects, except there are no properties, only internal slots |
|
Symbol(GcCell<Object>), |
|
} |
|
|
|
impl ValueData { |
|
/// Returns a new empty object |
|
pub fn new_obj(global: Option<&Value>) -> Value { |
|
if let Some(glob) = global { |
|
let obj_proto = glob.get_field_slice("Object").get_field_slice(PROTOTYPE); |
|
|
|
let obj = Object::create(obj_proto); |
|
Gc::new(Self::Object(GcCell::new(obj))) |
|
} else { |
|
let obj = Object::default(); |
|
Gc::new(Self::Object(GcCell::new(obj))) |
|
} |
|
} |
|
|
|
/// Similar to `new_obj`, but you can pass a prototype to create from, |
|
/// plus a kind |
|
pub fn new_obj_from_prototype(proto: Value, kind: ObjectKind) -> Value { |
|
let mut obj = Object::default(); |
|
obj.kind = kind; |
|
|
|
obj.internal_slots |
|
.insert(INSTANCE_PROTOTYPE.to_string(), proto); |
|
|
|
Gc::new(Self::Object(GcCell::new(obj))) |
|
} |
|
|
|
/// This will tell us if we can exten an object or not, not properly implemented yet |
|
/// |
|
/// For now always returns true. |
|
/// |
|
/// For scalar types it should be false, for objects check the private field for extensibilaty. |
|
/// By default true. |
|
/// |
|
/// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal would turn extensible to false/> |
|
/// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze would also turn extensible to false/> |
|
pub fn is_extensible(&self) -> bool { |
|
true |
|
} |
|
|
|
/// Returns true if the value is an object |
|
pub fn is_object(&self) -> bool { |
|
match *self { |
|
Self::Object(_) => true, |
|
_ => false, |
|
} |
|
} |
|
|
|
/// Returns true if the value is a symbol |
|
pub fn is_symbol(&self) -> bool { |
|
match *self { |
|
Self::Symbol(_) => true, |
|
_ => false, |
|
} |
|
} |
|
|
|
/// Returns true if the value is a function |
|
pub fn is_function(&self) -> bool { |
|
match *self { |
|
Self::Function(_) => true, |
|
Self::Object(ref o) => o.deref().borrow().get_internal_slot("call").is_function(), |
|
_ => false, |
|
} |
|
} |
|
|
|
/// Returns true if the value is undefined |
|
pub fn is_undefined(&self) -> bool { |
|
match *self { |
|
Self::Undefined => true, |
|
_ => false, |
|
} |
|
} |
|
|
|
/// Returns true if the value is null |
|
pub fn is_null(&self) -> bool { |
|
match *self { |
|
Self::Null => true, |
|
_ => false, |
|
} |
|
} |
|
|
|
/// Returns true if the value is null or undefined |
|
pub fn is_null_or_undefined(&self) -> bool { |
|
match *self { |
|
Self::Null | Self::Undefined => true, |
|
_ => false, |
|
} |
|
} |
|
|
|
/// Returns true if the value is a 64-bit floating-point number |
|
pub fn is_double(&self) -> bool { |
|
match *self { |
|
Self::Number(_) => true, |
|
_ => false, |
|
} |
|
} |
|
|
|
/// Returns true if the value is a number |
|
pub fn is_num(&self) -> bool { |
|
self.is_double() |
|
} |
|
|
|
/// Returns true if the value is a string |
|
pub fn is_string(&self) -> bool { |
|
match *self { |
|
Self::String(_) => true, |
|
_ => false, |
|
} |
|
} |
|
|
|
/// Returns true if the value is a boolean |
|
pub fn is_boolean(&self) -> bool { |
|
match *self { |
|
Self::Boolean(_) => true, |
|
_ => false, |
|
} |
|
} |
|
|
|
/// Returns true if the value is true |
|
/// |
|
/// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) |
|
pub fn is_true(&self) -> bool { |
|
match *self { |
|
Self::Object(_) => true, |
|
Self::String(ref s) if !s.is_empty() => true, |
|
Self::Number(n) if n != 0.0 && !n.is_nan() => true, |
|
Self::Integer(n) if n != 0 => true, |
|
Self::Boolean(v) => v, |
|
_ => false, |
|
} |
|
} |
|
|
|
/// Converts the value into a 64-bit floating point number |
|
pub fn to_num(&self) -> f64 { |
|
match *self { |
|
Self::Object(_) | Self::Symbol(_) | Self::Undefined | Self::Function(_) => NAN, |
|
Self::String(ref str) => match FromStr::from_str(str) { |
|
Ok(num) => num, |
|
Err(_) => NAN, |
|
}, |
|
Self::Number(num) => num, |
|
Self::Boolean(true) => 1.0, |
|
Self::Boolean(false) | Self::Null => 0.0, |
|
Self::Integer(num) => f64::from(num), |
|
} |
|
} |
|
|
|
/// Converts the value into a 32-bit integer |
|
pub fn to_int(&self) -> i32 { |
|
match *self { |
|
Self::Object(_) |
|
| Self::Undefined |
|
| Self::Symbol(_) |
|
| Self::Null |
|
| Self::Boolean(false) |
|
| Self::Function(_) => 0, |
|
Self::String(ref str) => match FromStr::from_str(str) { |
|
Ok(num) => num, |
|
Err(_) => 0, |
|
}, |
|
Self::Number(num) => num as i32, |
|
Self::Boolean(true) => 1, |
|
Self::Integer(num) => num, |
|
} |
|
} |
|
|
|
/// remove_prop removes a property from a Value object. |
|
/// |
|
/// It will return a boolean based on if the value was removed, if there was no value to remove false is returned |
|
pub fn remove_prop(&self, field: &str) { |
|
match *self { |
|
Self::Object(ref obj) => obj.borrow_mut().deref_mut().properties.remove(field), |
|
// Accesing .object on borrow() seems to automatically dereference it, so we don't need the * |
|
Self::Function(ref func) => match func.borrow_mut().deref_mut() { |
|
Function::NativeFunc(ref mut func) => func.object.properties.remove(field), |
|
Function::RegularFunc(ref mut func) => func.object.properties.remove(field), |
|
}, |
|
_ => None, |
|
}; |
|
} |
|
|
|
/// Resolve the property in the object. |
|
/// |
|
/// A copy of the Property is returned. |
|
pub fn get_prop(&self, field: &str) -> Option<Property> { |
|
// Spidermonkey has its own GetLengthProperty: https://searchfox.org/mozilla-central/source/js/src/vm/Interpreter-inl.h#154 |
|
// This is only for primitive strings, String() objects have their lengths calculated in string.rs |
|
if self.is_string() && field == "length" { |
|
if let Self::String(ref s) = *self { |
|
return Some(Property::default().value(to_value(s.len() as i32))); |
|
} |
|
} |
|
|
|
let obj: Object = match *self { |
|
Self::Object(ref obj) => { |
|
let hash = obj.clone(); |
|
// TODO: This will break, we should return a GcCellRefMut instead |
|
// into_inner will consume the wrapped value and remove it from the hashmap |
|
hash.into_inner() |
|
} |
|
// Accesing .object on borrow() seems to automatically dereference it, so we don't need the * |
|
Self::Function(ref func) => match func.clone().into_inner() { |
|
Function::NativeFunc(ref func) => func.object.clone(), |
|
Function::RegularFunc(ref func) => func.object.clone(), |
|
}, |
|
Self::Symbol(ref obj) => { |
|
let hash = obj.clone(); |
|
hash.into_inner() |
|
} |
|
_ => return None, |
|
}; |
|
|
|
match obj.properties.get(field) { |
|
Some(val) => Some(val.clone()), |
|
None => match obj.internal_slots.get(&INSTANCE_PROTOTYPE.to_string()) { |
|
Some(value) => value.get_prop(field), |
|
None => None, |
|
}, |
|
} |
|
} |
|
|
|
/// update_prop will overwrite individual [Property] fields, unlike |
|
/// Set_prop, which will overwrite prop with a new Property |
|
/// Mostly used internally for now |
|
pub fn update_prop( |
|
&self, |
|
field: &str, |
|
value: Option<Value>, |
|
enumerable: Option<bool>, |
|
writable: Option<bool>, |
|
configurable: Option<bool>, |
|
) { |
|
let obj: Option<Object> = match self { |
|
Self::Object(ref obj) => Some(obj.borrow_mut().deref_mut().clone()), |
|
// Accesing .object on borrow() seems to automatically dereference it, so we don't need the * |
|
Self::Function(ref func) => match func.borrow_mut().deref_mut() { |
|
Function::NativeFunc(ref mut func) => Some(func.object.clone()), |
|
Function::RegularFunc(ref mut func) => Some(func.object.clone()), |
|
}, |
|
_ => None, |
|
}; |
|
|
|
if let Some(mut obj_data) = obj { |
|
// Use value, or walk up the prototype chain |
|
if let Some(ref mut prop) = obj_data.properties.get_mut(field) { |
|
prop.value = value; |
|
prop.enumerable = enumerable; |
|
prop.writable = writable; |
|
prop.configurable = configurable; |
|
} |
|
} |
|
} |
|
|
|
/// Resolve the property in the object. |
|
/// |
|
/// Returns a copy of the Property. |
|
pub fn get_internal_slot(&self, field: &str) -> Value { |
|
let obj: Object = match *self { |
|
Self::Object(ref obj) => { |
|
let hash = obj.clone(); |
|
hash.into_inner() |
|
} |
|
Self::Symbol(ref obj) => { |
|
let hash = obj.clone(); |
|
hash.into_inner() |
|
} |
|
_ => return Gc::new(Self::Undefined), |
|
}; |
|
|
|
match obj.internal_slots.get(field) { |
|
Some(val) => val.clone(), |
|
None => Gc::new(Self::Undefined), |
|
} |
|
} |
|
|
|
/// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist |
|
/// get_field recieves a Property from get_prop(). It should then return the [[Get]] result value if that's set, otherwise fall back to [[Value]] |
|
/// TODO: this function should use the get Value if its set |
|
pub fn get_field(&self, field: Value) -> Value { |
|
match *field { |
|
// Our field will either be a String or a Symbol |
|
Self::String(ref s) => { |
|
match self.get_prop(s) { |
|
Some(prop) => { |
|
// If the Property has [[Get]] set to a function, we should run that and return the Value |
|
let prop_getter = match prop.get { |
|
Some(_) => None, |
|
None => None, |
|
}; |
|
|
|
// If the getter is populated, use that. If not use [[Value]] instead |
|
if let Some(val) = prop_getter { |
|
val |
|
} else { |
|
let val = prop |
|
.value |
|
.as_ref() |
|
.expect("Could not get property as reference"); |
|
val.clone() |
|
} |
|
} |
|
None => Gc::new(Self::Undefined), |
|
} |
|
} |
|
Self::Symbol(_) => unimplemented!(), |
|
_ => Gc::new(Self::Undefined), |
|
} |
|
} |
|
|
|
/// Check whether an object has an internal state set. |
|
pub fn has_internal_state(&self) -> bool { |
|
if let Self::Object(ref obj) = *self { |
|
obj.borrow().state.is_some() |
|
} else { |
|
false |
|
} |
|
} |
|
|
|
/// Get the internal state of an object. |
|
pub fn get_internal_state(&self) -> Option<InternalStateCell> { |
|
if let Self::Object(ref obj) = *self { |
|
obj.borrow() |
|
.state |
|
.as_ref() |
|
.map(|state| state.deref().clone()) |
|
} else { |
|
None |
|
} |
|
} |
|
|
|
/// Run a function with a reference to the internal state. |
|
/// |
|
/// # Panics |
|
/// |
|
/// This will panic if this value doesn't have an internal state or if the internal state doesn't |
|
/// have the concrete type `S`. |
|
pub fn with_internal_state_ref<S: Any + InternalState, R, F: FnOnce(&S) -> R>( |
|
&self, |
|
f: F, |
|
) -> R { |
|
if let Self::Object(ref obj) = *self { |
|
let o = obj.borrow(); |
|
let state = o |
|
.state |
|
.as_ref() |
|
.expect("no state") |
|
.downcast_ref() |
|
.expect("wrong state type"); |
|
f(state) |
|
} else { |
|
panic!("not an object"); |
|
} |
|
} |
|
|
|
/// Run a function with a mutable reference to the internal state. |
|
/// |
|
/// # Panics |
|
/// |
|
/// This will panic if this value doesn't have an internal state or if the internal state doesn't |
|
/// have the concrete type `S`. |
|
pub fn with_internal_state_mut<S: Any + InternalState, R, F: FnOnce(&mut S) -> R>( |
|
&self, |
|
f: F, |
|
) -> R { |
|
if let Self::Object(ref obj) = *self { |
|
let mut o = obj.borrow_mut(); |
|
let state = o |
|
.state |
|
.as_mut() |
|
.expect("no state") |
|
.downcast_mut() |
|
.expect("wrong state type"); |
|
f(state) |
|
} else { |
|
panic!("not an object"); |
|
} |
|
} |
|
|
|
/// Check to see if the Value has the field, mainly used by environment records |
|
pub fn has_field(&self, field: &str) -> bool { |
|
self.get_prop(field).is_some() |
|
} |
|
|
|
/// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist |
|
pub fn get_field_slice(&self, field: &str) -> Value { |
|
// get_field used to accept strings, but now Symbols accept it needs to accept a value |
|
// So this function will now need to Box strings back into values (at least for now) |
|
let f = Gc::new(Self::String(field.to_string())); |
|
self.get_field(f) |
|
} |
|
|
|
/// Set the field in the value |
|
/// Field could be a Symbol, so we need to accept a Value (not a string) |
|
pub fn set_field(&self, field: Value, val: Value) -> Value { |
|
match *self { |
|
Self::Object(ref obj) => { |
|
if obj.borrow().kind == ObjectKind::Array { |
|
if let Ok(num) = field.to_string().parse::<usize>() { |
|
if num > 0 { |
|
let len: i32 = from_value(self.get_field_slice("length")) |
|
.expect("Could not convert argument to i32"); |
|
if len < (num + 1) as i32 { |
|
self.set_field_slice("length", to_value(num + 1)); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Symbols get saved into a different bucket to general properties |
|
if field.is_symbol() { |
|
obj.borrow_mut().set(field, val.clone()); |
|
} else { |
|
obj.borrow_mut() |
|
.set(to_value(field.to_string()), val.clone()); |
|
} |
|
} |
|
Self::Function(ref func) => { |
|
match *func.borrow_mut().deref_mut() { |
|
Function::NativeFunc(ref mut f) => f |
|
.object |
|
.properties |
|
.insert(field.to_string(), Property::default().value(val.clone())), |
|
Function::RegularFunc(ref mut f) => f |
|
.object |
|
.properties |
|
.insert(field.to_string(), Property::default().value(val.clone())), |
|
}; |
|
} |
|
_ => (), |
|
} |
|
val |
|
} |
|
|
|
/// Set the field in the value |
|
pub fn set_field_slice<'a>(&self, field: &'a str, val: Value) -> Value { |
|
// set_field used to accept strings, but now Symbols accept it needs to accept a value |
|
// So this function will now need to Box strings back into values (at least for now) |
|
let f = Gc::new(Self::String(field.to_string())); |
|
self.set_field(f, val) |
|
} |
|
|
|
/// Set the private field in the value |
|
pub fn set_internal_slot(&self, field: &str, val: Value) -> Value { |
|
if let Self::Object(ref obj) = *self { |
|
obj.borrow_mut() |
|
.internal_slots |
|
.insert(field.to_string(), val.clone()); |
|
} |
|
val |
|
} |
|
|
|
/// Set the kind of an object |
|
pub fn set_kind(&self, kind: ObjectKind) -> ObjectKind { |
|
if let Self::Object(ref obj) = *self { |
|
obj.borrow_mut().kind = kind.clone(); |
|
} |
|
kind |
|
} |
|
|
|
/// Set the property in the value |
|
pub fn set_prop(&self, field: String, prop: Property) -> Property { |
|
match *self { |
|
Self::Object(ref obj) => { |
|
obj.borrow_mut().properties.insert(field, prop.clone()); |
|
} |
|
Self::Function(ref func) => { |
|
match *func.borrow_mut().deref_mut() { |
|
Function::NativeFunc(ref mut f) => { |
|
f.object.properties.insert(field, prop.clone()) |
|
} |
|
Function::RegularFunc(ref mut f) => { |
|
f.object.properties.insert(field, prop.clone()) |
|
} |
|
}; |
|
} |
|
_ => (), |
|
} |
|
prop |
|
} |
|
|
|
/// Set the property in the value |
|
pub fn set_prop_slice<'t>(&self, field: &'t str, prop: Property) -> Property { |
|
self.set_prop(field.to_string(), prop) |
|
} |
|
|
|
/// Set internal state of an Object. Discards the previous state if it was set. |
|
pub fn set_internal_state<T: Any + InternalState>(&self, state: T) { |
|
if let Self::Object(ref obj) = *self { |
|
obj.borrow_mut() |
|
.state |
|
.replace(Box::new(InternalStateCell::new(state))); |
|
} |
|
} |
|
|
|
/// Convert from a JSON value to a JS value |
|
pub fn from_json(json: JSONValue) -> Self { |
|
match json { |
|
JSONValue::Number(v) => { |
|
Self::Number(v.as_f64().expect("Could not convert value to f64")) |
|
} |
|
JSONValue::String(v) => Self::String(v), |
|
JSONValue::Bool(v) => Self::Boolean(v), |
|
JSONValue::Array(vs) => { |
|
let mut new_obj = Object::default(); |
|
for (idx, json) in vs.iter().enumerate() { |
|
new_obj.properties.insert( |
|
idx.to_string(), |
|
Property::default().value(to_value(json.clone())), |
|
); |
|
} |
|
new_obj.properties.insert( |
|
"length".to_string(), |
|
Property::default().value(to_value(vs.len() as i32)), |
|
); |
|
Self::Object(GcCell::new(new_obj)) |
|
} |
|
JSONValue::Object(obj) => { |
|
let mut new_obj = Object::default(); |
|
for (key, json) in obj.iter() { |
|
new_obj.properties.insert( |
|
key.clone(), |
|
Property::default().value(to_value(json.clone())), |
|
); |
|
} |
|
|
|
Self::Object(GcCell::new(new_obj)) |
|
} |
|
JSONValue::Null => Self::Null, |
|
} |
|
} |
|
|
|
/// Conversts the `Value` to `JSON`. |
|
pub fn to_json(&self) -> JSONValue { |
|
match *self { |
|
ValueData::Null |
|
| ValueData::Symbol(_) |
|
| ValueData::Undefined |
|
| ValueData::Function(_) => JSONValue::Null, |
|
ValueData::Boolean(b) => JSONValue::Bool(b), |
|
ValueData::Object(ref obj) => { |
|
let new_obj = obj |
|
.borrow() |
|
.properties |
|
.iter() |
|
.map(|(k, _)| (k.clone(), self.get_field_slice(k).to_json())) |
|
.collect::<Map<String, JSONValue>>(); |
|
JSONValue::Object(new_obj) |
|
} |
|
Self::String(ref str) => JSONValue::String(str.clone()), |
|
Self::Number(num) => JSONValue::Number( |
|
JSONNumber::from_f64(num).expect("Could not convert to JSONNumber"), |
|
), |
|
Self::Integer(val) => JSONValue::Number(JSONNumber::from(val)), |
|
} |
|
} |
|
|
|
/// Get the type of the value |
|
/// |
|
/// https://tc39.es/ecma262/#sec-typeof-operator |
|
pub fn get_type(&self) -> &'static str { |
|
match *self { |
|
Self::Number(_) | Self::Integer(_) => "number", |
|
Self::String(_) => "string", |
|
Self::Boolean(_) => "boolean", |
|
Self::Symbol(_) => "symbol", |
|
Self::Null => "null", |
|
Self::Undefined => "undefined", |
|
Self::Function(_) => "function", |
|
Self::Object(ref o) => { |
|
if o.deref().borrow().get_internal_slot("call").is_null() { |
|
"object" |
|
} else { |
|
"function" |
|
} |
|
} |
|
} |
|
} |
|
|
|
pub fn as_num_to_power(&self, other: Self) -> Self { |
|
Self::Number(self.to_num().powf(other.to_num())) |
|
} |
|
} |
|
|
|
impl Default for ValueData { |
|
fn default() -> Self { |
|
Self::Undefined |
|
} |
|
} |
|
|
|
/// A helper macro for printing objects |
|
/// Can be used to print both properties and internal slots |
|
/// All of the overloads take: |
|
/// - The object to be printed |
|
/// - The function with which to print |
|
/// - The indentation for the current level (for nested objects) |
|
/// - A HashSet with the addresses of the already printed objects for the current branch |
|
/// (used to avoid infinite loops when there are cyclic deps) |
|
macro_rules! print_obj_value { |
|
(all of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => { |
|
{ |
|
let mut internals = print_obj_value!(internals of $obj, $display_fn, $indent, $encounters); |
|
let mut props = print_obj_value!(props of $obj, $display_fn, $indent, $encounters, true); |
|
|
|
props.reserve(internals.len()); |
|
|
|
props.append(&mut internals); |
|
|
|
props |
|
} |
|
}; |
|
(internals of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => { |
|
print_obj_value!(impl internal_slots, $obj, |(key, val)| { |
|
format!( |
|
"{}{}: {}", |
|
String::from_utf8(vec![b' '; $indent]) |
|
.expect("Could not create indentation string"), |
|
key, |
|
$display_fn(&val, $encounters, $indent.wrapping_add(4), true) |
|
) |
|
}) |
|
}; |
|
(props of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr, $print_internals:expr) => { |
|
print_obj_value!(impl properties, $obj, |(key, val)| { |
|
let v = &val |
|
.value |
|
.as_ref() |
|
.expect("Could not get the property's value"); |
|
|
|
format!( |
|
"{}{}: {}", |
|
String::from_utf8(vec![b' '; $indent]) |
|
.expect("Could not create indentation string"), |
|
key, |
|
$display_fn(v, $encounters, $indent.wrapping_add(4), $print_internals) |
|
) |
|
}) |
|
}; |
|
|
|
// A private overload of the macro |
|
// DO NOT use directly |
|
(impl $field:ident, $v:expr, $f:expr) => { |
|
$v |
|
.borrow() |
|
.$field |
|
.iter() |
|
.map($f) |
|
.collect::<Vec<String>>() |
|
}; |
|
} |
|
|
|
pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { |
|
match x { |
|
// We don't want to print private (compiler) or prototype properties |
|
ValueData::Object(ref v) => { |
|
// Can use the private "type" field of an Object to match on |
|
// which type of Object it represents for special printing |
|
match v.borrow().kind { |
|
ObjectKind::String => from_value( |
|
v.borrow() |
|
.internal_slots |
|
.get("StringData") |
|
.expect("Cannot get primitive value from String") |
|
.clone(), |
|
) |
|
.expect("Cannot clone primitive value from String"), |
|
ObjectKind::Boolean => { |
|
let bool_data = v.borrow().get_internal_slot("BooleanData").to_string(); |
|
|
|
format!("Boolean {{ {} }}", bool_data) |
|
} |
|
ObjectKind::Array => { |
|
let len: i32 = from_value( |
|
v.borrow() |
|
.properties |
|
.get("length") |
|
.unwrap() |
|
.value |
|
.clone() |
|
.expect("Could not borrow value"), |
|
) |
|
.expect("Could not convert JS value to i32"); |
|
|
|
if len == 0 { |
|
return String::from("[]"); |
|
} |
|
|
|
let arr = (0..len) |
|
.map(|i| { |
|
// Introduce recursive call to stringify any objects |
|
// which are part of the Array |
|
log_string_from( |
|
&v.borrow() |
|
.properties |
|
.get(&i.to_string()) |
|
.unwrap() |
|
.value |
|
.clone() |
|
.expect("Could not borrow value"), |
|
print_internals, |
|
) |
|
}) |
|
.collect::<Vec<String>>() |
|
.join(", "); |
|
|
|
format!("[ {} ]", arr) |
|
} |
|
_ => display_obj(&x, print_internals), |
|
} |
|
} |
|
ValueData::Symbol(ref sym) => { |
|
let desc: Value = sym.borrow().get_internal_slot("Description"); |
|
match *desc { |
|
ValueData::String(ref st) => format!("Symbol(\"{}\")", st.to_string()), |
|
_ => String::from("Symbol()"), |
|
} |
|
} |
|
|
|
_ => format!("{}", x), |
|
} |
|
} |
|
|
|
/// A helper function for specifically printing object values |
|
pub(crate) fn display_obj(v: &ValueData, print_internals: bool) -> String { |
|
// A simple helper for getting the address of a value |
|
// TODO: Find a more general place for this, as it can be used in other situations as well |
|
fn address_of<T>(t: &T) -> usize { |
|
let my_ptr: *const T = t; |
|
my_ptr as usize |
|
} |
|
|
|
// We keep track of which objects we have encountered by keeping their |
|
// in-memory address in this set |
|
let mut encounters = HashSet::new(); |
|
|
|
fn display_obj_internal( |
|
data: &ValueData, |
|
encounters: &mut HashSet<usize>, |
|
indent: usize, |
|
print_internals: bool, |
|
) -> String { |
|
if let ValueData::Object(ref v) = *data { |
|
// The in-memory address of the current object |
|
let addr = address_of(v.borrow().deref()); |
|
|
|
// We need not continue if this object has already been |
|
// printed up the current chain |
|
if encounters.contains(&addr) { |
|
return String::from("[Cycle]"); |
|
} |
|
|
|
// Mark the current object as encountered |
|
encounters.insert(addr); |
|
|
|
let result = if print_internals { |
|
print_obj_value!(all of v, display_obj_internal, indent, encounters).join(",\n") |
|
} else { |
|
print_obj_value!(props of v, display_obj_internal, indent, encounters, print_internals) |
|
.join(",\n") |
|
}; |
|
|
|
// If the current object is referenced in a different branch, |
|
// it will not cause an infinte printing loop, so it is safe to be printed again |
|
encounters.remove(&addr); |
|
|
|
let closing_indent = String::from_utf8(vec![b' '; indent.wrapping_sub(4)]) |
|
.expect("Could not create the closing brace's indentation string"); |
|
|
|
format!("{{\n{}\n{}}}", result, closing_indent) |
|
} else { |
|
// Every other type of data is printed as is |
|
format!("{}", data) |
|
} |
|
} |
|
|
|
display_obj_internal(v, &mut encounters, 4, print_internals) |
|
} |
|
|
|
impl Display for ValueData { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
match self { |
|
Self::Null => write!(f, "null"), |
|
Self::Undefined => write!(f, "undefined"), |
|
Self::Boolean(v) => write!(f, "{}", v), |
|
Self::Symbol(ref v) => match *v.borrow().get_internal_slot("Description") { |
|
// If a description exists use it |
|
Self::String(ref v) => write!(f, "{}", format!("Symbol({})", v)), |
|
_ => write!(f, "Symbol()"), |
|
}, |
|
Self::String(ref v) => write!(f, "{}", v), |
|
Self::Number(v) => write!( |
|
f, |
|
"{}", |
|
match v { |
|
_ if v.is_nan() => "NaN".to_string(), |
|
_ if v.is_infinite() && v.is_sign_negative() => "-Infinity".to_string(), |
|
_ if v.is_infinite() => "Infinity".to_string(), |
|
_ => v.to_string(), |
|
} |
|
), |
|
Self::Object(_) => write!(f, "{}", log_string_from(self, true)), |
|
Self::Integer(v) => write!(f, "{}", v), |
|
Self::Function(ref v) => match *v.borrow() { |
|
Function::NativeFunc(_) => write!(f, "function() {{ [native code] }}"), |
|
Function::RegularFunc(ref rf) => { |
|
write!(f, "function{}(", if rf.args.is_empty() { "" } else { " " })?; |
|
for (index, arg) in rf.args.iter().enumerate() { |
|
write!(f, "{}", arg)?; |
|
if index + 1 != rf.args.len() { |
|
write!(f, ", ")?; |
|
} |
|
} |
|
write!(f, ") {}", rf.node) |
|
} |
|
}, |
|
} |
|
} |
|
} |
|
|
|
impl PartialEq for ValueData { |
|
fn eq(&self, other: &Self) -> bool { |
|
match (self.clone(), other.clone()) { |
|
// TODO: fix this |
|
// _ if self.ptr.to_inner() == &other.ptr.to_inner() => true, |
|
_ if self.is_null_or_undefined() && other.is_null_or_undefined() => true, |
|
(Self::String(_), _) | (_, Self::String(_)) => self.to_string() == other.to_string(), |
|
(Self::Boolean(a), Self::Boolean(b)) if a == b => true, |
|
(Self::Number(a), Self::Number(b)) if a == b && !a.is_nan() && !b.is_nan() => true, |
|
(Self::Number(a), _) if a == other.to_num() => true, |
|
(_, Self::Number(a)) if a == self.to_num() => true, |
|
(Self::Integer(a), Self::Integer(b)) if a == b => true, |
|
_ => false, |
|
} |
|
} |
|
} |
|
|
|
impl Add for ValueData { |
|
type Output = Self; |
|
fn add(self, other: Self) -> Self { |
|
match (self, other) { |
|
(Self::String(ref s), ref o) => { |
|
Self::String(format!("{}{}", s.clone(), &o.to_string())) |
|
} |
|
(ref s, Self::String(ref o)) => Self::String(format!("{}{}", s.to_string(), o)), |
|
(ref s, ref o) => Self::Number(s.to_num() + o.to_num()), |
|
} |
|
} |
|
} |
|
impl Sub for ValueData { |
|
type Output = Self; |
|
fn sub(self, other: Self) -> Self { |
|
Self::Number(self.to_num() - other.to_num()) |
|
} |
|
} |
|
impl Mul for ValueData { |
|
type Output = Self; |
|
fn mul(self, other: Self) -> Self { |
|
Self::Number(self.to_num() * other.to_num()) |
|
} |
|
} |
|
impl Div for ValueData { |
|
type Output = Self; |
|
fn div(self, other: Self) -> Self { |
|
Self::Number(self.to_num() / other.to_num()) |
|
} |
|
} |
|
impl Rem for ValueData { |
|
type Output = Self; |
|
fn rem(self, other: Self) -> Self { |
|
Self::Number(self.to_num() % other.to_num()) |
|
} |
|
} |
|
impl BitAnd for ValueData { |
|
type Output = Self; |
|
fn bitand(self, other: Self) -> Self { |
|
Self::Integer(self.to_int() & other.to_int()) |
|
} |
|
} |
|
impl BitOr for ValueData { |
|
type Output = Self; |
|
fn bitor(self, other: Self) -> Self { |
|
Self::Integer(self.to_int() | other.to_int()) |
|
} |
|
} |
|
impl BitXor for ValueData { |
|
type Output = Self; |
|
fn bitxor(self, other: Self) -> Self { |
|
Self::Integer(self.to_int() ^ other.to_int()) |
|
} |
|
} |
|
impl Shl for ValueData { |
|
type Output = Self; |
|
fn shl(self, other: Self) -> Self { |
|
Self::Integer(self.to_int() << other.to_int()) |
|
} |
|
} |
|
impl Shr for ValueData { |
|
type Output = Self; |
|
fn shr(self, other: Self) -> Self { |
|
Self::Integer(self.to_int() >> other.to_int()) |
|
} |
|
} |
|
impl Not for ValueData { |
|
type Output = Self; |
|
fn not(self) -> Self { |
|
Self::Boolean(!self.is_true()) |
|
} |
|
} |
|
|
|
/// Conversion to Javascript values from Rust values |
|
pub trait ToValue { |
|
/// Convert this value to a Rust value |
|
fn to_value(&self) -> Value; |
|
} |
|
/// Conversion to Rust values from Javascript values |
|
pub trait FromValue { |
|
/// Convert this value to a Javascript value |
|
fn from_value(value: Value) -> Result<Self, &'static str> |
|
where |
|
Self: Sized; |
|
} |
|
|
|
impl ToValue for Value { |
|
fn to_value(&self) -> Value { |
|
self.clone() |
|
} |
|
} |
|
|
|
impl FromValue for Value { |
|
fn from_value(value: Value) -> Result<Self, &'static str> { |
|
Ok(value) |
|
} |
|
} |
|
|
|
impl ToValue for String { |
|
fn to_value(&self) -> Value { |
|
Gc::new(ValueData::String(self.clone())) |
|
} |
|
} |
|
|
|
impl FromValue for String { |
|
fn from_value(v: Value) -> Result<Self, &'static str> { |
|
Ok(v.to_string()) |
|
} |
|
} |
|
|
|
impl<'s> ToValue for &'s str { |
|
fn to_value(&self) -> Value { |
|
Gc::new(ValueData::String( |
|
String::from_str(*self).expect("Could not convert string to self to String"), |
|
)) |
|
} |
|
} |
|
|
|
impl ToValue for char { |
|
fn to_value(&self) -> Value { |
|
Gc::new(ValueData::String(self.to_string())) |
|
} |
|
} |
|
impl FromValue for char { |
|
fn from_value(v: Value) -> Result<Self, &'static str> { |
|
Ok(v.to_string() |
|
.chars() |
|
.next() |
|
.expect("Could not get next char")) |
|
} |
|
} |
|
|
|
impl ToValue for f64 { |
|
fn to_value(&self) -> Value { |
|
Gc::new(ValueData::Number(*self)) |
|
} |
|
} |
|
impl FromValue for f64 { |
|
fn from_value(v: Value) -> Result<Self, &'static str> { |
|
Ok(v.to_num()) |
|
} |
|
} |
|
|
|
impl ToValue for i32 { |
|
fn to_value(&self) -> Value { |
|
Gc::new(ValueData::Integer(*self)) |
|
} |
|
} |
|
impl FromValue for i32 { |
|
fn from_value(v: Value) -> Result<Self, &'static str> { |
|
Ok(v.to_int()) |
|
} |
|
} |
|
|
|
impl ToValue for usize { |
|
fn to_value(&self) -> Value { |
|
Gc::new(ValueData::Integer(*self as i32)) |
|
} |
|
} |
|
impl FromValue for usize { |
|
fn from_value(v: Value) -> Result<Self, &'static str> { |
|
Ok(v.to_int() as Self) |
|
} |
|
} |
|
|
|
impl ToValue for bool { |
|
fn to_value(&self) -> Value { |
|
Gc::new(ValueData::Boolean(*self)) |
|
} |
|
} |
|
impl FromValue for bool { |
|
fn from_value(v: Value) -> Result<Self, &'static str> { |
|
Ok(v.is_true()) |
|
} |
|
} |
|
|
|
impl<'s, T: ToValue> ToValue for &'s [T] { |
|
fn to_value(&self) -> Value { |
|
let mut arr = Object::default(); |
|
for (i, item) in self.iter().enumerate() { |
|
arr.properties |
|
.insert(i.to_string(), Property::default().value(item.to_value())); |
|
} |
|
to_value(arr) |
|
} |
|
} |
|
impl<T: ToValue> ToValue for Vec<T> { |
|
fn to_value(&self) -> Value { |
|
let mut arr = Object::default(); |
|
for (i, item) in self.iter().enumerate() { |
|
arr.properties |
|
.insert(i.to_string(), Property::default().value(item.to_value())); |
|
} |
|
to_value(arr) |
|
} |
|
} |
|
|
|
impl<T: FromValue> FromValue for Vec<T> { |
|
fn from_value(v: Value) -> Result<Self, &'static str> { |
|
let len = v.get_field_slice("length").to_int(); |
|
let mut vec = Self::with_capacity(len as usize); |
|
for i in 0..len { |
|
vec.push(from_value(v.get_field_slice(&i.to_string()))?) |
|
} |
|
Ok(vec) |
|
} |
|
} |
|
|
|
impl ToValue for Object { |
|
fn to_value(&self) -> Value { |
|
Gc::new(ValueData::Object(GcCell::new(self.clone()))) |
|
} |
|
} |
|
|
|
impl FromValue for Object { |
|
fn from_value(v: Value) -> Result<Self, &'static str> { |
|
match *v { |
|
ValueData::Object(ref obj) => Ok(obj.clone().into_inner()), |
|
ValueData::Function(ref func) => Ok(match *func.borrow().deref() { |
|
Function::NativeFunc(ref data) => data.object.clone(), |
|
Function::RegularFunc(ref data) => data.object.clone(), |
|
}), |
|
_ => Err("Value is not a valid object"), |
|
} |
|
} |
|
} |
|
|
|
impl ToValue for JSONValue { |
|
fn to_value(&self) -> Value { |
|
Gc::new(ValueData::from_json(self.clone())) |
|
} |
|
} |
|
|
|
impl FromValue for JSONValue { |
|
fn from_value(v: Value) -> Result<Self, &'static str> { |
|
Ok(v.to_json()) |
|
} |
|
} |
|
|
|
impl ToValue for () { |
|
fn to_value(&self) -> Value { |
|
Gc::new(ValueData::Null) |
|
} |
|
} |
|
impl FromValue for () { |
|
fn from_value(_: Value) -> Result<(), &'static str> { |
|
Ok(()) |
|
} |
|
} |
|
|
|
impl<T: ToValue> ToValue for Option<T> { |
|
fn to_value(&self) -> Value { |
|
match *self { |
|
Some(ref v) => v.to_value(), |
|
None => Gc::new(ValueData::Null), |
|
} |
|
} |
|
} |
|
impl<T: FromValue> FromValue for Option<T> { |
|
fn from_value(value: Value) -> Result<Self, &'static str> { |
|
Ok(if value.is_null_or_undefined() { |
|
None |
|
} else { |
|
Some(FromValue::from_value(value)?) |
|
}) |
|
} |
|
} |
|
|
|
impl ToValue for NativeFunctionData { |
|
fn to_value(&self) -> Value { |
|
Gc::new(ValueData::Function(Box::new(GcCell::new( |
|
Function::NativeFunc(NativeFunction::new(*self)), |
|
)))) |
|
} |
|
} |
|
impl FromValue for NativeFunctionData { |
|
fn from_value(v: Value) -> Result<Self, &'static str> { |
|
match *v { |
|
ValueData::Function(ref func) => match *func.borrow() { |
|
Function::NativeFunc(ref data) => Ok(data.data), |
|
_ => Err("Value is not a native function"), |
|
}, |
|
_ => Err("Value is not a function"), |
|
} |
|
} |
|
} |
|
|
|
/// A utility function that just calls `FromValue::from_value` |
|
pub fn from_value<A: FromValue>(v: Value) -> Result<A, &'static str> { |
|
FromValue::from_value(v) |
|
} |
|
|
|
/// A utility function that just calls `ToValue::to_value` |
|
pub fn to_value<A: ToValue>(v: A) -> Value { |
|
v.to_value() |
|
} |
|
|
|
/// The internal comparison abstract operation SameValue(x, y), |
|
/// where x and y are ECMAScript language values, produces true or false. |
|
/// Such a comparison is performed as follows: |
|
/// |
|
/// https://tc39.es/ecma262/#sec-samevalue |
|
/// strict mode currently compares the pointers |
|
pub fn same_value(x: &Value, y: &Value, strict: bool) -> bool { |
|
if strict { |
|
// Do both Values point to the same underlying valueData? |
|
let x_ptr = Gc::into_raw(x.clone()); |
|
let y_ptr = Gc::into_raw(y.clone()); |
|
return x_ptr == y_ptr; |
|
} |
|
|
|
if x.get_type() != y.get_type() { |
|
return false; |
|
} |
|
|
|
if x.get_type() == "number" { |
|
let native_x: f64 = from_value(x.clone()).expect("failed to get value"); |
|
let native_y: f64 = from_value(y.clone()).expect("failed to get value"); |
|
return native_x.abs() - native_y.abs() == 0.0; |
|
} |
|
|
|
same_value_non_number(x, y) |
|
} |
|
|
|
pub fn same_value_non_number(x: &Value, y: &Value) -> bool { |
|
debug_assert!(x.get_type() == y.get_type()); |
|
match x.get_type() { |
|
"undefined" => true, |
|
"null" => true, |
|
"string" => { |
|
if x.to_string() == y.to_string() { |
|
return true; |
|
} |
|
false |
|
} |
|
"boolean" => { |
|
from_value::<bool>(x.clone()).expect("failed to get value") |
|
== from_value::<bool>(y.clone()).expect("failed to get value") |
|
} |
|
"object" => *x == *y, |
|
_ => false, |
|
} |
|
}
|
|
|