Browse Source

Cleanup `Context` APIs (#2504)

Just a general cleanup of the APIs of our `Context`.

- Reordered the `pub` and `pub(crate)/fn` methods to have a clear separation between our public and private APIs.
- Removed the call method and added it to `JsValue` instead, which semantically makes a bit more sense.
- Removed the `construct_object` method, and added an utility method `new` to `JsObject` instead.
- Rewrote some patterns I found while rewriting the calls of the removed function.
pull/2510/head
José Julián Espina 2 years ago
parent
commit
cbf07824cb
  1. 102
      boa_engine/src/builtins/array/mod.rs
  2. 2
      boa_engine/src/builtins/array/tests.rs
  3. 9
      boa_engine/src/builtins/array_buffer/mod.rs
  4. 2
      boa_engine/src/builtins/date/mod.rs
  5. 7
      boa_engine/src/builtins/function/arguments.rs
  6. 5
      boa_engine/src/builtins/function/tests.rs
  7. 40
      boa_engine/src/builtins/iterable/mod.rs
  8. 4
      boa_engine/src/builtins/json/mod.rs
  9. 22
      boa_engine/src/builtins/object/mod.rs
  10. 109
      boa_engine/src/builtins/promise/mod.rs
  11. 16
      boa_engine/src/builtins/promise/promise_job.rs
  12. 2
      boa_engine/src/builtins/proxy/mod.rs
  13. 35
      boa_engine/src/builtins/regexp/mod.rs
  14. 2
      boa_engine/src/builtins/set/mod.rs
  15. 60
      boa_engine/src/builtins/string/mod.rs
  16. 37
      boa_engine/src/builtins/typed_array/integer_indexed_object.rs
  17. 4
      boa_engine/src/builtins/typed_array/mod.rs
  18. 6
      boa_engine/src/context/intrinsics.rs
  19. 413
      boa_engine/src/context/mod.rs
  20. 9
      boa_engine/src/object/builtins/jsarraybuffer.rs
  21. 2
      boa_engine/src/object/builtins/jsproxy.rs
  22. 4
      boa_engine/src/object/internal_methods/global.rs
  23. 4
      boa_engine/src/object/internal_methods/mod.rs
  24. 46
      boa_engine/src/object/jsobject.rs
  25. 6
      boa_engine/src/object/mod.rs
  26. 31
      boa_engine/src/object/operations.rs
  27. 3
      boa_engine/src/value/serde_json.rs
  28. 6
      boa_engine/src/value/tests.rs
  29. 3
      boa_engine/src/vm/code_block.rs
  30. 6
      boa_engine/src/vm/opcode/binary_ops/mod.rs
  31. 10
      boa_engine/src/vm/opcode/generator/mod.rs
  32. 4
      boa_engine/src/vm/opcode/get/name.rs
  33. 4
      boa_engine/src/vm/opcode/push/object.rs
  34. 2
      boa_examples/src/bin/closures.rs

102
boa_engine/src/builtins/array/mod.rs

@ -426,11 +426,56 @@ impl Array {
};
// 4. Let usingIterator be ? GetMethod(items, @@iterator).
let using_iterator = items
.get_method(WellKnownSymbols::iterator(), context)?
.map(JsValue::from);
let using_iterator = items.get_method(WellKnownSymbols::iterator(), context)?;
let Some(using_iterator) = using_iterator else {
// 6. NOTE: items is not an Iterable so assume it is an array-like object.
// 7. Let arrayLike be ! ToObject(items).
let array_like = items
.to_object(context)
.expect("should not fail according to spec");
// 8. Let len be ? LengthOfArrayLike(arrayLike).
let len = array_like.length_of_array_like(context)?;
// 9. If IsConstructor(C) is true, then
// a. Let A be ? Construct(C, « 𝔽(len) »).
// 10. Else,
// a. Let A be ? ArrayCreate(len).
let a = match this.as_constructor() {
Some(constructor) => constructor.construct(&[len.into()], None, context)?,
_ => Self::array_create(len, None, context)?,
};
// 11. Let k be 0.
// 12. Repeat, while k < len,
// ...
// f. Set k to k + 1.
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kValue be ? Get(arrayLike, Pk).
let k_value = array_like.get(k, context)?;
let mapped_value = if let Some(mapfn) = mapping {
// c. If mapping is true, then
// i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »).
mapfn.call(this_arg, &[k_value, k.into()], context)?
} else {
// d. Else, let mappedValue be kValue.
k_value
};
// e. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
a.create_data_property_or_throw(k, mapped_value, context)?;
}
// 13. Perform ? Set(A, "length", 𝔽(len), true).
a.set("length", len, true, context)?;
// 14. Return A.
return Ok(a.into());
};
if let Some(using_iterator) = using_iterator {
// 5. If usingIterator is not undefined, then
// a. If IsConstructor(C) is true, then
@ -496,53 +541,6 @@ impl Array {
// 2. Return ? IteratorClose(iteratorRecord, error).
iterator_record.close(error, context)
} else {
// 6. NOTE: items is not an Iterable so assume it is an array-like object.
// 7. Let arrayLike be ! ToObject(items).
let array_like = items
.to_object(context)
.expect("should not fail according to spec");
// 8. Let len be ? LengthOfArrayLike(arrayLike).
let len = array_like.length_of_array_like(context)?;
// 9. If IsConstructor(C) is true, then
// a. Let A be ? Construct(C, « 𝔽(len) »).
// 10. Else,
// a. Let A be ? ArrayCreate(len).
let a = match this.as_constructor() {
Some(constructor) => constructor.construct(&[len.into()], None, context)?,
_ => Self::array_create(len, None, context)?,
};
// 11. Let k be 0.
// 12. Repeat, while k < len,
// ...
// f. Set k to k + 1.
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kValue be ? Get(arrayLike, Pk).
let k_value = array_like.get(k, context)?;
let mapped_value = if let Some(mapfn) = mapping {
// c. If mapping is true, then
// i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »).
mapfn.call(this_arg, &[k_value, k.into()], context)?
} else {
// d. Else, let mappedValue be kValue.
k_value
};
// e. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
a.create_data_property_or_throw(k, mapped_value, context)?;
}
// 13. Perform ? Set(A, "length", 𝔽(len), true).
a.set("length", len, true, context)?;
// 14. Return A.
Ok(a.into())
}
}
/// `Array.isArray( arg )`
@ -2957,7 +2955,7 @@ impl Array {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@unscopables
pub(crate) fn unscopables_intrinsic(context: &mut Context) -> JsObject {
// 1. Let unscopableList be OrdinaryObjectCreate(null).
let unscopable_list = JsObject::empty();
let unscopable_list = JsObject::with_null_proto();
// 2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true).
unscopable_list
.create_data_property_or_throw("at", true, context)

2
boa_engine/src/builtins/array/tests.rs

@ -1411,7 +1411,7 @@ fn array_spread_non_iterable() {
try {
const array2 = [...5];
} catch (err) {
err.name === "TypeError" && err.message === "Value is not callable"
err.name === "TypeError" && err.message === "value with type `number` is not iterable"
}
"#;
assert_eq!(forward(&mut context, init), "true");

9
boa_engine/src/builtins/array_buffer/mod.rs

@ -340,19 +340,20 @@ impl ArrayBuffer {
StandardConstructors::array_buffer,
context,
)?;
let obj = context.construct_object();
obj.set_prototype(prototype.into());
// 2. Let block be ? CreateByteDataBlock(byteLength).
let block = create_byte_data_block(byte_length)?;
// 3. Set obj.[[ArrayBufferData]] to block.
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
obj.borrow_mut().data = ObjectData::array_buffer(Self {
let obj = JsObject::from_proto_and_data(
prototype,
ObjectData::array_buffer(Self {
array_buffer_data: Some(block),
array_buffer_byte_length: byte_length,
array_buffer_detach_key: JsValue::Undefined,
});
}),
);
// 5. Return obj.
Ok(obj)

2
boa_engine/src/builtins/date/mod.rs

@ -1234,7 +1234,7 @@ impl Date {
// 4. Return ? Invoke(O, "toISOString").
let func = o.get("toISOString", context)?;
context.call(&func, &o.into(), &[])
func.call(this, &[], context)
}
/// [`Date.prototype.toLocaleDateString()`][spec].

7
boa_engine/src/builtins/function/arguments.rs

@ -77,11 +77,12 @@ impl Arguments {
let len = arguments_list.len();
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »).
let obj = context.construct_object();
// 3. Set obj.[[ParameterMap]] to undefined.
// skipped because the `Arguments` enum ensures ordinary argument objects don't have a `[[ParameterMap]]`
obj.borrow_mut().data = ObjectData::arguments(Self::Unmapped);
let obj = JsObject::from_proto_and_data(
context.intrinsics().constructors().object().prototype(),
ObjectData::arguments(Self::Unmapped),
);
// 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len),
// [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).

5
boa_engine/src/builtins/function/tests.rs

@ -1,7 +1,7 @@
use crate::{
error::JsNativeError,
forward, forward_val, js_string,
object::FunctionBuilder,
object::{FunctionBuilder, JsObject},
property::{Attribute, PropertyDescriptor},
string::utf16,
Context, JsNativeErrorKind,
@ -230,7 +230,8 @@ fn closure_capture_clone() {
let mut context = Context::default();
let string = js_string!("Hello");
let object = context.construct_object();
let object = JsObject::with_object_proto(&mut context);
object
.define_property_or_throw(
"key",

40
boa_engine/src/builtins/iterable/mod.rs

@ -136,7 +136,7 @@ pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Conte
// 1. Assert: Type(done) is Boolean.
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
let obj = context.construct_object();
let obj = JsObject::with_object_proto(context);
// 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value).
obj.create_data_property_or_throw("value", value, context)
@ -169,13 +169,13 @@ impl JsValue {
&self,
context: &mut Context,
hint: Option<IteratorHint>,
method: Option<Self>,
method: Option<JsObject>,
) -> JsResult<IteratorRecord> {
// 1. If hint is not present, set hint to sync.
let hint = hint.unwrap_or(IteratorHint::Sync);
// 2. If method is not present, then
let method = if let Some(method) = method {
let method = if method.is_some() {
method
} else {
// a. If hint is async, then
@ -184,17 +184,15 @@ impl JsValue {
if let Some(method) =
self.get_method(WellKnownSymbols::async_iterator(), context)?
{
method.into()
Some(method)
} else {
// ii. If method is undefined, then
// 1. Let syncMethod be ? GetMethod(obj, @@iterator).
let sync_method = self
.get_method(WellKnownSymbols::iterator(), context)?
.map_or(Self::Undefined, Self::from);
let sync_method = self.get_method(WellKnownSymbols::iterator(), context)?;
// 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod).
let sync_iterator_record =
self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method))?;
self.get_iterator(context, Some(IteratorHint::Sync), sync_method)?;
// 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord).
return Ok(AsyncFromSyncIterator::create(sync_iterator_record, context));
@ -202,17 +200,22 @@ impl JsValue {
} else {
// b. Otherwise, set method to ? GetMethod(obj, @@iterator).
self.get_method(WellKnownSymbols::iterator(), context)?
.map_or(Self::Undefined, Self::from)
}
};
}
.ok_or_else(|| {
JsNativeError::typ().with_message(format!(
"value with type `{}` is not iterable",
self.type_of()
))
})?;
// 3. Let iterator be ? Call(method, obj).
let iterator = context.call(&method, self, &[])?;
let iterator = method.call(self, &[], context)?;
// 4. If Type(iterator) is not Object, throw a TypeError exception.
let iterator_obj = iterator
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("the iterator is not an object"))?;
let iterator_obj = iterator.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("returned iterator is not an object")
})?;
// 5. Let nextMethod be ? GetV(iterator, "next").
let next_method = iterator.get_v("next", context)?;
@ -500,20 +503,15 @@ impl IteratorRecord {
pub(crate) fn iterable_to_list(
context: &mut Context,
items: &JsValue,
method: Option<JsValue>,
method: Option<JsObject>,
) -> JsResult<Vec<JsValue>> {
let _timer = Profiler::global().start_event("iterable_to_list", "iterator");
// 1. If method is present, then
let iterator_record = if let Some(method) = method {
// a. Let iteratorRecord be ? GetIterator(items, sync, method).
items.get_iterator(context, Some(IteratorHint::Sync), Some(method))?
} else {
// 2. Else,
// a. Let iteratorRecord be ? GetIterator(items, sync).
items.get_iterator(context, Some(IteratorHint::Sync), None)?
};
let iterator_record = items.get_iterator(context, Some(IteratorHint::Sync), method)?;
// 3. Let values be a new empty List.
let mut values = Vec::new();

4
boa_engine/src/builtins/json/mod.rs

@ -203,7 +203,7 @@ impl Json {
// 11. If IsCallable(reviver) is true, then
if let Some(obj) = args.get_or_undefined(1).as_callable() {
// a. Let root be ! OrdinaryObjectCreate(%Object.prototype%).
let root = context.construct_object();
let root = JsObject::with_object_proto(context);
// b. Let rootName be the empty String.
// c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered).
@ -435,7 +435,7 @@ impl Json {
};
// 9. Let wrapper be ! OrdinaryObjectCreate(%Object.prototype%).
let wrapper = context.construct_object();
let wrapper = JsObject::with_object_proto(context);
// 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value).
wrapper

22
boa_engine/src/builtins/object/mod.rs

@ -124,18 +124,24 @@ impl Object {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If NewTarget is neither undefined nor the active function object, then
if !new_target.is_undefined() {
// a. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%").
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::object, context)?;
let object = JsObject::from_proto_and_data(prototype, ObjectData::ordinary());
return Ok(object.into());
}
if let Some(arg) = args.get(0) {
if !arg.is_null_or_undefined() {
return Ok(arg.to_object(context)?.into());
}
let value = args.get_or_undefined(0);
// 2. If value is undefined or null, return OrdinaryObjectCreate(%Object.prototype%).
if value.is_null_or_undefined() {
Ok(JsObject::with_object_proto(context).into())
} else {
// 3. Return ! ToObject(value).
value.to_object(context).map(JsValue::from)
}
Ok(context.construct_object().into())
}
/// `get Object.prototype.__proto__`
@ -471,7 +477,7 @@ impl Object {
let own_keys = obj.__own_property_keys__(context)?;
// 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%).
let descriptors = context.construct_object();
let descriptors = JsObject::with_object_proto(context);
// 4. For each element key of ownKeys, do
for key in own_keys {
@ -512,7 +518,7 @@ impl Object {
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
// 3. Assert: obj is an extensible ordinary object with no own properties.
let obj = context.construct_object();
let obj = JsObject::with_object_proto(context);
// 4. If Desc has a [[Value]] field, then
if let Some(value) = desc.value() {
@ -1242,7 +1248,7 @@ impl Object {
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
// 3. Assert: obj is an extensible ordinary object with no own properties.
let obj = context.construct_object();
let obj = JsObject::with_object_proto(context);
// 4. Let closure be a new Abstract Closure with parameters (key, value) that captures
// obj and performs the following steps when called:

109
boa_engine/src/builtins/promise/mod.rs

@ -41,11 +41,9 @@ macro_rules! if_abrupt_reject_promise {
Err(err) => {
let err = err.to_opaque($context);
// a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).
$context.call(
&$capability.reject().clone().into(),
&JsValue::undefined(),
&[err],
)?;
$capability
.reject()
.call(&JsValue::undefined(), &[err], $context)?;
// b. Return capability.[[Promise]].
return Ok($capability.promise().clone().into());
@ -297,8 +295,8 @@ impl BuiltIn for Promise {
#[derive(Debug)]
struct ResolvingFunctionsRecord {
resolve: JsValue,
reject: JsValue,
resolve: JsFunction,
reject: JsFunction,
}
impl Promise {
@ -327,14 +325,11 @@ impl Promise {
.into());
}
let executor = args.get_or_undefined(0);
// 2. If IsCallable(executor) is false, throw a TypeError exception.
if !executor.is_callable() {
return Err(JsNativeError::typ()
.with_message("Promise executor is not callable")
.into());
}
let executor = args
.get_or_undefined(0)
.as_callable()
.ok_or_else(|| JsNativeError::typ().with_message("Promise executor is not callable"))?;
// 3. Let promise be ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »).
let promise =
@ -358,20 +353,22 @@ impl Promise {
let resolving_functions = Self::create_resolving_functions(&promise, context);
// 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ).
let completion = context.call(
executor,
let completion = executor.call(
&JsValue::Undefined,
&[
resolving_functions.resolve,
resolving_functions.reject.clone(),
resolving_functions.resolve.clone().into(),
resolving_functions.reject.clone().into(),
],
context,
);
// 10. If completion is an abrupt completion, then
if let Err(e) = completion {
let e = e.to_opaque(context);
// a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »).
context.call(&resolving_functions.reject, &JsValue::Undefined, &[e])?;
resolving_functions
.reject
.call(&JsValue::Undefined, &[e], context)?;
}
// 11. Return promise.
@ -809,7 +806,7 @@ impl Promise {
// 8. Let remainingElementsCount be F.[[RemainingElements]].
// 9. Let obj be OrdinaryObjectCreate(%Object.prototype%).
let obj = context.construct_object();
let obj = JsObject::with_object_proto(context);
// 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled").
obj.create_data_property_or_throw("status", "fulfilled", context)
@ -893,7 +890,7 @@ impl Promise {
// 8. Let remainingElementsCount be F.[[RemainingElements]].
// 9. Let obj be OrdinaryObjectCreate(%Object.prototype%).
let obj = context.construct_object();
let obj = JsObject::with_object_proto(context);
// 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected").
obj.create_data_property_or_throw("status", "rejected", context)
@ -1429,8 +1426,6 @@ impl Promise {
.build();
// 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }.
let resolve = resolve.conv::<JsValue>();
let reject = reject.conv::<JsValue>();
ResolvingFunctionsRecord { resolve, reject }
}
@ -1589,7 +1584,7 @@ impl Promise {
&mut iterator_record,
c,
&promise_capability,
&promise_resolve.into(),
&promise_resolve,
context,
);
@ -1625,7 +1620,7 @@ impl Promise {
iterator_record: &mut IteratorRecord,
constructor: &JsValue,
result_capability: &PromiseCapability,
promise_resolve: &JsValue,
promise_resolve: &JsObject,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Repeat,
@ -1654,7 +1649,7 @@ impl Promise {
let next_value = next_value?;
// h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »).
let next_promise = context.call(promise_resolve, constructor, &[next_value])?;
let next_promise = promise_resolve.call(constructor, &[next_value], context)?;
// i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »).
next_promise.invoke(
@ -1694,11 +1689,10 @@ impl Promise {
let promise_capability = PromiseCapability::new(c, context)?;
// 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
context.call(
&promise_capability.reject.clone().into(),
&JsValue::undefined(),
&[r.clone()],
)?;
promise_capability
.reject
.call(&JsValue::undefined(), &[r.clone()], context)?;
// 4. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise.clone().into())
@ -1779,26 +1773,34 @@ impl Promise {
let promise = this;
// 2. If Type(promise) is not Object, throw a TypeError exception.
let Some(promise_obj) = promise.as_object() else {
let Some(promise) = promise.as_object() else {
return Err(JsNativeError::typ()
.with_message("finally called with a non-object promise")
.into());
};
// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
let c = promise_obj.species_constructor(StandardConstructors::promise, context)?;
let c = promise.species_constructor(StandardConstructors::promise, context)?;
// 4. Assert: IsConstructor(C) is true.
debug_assert!(c.is_constructor());
let on_finally = args.get_or_undefined(0);
let Some(on_finally) = on_finally.as_callable() else {
// 5. If IsCallable(onFinally) is false, then
let (then_finally, catch_finally) = if on_finally.is_callable() {
// a. Let thenFinally be onFinally.
// b. Let catchFinally be onFinally.
// 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
let then = promise.get("then", context)?;
return then.call(this, &[on_finally.clone(), on_finally.clone()], context);
};
let (then_finally, catch_finally) = {
/// Capture object for the `thenFinallyClosure` abstract closure.
#[derive(Debug, Trace, Finalize)]
struct FinallyCaptures {
on_finally: JsValue,
on_finally: JsObject,
c: JsObject,
}
@ -1815,7 +1817,9 @@ impl Promise {
let value = args.get_or_undefined(0);
// i. Let result be ? Call(onFinally, undefined).
let result = context.call(&captures.on_finally, &JsValue::undefined(), &[])?;
let result = captures
.on_finally
.call(&JsValue::undefined(), &[], context)?;
// ii. Let promise be ? PromiseResolve(C, result).
let promise = Self::promise_resolve(captures.c.clone(), result, context)?;
@ -1860,7 +1864,9 @@ impl Promise {
let reason = args.get_or_undefined(0);
// i. Let result be ? Call(onFinally, undefined).
let result = context.call(&captures.on_finally, &JsValue::undefined(), &[])?;
let result = captures
.on_finally
.call(&JsValue::undefined(), &[], context)?;
// ii. Let promise be ? PromiseResolve(C, result).
let promise = Self::promise_resolve(captures.c.clone(), result, context)?;
@ -1892,16 +1898,12 @@ impl Promise {
// d. Let catchFinally be CreateBuiltinFunction(catchFinallyClosure, 1, "", « »).
let catch_finally = catch_finally_closure.length(1).name("").build();
(then_finally.into(), catch_finally.into()) // TODO
} else {
// 6. Else,
// a. Let thenFinally be onFinally.
// b. Let catchFinally be onFinally.
(on_finally.clone(), on_finally.clone())
(then_finally.into(), catch_finally.into())
};
// 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
promise.invoke("then", &[then_finally, catch_finally], context)
let then = promise.get("then", context)?;
then.call(this, &[then_finally, catch_finally], context)
}
/// `Promise.prototype.then ( onFulfilled, onRejected )`
@ -2081,11 +2083,9 @@ impl Promise {
let promise_capability = PromiseCapability::new(&c.into(), context)?;
// 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »).
context.call(
&promise_capability.resolve.clone().into(),
&JsValue::undefined(),
&[x],
)?;
promise_capability
.resolve
.call(&JsValue::Undefined, &[x], context)?;
// 4. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise.clone().into())
@ -2109,13 +2109,10 @@ impl Promise {
let promise_resolve = promise_constructor.get("resolve", context)?;
// 2. If IsCallable(promiseResolve) is false, throw a TypeError exception.
promise_resolve.as_callable().map_or_else(
|| {
Err(JsNativeError::typ()
promise_resolve.as_callable().cloned().ok_or_else(|| {
JsNativeError::typ()
.with_message("retrieving a non-callable promise resolver")
.into())
},
|promise_resolve| Ok(promise_resolve.clone()),
)
.into()
})
}
}

16
boa_engine/src/builtins/promise/promise_job.rs

@ -83,13 +83,13 @@ impl PromiseJob {
// h. If handlerResult is an abrupt completion, then
Err(value) => {
// i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »).
context.call(&reject.clone().into(), &JsValue::Undefined, &[value])
reject.call(&JsValue::Undefined, &[value], context)
}
// i. Else,
Ok(value) => {
// i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »).
context.call(&resolve.clone().into(), &JsValue::Undefined, &[value])
resolve.call(&JsValue::Undefined, &[value], context)
}
}
}
@ -138,8 +138,8 @@ impl PromiseJob {
let then_call_result = then.call_job_callback(
thenable,
&[
resolving_functions.resolve,
resolving_functions.reject.clone(),
resolving_functions.resolve.clone().into(),
resolving_functions.reject.clone().into(),
],
context,
);
@ -148,11 +148,9 @@ impl PromiseJob {
if let Err(value) = then_call_result {
let value = value.to_opaque(context);
// i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »).
return context.call(
&resolving_functions.reject,
&JsValue::Undefined,
&[value],
);
return resolving_functions
.reject
.call(&JsValue::Undefined, &[value], context);
}
// d. Return ? thenCallResult.

2
boa_engine/src/builtins/proxy/mod.rs

@ -173,7 +173,7 @@ impl Proxy {
let revoker = Self::revoker(p.clone(), context);
// 5. Let result be ! OrdinaryObjectCreate(%Object.prototype%).
let result = context.construct_object();
let result = JsObject::with_object_proto(context);
// 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p).
result

35
boa_engine/src/builtins/regexp/mod.rs

@ -975,7 +975,7 @@ impl RegExp {
let named_groups = match_value.named_groups();
let groups = if named_groups.clone().count() > 0 {
// a. Let groups be ! OrdinaryObjectCreate(null).
let groups = JsObject::empty();
let groups = JsObject::with_null_proto();
// Perform 27.f here
// f. If the ith capture of R was defined with a GroupName, then
@ -1237,17 +1237,14 @@ impl RegExp {
let length_arg_str = arg_str.len();
// 5. Let functionalReplace be IsCallable(replaceValue).
let mut replace_value = args.get_or_undefined(1).clone();
let functional_replace = replace_value
.as_object()
.map(JsObject::is_callable)
.unwrap_or_default();
let replace_value = args.get_or_undefined(1).clone();
let replace = if let Some(f) = replace_value.as_callable() {
Ok(f)
} else {
// 6. If functionalReplace is false, then
if !functional_replace {
// a. Set replaceValue to ? ToString(replaceValue).
replace_value = replace_value.to_string(context)?.into();
}
Err(replace_value.to_string(context)?)
};
// 7. Let global be ! ToBoolean(? Get(rx, "global")).
let global = rx.get("global", context)?.to_boolean();
@ -1356,8 +1353,8 @@ impl RegExp {
let mut named_captures = result.get("groups", context)?;
// k. If functionalReplace is true, then
// l. Else,
let replacement = if functional_replace {
let replacement = match replace {
Ok(replace_fn) => {
// i. Let replacerArgs be « matched ».
let mut replacer_args = vec![JsValue::new(matched)];
@ -1375,12 +1372,13 @@ impl RegExp {
}
// v. Let replValue be ? Call(replaceValue, undefined, replacerArgs).
let repl_value =
context.call(&replace_value, &JsValue::undefined(), &replacer_args)?;
// vi. Let replacement be ? ToString(replValue).
repl_value.to_string(context)?
} else {
replace_fn
.call(&JsValue::undefined(), &replacer_args, context)?
.to_string(context)?
}
// l. Else,
Err(ref replace_str) => {
// i. If namedCaptures is not undefined, then
if !named_captures.is_undefined() {
// 1. Set namedCaptures to ? ToObject(namedCaptures).
@ -1394,9 +1392,10 @@ impl RegExp {
position,
&captures,
&named_captures,
&replace_value.to_string(context)?,
replace_str,
context,
)?
}
};
// m. If position ≥ nextSourcePosition, then

2
boa_engine/src/builtins/set/mod.rs

@ -371,7 +371,7 @@ impl Set {
.ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Set"))?;
if let Some(arguments) = arguments {
context.call(callback_arg, &this_arg, &arguments)?;
callback_arg.call(&this_arg, &arguments, context)?;
}
index += 1;

60
boa_engine/src/builtins/string/mod.rs

@ -938,10 +938,7 @@ impl String {
let search_str = search_value.to_string(context)?;
// 5. Let functionalReplace be IsCallable(replaceValue).
let functional_replace = replace_value
.as_object()
.map(JsObject::is_callable)
.unwrap_or_default();
let replace_obj = replace_value.as_callable();
// 6. If functionalReplace is false, then
// a. Set replaceValue to ? ToString(replaceValue).
@ -960,13 +957,13 @@ impl String {
// 11. If functionalReplace is true, then
// 12. Else,
let replacement = if functional_replace {
let replacement = if let Some(replace_fn) = replace_obj {
// a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)).
context
replace_fn
.call(
replace_value,
&JsValue::undefined(),
&[search_str.into(), position.into(), this_str.clone().into()],
context,
)?
.to_string(context)?
} else {
@ -1060,17 +1057,12 @@ impl String {
let search_string = search_value.to_string(context)?;
// 5. Let functionalReplace be IsCallable(replaceValue).
let functional_replace = replace_value
.as_object()
.map(JsObject::is_callable)
.unwrap_or_default();
let replace_value_string = if functional_replace {
None
let replace = if let Some(f) = replace_value.as_callable() {
Ok(f)
} else {
// a. Set replaceValue to ? ToString(replaceValue).
// 6. If functionalReplace is false, then
Some(replace_value.to_string(context)?)
// a. Set replaceValue to ? ToString(replaceValue).
Err(replace_value.to_string(context)?)
};
// 7. Let searchLength be the length of searchString.
@ -1106,35 +1098,35 @@ impl String {
let preserved = &string[end_of_last_match..p];
// c. Else,
let replacement = if let Some(ref replace_value) = replace_value_string {
// i. Assert: Type(replaceValue) is String.
// ii. Let captures be a new empty List.
// iii. Let replacement be ! GetSubstitution(searchString, string, p, captures, undefined, replaceValue).
get_substitution(
&search_string,
&string,
p,
&[],
&JsValue::undefined(),
replace_value,
context,
)
.expect("GetSubstitution should never fail here.")
}
let replacement = match replace {
// b. If functionalReplace is true, then
else {
Ok(replace_fn) => {
// i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)).
context
replace_fn
.call(
replace_value,
&JsValue::undefined(),
&[
search_string.clone().into(),
p.into(),
string.clone().into(),
],
context,
)?
.to_string(context)?
}
// i. Assert: Type(replaceValue) is String.
// ii. Let captures be a new empty List.
// iii. Let replacement be ! GetSubstitution(searchString, string, p, captures, undefined, replaceValue).
Err(ref replace_str) => get_substitution(
&search_string,
&string,
p,
&[],
&JsValue::undefined(),
replace_str,
context,
)
.expect("GetSubstitution should never fail here."),
};
// d. Set result to the string-concatenation of result, preserved, and replacement.

37
boa_engine/src/builtins/typed_array/integer_indexed_object.rs

@ -8,11 +8,7 @@
//!
//! [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects
use crate::{
builtins::typed_array::TypedArrayKind,
object::{JsObject, ObjectData},
Context,
};
use crate::{builtins::typed_array::TypedArrayKind, object::JsObject};
use boa_gc::{Finalize, Trace};
/// Type of the array content.
@ -50,37 +46,6 @@ impl IntegerIndexed {
}
}
/// `IntegerIndexedObjectCreate ( prototype )`
///
/// Create a new `JsObject` from a prototype and a `IntegerIndexedObject`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-integerindexedobjectcreate
pub(super) fn create(prototype: JsObject, data: Self, context: &Context) -> JsObject {
// 1. Let internalSlotsList be « [[Prototype]], [[Extensible]], [[ViewedArrayBuffer]],
// [[TypedArrayName]], [[ContentType]], [[ByteLength]], [[ByteOffset]],
// [[ArrayLength]] ».
// 2. Let A be ! MakeBasicObject(internalSlotsList).
let a = context.construct_object();
// 3. Set A.[[GetOwnProperty]] as specified in 10.4.5.1.
// 4. Set A.[[HasProperty]] as specified in 10.4.5.2.
// 5. Set A.[[DefineOwnProperty]] as specified in 10.4.5.3.
// 6. Set A.[[Get]] as specified in 10.4.5.4.
// 7. Set A.[[Set]] as specified in 10.4.5.5.
// 8. Set A.[[Delete]] as specified in 10.4.5.6.
// 9. Set A.[[OwnPropertyKeys]] as specified in 10.4.5.7.
a.borrow_mut().data = ObjectData::integer_indexed(data);
// 10. Set A.[[Prototype]] to prototype.
a.set_prototype(prototype.into());
// 11. Return A.
a
}
/// Abstract operation `IsDetachedBuffer ( arrayBuffer )`.
///
/// Check if `[[ArrayBufferData]]` is null.

4
boa_engine/src/builtins/typed_array/mod.rs

@ -433,7 +433,7 @@ impl TypedArray {
// 6. If usingIterator is not undefined, then
if let Some(using_iterator) = using_iterator {
// a. Let values be ? IterableToList(source, usingIterator).
let values = iterable_to_list(context, source, Some(using_iterator.into()))?;
let values = iterable_to_list(context, source, Some(using_iterator))?;
// b. Let len be the number of elements in values.
// c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »).
@ -3188,7 +3188,7 @@ impl TypedArray {
}
// 2. Let obj be ! IntegerIndexedObjectCreate(proto).
let obj = IntegerIndexed::create(proto, indexed, context);
let obj = JsObject::from_proto_and_data(proto, ObjectData::integer_indexed(indexed));
// 9. Return obj.
Ok(obj)

6
boa_engine/src/context/intrinsics.rs

@ -42,8 +42,8 @@ pub struct StandardConstructor {
impl Default for StandardConstructor {
fn default() -> Self {
Self {
constructor: JsObject::empty(),
prototype: JsObject::empty(),
constructor: JsObject::with_null_proto(),
prototype: JsObject::with_null_proto(),
}
}
}
@ -52,7 +52,7 @@ impl StandardConstructor {
/// Build a constructor with a defined prototype.
fn with_prototype(prototype: JsObject) -> Self {
Self {
constructor: JsObject::empty(),
constructor: JsObject::with_null_proto(),
prototype,
}
}

413
boa_engine/src/context/mod.rs

@ -17,13 +17,12 @@ use crate::{
builtins::{self, function::NativeFunctionSignature},
bytecompiler::ByteCompiler,
class::{Class, ClassBuilder},
error::JsNativeError,
job::JobCallback,
object::{FunctionBuilder, GlobalPropertyMap, JsObject, ObjectData},
object::{FunctionBuilder, GlobalPropertyMap, JsObject},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
vm::{CallFrame, CodeBlock, FinallyReturn, GeneratorResumeKind, Vm},
JsResult, JsString, JsValue,
JsResult, JsValue,
};
use boa_ast::StatementList;
@ -131,6 +130,8 @@ impl Default for Context {
}
}
// ==== Public API ====
impl Context {
/// Create a new [`ContextBuilder`] to specify the [`Interner`] and/or
/// the icu data provider.
@ -139,44 +140,34 @@ impl Context {
ContextBuilder::default()
}
/// Gets the string interner.
#[inline]
pub const fn interner(&self) -> &Interner {
&self.interner
}
/// Gets a mutable reference to the string interner.
#[inline]
pub fn interner_mut(&mut self) -> &mut Interner {
&mut self.interner
}
/// A helper function for getting an immutable reference to the `console` object.
#[cfg(feature = "console")]
pub(crate) const fn console(&self) -> &Console {
&self.console
}
/// Evaluates the given code by compiling down to bytecode, then interpreting the bytecode into a value
///
/// # Examples
/// ```
/// # use boa_engine::Context;
/// let mut context = Context::default();
///
/// let value = context.eval("1 + 3").unwrap();
///
/// assert!(value.is_number());
/// assert_eq!(value.as_number().unwrap(), 4.0);
/// ```
#[allow(clippy::unit_arg, clippy::drop_copy)]
pub fn eval<S>(&mut self, src: S) -> JsResult<JsValue>
where
S: AsRef<[u8]>,
{
let main_timer = Profiler::global().start_event("Evaluation", "Main");
/// A helper function for getting a mutable reference to the `console` object.
#[cfg(feature = "console")]
pub(crate) fn console_mut(&mut self) -> &mut Console {
&mut self.console
}
let statement_list = self.parse(src)?;
let code_block = self.compile(&statement_list)?;
let result = self.execute(code_block);
/// Sets up the default global objects within Global
fn create_intrinsics(&mut self) {
let _timer = Profiler::global().start_event("create_intrinsics", "interpreter");
// Create intrinsics, add global objects here
builtins::init(self);
}
// The main_timer needs to be dropped before the Profiler is.
drop(main_timer);
Profiler::global().drop();
/// Constructs an object with the `%Object.prototype%` prototype.
#[inline]
pub fn construct_object(&self) -> JsObject {
JsObject::from_proto_and_data(
self.intrinsics().constructors().object().prototype(),
ObjectData::ordinary(),
)
result
}
/// Parse the given source text.
@ -184,70 +175,91 @@ impl Context {
where
S: AsRef<[u8]>,
{
let _timer = Profiler::global().start_event("Parsing", "Main");
let mut parser = Parser::new(src.as_ref());
parser.parse_all(&mut self.interner)
}
/// `Call ( F, V [ , argumentsList ] )`
///
/// The abstract operation `Call` takes arguments `F` (an ECMAScript language value) and `V`
/// (an ECMAScript language value) and optional argument `argumentsList` (a `List` of
/// ECMAScript language values) and returns either a normal completion containing an ECMAScript
/// language value or a throw completion. It is used to call the `[[Call]]` internal method of
/// a function object. `F` is the function object, `V` is an ECMAScript language value that is
/// the `this` value of the `[[Call]]`, and `argumentsList` is the value passed to the
/// corresponding argument of the internal method. If `argumentsList` is not present, a new
/// empty `List` is used as its value.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-call
pub(crate) fn call(
&mut self,
f: &JsValue,
v: &JsValue,
arguments_list: &[JsValue],
) -> JsResult<JsValue> {
// 1. If argumentsList is not present, set argumentsList to a new empty List.
// 2. If IsCallable(F) is false, throw a TypeError exception.
// 3. Return ? F.[[Call]](V, argumentsList).
f.as_callable()
.ok_or_else(|| {
JsNativeError::typ()
.with_message("Value is not callable")
.into()
})
.and_then(|f| f.call(v, arguments_list, self))
/// Compile the AST into a `CodeBlock` ready to be executed by the VM.
pub fn compile(&mut self, statement_list: &StatementList) -> JsResult<Gc<CodeBlock>> {
let _timer = Profiler::global().start_event("Compilation", "Main");
let mut compiler = ByteCompiler::new(Sym::MAIN, statement_list.strict(), false, self);
compiler.create_decls(statement_list, false);
compiler.compile_statement_list(statement_list, true, false)?;
Ok(Gc::new(compiler.finish()))
}
/// Return the global object.
#[inline]
pub const fn global_object(&self) -> &JsObject {
self.realm.global_object()
}
/// Call the VM with a `CodeBlock` and return the result.
///
/// Since this function receives a `Gc<CodeBlock>`, cloning the code is very cheap, since it's
/// just a pointer copy. Therefore, if you'd like to execute the same `CodeBlock` multiple
/// times, there is no need to re-compile it, and you can just call `clone()` on the
/// `Gc<CodeBlock>` returned by the [`Self::compile()`] function.
pub fn execute(&mut self, code_block: Gc<CodeBlock>) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Execution", "Main");
/// Return a mutable reference to the global object string bindings.
pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap {
self.realm.global_bindings_mut()
self.vm.push_frame(CallFrame {
code: code_block,
pc: 0,
catch: Vec::new(),
finally_return: FinallyReturn::None,
finally_jump: Vec::new(),
pop_on_return: 0,
loop_env_stack: Vec::from([0]),
try_env_stack: Vec::from([crate::vm::TryStackEntry {
num_env: 0,
num_loop_stack_entries: 0,
}]),
param_count: 0,
arg_count: 0,
generator_resume_kind: GeneratorResumeKind::Normal,
thrown: false,
async_generator: None,
});
self.realm.set_global_binding_number();
let result = self.run();
self.vm.pop_frame();
self.clear_kept_objects();
self.run_queued_jobs()?;
let (result, _) = result?;
Ok(result)
}
/// Constructs a `Error` with the specified message.
pub fn construct_error<M>(&mut self, message: M) -> JsValue
/// Register a global property.
///
/// # Example
/// ```
/// use boa_engine::{
/// object::ObjectInitializer,
/// property::{Attribute, PropertyDescriptor},
/// Context,
/// };
///
/// let mut context = Context::default();
///
/// context.register_global_property("myPrimitiveProperty", 10, Attribute::all());
///
/// let object = ObjectInitializer::new(&mut context)
/// .property("x", 0, Attribute::all())
/// .property("y", 1, Attribute::all())
/// .build();
/// context.register_global_property("myObjectProperty", object, Attribute::all());
/// ```
pub fn register_global_property<K, V>(&mut self, key: K, value: V, attribute: Attribute)
where
M: Into<JsString>,
K: Into<PropertyKey>,
V: Into<JsValue>,
{
crate::builtins::error::Error::constructor(
&self
.intrinsics()
.constructors()
.error()
.constructor()
.into(),
&[message.into().into()],
self,
)
.expect("Into<String> used as message")
self.realm.global_property_map.insert(
&key.into(),
PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable())
.build(),
);
}
/// Register a global native function.
@ -372,12 +384,6 @@ impl Context {
Ok(())
}
/// <https://tc39.es/ecma262/#sec-hasproperty>
pub(crate) fn has_property(&mut self, obj: &JsValue, key: &PropertyKey) -> JsResult<bool> {
obj.as_object()
.map_or(Ok(false), |obj| obj.__has_property__(key, self))
}
/// Register a global class of type `T`, where `T` implements `Class`.
///
/// # Example
@ -410,84 +416,82 @@ impl Context {
Ok(())
}
/// Register a global property.
///
/// # Example
/// ```
/// use boa_engine::{
/// object::ObjectInitializer,
/// property::{Attribute, PropertyDescriptor},
/// Context,
/// };
///
/// let mut context = Context::default();
///
/// context.register_global_property("myPrimitiveProperty", 10, Attribute::all());
///
/// let object = ObjectInitializer::new(&mut context)
/// .property("x", 0, Attribute::all())
/// .property("y", 1, Attribute::all())
/// .build();
/// context.register_global_property("myObjectProperty", object, Attribute::all());
/// ```
pub fn register_global_property<K, V>(&mut self, key: K, value: V, attribute: Attribute)
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
self.realm.global_property_map.insert(
&key.into(),
PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable())
.build(),
);
/// Gets the string interner.
#[inline]
pub const fn interner(&self) -> &Interner {
&self.interner
}
/// Evaluates the given code by compiling down to bytecode, then interpreting the bytecode into a value
/// Gets a mutable reference to the string interner.
#[inline]
pub fn interner_mut(&mut self) -> &mut Interner {
&mut self.interner
}
/// Return the global object.
#[inline]
pub const fn global_object(&self) -> &JsObject {
self.realm.global_object()
}
/// Return the intrinsic constructors and objects.
#[inline]
pub const fn intrinsics(&self) -> &Intrinsics {
&self.intrinsics
}
/// Set the value of trace on the context
pub fn set_trace(&mut self, trace: bool) {
self.vm.trace = trace;
}
/// More information:
/// - [ECMAScript reference][spec]
///
/// # Examples
/// ```
/// # use boa_engine::Context;
/// let mut context = Context::default();
/// [spec]: https://tc39.es/ecma262/#sec-hostenqueuepromisejob
pub fn host_enqueue_promise_job(&mut self, job: JobCallback /* , realm: Realm */) {
// If realm is not null ...
// TODO
// Let scriptOrModule be ...
// TODO
self.promise_job_queue.push_back(job);
}
/// Abstract operation [`ClearKeptObjects`][clear].
///
/// let value = context.eval("1 + 3").unwrap();
/// Clears all objects maintained alive by calls to the [`AddToKeptObjects`][add] abstract
/// operation, used within the [`WeakRef`][weak] constructor.
///
/// assert!(value.is_number());
/// assert_eq!(value.as_number().unwrap(), 4.0);
/// ```
#[allow(clippy::unit_arg, clippy::drop_copy)]
pub fn eval<S>(&mut self, src: S) -> JsResult<JsValue>
where
S: AsRef<[u8]>,
{
let main_timer = Profiler::global().start_event("Evaluation", "Main");
let statement_list = Parser::new(src.as_ref()).parse_all(&mut self.interner)?;
/// [clear]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-clear-kept-objects
/// [add]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-addtokeptobjects
/// [weak]: https://tc39.es/ecma262/multipage/managing-memory.html#sec-weak-ref-objects
pub fn clear_kept_objects(&mut self) {
self.kept_alive.clear();
}
}
let code_block = self.compile(&statement_list)?;
let result = self.execute(code_block);
// ==== Private API ====
// The main_timer needs to be dropped before the Profiler is.
drop(main_timer);
Profiler::global().drop();
impl Context {
/// A helper function for getting an immutable reference to the `console` object.
#[cfg(feature = "console")]
pub(crate) const fn console(&self) -> &Console {
&self.console
}
result
/// A helper function for getting a mutable reference to the `console` object.
#[cfg(feature = "console")]
pub(crate) fn console_mut(&mut self) -> &mut Console {
&mut self.console
}
/// Compile the AST into a `CodeBlock` ready to be executed by the VM.
pub fn compile(&mut self, statement_list: &StatementList) -> JsResult<Gc<CodeBlock>> {
let _timer = Profiler::global().start_event("Compilation", "Main");
let mut compiler = ByteCompiler::new(Sym::MAIN, statement_list.strict(), false, self);
compiler.create_decls(statement_list, false);
compiler.compile_statement_list(statement_list, true, false)?;
Ok(Gc::new(compiler.finish()))
/// Return a mutable reference to the global object string bindings.
pub(crate) fn global_bindings_mut(&mut self) -> &mut GlobalPropertyMap {
self.realm.global_bindings_mut()
}
/// Compile the AST into a `CodeBlock` ready to be executed by the VM in a `JSON.parse` context.
pub fn compile_json_parse(
pub(crate) fn compile_json_parse(
&mut self,
statement_list: &StatementList,
) -> JsResult<Gc<CodeBlock>> {
@ -510,41 +514,17 @@ impl Context {
Ok(Gc::new(compiler.finish()))
}
/// Call the VM with a `CodeBlock` and return the result.
///
/// Since this function receives a `Gc<CodeBlock>`, cloning the code is very cheap, since it's
/// just a pointer copy. Therefore, if you'd like to execute the same `CodeBlock` multiple
/// times, there is no need to re-compile it, and you can just call `clone()` on the
/// `Gc<CodeBlock>` returned by the [`Self::compile()`] function.
pub fn execute(&mut self, code_block: Gc<CodeBlock>) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Execution", "Main");
self.vm.push_frame(CallFrame {
code: code_block,
pc: 0,
catch: Vec::new(),
finally_return: FinallyReturn::None,
finally_jump: Vec::new(),
pop_on_return: 0,
loop_env_stack: Vec::from([0]),
try_env_stack: Vec::from([crate::vm::TryStackEntry {
num_env: 0,
num_loop_stack_entries: 0,
}]),
param_count: 0,
arg_count: 0,
generator_resume_kind: GeneratorResumeKind::Normal,
thrown: false,
async_generator: None,
});
/// Get the ICU related utilities
#[cfg(feature = "intl")]
pub(crate) const fn icu(&self) -> &icu::Icu<BoaProvider> {
&self.icu
}
self.realm.set_global_binding_number();
let result = self.run();
self.vm.pop_frame();
self.clear_kept_objects();
self.run_queued_jobs()?;
let (result, _) = result?;
Ok(result)
/// Sets up the default global objects within Global
fn create_intrinsics(&mut self) {
let _timer = Profiler::global().start_event("create_intrinsics", "interpreter");
// Create intrinsics, add global objects here
builtins::init(self);
}
/// Runs all the jobs in the job queue.
@ -555,47 +535,6 @@ impl Context {
}
Ok(())
}
/// Return the intrinsic constructors and objects.
#[inline]
pub const fn intrinsics(&self) -> &Intrinsics {
&self.intrinsics
}
/// Set the value of trace on the context
pub fn set_trace(&mut self, trace: bool) {
self.vm.trace = trace;
}
#[cfg(feature = "intl")]
/// Get the ICU related utilities
pub(crate) const fn icu(&self) -> &icu::Icu<BoaProvider> {
&self.icu
}
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-hostenqueuepromisejob
pub fn host_enqueue_promise_job(&mut self, job: JobCallback /* , realm: Realm */) {
// If realm is not null ...
// TODO
// Let scriptOrModule be ...
// TODO
self.promise_job_queue.push_back(job);
}
/// Abstract operation [`ClearKeptObjects`][clear].
///
/// Clears all objects maintained alive by calls to the [`AddToKeptObjects`][add] abstract
/// operation, used within the [`WeakRef`][weak] constructor.
///
/// [clear]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-clear-kept-objects
/// [add]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-addtokeptobjects
/// [weak]: https://tc39.es/ecma262/multipage/managing-memory.html#sec-weak-ref-objects
pub fn clear_kept_objects(&mut self) {
self.kept_alive.clear();
}
}
/// Builder for the [`Context`] type.
///

9
boa_engine/src/object/builtins/jsarraybuffer.rs

@ -91,8 +91,6 @@ impl JsArrayBuffer {
StandardConstructors::array_buffer,
context,
)?;
let obj = context.construct_object();
obj.set_prototype(prototype.into());
// 2. Let block be ? CreateByteDataBlock(byteLength).
//
@ -102,11 +100,14 @@ impl JsArrayBuffer {
// 3. Set obj.[[ArrayBufferData]] to block.
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
obj.borrow_mut().data = ObjectData::array_buffer(ArrayBuffer {
let obj = JsObject::from_proto_and_data(
prototype,
ObjectData::array_buffer(ArrayBuffer {
array_buffer_data: Some(block),
array_buffer_byte_length: byte_length as u64,
array_buffer_detach_key: JsValue::Undefined,
});
}),
);
Ok(Self { inner: obj })
}

2
boa_engine/src/object/builtins/jsproxy.rs

@ -371,7 +371,7 @@ impl JsProxyBuilder {
/// inside Rust code.
#[must_use]
pub fn build(self, context: &mut Context) -> JsProxy {
let handler = context.construct_object();
let handler = JsObject::with_object_proto(context);
if let Some(apply) = self.apply {
let f = FunctionBuilder::native(context, apply).length(3).build();

4
boa_engine/src/object/internal_methods/global.rs

@ -254,7 +254,7 @@ pub(crate) fn global_get(
// 6. Let getter be desc.[[Get]].
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
// 8. Return ? Call(getter, Receiver).
context.call(get, &receiver, &[])
get.call(&receiver, &[], context)
}
// 7. If getter is undefined, return undefined.
_ => Ok(JsValue::undefined()),
@ -360,7 +360,7 @@ pub(crate) fn global_set_no_receiver(
match own_desc.set() {
Some(set) if !set.is_undefined() => {
// 7. Perform ? Call(setter, Receiver, « V »).
context.call(set, &context.global_object().clone().into(), &[value])?;
set.call(&context.global_object().clone().into(), &[value], context)?;
// 8. Return true.
Ok(true)

4
boa_engine/src/object/internal_methods/mod.rs

@ -544,7 +544,7 @@ pub(crate) fn ordinary_get(
// 6. Let getter be desc.[[Get]].
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
// 8. Return ? Call(getter, Receiver).
context.call(get, &receiver, &[])
get.call(&receiver, &[], context)
}
// 7. If getter is undefined, return undefined.
_ => Ok(JsValue::undefined()),
@ -644,7 +644,7 @@ pub(crate) fn ordinary_set(
match own_desc.set() {
Some(set) if !set.is_undefined() => {
// 7. Perform ? Call(setter, Receiver, « V »).
context.call(set, &receiver, &[value])?;
set.call(&receiver, &[value], context)?;
// 8. Return true.
Ok(true)

46
boa_engine/src/object/jsobject.rs

@ -33,32 +33,50 @@ pub struct JsObject {
}
impl JsObject {
/// Create a new `JsObject` from an internal `Object`.
fn from_object(object: Object) -> Self {
Self {
inner: Gc::new(GcCell::new(object)),
}
/// Creates a new ordinary object with its prototype set to the `Object` prototype.
///
/// This is equivalent to calling the specification's abstract operation
/// [`OrdinaryObjectCreate(%Object.prototype%)`][call].
///
/// [call]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
#[inline]
#[must_use]
pub fn with_object_proto(context: &mut Context) -> Self {
Self::from_proto_and_data(
context.intrinsics().constructors().object().prototype(),
ObjectData::ordinary(),
)
}
/// Create a new empty `JsObject`, with `prototype` set to `JsValue::Null`
/// and `data` set to `ObjectData::ordinary`
/// Creates a new ordinary object, with its prototype set to null.
///
/// This is equivalent to calling the specification's abstract operation
/// [`OrdinaryObjectCreate(null)`][call].
///
/// [call]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
#[inline]
#[must_use]
pub fn empty() -> Self {
Self::from_object(Object::default())
pub fn with_null_proto() -> Self {
Self::from_proto_and_data(None, ObjectData::ordinary())
}
/// The more general form of `OrdinaryObjectCreate` and `MakeBasicObject`.
/// Creates a new object with the provided prototype and object data.
///
/// Create a `JsObject` and automatically set its internal methods and
/// internal slots from the `data` provided.
/// This is equivalent to calling the specification's abstract operation [`OrdinaryObjectCreate`],
/// with the difference that the `additionalInternalSlotsList` parameter is automatically set by
/// the [`ObjectData`] provided.
///
/// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
pub fn from_proto_and_data<O: Into<Option<Self>>>(prototype: O, data: ObjectData) -> Self {
Self::from_object(Object {
Self {
inner: Gc::new(GcCell::new(Object {
data,
prototype: prototype.into(),
extensible: true,
properties: PropertyMap::default(),
private_elements: FxHashMap::default(),
})
})),
}
}
/// Immutably borrows the `Object`.

6
boa_engine/src/object/mod.rs

@ -2095,7 +2095,7 @@ impl<'context> ObjectInitializer<'context> {
/// Create a new `ObjectBuilder`.
#[inline]
pub fn new(context: &'context mut Context) -> Self {
let object = context.construct_object();
let object = JsObject::with_object_proto(context);
Self { context, object }
}
@ -2186,8 +2186,8 @@ impl<'context> ConstructorBuilder<'context> {
Self {
context,
function,
object: JsObject::empty(),
prototype: JsObject::empty(),
object: JsObject::with_null_proto(),
prototype: JsObject::with_null_proto(),
length: 0,
name: js_string!(),
callable: true,

31
boa_engine/src/object/operations.rs

@ -762,6 +762,35 @@ impl JsValue {
Ok(list)
}
/// Abstract operation [`Call ( F, V [ , argumentsList ] )`][call].
///
/// Calls this value if the value is a callable object.
///
/// # Note
///
/// It is almost always better to try to obtain a callable object first with [`JsValue::as_callable`],
/// then calling [`JsObject::call`], since that allows reusing the unwrapped function for other
/// operations. This method is only an utility method for when the spec directly uses `Call`
/// without using the value as a proper object.
///
/// [call]: https://tc39.es/ecma262/#sec-call
#[inline]
pub(crate) fn call(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
self.as_callable()
.ok_or_else(|| {
JsNativeError::typ().with_message(format!(
"value with type `{}` is not callable",
self.type_of()
))
})?
.__call__(this, args, context)
}
/// Abstract operation `( V, P [ , argumentsList ] )`
///
/// Calls a method property of an ECMAScript language value.
@ -779,7 +808,7 @@ impl JsValue {
let func = self.get_v(key, context)?;
// 3. Return ? Call(func, V, argumentsList)
context.call(&func, self, args)
func.call(self, args, context)
}
/// Abstract operation `OrdinaryHasInstance ( C, O )`

3
boa_engine/src/value/serde_json.rs

@ -4,6 +4,7 @@ use super::JsValue;
use crate::{
builtins::Array,
error::JsNativeError,
object::JsObject,
property::{PropertyDescriptor, PropertyKey},
Context, JsResult,
};
@ -63,7 +64,7 @@ impl JsValue {
Ok(Array::create_array_from_list(arr, context).into())
}
Value::Object(obj) => {
let js_obj = context.construct_object();
let js_obj = JsObject::with_object_proto(context);
for (key, value) in obj {
let property = PropertyDescriptor::builder()
.value(Self::from_json(value, context)?)

6
boa_engine/src/value/tests.rs

@ -24,7 +24,7 @@ fn undefined() {
#[test]
fn get_set_field() {
let mut context = Context::default();
let obj = &context.construct_object();
let obj = &JsObject::with_object_proto(&mut context);
// Create string and convert it to a Value
let s = JsValue::new("bar");
obj.set("foo", s, false, &mut context).unwrap();
@ -132,11 +132,11 @@ fn hash_rational() {
#[test]
#[allow(clippy::eq_op)]
fn hash_object() {
let object1 = JsValue::new(JsObject::empty());
let object1 = JsValue::new(JsObject::with_null_proto());
assert_eq!(object1, object1);
assert_eq!(object1, object1.clone());
let object2 = JsValue::new(JsObject::empty());
let object2 = JsValue::new(JsObject::with_null_proto());
assert_ne!(object1, object2);
assert_eq!(hash_value(&object1), hash_value(&object1.clone()));

3
boa_engine/src/vm/code_block.rs

@ -506,8 +506,6 @@ pub(crate) fn create_function_object(
context.intrinsics().constructors().function().prototype()
};
let prototype = context.construct_object();
let name_property = PropertyDescriptor::builder()
.value(
context
@ -566,6 +564,7 @@ pub(crate) fn create_function_object(
.configurable(true)
.build();
let prototype = JsObject::with_object_proto(context);
prototype
.define_property_or_throw(js_string!("constructor"), constructor_property, context)
.expect("failed to define the constructor property of the function");

6
boa_engine/src/vm/opcode/binary_ops/mod.rs

@ -83,16 +83,16 @@ impl Operation for In {
let rhs = context.vm.pop();
let lhs = context.vm.pop();
if !rhs.is_object() {
let Some(rhs) = rhs.as_object() else {
return Err(JsNativeError::typ()
.with_message(format!(
"right-hand side of 'in' should be an object, got `{}`",
rhs.type_of()
))
.into());
}
};
let key = lhs.to_property_key(context)?;
let value = context.has_property(&rhs, &key)?;
let value = rhs.has_property(key, context)?;
context.vm.push(value);
Ok(ShouldExit::False)
}

10
boa_engine/src/vm/opcode/generator/mod.rs

@ -147,18 +147,18 @@ impl Operation for GeneratorNextDelegate {
match context.vm.frame().generator_resume_kind {
GeneratorResumeKind::Normal => {
let result = context.call(&next_method, &iterator.clone().into(), &[received])?;
let result_object = result.as_object().ok_or_else(|| {
let result = next_method.call(&iterator.clone().into(), &[received], context)?;
let result = result.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("generator next method returned non-object")
})?;
let done = result_object.get("done", context)?.to_boolean();
let done = result.get("done", context)?.to_boolean();
if done {
context.vm.frame_mut().pc = done_address as usize;
let value = result_object.get("value", context)?;
let value = result.get("value", context)?;
context.vm.push(value);
return Ok(ShouldExit::False);
}
let value = result_object.get("value", context)?;
let value = result.get("value", context)?;
context.vm.push(iterator.clone());
context.vm.push(next_method.clone());
context.vm.push(done);

4
boa_engine/src/vm/opcode/get/name.rs

@ -40,7 +40,7 @@ impl Operation for GetName {
} => value.clone(),
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
let get = get.clone();
context.call(&get, &context.global_object().clone().into(), &[])?
get.call(&context.global_object().clone().into(), &[], context)?
}
_ => {
return Err(JsNativeError::reference()
@ -113,7 +113,7 @@ impl Operation for GetNameOrUndefined {
} => value.clone(),
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
let get = get.clone();
context.call(&get, &context.global_object().clone().into(), &[])?
get.call(&context.global_object().clone().into(), &[], context)?
}
_ => JsValue::undefined(),
},

4
boa_engine/src/vm/opcode/push/object.rs

@ -1,4 +1,5 @@
use crate::{
object::JsObject,
vm::{opcode::Operation, ShouldExit},
Context, JsResult,
};
@ -15,7 +16,8 @@ impl Operation for PushEmptyObject {
const INSTRUCTION: &'static str = "INST - PushEmptyObject";
fn execute(context: &mut Context) -> JsResult<ShouldExit> {
context.vm.push(context.construct_object());
let o = JsObject::with_object_proto(context);
context.vm.push(o);
Ok(ShouldExit::False)
}
}

2
boa_examples/src/bin/closures.rs

@ -42,7 +42,7 @@ fn main() -> Result<(), JsError> {
}
// We create a new `JsObject` with some data
let object = context.construct_object();
let object = JsObject::with_object_proto(&mut context);
object.define_property_or_throw(
"name",
PropertyDescriptor::builder()

Loading…
Cancel
Save