Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
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.
 
 

648 lines
19 KiB

//! This module implements the global `Object` object.
//!
//! The `Object` class represents one of JavaScript's data types.
//!
//! It is used to store various keyed collections and more complex entities.
//! Objects can be created using the `Object()` constructor or the
//! object initializer / literal syntax.
//!
//! More information:
//! - [ECMAScript reference][spec]
//! - [MDN documentation][mdn]
//!
//! [spec]: https://tc39.es/ecma262/#sec-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
use crate::{
builtins::{
function::Function,
map::ordered_map::OrderedMap,
property::{Property, PropertyKey},
value::{RcBigInt, RcString, RcSymbol, Value},
BigInt, Date, RegExp,
},
exec::Interpreter,
BoaProfiler, Result,
};
use gc::{Finalize, Trace};
use rustc_hash::FxHashMap;
use std::fmt::{Debug, Display, Error, Formatter};
use std::{any::Any, result::Result as StdResult};
use super::function::{make_builtin_fn, make_constructor_fn};
use crate::builtins::value::same_value;
mod gcobject;
mod internal_methods;
mod iter;
pub use gcobject::{GcObject, Ref, RefMut};
pub use iter::*;
#[cfg(test)]
mod tests;
/// Static `prototype`, usually set on constructors as a key to point to their respective prototype object.
pub static PROTOTYPE: &str = "prototype";
/// This trait allows Rust types to be passed around as objects.
///
/// This is automatically implemented, when a type implements `Debug`, `Any` and `Trace`.
pub trait NativeObject: Debug + Any + Trace {
/// Convert the Rust type which implements `NativeObject` to a `&dyn Any`.
fn as_any(&self) -> &dyn Any;
/// Convert the Rust type which implements `NativeObject` to a `&mut dyn Any`.
fn as_mut_any(&mut self) -> &mut dyn Any;
}
impl<T: Any + Debug + Trace> NativeObject for T {
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self as &mut dyn Any
}
}
/// The internal representation of an JavaScript object.
#[derive(Debug, Trace, Finalize)]
pub struct Object {
/// The type of the object.
pub data: ObjectData,
indexed_properties: FxHashMap<u32, Property>,
/// Properties
string_properties: FxHashMap<RcString, Property>,
/// Symbol Properties
symbol_properties: FxHashMap<RcSymbol, Property>,
/// Instance prototype `__proto__`.
prototype: Value,
/// Whether it can have new properties added to it.
extensible: bool,
}
/// Defines the different types of objects.
#[derive(Debug, Trace, Finalize)]
pub enum ObjectData {
Array,
Map(OrderedMap<Value, Value>),
RegExp(Box<RegExp>),
BigInt(RcBigInt),
Boolean(bool),
Function(Function),
String(RcString),
Number(f64),
Symbol(RcSymbol),
Error,
Ordinary,
Date(Date),
Global,
NativeObject(Box<dyn NativeObject>),
}
impl Display for ObjectData {
fn fmt(&self, f: &mut Formatter<'_>) -> StdResult<(), Error> {
write!(
f,
"{}",
match self {
Self::Array => "Array",
Self::Function(_) => "Function",
Self::RegExp(_) => "RegExp",
Self::Map(_) => "Map",
Self::String(_) => "String",
Self::Symbol(_) => "Symbol",
Self::Error => "Error",
Self::Ordinary => "Ordinary",
Self::Boolean(_) => "Boolean",
Self::Number(_) => "Number",
Self::BigInt(_) => "BigInt",
Self::Date(_) => "Date",
Self::Global => "Global",
Self::NativeObject(_) => "NativeObject",
}
)
}
}
impl Default for Object {
/// Return a new ObjectData struct, with `kind` set to Ordinary
#[inline]
fn default() -> Self {
Self {
data: ObjectData::Ordinary,
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
}
impl Object {
#[inline]
pub fn new() -> Self {
Default::default()
}
/// Return a new ObjectData struct, with `kind` set to Ordinary
pub fn function(function: Function, prototype: Value) -> Self {
let _timer = BoaProfiler::global().start_event("Object::Function", "object");
Self {
data: ObjectData::Function(function),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype,
extensible: true,
}
}
/// ObjectCreate is used to specify the runtime creation of new ordinary objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-objectcreate
// TODO: proto should be a &Value here
pub fn create(proto: Value) -> Self {
let mut obj = Self::default();
obj.prototype = proto;
obj
}
/// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument.
pub fn boolean(value: bool) -> Self {
Self {
data: ObjectData::Boolean(value),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
/// Return a new `Number` object whose `[[NumberData]]` internal slot is set to argument.
pub fn number(value: f64) -> Self {
Self {
data: ObjectData::Number(value),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
/// Return a new `String` object whose `[[StringData]]` internal slot is set to argument.
pub fn string<S>(value: S) -> Self
where
S: Into<RcString>,
{
Self {
data: ObjectData::String(value.into()),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
/// Return a new `BigInt` object whose `[[BigIntData]]` internal slot is set to argument.
pub fn bigint(value: RcBigInt) -> Self {
Self {
data: ObjectData::BigInt(value),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
/// Create a new native object of type `T`.
pub fn native_object<T>(value: T) -> Self
where
T: NativeObject,
{
Self {
data: ObjectData::NativeObject(Box::new(value)),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
/// It determines if Object is a callable function with a [[Call]] internal method.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline]
pub fn is_callable(&self) -> bool {
matches!(self.data, ObjectData::Function(ref f) if f.is_callable())
}
/// It determines if Object is a function object with a [[Construct]] internal method.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isconstructor
#[inline]
pub fn is_constructable(&self) -> bool {
matches!(self.data, ObjectData::Function(ref f) if f.is_constructable())
}
/// Checks if it an `Array` object.
#[inline]
pub fn is_array(&self) -> bool {
matches!(self.data, ObjectData::Array)
}
#[inline]
pub fn as_array(&self) -> Option<()> {
match self.data {
ObjectData::Array => Some(()),
_ => None,
}
}
/// Checks if it is a `Map` object.pub
#[inline]
pub fn is_map(&self) -> bool {
matches!(self.data, ObjectData::Map(_))
}
#[inline]
pub fn as_map_ref(&self) -> Option<&OrderedMap<Value, Value>> {
match self.data {
ObjectData::Map(ref map) => Some(map),
_ => None,
}
}
#[inline]
pub fn as_map_mut(&mut self) -> Option<&mut OrderedMap<Value, Value>> {
match &mut self.data {
ObjectData::Map(map) => Some(map),
_ => None,
}
}
/// Checks if it a `String` object.
#[inline]
pub fn is_string(&self) -> bool {
matches!(self.data, ObjectData::String(_))
}
#[inline]
pub fn as_string(&self) -> Option<RcString> {
match self.data {
ObjectData::String(ref string) => Some(string.clone()),
_ => None,
}
}
/// Checks if it a `Function` object.
#[inline]
pub fn is_function(&self) -> bool {
matches!(self.data, ObjectData::Function(_))
}
#[inline]
pub fn as_function(&self) -> Option<&Function> {
match self.data {
ObjectData::Function(ref function) => Some(function),
_ => None,
}
}
/// Checks if it a Symbol object.
#[inline]
pub fn is_symbol(&self) -> bool {
matches!(self.data, ObjectData::Symbol(_))
}
#[inline]
pub fn as_symbol(&self) -> Option<RcSymbol> {
match self.data {
ObjectData::Symbol(ref symbol) => Some(symbol.clone()),
_ => None,
}
}
/// Checks if it an Error object.
#[inline]
pub fn is_error(&self) -> bool {
matches!(self.data, ObjectData::Error)
}
#[inline]
pub fn as_error(&self) -> Option<()> {
match self.data {
ObjectData::Error => Some(()),
_ => None,
}
}
/// Checks if it a Boolean object.
#[inline]
pub fn is_boolean(&self) -> bool {
matches!(self.data, ObjectData::Boolean(_))
}
#[inline]
pub fn as_boolean(&self) -> Option<bool> {
match self.data {
ObjectData::Boolean(boolean) => Some(boolean),
_ => None,
}
}
/// Checks if it a `Number` object.
#[inline]
pub fn is_number(&self) -> bool {
matches!(self.data, ObjectData::Number(_))
}
#[inline]
pub fn as_number(&self) -> Option<f64> {
match self.data {
ObjectData::Number(number) => Some(number),
_ => None,
}
}
/// Checks if it a `BigInt` object.
#[inline]
pub fn is_bigint(&self) -> bool {
matches!(self.data, ObjectData::BigInt(_))
}
#[inline]
pub fn as_bigint(&self) -> Option<&BigInt> {
match self.data {
ObjectData::BigInt(ref bigint) => Some(bigint),
_ => None,
}
}
/// Checks if it a `RegExp` object.
#[inline]
pub fn is_regexp(&self) -> bool {
matches!(self.data, ObjectData::RegExp(_))
}
#[inline]
pub fn as_regexp(&self) -> Option<&RegExp> {
match self.data {
ObjectData::RegExp(ref regexp) => Some(regexp),
_ => None,
}
}
/// Checks if it an ordinary object.
#[inline]
pub fn is_ordinary(&self) -> bool {
matches!(self.data, ObjectData::Ordinary)
}
pub fn prototype(&self) -> &Value {
&self.prototype
}
pub fn set_prototype(&mut self, prototype: Value) {
assert!(prototype.is_null() || prototype.is_object());
self.prototype = prototype
}
/// Returns `true` if it holds an Rust type that implements `NativeObject`.
pub fn is_native_object(&self) -> bool {
matches!(self.data, ObjectData::NativeObject(_))
}
/// Reeturn `true` if it is a native object and the native type is `T`.
pub fn is<T>(&self) -> bool
where
T: NativeObject,
{
use std::ops::Deref;
match self.data {
ObjectData::NativeObject(ref object) => object.deref().as_any().is::<T>(),
_ => false,
}
}
/// Downcast a reference to the object,
/// if the object is type native object type `T`.
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: NativeObject,
{
use std::ops::Deref;
match self.data {
ObjectData::NativeObject(ref object) => object.deref().as_any().downcast_ref::<T>(),
_ => None,
}
}
/// Downcast a mutable reference to the object,
/// if the object is type native object type `T`.
pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
T: NativeObject,
{
use std::ops::DerefMut;
match self.data {
ObjectData::NativeObject(ref mut object) => {
object.deref_mut().as_mut_any().downcast_mut::<T>()
}
_ => None,
}
}
}
/// Create a new object.
pub fn make_object(_: &Value, args: &[Value], ctx: &mut Interpreter) -> Result<Value> {
if let Some(arg) = args.get(0) {
if !arg.is_null_or_undefined() {
return arg.to_object(ctx);
}
}
Ok(Value::new_object(Some(ctx.global())))
}
/// `Object.create( proto, [propertiesObject] )`
///
/// Creates a new object from the provided prototype.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-object.create
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
pub fn create(_: &Value, args: &[Value], interpreter: &mut Interpreter) -> Result<Value> {
let prototype = args.get(0).cloned().unwrap_or_else(Value::undefined);
let properties = args.get(1).cloned().unwrap_or_else(Value::undefined);
if properties != Value::Undefined {
unimplemented!("propertiesObject argument of Object.create")
}
match prototype {
Value::Object(_) | Value::Null => Ok(Value::new_object_from_prototype(
prototype,
ObjectData::Ordinary,
)),
_ => interpreter.throw_type_error(format!(
"Object prototype may only be an Object or null: {}",
prototype.display()
)),
}
}
/// Uses the SameValue algorithm to check equality of objects
pub fn is(_: &Value, args: &[Value], _: &mut Interpreter) -> Result<Value> {
let x = args.get(0).cloned().unwrap_or_else(Value::undefined);
let y = args.get(1).cloned().unwrap_or_else(Value::undefined);
Ok(same_value(&x, &y).into())
}
/// Get the `prototype` of an object.
pub fn get_prototype_of(_: &Value, args: &[Value], _: &mut Interpreter) -> Result<Value> {
let obj = args.get(0).expect("Cannot get object");
Ok(obj
.as_object()
.map_or_else(Value::undefined, |object| object.prototype.clone()))
}
/// Set the `prototype` of an object.
pub fn set_prototype_of(_: &Value, args: &[Value], _: &mut Interpreter) -> Result<Value> {
let obj = args.get(0).expect("Cannot get object").clone();
let proto = args.get(1).expect("Cannot get object").clone();
obj.as_object_mut().unwrap().prototype = proto;
Ok(obj)
}
/// Define a property in an object
pub fn define_property(_: &Value, args: &[Value], ctx: &mut Interpreter) -> Result<Value> {
let obj = args.get(0).expect("Cannot get object");
let prop = args.get(1).expect("Cannot get object").to_string(ctx)?;
let desc = Property::from(args.get(2).expect("Cannot get object"));
obj.set_property(prop, desc);
Ok(Value::undefined())
}
/// `Object.prototype.toString()`
///
/// This method returns a string representing the object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-object.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> Result<Value> {
// FIXME: it should not display the object.
Ok(this.display().to_string().into())
}
/// `Object.prototype.hasOwnPrototype( property )`
///
/// The method returns a boolean indicating whether the object has the specified property
/// as its own property (as opposed to inheriting it).
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-object.prototype.hasownproperty
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
pub fn has_own_property(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result<Value> {
let prop = if args.is_empty() {
None
} else {
Some(args.get(0).expect("Cannot get object").to_string(ctx)?)
};
let own_property = this
.as_object()
.as_deref()
.expect("Cannot get THIS object")
.get_own_property(&prop.expect("cannot get prop").into());
if own_property.is_none() {
Ok(Value::from(false))
} else {
Ok(Value::from(true))
}
}
pub fn property_is_enumerable(
this: &Value,
args: &[Value],
ctx: &mut Interpreter,
) -> Result<Value> {
let key = match args.get(0) {
None => return Ok(Value::from(false)),
Some(key) => key,
};
let key = key.to_property_key(ctx)?;
let own_property = this.to_object(ctx).map(|obj| {
obj.as_object()
.expect("Unable to deref object")
.get_own_property(&key)
});
Ok(own_property.map_or(Value::from(false), |own_prop| {
Value::from(own_prop.enumerable_or(false))
}))
}
/// Initialise the `Object` object on the global object.
#[inline]
pub fn init(interpreter: &mut Interpreter) -> (&'static str, Value) {
let global = interpreter.global();
let _timer = BoaProfiler::global().start_event("object", "init");
let prototype = Value::new_object(None);
make_builtin_fn(
has_own_property,
"hasOwnProperty",
&prototype,
0,
interpreter,
);
make_builtin_fn(
property_is_enumerable,
"propertyIsEnumerable",
&prototype,
0,
interpreter,
);
make_builtin_fn(to_string, "toString", &prototype, 0, interpreter);
let object = make_constructor_fn("Object", 1, make_object, global, prototype, true, true);
// static methods of the builtin Object
make_builtin_fn(create, "create", &object, 2, interpreter);
make_builtin_fn(set_prototype_of, "setPrototypeOf", &object, 2, interpreter);
make_builtin_fn(get_prototype_of, "getPrototypeOf", &object, 1, interpreter);
make_builtin_fn(define_property, "defineProperty", &object, 3, interpreter);
make_builtin_fn(is, "is", &object, 2, interpreter);
("Object", object)
}