Browse Source

Implement Set builtin object (#1111)

Co-authored-by: HalidOdat <halidodat@gmail.com>
pull/1265/head
João Borges 4 years ago committed by GitHub
parent
commit
c4460b7bb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      boa/src/builtins/iterable/mod.rs
  2. 1
      boa/src/builtins/map/map_iterator.rs
  3. 83
      boa/src/builtins/map/mod.rs
  4. 2
      boa/src/builtins/map/tests.rs
  5. 4
      boa/src/builtins/mod.rs
  6. 419
      boa/src/builtins/set/mod.rs
  7. 130
      boa/src/builtins/set/ordered_set.rs
  8. 151
      boa/src/builtins/set/set_iterator.rs
  9. 248
      boa/src/builtins/set/tests.rs
  10. 15
      boa/src/context.rs
  11. 41
      boa/src/object/internal_methods.rs
  12. 35
      boa/src/object/mod.rs
  13. 2
      boa/src/property/mod.rs
  14. 23
      boa/src/syntax/ast/node/operator/unary_op/mod.rs
  15. 29
      boa/src/value/display.rs
  16. 1
      boa/src/value/equality.rs
  17. 2
      boa/src/value/hash.rs

42
boa/src/builtins/iterable/mod.rs

@ -3,6 +3,7 @@ use crate::{
builtins::ArrayIterator,
builtins::ForInIterator,
builtins::MapIterator,
builtins::SetIterator,
object::{GcObject, ObjectInitializer},
property::{Attribute, DataDescriptor},
symbol::WellKnownSymbols,
@ -13,6 +14,7 @@ use crate::{
pub struct IteratorPrototypes {
iterator_prototype: GcObject,
array_iterator: GcObject,
set_iterator: GcObject,
string_iterator: GcObject,
map_iterator: GcObject,
for_in_iterator: GcObject,
@ -26,6 +28,7 @@ impl IteratorPrototypes {
context,
iterator_prototype.clone().into(),
),
set_iterator: SetIterator::create_prototype(context, iterator_prototype.clone().into()),
string_iterator: StringIterator::create_prototype(
context,
iterator_prototype.clone().into(),
@ -49,6 +52,11 @@ impl IteratorPrototypes {
self.iterator_prototype.clone()
}
#[inline]
pub fn set_iterator(&self) -> GcObject {
self.set_iterator.clone()
}
#[inline]
pub fn string_iterator(&self) -> GcObject {
self.string_iterator.clone()
@ -139,6 +147,40 @@ impl IteratorRecord {
let next_result = next.get_field("value", context)?;
Ok(IteratorResult::new(next_result, done))
}
/// Cleanup the iterator
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iteratorclose
pub(crate) fn close(&self, completion: Result<Value>, context: &mut Context) -> Result<Value> {
let mut inner_result = self.iterator_object.get_field("return", context);
// 5
if let Ok(inner_value) = inner_result {
// b
if inner_value.is_undefined() {
return completion;
}
// c
inner_result = context.call(&inner_value, &self.iterator_object, &[]);
}
// 6
let completion = completion?;
// 7
let inner_result = inner_result?;
// 8
if !inner_result.is_object() {
return context.throw_type_error("`return` method of iterator didn't return an Object");
}
// 9
Ok(completion)
}
}
#[derive(Debug)]

1
boa/src/builtins/map/map_iterator.rs

@ -30,6 +30,7 @@ pub struct MapIterator {
impl MapIterator {
pub(crate) const NAME: &'static str = "MapIterator";
/// Constructs a new `MapIterator`, that will iterate over `map`, starting at index 0
fn new(map: Value, kind: MapIterationKind) -> Self {
MapIterator {
iterated_map: map,

83
boa/src/builtins/map/mod.rs

@ -1,3 +1,15 @@
//! This module implements the global `Map` objest.
//!
//! The JavaScript `Map` class is a global object that is used in the construction of maps; which
//! are high-level, key-value stores.
//!
//! More information:
//! - [ECMAScript reference][spec]
//! - [MDN documentation][mdn]
//!
//! [spec]: https://tc39.es/ecma262/#sec-map-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
#![allow(clippy::mutable_key_type)]
use crate::{
@ -40,41 +52,44 @@ impl BuiltIn for Map {
.constructable(false)
.build();
let map_object = ConstructorBuilder::new(context, Self::constructor)
.name(Self::NAME)
.length(Self::LENGTH)
.property(
"entries",
entries_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
to_string_tag,
"Map",
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
iterator_symbol,
entries_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.method(Self::keys, "keys", 0)
.method(Self::set, "set", 2)
.method(Self::delete, "delete", 1)
.method(Self::get, "get", 1)
.method(Self::clear, "clear", 0)
.method(Self::has, "has", 1)
.method(Self::for_each, "forEach", 1)
.method(Self::values, "values", 0)
.callable(false)
.build();
let map_object = ConstructorBuilder::with_standard_object(
context,
Self::constructor,
context.standard_objects().map_object().clone(),
)
.name(Self::NAME)
.length(Self::LENGTH)
.property(
"entries",
entries_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
to_string_tag,
"Map",
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
iterator_symbol,
entries_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.method(Self::keys, "keys", 0)
.method(Self::set, "set", 2)
.method(Self::delete, "delete", 1)
.method(Self::get, "get", 1)
.method(Self::clear, "clear", 0)
.method(Self::has, "has", 1)
.method(Self::for_each, "forEach", 1)
.method(Self::values, "values", 0)
.build();
(Self::NAME, map_object.into(), Self::attribute())
}
}
impl Map {
pub(crate) const LENGTH: usize = 1;
pub(crate) const LENGTH: usize = 0;
/// Create a new map
pub(crate) fn constructor(
@ -83,14 +98,10 @@ impl Map {
context: &mut Context,
) -> Result<Value> {
if new_target.is_undefined() {
return context.throw_type_error("Map requires new");
return context
.throw_type_error("calling a builtin Map constructor without new is forbidden");
}
let map_prototype = context
.global_object()
.get(&"Map".into(), context.global_object().into(), context)?
.get_field(PROTOTYPE, context)?
.as_object()
.expect("'Map' global property should be an object");
let map_prototype = context.standard_objects().map_object().prototype();
let prototype = new_target
.as_object()
.and_then(|obj| {

2
boa/src/builtins/map/tests.rs

@ -351,6 +351,6 @@ fn not_a_function() {
";
assert_eq!(
forward(&mut context, init),
"\"TypeError: function object is not callable\""
"\"TypeError: calling a builtin Map constructor without new is forbidden\""
);
}

4
boa/src/builtins/mod.rs

@ -19,6 +19,7 @@ pub mod number;
pub mod object;
pub mod reflect;
pub mod regexp;
pub mod set;
pub mod string;
pub mod symbol;
pub mod undefined;
@ -42,6 +43,8 @@ pub(crate) use self::{
object::Object as BuiltInObjectObject,
reflect::Reflect,
regexp::RegExp,
set::set_iterator::SetIterator,
set::Set,
string::String,
symbol::Symbol,
undefined::Undefined,
@ -78,6 +81,7 @@ pub fn init(context: &mut Context) {
Date::init,
Map::init,
Number::init,
Set::init,
String::init,
RegExp::init,
Symbol::init,

419
boa/src/builtins/set/mod.rs

@ -0,0 +1,419 @@
//! This module implements the global `Set` objest.
//!
//! The JavaScript `Set` class is a global object that is used in the construction of sets; which
//! are high-level, collections of values.
//!
//! More information:
//! - [ECMAScript reference][spec]
//! - [MDN documentation][mdn]
//!
//! [spec]: https://tc39.es/ecma262/#sec-set-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
use crate::{
builtins::{iterable::get_iterator, BuiltIn},
object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE},
property::Attribute,
symbol::WellKnownSymbols,
BoaProfiler, Context, Result, Value,
};
use ordered_set::OrderedSet;
pub mod set_iterator;
use set_iterator::{SetIterationKind, SetIterator};
pub mod ordered_set;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone)]
pub(crate) struct Set(OrderedSet<Value>);
impl BuiltIn for Set {
const NAME: &'static str = "Set";
fn attribute() -> Attribute {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
}
fn init(context: &mut Context) -> (&'static str, Value, Attribute) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
let species = WellKnownSymbols::species();
let species_getter = FunctionBuilder::new(context, Self::species_getter)
.callable(true)
.constructable(false)
.name("get [Symbol.species]")
.build();
let size_getter = FunctionBuilder::new(context, Self::size_getter)
.callable(true)
.constructable(false)
.name("get size")
.build();
let iterator_symbol = WellKnownSymbols::iterator();
let to_string_tag = WellKnownSymbols::to_string_tag();
let values_function = FunctionBuilder::new(context, Self::values)
.name("values")
.length(0)
.callable(true)
.constructable(false)
.build();
let set_object = ConstructorBuilder::with_standard_object(
context,
Self::constructor,
context.standard_objects().set_object().clone(),
)
.name(Self::NAME)
.length(Self::LENGTH)
.static_accessor(species, Some(species_getter), None, Attribute::CONFIGURABLE)
.method(Self::add, "add", 1)
.method(Self::clear, "clear", 0)
.method(Self::delete, "delete", 1)
.method(Self::entries, "entries", 0)
.method(Self::for_each, "forEach", 1)
.method(Self::has, "has", 1)
.property(
"keys",
values_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.accessor("size", Some(size_getter), None, Attribute::CONFIGURABLE)
.property(
"values",
values_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
iterator_symbol,
values_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(to_string_tag, "Set", Attribute::CONFIGURABLE)
.build();
(Self::NAME, set_object.into(), Self::attribute())
}
}
impl Set {
pub(crate) const LENGTH: usize = 0;
/// Create a new set
pub(crate) fn constructor(
new_target: &Value,
args: &[Value],
context: &mut Context,
) -> Result<Value> {
// 1
if new_target.is_undefined() {
return context
.throw_type_error("calling a builtin Set constructor without new is forbidden");
}
// 2
let set_prototype = context.standard_objects().set_object().prototype();
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or(set_prototype);
let mut obj = context.construct_object();
obj.set_prototype_instance(prototype.into());
let set = Value::from(obj);
// 3
set.set_data(ObjectData::Set(OrderedSet::default()));
let iterable = args.get(0).cloned().unwrap_or_default();
// 4
if iterable.is_null_or_undefined() {
return Ok(set);
}
// 5
let adder = set.get_field("add", context)?;
// 6
if !adder.is_function() {
return context.throw_type_error("'add' of 'newTarget' is not a function");
}
// 7
let iterator_record = get_iterator(context, iterable)?;
// 8.a
let mut next = iterator_record.next(context)?;
// 8
while !next.is_done() {
// c
let next_value = next.value();
// d, e
if let Err(status) = context.call(&adder, &set, &[next_value]) {
return iterator_record.close(Err(status), context);
}
next = iterator_record.next(context)?
}
// 8.b
Ok(set)
}
/// `get Set [ @@species ]`
///
/// get accessor for the @@species property of Set
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-set-@@species
fn species_getter(this: &Value, _: &[Value], _: &mut Context) -> Result<Value> {
Ok(this.clone())
}
/// `Set.prototype.add( value )`
///
/// This method adds an entry with value into the set. Returns the set object
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.add
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add
pub(crate) fn add(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let mut value = args.get(0).cloned().unwrap_or_default();
if let Some(object) = this.as_object() {
if let Some(set) = object.borrow_mut().as_set_mut() {
if value.as_number().map(|n| n == -0f64).unwrap_or(false) {
value = Value::Integer(0);
}
set.add(value);
} else {
return context.throw_type_error("'this' is not a Set");
}
} else {
return context.throw_type_error("'this' is not a Set");
};
Ok(this.clone())
}
/// `Set.prototype.clear( )`
///
/// This method removes all entries from the set.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.clear
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear
pub(crate) fn clear(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
if let Some(object) = this.as_object() {
if object.borrow_mut().is_set() {
this.set_data(ObjectData::Set(OrderedSet::new()));
Ok(Value::Undefined)
} else {
context.throw_type_error("'this' is not a Set")
}
} else {
context.throw_type_error("'this' is not a Set")
}
}
/// `Set.prototype.delete( value )`
///
/// This method removes the entry for the given value if it exists.
/// Returns true if there was an element, false otherwise.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.delete
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete
pub(crate) fn delete(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let value = args.get(0).cloned().unwrap_or_default();
let res = if let Some(object) = this.as_object() {
if let Some(set) = object.borrow_mut().as_set_mut() {
set.delete(&value)
} else {
return context.throw_type_error("'this' is not a Set");
}
} else {
return context.throw_type_error("'this' is not a Set");
};
Ok(res.into())
}
/// `Set.prototype.entries( )`
///
/// This method returns an iterator over the entries of the set
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.entries
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries
pub(crate) fn entries(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
if let Some(object) = this.as_object() {
let object = object.borrow();
if !object.is_set() {
return context.throw_type_error(
"Method Set.prototype.entries called on incompatible receiver",
);
}
} else {
return context
.throw_type_error("Method Set.prototype.entries called on incompatible receiver");
}
Ok(SetIterator::create_set_iterator(
context,
this.clone(),
SetIterationKind::KeyAndValue,
))
}
/// `Set.prototype.forEach( callbackFn [ , thisArg ] )`
///
/// This method executes the provided callback function for each value in the set
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.foreach
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/foreach
pub(crate) fn for_each(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
if args.is_empty() {
return Err(Value::from("Missing argument for Set.prototype.forEach"));
}
let callback_arg = &args[0];
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined);
// TODO: if condition should also check that we are not in strict mode
let this_arg = if this_arg.is_undefined() {
Value::Object(context.global_object())
} else {
this_arg
};
let mut index = 0;
while index < Set::get_size(this, context)? {
let arguments = if let Value::Object(ref object) = this {
let object = object.borrow();
if let Some(set) = object.as_set_ref() {
set.get_index(index)
.map(|value| [value.clone(), value.clone(), this.clone()])
} else {
return context.throw_type_error("'this' is not a Set");
}
} else {
return context.throw_type_error("'this' is not a Set");
};
if let Some(arguments) = arguments {
context.call(callback_arg, &this_arg, &arguments)?;
}
index += 1;
}
Ok(Value::Undefined)
}
/// `Map.prototype.has( key )`
///
/// This method checks if the map contains an entry with the given key.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.has
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has
pub(crate) fn has(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let undefined = Value::Undefined;
let value = match args.len() {
0 => &undefined,
_ => &args[0],
};
if let Value::Object(ref object) = this {
let object = object.borrow();
if let Some(set) = object.as_set_ref() {
return Ok(set.contains(value).into());
}
}
Err(context.construct_type_error("'this' is not a Set"))
}
/// `Set.prototype.values( )`
///
/// This method returns an iterator over the values of the set
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.values
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values
pub(crate) fn values(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
if let Some(object) = this.as_object() {
let object = object.borrow();
if !object.is_set() {
return context.throw_type_error(
"Method Set.prototype.values called on incompatible receiver",
);
}
} else {
return context
.throw_type_error("Method Set.prototype.values called on incompatible receiver");
}
Ok(SetIterator::create_set_iterator(
context,
this.clone(),
SetIterationKind::Value,
))
}
fn size_getter(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
Set::get_size(this, context).map(Value::from)
}
/// Helper function to get the size of the set.
fn get_size(set: &Value, context: &mut Context) -> Result<usize> {
if let Value::Object(ref object) = set {
let object = object.borrow();
if let Some(set) = object.as_set_ref() {
Ok(set.size())
} else {
Err(context.construct_type_error("'this' is not a Set"))
}
} else {
Err(context.construct_type_error("'this' is not a Set"))
}
}
}

130
boa/src/builtins/set/ordered_set.rs

@ -0,0 +1,130 @@
use crate::gc::{custom_trace, Finalize, Trace};
use indexmap::{
set::{IntoIter, Iter},
IndexSet,
};
use std::{
collections::hash_map::RandomState,
fmt::Debug,
hash::{BuildHasher, Hash},
};
/// A newtype wrapping indexmap::IndexSet
#[derive(Clone)]
pub struct OrderedSet<V, S = RandomState>(IndexSet<V, S>)
where
V: Hash + Eq;
impl<V: Eq + Hash + Trace, S: BuildHasher> Finalize for OrderedSet<V, S> {}
unsafe impl<V: Eq + Hash + Trace, S: BuildHasher> Trace for OrderedSet<V, S> {
custom_trace!(this, {
for v in this.0.iter() {
mark(v);
}
});
}
impl<V: Hash + Eq + Debug> Debug for OrderedSet<V> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
self.0.fmt(formatter)
}
}
impl<V: Hash + Eq> Default for OrderedSet<V> {
fn default() -> Self {
Self::new()
}
}
impl<V> OrderedSet<V>
where
V: Hash + Eq,
{
pub fn new() -> Self {
OrderedSet(IndexSet::new())
}
pub fn with_capacity(capacity: usize) -> Self {
OrderedSet(IndexSet::with_capacity(capacity))
}
/// Return the number of key-value pairs in the map.
///
/// Computes in **O(1)** time.
pub fn size(&self) -> usize {
self.0.len()
}
/// Returns true if the map contains no elements.
///
/// Computes in **O(1)** time.
pub fn is_empty(&self) -> bool {
self.0.len() == 0
}
/// Insert a value pair in the set.
///
/// If an equivalent value already exists in the set: ???
///
/// If no equivalent value existed in the set: the new value is
/// inserted, last in order, and false
///
/// Computes in **O(1)** time (amortized average).
pub fn add(&mut self, value: V) -> bool {
self.0.insert(value)
}
/// Delete the `value` from the set and return true if successful
///
/// Return `false` if `value` is not in map.
///
/// Computes in **O(n)** time (average).
pub fn delete(&mut self, value: &V) -> bool {
self.0.shift_remove(value)
}
/// Checks if a given value is present in the set
///
/// Return `true` if `value` is present in set, false otherwise.
///
/// Computes in **O(n)** time (average).
pub fn contains(&self, value: &V) -> bool {
self.0.contains(value)
}
/// Get a key-value pair by index
/// Valid indices are 0 <= index < self.len()
/// Computes in O(1) time.
pub fn get_index(&self, index: usize) -> Option<&V> {
self.0.get_index(index)
}
/// Return an iterator over the values of the set, in their order
pub fn iter(&self) -> Iter<'_, V> {
self.0.iter()
}
}
impl<'a, V, S> IntoIterator for &'a OrderedSet<V, S>
where
V: Hash + Eq,
S: BuildHasher,
{
type Item = &'a V;
type IntoIter = Iter<'a, V>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<V, S> IntoIterator for OrderedSet<V, S>
where
V: Hash + Eq,
S: BuildHasher,
{
type Item = V;
type IntoIter = IntoIter<V>;
fn into_iter(self) -> IntoIter<V> {
self.0.into_iter()
}
}

151
boa/src/builtins/set/set_iterator.rs

@ -0,0 +1,151 @@
use crate::{
builtins::function::make_builtin_fn,
builtins::iterable::create_iter_result_object,
builtins::Array,
builtins::Value,
object::{GcObject, ObjectData},
property::{Attribute, DataDescriptor},
symbol::WellKnownSymbols,
BoaProfiler, Context, Result,
};
use gc::{Finalize, Trace};
#[derive(Debug, Clone, Finalize, Trace)]
pub enum SetIterationKind {
Value,
KeyAndValue,
}
/// The Set Iterator object represents an iteration over a set. It implements the iterator protocol.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-set-iterator-objects
#[derive(Debug, Clone, Finalize, Trace)]
pub struct SetIterator {
iterated_set: Value,
next_index: usize,
iteration_kind: SetIterationKind,
}
impl SetIterator {
pub(crate) const NAME: &'static str = "SetIterator";
/// Constructs a new `SetIterator`, that will iterate over `set`, starting at index 0
fn new(set: Value, kind: SetIterationKind) -> Self {
SetIterator {
iterated_set: set,
next_index: 0,
iteration_kind: kind,
}
}
/// Abstract operation CreateSetIterator( set, kind )
///
/// Creates a new iterator over the given set.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://www.ecma-international.org/ecma-262/11.0/index.html#sec-createsetiterator
pub(crate) fn create_set_iterator(
context: &Context,
set: Value,
kind: SetIterationKind,
) -> Value {
let set_iterator = Value::new_object(context);
set_iterator.set_data(ObjectData::SetIterator(Self::new(set, kind)));
set_iterator
.as_object()
.expect("set iterator object")
.set_prototype_instance(context.iterator_prototypes().set_iterator().into());
set_iterator
}
/// %SetIteratorPrototype%.next( )
///
/// Advances the iterator and gets the next result in the set.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next
pub(crate) fn next(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
if let Value::Object(ref object) = this {
let mut object = object.borrow_mut();
if let Some(set_iterator) = object.as_set_iterator_mut() {
let m = &set_iterator.iterated_set;
let mut index = set_iterator.next_index;
let item_kind = &set_iterator.iteration_kind;
if set_iterator.iterated_set.is_undefined() {
return Ok(create_iter_result_object(context, Value::undefined(), true));
}
if let Value::Object(ref object) = m {
if let Some(entries) = object.borrow().as_set_ref() {
let num_entries = entries.size();
while index < num_entries {
let e = entries.get_index(index);
index += 1;
set_iterator.next_index = index;
if let Some(value) = e {
match item_kind {
SetIterationKind::Value => {
return Ok(create_iter_result_object(
context,
value.clone(),
false,
));
}
SetIterationKind::KeyAndValue => {
let result = Array::construct_array(
&Array::new_array(context),
&[value.clone(), value.clone()],
context,
)?;
return Ok(create_iter_result_object(
context, result, false,
));
}
}
}
}
} else {
return Err(context.construct_type_error("'this' is not a Set"));
}
} else {
return Err(context.construct_type_error("'this' is not a Set"));
}
set_iterator.iterated_set = Value::undefined();
Ok(create_iter_result_object(context, Value::undefined(), true))
} else {
context.throw_type_error("`this` is not an SetIterator")
}
} else {
context.throw_type_error("`this` is not an SetIterator")
}
}
/// Create the %SetIteratorPrototype% object
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%-object
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: Value) -> GcObject {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype
let mut set_iterator = context.construct_object();
make_builtin_fn(Self::next, "next", &set_iterator, 0, context);
set_iterator.set_prototype_instance(iterator_prototype);
let to_string_tag = WellKnownSymbols::to_string_tag();
let to_string_tag_property = DataDescriptor::new("Set Iterator", Attribute::CONFIGURABLE);
set_iterator.insert(to_string_tag, to_string_tag_property);
set_iterator
}
}

248
boa/src/builtins/set/tests.rs

@ -0,0 +1,248 @@
use crate::{forward, Context};
#[test]
fn construct_empty() {
let mut context = Context::new();
let init = r#"
var empty = new Set();
"#;
forward(&mut context, init);
let result = forward(&mut context, "empty.size");
assert_eq!(result, "0");
}
#[test]
fn construct_from_array() {
let mut context = Context::new();
let init = r#"
let set = new Set(["one", "two"]);
"#;
forward(&mut context, init);
let result = forward(&mut context, "set.size");
assert_eq!(result, "2");
}
#[test]
fn clone() {
let mut context = Context::new();
let init = r#"
let original = new Set(["one", "two"]);
let clone = new Set(original);
"#;
forward(&mut context, init);
let result = forward(&mut context, "clone.size");
assert_eq!(result, "2");
let result = forward(
&mut context,
r#"
original.add("three");
original.size"#,
);
assert_eq!(result, "3");
let result = forward(&mut context, "clone.size");
assert_eq!(result, "2");
}
#[test]
fn symbol_iterator() {
let mut context = Context::new();
let init = r#"
const set1 = new Set();
set1.add('foo');
set1.add('bar');
const iterator = set1[Symbol.iterator]();
let item1 = iterator.next();
let item2 = iterator.next();
let item3 = iterator.next();
"#;
forward(&mut context, init);
let result = forward(&mut context, "item1.value");
assert_eq!(result, "\"foo\"");
let result = forward(&mut context, "item1.done");
assert_eq!(result, "false");
let result = forward(&mut context, "item2.value");
assert_eq!(result, "\"bar\"");
let result = forward(&mut context, "item2.done");
assert_eq!(result, "false");
let result = forward(&mut context, "item3.value");
assert_eq!(result, "undefined");
let result = forward(&mut context, "item3.done");
assert_eq!(result, "true");
}
#[test]
fn entries() {
let mut context = Context::new();
let init = r#"
const set1 = new Set();
set1.add('foo');
set1.add('bar');
const entriesIterator = set1.entries();
let item1 = entriesIterator.next();
let item2 = entriesIterator.next();
let item3 = entriesIterator.next();
"#;
forward(&mut context, init);
let result = forward(&mut context, "item1.value.length");
assert_eq!(result, "2");
let result = forward(&mut context, "item1.value[0]");
assert_eq!(result, "\"foo\"");
let result = forward(&mut context, "item1.value[1]");
assert_eq!(result, "\"foo\"");
let result = forward(&mut context, "item1.done");
assert_eq!(result, "false");
let result = forward(&mut context, "item2.value.length");
assert_eq!(result, "2");
let result = forward(&mut context, "item2.value[0]");
assert_eq!(result, "\"bar\"");
let result = forward(&mut context, "item2.value[1]");
assert_eq!(result, "\"bar\"");
let result = forward(&mut context, "item2.done");
assert_eq!(result, "false");
let result = forward(&mut context, "item3.value");
assert_eq!(result, "undefined");
let result = forward(&mut context, "item3.done");
assert_eq!(result, "true");
}
#[test]
fn merge() {
let mut context = Context::new();
let init = r#"
let first = new Set(["one", "two"]);
let second = new Set(["three", "four"]);
let third = new Set(["four", "five"]);
let merged1 = new Set([...first, ...second]);
let merged2 = new Set([...second, ...third]);
"#;
forward(&mut context, init);
let result = forward(&mut context, "merged1.size");
assert_eq!(result, "4");
let result = forward(&mut context, "merged2.size");
assert_eq!(result, "3");
}
#[test]
fn clear() {
let mut context = Context::new();
let init = r#"
let set = new Set(["one", "two"]);
set.clear();
"#;
forward(&mut context, init);
let result = forward(&mut context, "set.size");
assert_eq!(result, "0");
}
#[test]
fn delete() {
let mut context = Context::new();
let init = r#"
let set = new Set(["one", "two"]);
"#;
forward(&mut context, init);
let result = forward(&mut context, "set.delete('one')");
assert_eq!(result, "true");
let result = forward(&mut context, "set.size");
assert_eq!(result, "1");
let result = forward(&mut context, "set.delete('one')");
assert_eq!(result, "false");
}
#[test]
fn has() {
let mut context = Context::new();
let init = r#"
let set = new Set(["one", "two"]);
"#;
forward(&mut context, init);
let result = forward(&mut context, "set.has('one')");
assert_eq!(result, "true");
let result = forward(&mut context, "set.has('two')");
assert_eq!(result, "true");
let result = forward(&mut context, "set.has('three')");
assert_eq!(result, "false");
let result = forward(&mut context, "set.has()");
assert_eq!(result, "false");
}
#[test]
fn values_and_keys() {
let mut context = Context::new();
let init = r#"
const set1 = new Set();
set1.add('foo');
set1.add('bar');
const valuesIterator = set1.values();
let item1 = valuesIterator.next();
let item2 = valuesIterator.next();
let item3 = valuesIterator.next();
"#;
forward(&mut context, init);
let result = forward(&mut context, "item1.value");
assert_eq!(result, "\"foo\"");
let result = forward(&mut context, "item1.done");
assert_eq!(result, "false");
let result = forward(&mut context, "item2.value");
assert_eq!(result, "\"bar\"");
let result = forward(&mut context, "item2.done");
assert_eq!(result, "false");
let result = forward(&mut context, "item3.value");
assert_eq!(result, "undefined");
let result = forward(&mut context, "item3.done");
assert_eq!(result, "true");
let result = forward(&mut context, "set1.values == set1.keys");
assert_eq!(result, "true");
}
#[test]
fn for_each() {
let mut context = Context::new();
let init = r#"
let set = new Set([5, 10, 15]);
let value1Sum = 0;
let value2Sum = 0;
let sizeSum = 0;
function callingCallback(value1, value2, set) {
value1Sum += value1;
value2Sum += value2;
sizeSum += set.size;
}
set.forEach(callingCallback);
"#;
forward(&mut context, init);
assert_eq!(forward(&mut context, "value1Sum"), "30");
assert_eq!(forward(&mut context, "value2Sum"), "30");
assert_eq!(forward(&mut context, "sizeSum"), "9");
}
#[test]
fn recursive_display() {
let mut context = Context::new();
let init = r#"
let set = new Set();
let array = new Array([set]);
set.add(set);
"#;
forward(&mut context, init);
let result = forward(&mut context, "set");
assert_eq!(result, "Set { Set(1) }");
let result = forward(&mut context, "set.add(array)");
assert_eq!(result, "Set { Set(2), Array(1) }");
}
#[test]
fn not_a_function() {
let mut context = Context::new();
let init = r"
try {
let set = Set()
} catch(e) {
e.toString()
}
";
assert_eq!(
forward(&mut context, init),
"\"TypeError: calling a builtin Set constructor without new is forbidden\""
);
}

15
boa/src/context.rs

@ -96,6 +96,8 @@ pub struct StandardObjects {
syntax_error: StandardConstructor,
eval_error: StandardConstructor,
uri_error: StandardConstructor,
map: StandardConstructor,
set: StandardConstructor,
}
impl Default for StandardObjects {
@ -117,6 +119,8 @@ impl Default for StandardObjects {
syntax_error: StandardConstructor::default(),
eval_error: StandardConstructor::default(),
uri_error: StandardConstructor::default(),
map: StandardConstructor::default(),
set: StandardConstructor::default(),
}
}
}
@ -197,9 +201,20 @@ impl StandardObjects {
&self.eval_error
}
#[inline]
pub fn uri_error_object(&self) -> &StandardConstructor {
&self.uri_error
}
#[inline]
pub fn map_object(&self) -> &StandardConstructor {
&self.map
}
#[inline]
pub fn set_object(&self) -> &StandardConstructor {
&self.set
}
}
/// Javascript context. It is the primary way to interact with the runtime.

41
boa/src/object/internal_methods.rs

@ -211,36 +211,47 @@ impl GcObject {
}
match (&current, &desc) {
(PropertyDescriptor::Data(current), PropertyDescriptor::Data(desc)) => {
if !current.configurable() && !current.writable() {
if desc.writable() {
return false;
}
if !same_value(&desc.value(), &current.value()) {
return false;
}
}
}
(PropertyDescriptor::Data(current), PropertyDescriptor::Accessor(_)) => {
(
PropertyDescriptor::Data(current),
PropertyDescriptor::Accessor(AccessorDescriptor { get, set, .. }),
) => {
// 6. b
if !current.configurable() {
return false;
}
let current = AccessorDescriptor::new(None, None, current.attributes());
let current =
AccessorDescriptor::new(get.clone(), set.clone(), current.attributes());
self.insert(key, current);
return true;
}
(PropertyDescriptor::Accessor(current), PropertyDescriptor::Data(_)) => {
(
PropertyDescriptor::Accessor(current),
PropertyDescriptor::Data(DataDescriptor { value, .. }),
) => {
// 6. c
if !current.configurable() {
return false;
}
let current = DataDescriptor::new(Value::undefined(), current.attributes());
let current = DataDescriptor::new(value, current.attributes());
self.insert(key, current);
return true;
}
(PropertyDescriptor::Data(current), PropertyDescriptor::Data(desc)) => {
// 7.
if !current.configurable() && !current.writable() {
if desc.writable() {
return false;
}
if !same_value(&desc.value(), &current.value()) {
return false;
}
}
}
(PropertyDescriptor::Accessor(current), PropertyDescriptor::Accessor(desc)) => {
// 8.
if !current.configurable() {
if let (Some(current_get), Some(desc_get)) = (current.getter(), desc.getter()) {
if !GcObject::equals(&current_get, &desc_get) {

35
boa/src/object/mod.rs

@ -6,6 +6,8 @@ use crate::{
function::{BuiltInFunction, Function, FunctionFlags, NativeFunction},
map::map_iterator::MapIterator,
map::ordered_map::OrderedMap,
set::ordered_set::OrderedSet,
set::set_iterator::SetIterator,
string::string_iterator::StringIterator,
BigInt, Date, RegExp,
},
@ -88,6 +90,8 @@ pub enum ObjectData {
Boolean(bool),
ForInIterator(ForInIterator),
Function(Function),
Set(OrderedSet<Value>),
SetIterator(SetIterator),
String(RcString),
StringIterator(StringIterator),
Number(f64),
@ -112,6 +116,8 @@ impl Display for ObjectData {
Self::RegExp(_) => "RegExp",
Self::Map(_) => "Map",
Self::MapIterator(_) => "MapIterator",
Self::Set(_) => "Set",
Self::SetIterator(_) => "SetIterator",
Self::String(_) => "String",
Self::StringIterator(_) => "StringIterator",
Self::Symbol(_) => "Symbol",
@ -361,6 +367,35 @@ impl Object {
}
}
#[inline]
pub fn is_set(&self) -> bool {
matches!(self.data, ObjectData::Set(_))
}
#[inline]
pub fn as_set_ref(&self) -> Option<&OrderedSet<Value>> {
match self.data {
ObjectData::Set(ref set) => Some(set),
_ => None,
}
}
#[inline]
pub fn as_set_mut(&mut self) -> Option<&mut OrderedSet<Value>> {
match &mut self.data {
ObjectData::Set(set) => Some(set),
_ => None,
}
}
#[inline]
pub fn as_set_iterator_mut(&mut self) -> Option<&mut SetIterator> {
match &mut self.data {
ObjectData::SetIterator(iter) => Some(iter),
_ => None,
}
}
/// Checks if it a `String` object.
#[inline]
pub fn is_string(&self) -> bool {

2
boa/src/property/mod.rs

@ -35,7 +35,7 @@ pub use attribute::Attribute;
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
#[derive(Debug, Clone, Trace, Finalize)]
pub struct DataDescriptor {
value: Value,
pub(crate) value: Value,
attributes: Attribute,
}

23
boa/src/syntax/ast/node/operator/unary_op/mod.rs

@ -55,34 +55,34 @@ impl UnaryOp {
impl Executable for UnaryOp {
fn run(&self, context: &mut Context) -> Result<Value> {
let x = self.target().run(context)?;
Ok(match self.op() {
op::UnaryOp::Minus => x.neg(context)?,
op::UnaryOp::Plus => Value::from(x.to_number(context)?),
op::UnaryOp::Minus => self.target().run(context)?.neg(context)?,
op::UnaryOp::Plus => Value::from(self.target().run(context)?.to_number(context)?),
op::UnaryOp::IncrementPost => {
let x = self.target().run(context)?;
let ret = x.clone();
let result = x.to_number(context)? + 1.0;
context.set_value(self.target(), result.into())?;
ret
}
op::UnaryOp::IncrementPre => {
let result = x.to_number(context)? + 1.0;
let result = self.target().run(context)?.to_number(context)? + 1.0;
context.set_value(self.target(), result.into())?
}
op::UnaryOp::DecrementPost => {
let x = self.target().run(context)?;
let ret = x.clone();
let result = x.to_number(context)? - 1.0;
context.set_value(self.target(), result.into())?;
ret
}
op::UnaryOp::DecrementPre => {
let result = x.to_number(context)? - 1.0;
let result = self.target().run(context)?.to_number(context)? - 1.0;
context.set_value(self.target(), result.into())?
}
op::UnaryOp::Not => x.not(context)?.into(),
op::UnaryOp::Not => self.target().run(context)?.not(context)?.into(),
op::UnaryOp::Tilde => {
let num_v_a = x.to_number(context)?;
let num_v_a = self.target().run(context)?.to_number(context)?;
Value::from(if num_v_a.is_nan() {
-1
} else {
@ -90,7 +90,10 @@ impl Executable for UnaryOp {
!(num_v_a as i32)
})
}
op::UnaryOp::Void => Value::undefined(),
op::UnaryOp::Void => {
self.target().run(context)?;
Value::undefined()
}
op::UnaryOp::Delete => match *self.target() {
Node::GetConstField(ref get_const_field) => Value::boolean(
get_const_field
@ -118,7 +121,7 @@ impl Executable for UnaryOp {
| Node::UnaryOp(_) => Value::boolean(true),
_ => return context.throw_syntax_error(format!("wrong delete argument {}", self)),
},
op::UnaryOp::TypeOf => Value::from(x.get_type().as_str()),
op::UnaryOp::TypeOf => Value::from(self.target().run(context)?.get_type().as_str()),
})
}
}

29
boa/src/value/display.rs

@ -140,16 +140,7 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children:
}
}
ObjectData::Map(ref map) => {
let size = v
.get_own_property(&PropertyKey::from("size"))
// TODO: do this in a better way "unwrap"
.unwrap()
// FIXME: handle accessor descriptors
.as_data_descriptor()
.unwrap()
.value()
.as_number()
.unwrap() as i32;
let size = map.len();
if size == 0 {
return String::from("Map(0)");
}
@ -169,6 +160,24 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children:
format!("Map({})", size)
}
}
ObjectData::Set(ref set) => {
let size = set.size();
if size == 0 {
return String::from("Set(0)");
}
if print_children {
let entries = set
.iter()
.map(|value| log_string_from(value, print_internals, false))
.collect::<Vec<String>>()
.join(", ");
format!("Set {{ {} }}", entries)
} else {
format!("Set({})", size)
}
}
_ => display_obj(&x, print_internals),
}
}

1
boa/src/value/equality.rs

@ -192,6 +192,7 @@ fn same_value_non_numeric(x: &Value, y: &Value) -> bool {
(Value::String(ref x), Value::String(ref y)) => x == y,
(Value::Boolean(x), Value::Boolean(y)) => x == y,
(Value::Object(ref x), Value::Object(ref y)) => GcObject::equals(x, y),
(Value::Symbol(ref x), Value::Symbol(ref y)) => x == y,
_ => false,
}
}

2
boa/src/value/hash.rs

@ -43,7 +43,7 @@ impl Hash for Value {
Self::Null => NullHashable.hash(state),
Self::String(ref string) => string.hash(state),
Self::Boolean(boolean) => boolean.hash(state),
Self::Integer(integer) => integer.hash(state),
Self::Integer(integer) => RationalHashable(f64::from(*integer)).hash(state),
Self::BigInt(ref bigint) => bigint.hash(state),
Self::Rational(rational) => RationalHashable(*rational).hash(state),
Self::Symbol(ref symbol) => Hash::hash(symbol, state),

Loading…
Cancel
Save