diff --git a/nova_vm/src/ecmascript.rs b/nova_vm/src/ecmascript.rs index 718ba47b3..6d31db4ae 100644 --- a/nova_vm/src/ecmascript.rs +++ b/nova_vm/src/ecmascript.rs @@ -1,3 +1,4 @@ +pub(crate) mod abstract_operations; pub mod builtins; pub mod execution; pub mod scripts_and_modules; diff --git a/nova_vm/src/ecmascript/abstract_operations.rs b/nova_vm/src/ecmascript/abstract_operations.rs new file mode 100644 index 000000000..6eb2da6e8 --- /dev/null +++ b/nova_vm/src/ecmascript/abstract_operations.rs @@ -0,0 +1,3 @@ +pub(crate) mod operations_on_objects; +pub(crate) mod testing_and_comparison; +pub(crate) mod type_conversion; diff --git a/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs b/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs new file mode 100644 index 000000000..d2f43b3a4 --- /dev/null +++ b/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs @@ -0,0 +1,133 @@ +//! ## [7.3 Operations on Objects](https://tc39.es/ecma262/#sec-operations-on-objects) + +use super::{testing_and_comparison::is_callable, type_conversion::to_object}; +use crate::ecmascript::{ + execution::{agent::JsError, Agent, JsResult}, + types::{Function, InternalMethods, Object, PropertyKey, Value}, +}; + +/// ### [7.3.1 MakeBasicObject ( internalSlotsList )](https://tc39.es/ecma262/#sec-makebasicobject) +/// +/// The abstract operation MakeBasicObject takes argument internalSlotsList (a +/// List of internal slot names) and returns an Object. It is the source of all +/// ECMAScript objects that are created algorithmically, including both +/// ordinary objects and exotic objects. It factors out common steps used in +/// creating all objects, and centralizes object creation. It performs the +/// following steps when called: +/// +/// > NOTE: Within this specification, exotic objects are created in abstract +/// > operations such as ArrayCreate and BoundFunctionCreate by first calling +/// > MakeBasicObject to obtain a basic, foundational object, and then +/// > overriding some or all of that object's internal methods. In order to +/// > encapsulate exotic object creation, the object's essential internal +/// > methods are never modified outside those operations. +pub(crate) fn make_basic_object(agent: &mut Agent, internal_slots_list: ()) -> Object { + // 1. Let obj be a newly created object with an internal slot for each name in internalSlotsList. + // 2. Set obj's essential internal methods to the default ordinary object definitions specified in 10.1. + // 3. Assert: If the caller will not be overriding both obj's [[GetPrototypeOf]] and [[SetPrototypeOf]] essential + // internal methods, then internalSlotsList contains [[Prototype]]. + // 4. Assert: If the caller will not be overriding all of obj's [[SetPrototypeOf]], [[IsExtensible]], and + // [[PreventExtensions]] essential internal methods, then internalSlotsList contains [[Extensible]]. + // 5. If internalSlotsList contains [[Extensible]], set obj.[[Extensible]] to true. + // 6. Return obj. + todo!() +} + +/// ### [7.3.2 Get ( O, P )](https://tc39.es/ecma262/#sec-get-o-p) +/// +/// The abstract operation Get takes arguments O (an Object) and P (a property +/// key) and returns either a normal completion containing an ECMAScript +/// language value or a throw completion. It is used to retrieve the value of a +/// specific property of an object. +pub(crate) fn get(agent: &mut Agent, o: Object, p: PropertyKey) -> JsResult { + // 1. Return ? O.[[Get]](P, O). + Object::get(agent, o, p, o.into()) +} + +/// ### [7.3.3 GetV ( V, P )](https://tc39.es/ecma262/#sec-getv) +/// +/// The abstract operation GetV takes arguments V (an ECMAScript language +/// value) and P (a property key) and returns either a normal completion +/// containing an ECMAScript language value or a throw completion. It is used +/// to retrieve the value of a specific property of an ECMAScript language +/// value. If the value is not an object, the property lookup is performed +/// using a wrapper object appropriate for the type of the value. +pub(crate) fn get_v(agent: &mut Agent, v: Value, p: PropertyKey) -> JsResult { + // 1. Let O be ? ToObject(V). + let o = to_object(agent, v)?; + // 2. Return ? O.[[Get]](P, V). + Object::get(agent, o, p, o.into()) +} + +/// ### [7.3.11 GetMethod ( V, P )](https://tc39.es/ecma262/#sec-getmethod) +/// +/// The abstract operation GetMethod takes arguments V (an ECMAScript language +/// value) and P (a property key) and returns either a normal completion +/// containing either a function object or undefined, or a throw completion. It +/// is used to get the value of a specific property of an ECMAScript language +/// value when the value of the property is expected to be a function. + +pub(crate) fn get_method( + agent: &mut Agent, + v: Value, + p: PropertyKey, +) -> JsResult> { + // 1. Let func be ? GetV(V, P). + let func = get_v(agent, v, p)?; + // 2. If func is either undefined or null, return undefined. + if func.is_undefined() || func.is_null() { + return Ok(None); + } + // 3. If IsCallable(func) is false, throw a TypeError exception. + if !is_callable(func) { + return Err(JsError {}); + } + // 4. Return func. + match func { + Value::Function(idx) => Ok(Some(Function::from(idx))), + _ => unreachable!(), + } +} + +/// ### [7.3.14 Call ( F, V \[ , argumentsList \] )](https://tc39.es/ecma262/#sec-call) +/// +/// 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. +pub(crate) fn call( + agent: &mut Agent, + f: Value, + v: Value, + arguments_list: Option<&[Value]>, +) -> JsResult { + // 1. If argumentsList is not present, set argumentsList to a new empty List. + let arguments_list = arguments_list.unwrap_or(&[]); + // 2. If IsCallable(F) is false, throw a TypeError exception. + if !is_callable(f) { + Err(JsError {}) + } else { + // 3. Return ? F.[[Call]](V, argumentsList). + if let Value::Function(idx) = f { + Function::call(agent, Function(idx), v, arguments_list) + } else { + unreachable!(); + } + } +} + +/// Abstract operation Call specialized for a Function. +pub(crate) fn call_function( + agent: &mut Agent, + f: Function, + v: Value, + arguments_list: Option<&[Value]>, +) -> JsResult { + let arguments_list = arguments_list.unwrap_or(&[]); + Function::call(agent, f, v, arguments_list) +} diff --git a/nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs b/nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs new file mode 100644 index 000000000..7d6f8a33a --- /dev/null +++ b/nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs @@ -0,0 +1,50 @@ +//! ## [7.2 Testing and Comparison Operations](https://tc39.es/ecma262/#sec-testing-and-comparison-operations) + +use crate::ecmascript::{ + execution::{agent::JsError, Agent, JsResult}, + types::Value, +}; + +/// ### [7.2.1 RequireObjectCoercible ( argument )](https://tc39.es/ecma262/#sec-requireobjectcoercible) +/// +/// The abstract operation RequireObjectCoercible takes argument argument (an +/// ECMAScript language value) and returns either a normal completion +/// containing an ECMAScript language value or a throw completion. It throws an +/// error if argument is a value that cannot be converted to an Object using +/// ToObject. It is defined by [Table 14](https://tc39.es/ecma262/#table-requireobjectcoercible-results): +pub(crate) fn require_object_coercible(agent: &mut Agent, argument: Value) -> JsResult { + if argument.is_undefined() || argument.is_null() { + Err(JsError {}) + } else { + Ok(argument) + } +} + +/// ### [7.2.2 IsArray ( argument )](https://tc39.es/ecma262/#sec-isarray) +/// +/// The abstract operation IsArray takes argument argument (an ECMAScript +/// language value) and returns either a normal completion containing a Boolean +/// or a throw completion. +pub(crate) fn is_array(agent: &Agent, argument: Value) -> JsResult { + // 1. If argument is not an Object, return false. + // 2. If argument is an Array exotic object, return true. + Ok(matches!(argument, Value::Array(_))) + // TODO: Proxy + // 3. If argument is a Proxy exotic object, then + // a. Perform ? ValidateNonRevokedProxy(argument). + // b. Let proxyTarget be argument.[[ProxyTarget]]. + // c. Return ? IsArray(proxyTarget). + // 4. Return false. +} + +/// ### [7.2.3 IsCallable ( argument )](https://tc39.es/ecma262/#sec-iscallable) +/// +/// The abstract operation IsCallable takes argument argument (an ECMAScript +/// language value) and returns a Boolean. It determines if argument is a +/// callable function with a [[Call]] internal method. +pub(crate) fn is_callable(argument: Value) -> bool { + // 1. If argument is not an Object, return false. + // 2. If argument has a [[Call]] internal method, return true. + // 3. Return false. + matches!(argument, Value::Function(_)) +} diff --git a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs new file mode 100644 index 000000000..2e24b3345 --- /dev/null +++ b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs @@ -0,0 +1,473 @@ +//! ## [7.1 Type Conversion](https://tc39.es/ecma262/#sec-type-conversion) +//! +//! The ECMAScript language implicitly performs automatic type conversion as +//! needed. To clarify the semantics of certain constructs it is useful to +//! define a set of conversion abstract operations. The conversion abstract +//! operations are polymorphic; they can accept a value of any ECMAScript +//! language type. But no other specification types are used with these +//! operations. +//! +//! The BigInt type has no implicit conversions in the ECMAScript language; +//! programmers must call BigInt explicitly to convert values from other types. + +use crate::{ + ecmascript::{ + execution::{agent::JsError, Agent, JsResult}, + types::{BigInt, Number, Object, PropertyKey, String, Value}, + }, + heap::WellKnownSymbolIndexes, +}; + +use super::{ + operations_on_objects::{call, call_function, get, get_method}, + testing_and_comparison::is_callable, +}; + +#[derive(Debug, Clone, Copy)] +pub enum PreferredType { + String = 1, + Number, +} + +/// ### [7.1.1 ToPrimitive ( input \[ , preferredType \] )](https://tc39.es/ecma262/#sec-toprimitive) +/// +/// The abstract operation ToPrimitive takes argument input (an ECMAScript +/// language value) and optional argument preferredType (STRING or NUMBER) and +/// returns either a normal completion containing an ECMAScript language value +/// or a throw completion. It converts its input argument to a non-Object type. +/// If an object is capable of converting to more than one primitive type, it +/// may use the optional hint preferredType to favour that type. It performs +/// the following steps when called: +/// +/// > NOTE: When ToPrimitive is called without a hint, then it generally +/// behaves as if the hint were NUMBER. However, objects may over-ride this +/// behaviour by defining a @@toPrimitive method. Of the objects defined in +/// this specification only Dates (see 21.4.4.45) and Symbol objects (see +/// 20.4.3.5) over-ride the default ToPrimitive behaviour. Dates treat the +/// absence of a hint as if the hint were STRING. +pub(crate) fn to_primitive( + agent: &mut Agent, + input: Value, + preferred_type: Option, +) -> JsResult { + // 1. If input is an Object, then + if let Ok(input) = Object::try_from(input) { + // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive). + let exotic_to_prim = get_method( + agent, + input.into_value(), + PropertyKey::Symbol(WellKnownSymbolIndexes::ToPrimitive.into()), + )?; + // b. If exoticToPrim is not undefined, then + if let Some(exotic_to_prim) = exotic_to_prim { + let hint = match preferred_type { + // i. If preferredType is not present, then + // 1. Let hint be "default". + None => String::from_small_string("default"), + // ii. Else if preferredType is STRING, then + // 1. Let hint be "string". + Some(PreferredType::String) => String::from_small_string("string"), + // iii. Else, + // 1. Assert: preferredType is NUMBER. + // 2. Let hint be "number". + Some(PreferredType::Number) => String::from_small_string("number"), + }; + // iv. Let result be ? Call(exoticToPrim, input, « hint »). + let result: Value = + call_function(agent, exotic_to_prim, input.into(), Some(&[hint.into()]))?; + if !result.is_object() { + // v. If result is not an Object, return result. + Ok(result) + } else { + // vi. Throw a TypeError exception. + Err(JsError {}) + } + } else { + // c. If preferredType is not present, let preferredType be NUMBER. + // d. Return ? OrdinaryToPrimitive(input, preferredType). + ordinary_to_primitive( + agent, + input, + preferred_type.unwrap_or(PreferredType::Number), + ) + } + } else { + // 2. Return input. + Ok(input) + } +} + +/// #### [7.1.1.1 OrdinaryToPrimitive ( O, hint )](https://tc39.es/ecma262/#sec-ordinarytoprimitive) +/// +/// The abstract operation OrdinaryToPrimitive takes arguments O (an Object) +/// and hint (STRING or NUMBER) and returns either a normal completion +/// containing an ECMAScript language value or a throw completion. +pub(crate) fn ordinary_to_primitive( + agent: &mut Agent, + o: Object, + hint: PreferredType, +) -> JsResult { + let to_string_key = PropertyKey::from(String::from_str(agent, "toString")); + let value_of_key = PropertyKey::from(String::from_small_string("valueOf")); + let method_names = match hint { + PreferredType::String => { + // 1. If hint is STRING, then + // a. Let methodNames be « "toString", "valueOf" ». + [to_string_key, value_of_key] + } + PreferredType::Number => { + // 2. Else, + // a. Let methodNames be « "valueOf", "toString" ». + [value_of_key, to_string_key] + } + }; + // 3. For each element name of methodNames, do + for name in method_names { + // a. Let method be ? Get(O, name). + let method = get(agent, o, name)?; + // b. If IsCallable(method) is true, then + if is_callable(method) { + // i. Let result be ? Call(method, O). + let result: Value = call(agent, method, o.into(), None)?; + // ii. If result is not an Object, return result. + if !result.is_object() { + return Ok(result); + } + } + } + // 4. Throw a TypeError exception. + Err(JsError {}) +} + +/// ### [7.1.2 ToBoolean ( argument )](https://tc39.es/ecma262/#sec-toboolean) +pub(crate) fn to_boolean(agent: &mut Agent, argument: Value) -> JsResult { + // 1. If argument is a Boolean, return argument. + if argument.is_boolean() { + return Ok(argument); + } + + // 2. If argument is one of undefined, null, +0𝔽, -0𝔽, NaN, 0ℤ, or the empty String, return false. + // TODO: checks for 0ℤ + if argument.is_undefined() + || argument.is_null() + || argument.is_pos_zero(agent) + || argument.is_neg_zero(agent) + || argument.is_nan(agent) + || argument.is_empty_string() + { + return Ok(false.into()); + } + + // 3. NOTE: This step is replaced in section B.3.6.1. + + // 4. Return true. + Ok(true.into()) +} + +/// ### [7.1.3 ToNumeric ( value )](https://tc39.es/ecma262/#sec-tonumeric) +pub(crate) fn to_numeric(agent: &mut Agent, value: Value) -> JsResult { + // 1. Let primValue be ? ToPrimitive(value, number). + let prim_value = to_primitive(agent, value, Some(PreferredType::Number))?; + + // 2. If primValue is a BigInt, return primValue. + if prim_value.is_bigint() { + return Ok(prim_value); + } + + // 3. Return ? ToNumber(primValue). + to_number(agent, value).map(|n| n.into_value()) +} + +/// ### [7.1.4 ToNumber ( argument )](https://tc39.es/ecma262/#sec-tonumber) +pub(crate) fn to_number(agent: &mut Agent, argument: Value) -> JsResult { + // 1. If argument is a Number, return argument. + if let Ok(argument) = Number::try_from(argument) { + return Ok(argument); + } + + // 2. If argument is either a Symbol or a BigInt, throw a TypeError exception. + if argument.is_symbol() || argument.is_bigint() { + todo!(); + } + + // 3. If argument is undefined, return NaN. + if argument.is_undefined() { + return Ok(Number::nan()); + } + + // 4. If argument is either null or false, return +0𝔽. + if argument.is_null() || argument.is_false() { + return Ok(Number::from(0)); + } + + // 5. If argument is true, return 1𝔽. + if argument.is_true() { + return Ok(Number::from(1)); + } + + // 6. If argument is a String, return StringToNumber(argument). + if argument.is_string() { + todo!(); + } + + // 7. Assert: argument is an Object. + debug_assert!(argument.is_object()); + + // 8. Let primValue be ? ToPrimitive(argument, number). + let prim_value = to_primitive(agent, argument, Some(PreferredType::Number))?; + + // 9. Assert: primValue is not an Object. + debug_assert!(!prim_value.is_object()); + + // 10. Return ? ToNumber(primValue). + to_number(agent, prim_value) +} + +/// ### [7.1.5 ToIntegerOrInfinity ( argument )](https://tc39.es/ecma262/#sec-tointegerorinfinity) +// TODO: Should we add another [`Value`] newtype for IntegerOrInfinity? +pub(crate) fn to_integer_or_infinty(agent: &mut Agent, argument: Value) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = to_number(agent, argument)?; + + // 2. If number is one of NaN, +0𝔽, or -0𝔽, return 0. + if number.is_nan(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(Number::pos_zero()); + } + + // 3. If number is +∞𝔽, return +∞. + if number.is_pos_infinity(agent) { + return Ok(Number::pos_inf()); + } + + // 4. If number is -∞𝔽, return -∞. + if number.is_neg_infinity(agent) { + return Ok(Number::neg_inf()); + } + + // 5. Return truncate(ℝ(number)). + Ok(number.truncate(agent)) +} + +/// ### [7.1.6 ToInt32 ( argument )](https://tc39.es/ecma262/#sec-toint32) +pub(crate) fn to_int32(agent: &mut Agent, argument: Value) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = to_number(agent, argument)?; + + // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(ℝ(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int32bit be int modulo 2^32. + let int32bit = int % 2f64.powi(32); + + // 5. If int32bit ≥ 2^31, return 𝔽(int32bit - 2^32); otherwise return 𝔽(int32bit). + Ok(if int32bit >= 2f64.powi(32) { + int32bit - 2f64.powi(32) + } else { + int32bit + } as i32) +} + +/// ### [7.1.7 ToUint32 ( argument )](https://tc39.es/ecma262/#sec-touint32) +pub(crate) fn to_uint32(agent: &mut Agent, argument: Value) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = to_number(agent, argument)?; + + // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(ℝ(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int32bit be int modulo 2^32. + let int32bit = int % 2f64.powi(32); + + // 5. Return 𝔽(int32bit). + Ok(int32bit as u32) +} + +/// ### [7.1.8 ToInt16 ( argument )](https://tc39.es/ecma262/#sec-toint16) +pub(crate) fn to_int16(agent: &mut Agent, argument: Value) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = to_number(agent, argument)?; + + // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(ℝ(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int16bit be int modulo 2^16. + let int16bit = int % 2f64.powi(16); + + // 5. If int16bit ≥ 2^15, return 𝔽(int16bit - 2^16); otherwise return 𝔽(int16bit). + Ok(if int16bit >= 2f64.powi(15) { + int16bit - 2f64.powi(16) + } else { + int16bit + } as i16) +} + +/// ### [7.1.9 ToUint16 ( argument )](https://tc39.es/ecma262/#sec-touint16) +pub(crate) fn to_uint16(agent: &mut Agent, argument: Value) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = to_number(agent, argument)?; + + // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(ℝ(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int16bit be int modulo 2^16. + let int16bit = int % 2f64.powi(16); + + // Return 𝔽(int16bit). + Ok(int16bit as i16) +} + +/// ### [7.1.10 ToInt8 ( argument )](https://tc39.es/ecma262/#sec-toint8) +pub(crate) fn to_int8(agent: &mut Agent, argument: Value) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = to_number(agent, argument)?; + + // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(ℝ(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int8bit be int modulo 2^8. + let int8bit = int % 2f64.powi(8); + + // 5. If int8bit ≥ 2^7, return 𝔽(int8bit - 2^8); otherwise return 𝔽(int8bit). + Ok(if int8bit >= 2f64.powi(7) { + int8bit - 2f64.powi(8) + } else { + int8bit + } as i8) +} + +/// ### [7.1.11 ToUint8 ( argument )](https://tc39.es/ecma262/#sec-touint8) +pub(crate) fn to_uint8(agent: &mut Agent, argument: Value) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = to_number(agent, argument)?; + + // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(ℝ(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int8bit be int modulo 2^8. + let int8bit = int % 2f64.powi(8); + + // 5. Return 𝔽(int8bit). + Ok(int8bit as u8) +} + +/// ### [7.1.12 ToUint8Clamp ( argument )](https://tc39.es/ecma262/#sec-touint8clamp) +pub(crate) fn to_uint8_clamp(agent: &mut Agent, argument: Value) -> JsResult { + // 1. Let number be ? ToNumber(argument). + let number = to_number(agent, argument)?; + + // 2. If number is NaN, return +0𝔽. + if number.is_nan(agent) { + return Ok(0); + } + + // 3. Let mv be the extended mathematical value of number. + // TODO: Is there a better way? + let mv = number.into_f64(agent); + + // 4. Let clamped be the result of clamping mv between 0 and 255. + let clamped = mv.clamp(0.0, 255.0); + + // 5. Let f be floor(clamped). + let f = clamped.floor(); + + Ok( + // 6. If clamped < f + 0.5, return 𝔽(f). + if clamped < f + 0.5 { + f as u8 + } + // 7. If clamped > f + 0.5, return 𝔽(f + 1). + else if clamped > f + 0.5 { + f as u8 + 1 + } + // 8. If f is even, return 𝔽(f). Otherwise, return 𝔽(f + 1). + else if f % 2.0 == 0.0 { + f as u8 + } else { + f as u8 + 1 + }, + ) +} + +/// ### [7.1.13 ToBigInt ( argument )](https://tc39.es/ecma262/#sec-tobigint) +pub(crate) fn to_big_int(agent: &mut Agent, argument: Value) -> JsResult { + // 1. Let prim be ? ToPrimitive(argument, number). + let _prim = to_primitive(agent, argument, Some(PreferredType::Number))?; + + // 2. Return the value that prim corresponds to in Table 12. + todo!() +} + +/// ### [7.1.17 ToString ( argument )](https://tc39.es/ecma262/#sec-tostring) +pub(crate) fn to_string(_agent: &mut Agent, _argument: Value) -> JsResult { + // TODO: 1. If argument is a String, return argument. + // 2. If argument is a Symbol, throw a TypeError exception. + // 3. If argument is undefined, return "undefined". + // 4. If argument is null, return "null". + // 5. If argument is true, return "true". + // 6. If argument is false, return "false". + // 7. If argument is a Number, return Number::toString(argument, 10). + // 8. If argument is a BigInt, return BigInt::toString(argument, 10). + // 9. Assert: argument is an Object. + // 10. Let primValue be ? ToPrimitive(argument, string). + // 11. Assert: primValue is not an Object. + // 12. Return ? ToString(primValue). + + todo!() +} + +/// ### [7.1.18 ToObject ( argument )](https://tc39.es/ecma262/#sec-toobject) +/// +/// The abstract operation ToObject takes argument argument (an ECMAScript +/// language value) and returns either a normal completion containing an Object +/// or a throw completion. It converts argument to a value of type Object +/// according to [Table 13](https://tc39.es/ecma262/#table-toobject-conversions): +pub(crate) fn to_object(agent: &mut Agent, argument: Value) -> JsResult { + match argument { + Value::Undefined | Value::Null => Err(JsError {}), + // Return a new Boolean object whose [[BooleanData]] internal slot is set to argument. + Value::Boolean(_) => todo!("BooleanObject"), + // Return a new String object whose [[StringData]] internal slot is set to argument. + Value::String(_) => todo!("StringObject"), + Value::SmallString(_) => todo!("StringObject"), + // Return a new Symbol object whose [[SymbolnData]] internal slot is set to argument. + Value::Symbol(_) => todo!("SymbolObject"), + // Return a new Number object whose [[NumberData]] internal slot is set to argument. + Value::Number(_) => todo!("NumberObject"), + Value::Integer(_) => todo!("NumberObject"), + Value::Float(_) => todo!("NumberObject"), + // Return a new BigInt object whose [[BigIntData]] internal slot is set to argument. + Value::BigInt(_) => todo!("BigIntObject"), + Value::SmallBigInt(_) => todo!("BigIntObject"), + _ => Ok(Object::try_from(argument).unwrap()), + } +} diff --git a/nova_vm/src/ecmascript/builtins/array.rs b/nova_vm/src/ecmascript/builtins/array.rs index 2903d0cd0..d0c581c78 100644 --- a/nova_vm/src/ecmascript/builtins/array.rs +++ b/nova_vm/src/ecmascript/builtins/array.rs @@ -5,13 +5,18 @@ mod data; use super::{create_builtin_function, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs}; -use crate::ecmascript::{ - execution::{Agent, JsResult}, - types::{Object, Value}, +use crate::{ + ecmascript::{ + execution::{Agent, JsResult}, + types::{Object, Value}, + }, + heap::indexes::ArrayIndex, }; pub use data::ArrayHeapData; +pub struct Array(ArrayIndex); + pub struct ArrayConstructor; impl Builtin for ArrayConstructor { diff --git a/nova_vm/src/ecmascript/builtins/array_buffer.rs b/nova_vm/src/ecmascript/builtins/array_buffer.rs index 823a62f21..6579189e4 100644 --- a/nova_vm/src/ecmascript/builtins/array_buffer.rs +++ b/nova_vm/src/ecmascript/builtins/array_buffer.rs @@ -5,7 +5,7 @@ mod data; use crate::{ ecmascript::{ execution::{agent::JsError, Agent, JsResult}, - types::{DataBlock, Function, Number, Object, PropertyKey, Value}, + types::{DataBlock, Function, InternalMethods, Number, Object, PropertyKey, Value}, }, heap::{indexes::ArrayBufferIndex, GetHeapData}, Heap, @@ -211,8 +211,7 @@ impl ArrayBuffer { }; // 2. Let maxByteLength be ? Get(options, "maxByteLength"). let property = PropertyKey::from_str(&mut agent.heap, "maxByteLength"); - let max_byte_length = - (options.internal_methods(agent).get)(agent, options, property, options.into_value())?; + let max_byte_length = Object::get(agent, options, property, options.into_value())?; // 3. If maxByteLength is undefined, return EMPTY. if max_byte_length.is_undefined() { return Ok(None); diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 44cab040f..e4ed710b4 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -1,52 +1,137 @@ -use crate::ecmascript::{ - execution::{Agent, JsResult}, - types::{InternalMethods, Object, PropertyDescriptor, PropertyKey, Value}, +use crate::{ + ecmascript::{ + execution::{Agent, JsResult}, + types::{InternalMethods, Object, OrdinaryObject, PropertyDescriptor, PropertyKey, Value}, + }, + heap::GetHeapData, }; /// 10.1 Ordinary Object Internal Methods and Internal Slots /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots -pub static METHODS: InternalMethods = InternalMethods { - get_prototype_of, - set_prototype_of, - is_extensible, - prevent_extensions, - get_own_property, - define_own_property, - has_property, - get, - set, - delete, - own_property_keys, - // call: todo!(), - // construct: todo!(), - call: None, - construct: None, -}; +impl InternalMethods for OrdinaryObject { + /// ### [10.1.1 \[\[GetPrototypeOf\]\] ( )](https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof) + fn get_prototype_of(agent: &mut Agent, object: OrdinaryObject) -> JsResult> { + Ok(ordinary_get_prototype_of(agent, object.into())) + } -/// 10.1.1 [[GetPrototypeOf]] ( ) -/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof -fn get_prototype_of(agent: &mut Agent, object: Object) -> Option { - // 1. Return OrdinaryGetPrototypeOf(O). - ordinary_get_prototype_of(agent, object) -} + /// ### [10.1.2 \[\[SetPrototypeOf\]\] ( V )](https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v) + fn set_prototype_of( + agent: &mut Agent, + object: OrdinaryObject, + prototype: Option, + ) -> JsResult { + ordinary_set_prototype_of(agent, object.into(), prototype) + } -/// 10.1.1.1 OrdinaryGetPrototypeOf ( O ) -/// https://tc39.es/ecma262/#sec-ordinarygetprototypeof -pub fn ordinary_get_prototype_of(agent: &mut Agent, object: Object) -> Option { - // 1. Return O.[[Prototype]]. - // TODO: This is wrong. - Some(Object::try_from(object.prototype(agent).unwrap()).unwrap()) + /// 10.1.3 [[IsExtensible]] ( ) + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible + fn is_extensible(agent: &mut Agent, object: OrdinaryObject) -> JsResult { + // 1. Return OrdinaryIsExtensible(O). + Ok(ordinary_is_extensible(agent, object.into())) + } + + /// 10.1.4 [[PreventExtensions]] ( ) + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions + fn prevent_extensions(agent: &mut Agent, object: OrdinaryObject) -> JsResult { + // 1. Return OrdinaryPreventExtensions(O). + Ok(ordinary_prevent_extensions(agent, object.into())) + } + + /// 10.1.5 [[GetOwnProperty]] ( P ) + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p + fn get_own_property( + agent: &mut Agent, + object: OrdinaryObject, + property_key: PropertyKey, + ) -> JsResult> { + // 1. Return OrdinaryGetOwnProperty(O, P). + Ok(ordinary_get_own_property( + agent, + object.into(), + property_key, + )) + } + + /// 10.1.6 [[DefineOwnProperty]] ( P, Desc ) + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc + fn define_own_property( + agent: &mut Agent, + object: OrdinaryObject, + property_key: PropertyKey, + descriptor: PropertyDescriptor, + ) -> JsResult { + ordinary_define_own_property(agent, object.into(), property_key, descriptor) + } + + /// 10.1.7 [[HasProperty]] ( P ) + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p + fn has_property( + agent: &mut Agent, + object: OrdinaryObject, + property_key: PropertyKey, + ) -> JsResult { + // 1. Return ? OrdinaryHasProperty(O, P). + ordinary_has_property(agent, object.into(), property_key) + } + + /// 10.1.8 [[Get]] ( P, Receiver ) + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver + fn get( + agent: &mut Agent, + object: OrdinaryObject, + property_key: PropertyKey, + receiver: Value, + ) -> JsResult { + // 1. Return ? OrdinaryGet(O, P, Receiver). + ordinary_get(agent, object.into(), property_key, receiver) + } + + /// 10.1.9 [[Set]] ( P, V, Receiver ) + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver + fn set( + agent: &mut Agent, + object: OrdinaryObject, + property_key: PropertyKey, + value: Value, + receiver: Value, + ) -> JsResult { + // 1. Return ? OrdinarySet(O, P, V, Receiver). + ordinary_set(agent, object.into(), property_key, value, receiver) + } + + /// 10.1.10 [[Delete]] ( P ) + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-delete-p + fn delete( + agent: &mut Agent, + object: OrdinaryObject, + property_key: PropertyKey, + ) -> JsResult { + // 1. Return ? OrdinaryDelete(O, P). + ordinary_delete(agent, object.into(), property_key) + } + + /// 10.1.11 [[OwnPropertyKeys]] ( ) + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys + fn own_property_keys(agent: &mut Agent, object: OrdinaryObject) -> JsResult> { + // 1. Return OrdinaryOwnPropertyKeys(O). + ordinary_own_property_keys(agent, object.into()) + } } -/// 10.1.2 [[SetPrototypeOf]] ( V ) -/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v -fn set_prototype_of( - agent: &mut Agent, - object: Object, - prototype: Option, -) -> JsResult { - // 1. Return OrdinarySetPrototypeOf(O, V). - ordinary_set_prototype_of(agent, object, prototype) +/// #### [10.1.1.1 OrdinaryGetPrototypeOf ( O )](https://tc39.es/ecma262/#sec-ordinarygetprototypeof) +fn ordinary_get_prototype_of(agent: &mut Agent, object: Object) -> Option { + // 1. Return O.[[Prototype]]. + match object { + Object::Object(idx) => agent.heap.get(idx).prototype, + Object::Array(idx) => agent.heap.get(idx).object_index.map_or_else( + || Some(agent.current_realm().intrinsics().array_prototype()), + |idx| agent.heap.get(idx).prototype, + ), + Object::Function(idx) => { + // TODO: Not proper + Some(agent.current_realm().intrinsics().function_prototype()) + } + } } /// 10.1.2.1 OrdinarySetPrototypeOf ( O, V ) @@ -97,9 +182,9 @@ pub fn ordinary_set_prototype_of( // c. Else, // i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined in 10.1.1, // set done to true. - if parent_prototype.internal_methods(agent).get_prototype_of != get_prototype_of { - break; - } + // if parent_prototype.get_prototype_of != get_prototype_of { + // break; + // } // ii. Else, set p to p.[[Prototype]]. // TODO: This is wrong @@ -114,13 +199,6 @@ pub fn ordinary_set_prototype_of( Ok(true) } -/// 10.1.3 [[IsExtensible]] ( ) -/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible -fn is_extensible(agent: &mut Agent, object: Object) -> JsResult { - // 1. Return OrdinaryIsExtensible(O). - Ok(ordinary_is_extensible(agent, object)) -} - /// 10.1.3.1 OrdinaryIsExtensible ( O ) /// https://tc39.es/ecma262/#sec-ordinaryisextensible pub fn ordinary_is_extensible(agent: &mut Agent, object: Object) -> bool { @@ -128,13 +206,6 @@ pub fn ordinary_is_extensible(agent: &mut Agent, object: Object) -> bool { object.extensible(agent) } -/// 10.1.4 [[PreventExtensions]] ( ) -/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions -fn prevent_extensions(agent: &mut Agent, object: Object) -> JsResult { - // 1. Return OrdinaryPreventExtensions(O). - Ok(ordinary_prevent_extensions(agent, object)) -} - /// 10.1.4.1 OrdinaryPreventExtensions ( O ) /// https://tc39.es/ecma262/#sec-ordinarypreventextensions pub fn ordinary_prevent_extensions(agent: &mut Agent, object: Object) -> bool { @@ -145,17 +216,6 @@ pub fn ordinary_prevent_extensions(agent: &mut Agent, object: Object) -> bool { true } -/// 10.1.5 [[GetOwnProperty]] ( P ) -/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p -fn get_own_property( - agent: &mut Agent, - object: Object, - property_key: PropertyKey, -) -> JsResult> { - // 1. Return OrdinaryGetOwnProperty(O, P). - Ok(ordinary_get_own_property(agent, object, property_key)) -} - /// 10.1.5.1 OrdinaryGetOwnProperty ( O, P ) /// https://tc39.es/ecma262/#sec-ordinarygetownproperty pub fn ordinary_get_own_property( @@ -200,17 +260,6 @@ pub fn ordinary_get_own_property( Some(descriptor) } -/// 10.1.6 [[DefineOwnProperty]] ( P, Desc ) -/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc -pub fn define_own_property( - agent: &mut Agent, - object: Object, - property_key: PropertyKey, - descriptor: PropertyDescriptor, -) -> JsResult { - ordinary_define_own_property(agent, object, property_key, descriptor) -} - /// 10.1.6.1 OrdinaryDefineOwnProperty ( O, P, Desc ) /// https://tc39.es/ecma262/#sec-ordinarydefineownproperty pub fn ordinary_define_own_property( @@ -220,7 +269,7 @@ pub fn ordinary_define_own_property( descriptor: PropertyDescriptor, ) -> JsResult { // 1. Let current be ? O.[[GetOwnProperty]](P). - let current = (object.internal_methods(agent).get_own_property)(agent, object, property_key)?; + let current = Object::get_own_property(agent, object, property_key)?; // 2. Let extensible be ? IsExtensible(O). let extensible = object.extensible(agent); @@ -483,17 +532,6 @@ fn validate_and_apply_property_descriptor( Ok(true) } -/// 10.1.7 [[HasProperty]] ( P ) -/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p -pub fn has_property( - agent: &mut Agent, - object: Object, - property_key: PropertyKey, -) -> JsResult { - // 1. Return ? OrdinaryHasProperty(O, P). - ordinary_has_property(agent, object, property_key) -} - /// 10.1.7.1 OrdinaryHasProperty ( O, P ) /// https://tc39.es/ecma262/#sec-ordinaryhasproperty pub fn ordinary_has_property( @@ -502,7 +540,7 @@ pub fn ordinary_has_property( property_key: PropertyKey, ) -> JsResult { // 1. Let hasOwn be ? O.[[GetOwnProperty]](P). - let has_own = (object.internal_methods(agent).get_own_property)(agent, object, property_key)?; + let has_own = Object::get_own_property(agent, object, property_key)?; // 2. If hasOwn is not undefined, return true. if has_own.is_some() { @@ -510,30 +548,18 @@ pub fn ordinary_has_property( } // 3. Let parent be ? O.[[GetPrototypeOf]](). - let parent = (object.internal_methods(agent).get_prototype_of)(agent, object); + let parent = Object::get_prototype_of(agent, object)?; // 4. If parent is not null, then if let Some(parent) = parent { // a. Return ? parent.[[HasProperty]](P). - return (parent.internal_methods(agent).has_property)(agent, parent, property_key); + return Object::has_property(agent, parent, property_key); } // 5. Return false. Ok(false) } -/// 10.1.8 [[Get]] ( P, Receiver ) -/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver -fn get( - agent: &mut Agent, - object: Object, - property_key: PropertyKey, - receiver: Value, -) -> JsResult { - // 1. Return ? OrdinaryGet(O, P, Receiver). - ordinary_get(agent, object, property_key, receiver) -} - /// 10.1.8.1 OrdinaryGet ( O, P, Receiver ) /// https://tc39.es/ecma262/#sec-ordinaryget pub fn ordinary_get( @@ -543,18 +569,16 @@ pub fn ordinary_get( receiver: Value, ) -> JsResult { // 1. Let desc be ? O.[[GetOwnProperty]](P). - let Some(descriptor) = - (object.internal_methods(agent).get_own_property)(agent, object, property_key)? - else { + let Some(descriptor) = Object::get_own_property(agent, object, property_key)? else { // 2. If desc is undefined, then // a. Let parent be ? O.[[GetPrototypeOf]](). - let Some(parent) = (object.internal_methods(agent).get_prototype_of)(agent, object) else { + let Some(parent) = Object::get_prototype_of(agent, object)? else { return Ok(Value::Undefined); }; // c. Return ? parent.[[Get]](P, Receiver). - return (parent.internal_methods(agent).get)(agent, parent, property_key, receiver); + return Object::get(agent, parent, property_key, receiver); }; // 3. If IsDataDescriptor(desc) is true, return desc.[[Value]]. @@ -576,19 +600,6 @@ pub fn ordinary_get( todo!() } -/// 10.1.9 [[Set]] ( P, V, Receiver ) -/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver -fn set( - agent: &mut Agent, - object: Object, - property_key: PropertyKey, - value: Value, - receiver: Value, -) -> JsResult { - // 1. Return ? OrdinarySet(O, P, V, Receiver). - ordinary_set(agent, object, property_key, value, receiver) -} - /// 10.1.9.1 OrdinarySet ( O, P, V, Receiver ) /// https://tc39.es/ecma262/#sec-ordinaryset pub fn ordinary_set( @@ -599,8 +610,7 @@ pub fn ordinary_set( receiver: Value, ) -> JsResult { // 1. Let ownDesc be ? O.[[GetOwnProperty]](P). - let own_descriptor = - (object.internal_methods(agent).get_own_property)(agent, object, property_key)?; + let own_descriptor = Object::get_own_property(agent, object, property_key)?; // 2. Return ? OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc). ordinary_set_with_own_descriptor(agent, object, property_key, value, receiver, own_descriptor) @@ -621,18 +631,12 @@ pub fn ordinary_set_with_own_descriptor( } else { // 1. If ownDesc is undefined, then // a. Let parent be ? O.[[GetPrototypeOf]](). - let parent = (object.internal_methods(agent).get_prototype_of)(agent, object); + let parent = Object::get_prototype_of(agent, object)?; // b. If parent is not null, then if let Some(parent) = parent { // i. Return ? parent.[[Set]](P, V, Receiver). - return (parent.internal_methods(agent).set)( - agent, - parent, - property_key, - value, - receiver, - ); + return Object::set(agent, parent, property_key, value, receiver); } // c. Else, else { @@ -662,8 +666,7 @@ pub fn ordinary_set_with_own_descriptor( }; // c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P). - let existing_descriptor = - (receiver.internal_methods(agent).get_own_property)(agent, receiver, property_key)?; + let existing_descriptor = Object::get_own_property(agent, receiver, property_key)?; // d. If existingDescriptor is not undefined, then if let Some(existing_descriptor) = existing_descriptor { @@ -684,8 +687,7 @@ pub fn ordinary_set_with_own_descriptor( }; // iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc). - let define_own_property = receiver.internal_methods(agent).define_own_property; - return define_own_property(agent, receiver, property_key, value_descriptor); + return Object::define_own_property(agent, receiver, property_key, value_descriptor); } // e. Else, else { @@ -711,13 +713,6 @@ pub fn ordinary_set_with_own_descriptor( // 7. Return true. } -/// 10.1.10 [[Delete]] ( P ) -/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-delete-p -fn delete(agent: &mut Agent, object: Object, property_key: PropertyKey) -> JsResult { - // 1. Return ? OrdinaryDelete(O, P). - ordinary_delete(agent, object, property_key) -} - /// 10.1.10.1 OrdinaryDelete ( O, P ) /// https://tc39.es/ecma262/#sec-ordinarydelete pub fn ordinary_delete( @@ -726,8 +721,7 @@ pub fn ordinary_delete( property_key: PropertyKey, ) -> JsResult { // 1. Let desc be ? O.[[GetOwnProperty]](P). - let descriptor = - (object.internal_methods(agent).get_own_property)(agent, object, property_key)?; + let descriptor = Object::get_own_property(agent, object, property_key)?; // 2. If desc is undefined, return true. let Some(descriptor) = descriptor else { @@ -747,13 +741,6 @@ pub fn ordinary_delete( Ok(false) } -/// 10.1.11 [[OwnPropertyKeys]] ( ) -/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys -fn own_property_keys(agent: &mut Agent, object: Object) -> JsResult> { - // 1. Return OrdinaryOwnPropertyKeys(O). - ordinary_own_property_keys(agent, object) -} - /// 10.1.11.1 OrdinaryOwnPropertyKeys ( O ) /// https://tc39.es/ecma262/#sec-ordinaryownpropertykeys pub fn ordinary_own_property_keys( diff --git a/nova_vm/src/ecmascript/types.rs b/nova_vm/src/ecmascript/types.rs index 49f97db7c..5cafefac1 100644 --- a/nova_vm/src/ecmascript/types.rs +++ b/nova_vm/src/ecmascript/types.rs @@ -1,10 +1,12 @@ mod language; mod spec; +pub use language::{ + BigInt, Function, InternalMethods, Number, Object, OrdinaryObject, PropertyKey, String, Value, +}; pub(crate) use language::{ BigIntHeapData, FunctionHeapData, NumberHeapData, ObjectHeapData, StringHeapData, }; -pub use language::{Function, InternalMethods, Number, Object, PropertyKey, String, Value}; pub(crate) use spec::DataBlock; pub use spec::{Base, PropertyDescriptor, Reference, ReferencedName}; diff --git a/nova_vm/src/ecmascript/types/language.rs b/nova_vm/src/ecmascript/types/language.rs index 153b05e85..a78280147 100644 --- a/nova_vm/src/ecmascript/types/language.rs +++ b/nova_vm/src/ecmascript/types/language.rs @@ -8,6 +8,8 @@ mod value; pub use bigint::{BigInt, BigIntHeapData}; pub use function::{Function, FunctionHeapData}; pub use number::{Number, NumberHeapData}; -pub use object::{InternalMethods, Object, ObjectHeapData, PropertyKey, PropertyStorage}; +pub use object::{ + InternalMethods, Object, ObjectHeapData, OrdinaryObject, PropertyKey, PropertyStorage, +}; pub use string::{String, StringHeapData}; pub use value::Value; diff --git a/nova_vm/src/ecmascript/types/language/function.rs b/nova_vm/src/ecmascript/types/language/function.rs index eb4ee2821..e24cfd8e1 100644 --- a/nova_vm/src/ecmascript/types/language/function.rs +++ b/nova_vm/src/ecmascript/types/language/function.rs @@ -1,7 +1,10 @@ mod data; -use super::{Object, Value}; -use crate::heap::indexes::FunctionIndex; +use super::{InternalMethods, Object, Value}; +use crate::{ + ecmascript::execution::{Agent, JsResult}, + heap::indexes::FunctionIndex, +}; pub use data::FunctionHeapData; @@ -64,3 +67,107 @@ impl Function { Object::Function(self.0) } } + +impl InternalMethods for Function { + fn get_prototype_of( + agent: &mut Agent, + object: Self, + ) -> crate::ecmascript::execution::JsResult> { + todo!() + } + + fn set_prototype_of( + agent: &mut Agent, + object: Self, + prototype: Option, + ) -> crate::ecmascript::execution::JsResult { + todo!() + } + + fn is_extensible( + agent: &mut Agent, + object: Self, + ) -> crate::ecmascript::execution::JsResult { + todo!() + } + + fn prevent_extensions( + agent: &mut Agent, + object: Self, + ) -> crate::ecmascript::execution::JsResult { + todo!() + } + + fn get_own_property( + agent: &mut Agent, + object: Self, + property_key: super::PropertyKey, + ) -> crate::ecmascript::execution::JsResult> + { + todo!() + } + + fn define_own_property( + agent: &mut Agent, + object: Self, + property_key: super::PropertyKey, + property_descriptor: crate::ecmascript::types::PropertyDescriptor, + ) -> crate::ecmascript::execution::JsResult { + todo!() + } + + fn has_property( + agent: &mut Agent, + object: Self, + property_key: super::PropertyKey, + ) -> crate::ecmascript::execution::JsResult { + todo!() + } + + fn get( + agent: &mut Agent, + object: Self, + property_key: super::PropertyKey, + receiver: Value, + ) -> crate::ecmascript::execution::JsResult { + todo!() + } + + fn set( + agent: &mut Agent, + object: Self, + property_key: super::PropertyKey, + value: Value, + receiver: Value, + ) -> crate::ecmascript::execution::JsResult { + todo!() + } + + fn delete( + agent: &mut Agent, + object: Self, + property_key: super::PropertyKey, + ) -> crate::ecmascript::execution::JsResult { + todo!() + } + + fn own_property_keys( + agent: &mut Agent, + object: Self, + ) -> crate::ecmascript::execution::JsResult> { + todo!() + } + + fn call( + agent: &mut Agent, + object: Self, + this_value: Value, + arguments_list: &[Value], + ) -> JsResult { + todo!() + } + + fn construct(agent: &mut Agent, object: Self, arguments_list: &[Value]) -> JsResult { + todo!() + } +} diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index d2133a7da..85d73ddbe 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -4,12 +4,15 @@ mod property_key; mod property_storage; use super::{ value::{ARRAY_DISCRIMINANT, FUNCTION_DISCRIMINANT, OBJECT_DISCRIMINANT}, - Value, + Function, Value, }; use crate::{ ecmascript::{ builtins::ordinary, - execution::{agent::ExceptionType, Agent, JsResult}, + execution::{ + agent::{ExceptionType, JsError}, + Agent, JsResult, + }, types::PropertyDescriptor, }, heap::{ @@ -39,6 +42,14 @@ pub enum Object { //RegExp(RegExpIndex) = REGEXP_DISCRIMINANT, } +pub struct OrdinaryObject(ObjectIndex); + +impl From for Object { + fn from(value: OrdinaryObject) -> Self { + Self::Object(value.0) + } +} + impl From for Object { fn from(value: ObjectIndex) -> Self { Object::Object(value) @@ -150,11 +161,6 @@ impl Object { object.prototype = prototype; } - pub fn internal_methods<'a>(self, _agent: &mut Agent) -> &'a InternalMethods { - // TODO: Logic for fetching methods for objects/anything else. - &ordinary::METHODS - } - pub fn property_storage(self) -> PropertyStorage { PropertyStorage::new(self) } @@ -168,12 +174,7 @@ impl Object { property_descriptor: PropertyDescriptor, ) -> JsResult<()> { // 1. Let success be ? O.[[DefineOwnProperty]](P, desc). - let success = (self.internal_methods(agent).define_own_property)( - agent, - self, - property_key, - property_descriptor, - )?; + let success = Object::define_own_property(agent, self, property_key, property_descriptor)?; // 2. If success is false, throw a TypeError exception. if !success { @@ -207,7 +208,163 @@ impl Object { }; // 2. Return ? O.[[DefineOwnProperty]](P, newDesc). - let define_own_property = self.internal_methods(agent).define_own_property; - define_own_property(agent, self, property_key, new_descriptor) + Object::define_own_property(agent, self, property_key, new_descriptor) + } +} + +impl InternalMethods for Object { + fn get_prototype_of(agent: &mut Agent, object: Self) -> JsResult> { + match object { + Object::Object(idx) => OrdinaryObject::get_prototype_of(agent, OrdinaryObject(idx)), + Object::Array(idx) => todo!(), + Object::Function(idx) => Function::get_prototype_of(agent, Function(idx)), + } + } + + fn set_prototype_of( + agent: &mut Agent, + object: Self, + prototype: Option, + ) -> JsResult { + match object { + Object::Object(idx) => { + OrdinaryObject::set_prototype_of(agent, OrdinaryObject(idx), prototype) + } + Object::Array(idx) => todo!(), + Object::Function(idx) => Function::set_prototype_of(agent, Function(idx), prototype), + } + } + + fn is_extensible(agent: &mut Agent, object: Self) -> JsResult { + match object { + Object::Object(idx) => OrdinaryObject::is_extensible(agent, OrdinaryObject(idx)), + Object::Array(idx) => todo!(), + Object::Function(idx) => Function::is_extensible(agent, Function(idx)), + } + } + + fn prevent_extensions(agent: &mut Agent, object: Self) -> JsResult { + match object { + Object::Object(idx) => OrdinaryObject::prevent_extensions(agent, OrdinaryObject(idx)), + Object::Array(idx) => todo!(), + Object::Function(idx) => Function::prevent_extensions(agent, Function(idx)), + } + } + + fn get_own_property( + agent: &mut Agent, + object: Self, + property_key: PropertyKey, + ) -> JsResult> { + match object { + Object::Object(idx) => { + OrdinaryObject::get_own_property(agent, OrdinaryObject(idx), property_key) + } + Object::Array(idx) => todo!(), + Object::Function(idx) => Function::get_own_property(agent, Function(idx), property_key), + } + } + + fn define_own_property( + agent: &mut Agent, + object: Self, + property_key: PropertyKey, + property_descriptor: PropertyDescriptor, + ) -> JsResult { + match object { + Object::Object(idx) => OrdinaryObject::define_own_property( + agent, + OrdinaryObject(idx), + property_key, + property_descriptor, + ), + Object::Array(idx) => todo!(), + Object::Function(idx) => Function::define_own_property( + agent, + Function(idx), + property_key, + property_descriptor, + ), + } + } + + fn has_property(agent: &mut Agent, object: Self, property_key: PropertyKey) -> JsResult { + match object { + Object::Object(idx) => { + OrdinaryObject::has_property(agent, OrdinaryObject(idx), property_key) + } + Object::Array(idx) => todo!(), + Object::Function(idx) => Function::has_property(agent, Function(idx), property_key), + } + } + + fn get( + agent: &mut Agent, + object: Self, + property_key: PropertyKey, + receiver: Value, + ) -> JsResult { + match object { + Object::Object(idx) => { + OrdinaryObject::get(agent, OrdinaryObject(idx), property_key, receiver) + } + Object::Array(idx) => todo!(), + Object::Function(idx) => Function::get(agent, Function(idx), property_key, receiver), + } + } + + fn set( + agent: &mut Agent, + object: Self, + property_key: PropertyKey, + value: Value, + receiver: Value, + ) -> JsResult { + match object { + Object::Object(idx) => { + OrdinaryObject::set(agent, OrdinaryObject(idx), property_key, value, receiver) + } + Object::Array(idx) => todo!(), + Object::Function(idx) => { + Function::set(agent, Function(idx), property_key, value, receiver) + } + } + } + + fn delete(agent: &mut Agent, object: Self, property_key: PropertyKey) -> JsResult { + match object { + Object::Object(idx) => OrdinaryObject::delete(agent, OrdinaryObject(idx), property_key), + Object::Array(idx) => todo!(), + Object::Function(idx) => Function::delete(agent, Function(idx), property_key), + } + } + + fn own_property_keys(agent: &mut Agent, object: Self) -> JsResult> { + match object { + Object::Object(idx) => OrdinaryObject::own_property_keys(agent, OrdinaryObject(idx)), + Object::Array(idx) => todo!(), + Object::Function(idx) => Function::own_property_keys(agent, Function(idx)), + } + } + + fn call( + agent: &mut Agent, + object: Self, + this_value: Value, + arguments_list: &[Value], + ) -> JsResult { + match object { + Object::Function(idx) => { + Function::call(agent, Function(idx), this_value, arguments_list) + } + _ => unreachable!(), + } + } + + fn construct(agent: &mut Agent, object: Self, arguments_list: &[Value]) -> JsResult { + match object { + Object::Function(idx) => Function::construct(agent, Function(idx), arguments_list), + _ => unreachable!(), + } } } diff --git a/nova_vm/src/ecmascript/types/language/object/internal_methods.rs b/nova_vm/src/ecmascript/types/language/object/internal_methods.rs index 818860ae9..f85d6897b 100644 --- a/nova_vm/src/ecmascript/types/language/object/internal_methods.rs +++ b/nova_vm/src/ecmascript/types/language/object/internal_methods.rs @@ -5,89 +5,90 @@ use crate::ecmascript::{ types::{PropertyDescriptor, Value}, }; -pub type GetPrototypeOf = fn(agent: &mut Agent, object: Object) -> Option; -pub type SetPrototypeOf = - fn(agent: &mut Agent, object: Object, prototype: Option) -> JsResult; -pub type IsExtensible = fn(agent: &mut Agent, object: Object) -> JsResult; -pub type PreventExtensions = fn(agent: &mut Agent, object: Object) -> JsResult; -pub type GetOwnProperty = fn( +pub type Call = fn( agent: &mut Agent, - object: Object, - property_key: PropertyKey, -) -> JsResult>; -pub type DefineOwnProperty = fn( - agent: &mut Agent, - object: Object, - property_key: PropertyKey, - property_descriptor: PropertyDescriptor, -) -> JsResult; -pub type HasProperty = - fn(agent: &mut Agent, object: Object, property_key: PropertyKey) -> JsResult; -pub type Get = fn( - agent: &mut Agent, - object: Object, - property_key: PropertyKey, - receiver: Value, -) -> JsResult; -pub type Set = fn( - agent: &mut Agent, - object: Object, - property_key: PropertyKey, - value: Value, - receiver: Value, -) -> JsResult; -pub type Delete = - fn(agent: &mut Agent, object: Object, property_key: PropertyKey) -> JsResult; -pub type OwnPropertyKeys = fn(agent: &mut Agent, object: Object) -> JsResult>; -pub type Call = fn( - agent: &mut Agent, - object: Object, + object: T, this_value: Value, arguments_list: ArgumentsList, ) -> JsResult; -pub type Construct = - fn(agent: &mut Agent, object: Object, arguments_list: ArgumentsList) -> JsResult; +pub type Construct = + fn(agent: &mut Agent, object: T, arguments_list: ArgumentsList) -> JsResult; /// 6.1.7.2 Object Internal Methods and Internal Slots /// https://tc39.es/ecma262/#sec-object-internal-methods-and-internal-slots -#[derive(Debug, Clone)] -pub struct InternalMethods { - /// [[GetPrototypeOf]] - pub get_prototype_of: GetPrototypeOf, +pub trait InternalMethods +where + Self: Sized, +{ + /// \[\[GetPrototypeOf\]\] + fn get_prototype_of(agent: &mut Agent, object: Self) -> JsResult>; - /// [[SetPrototypeOf]] - pub set_prototype_of: SetPrototypeOf, + /// \[\[SetPrototypeOf\]\] + fn set_prototype_of( + agent: &mut Agent, + object: Self, + prototype: Option, + ) -> JsResult; - /// [[IsExtensible]] - pub is_extensible: IsExtensible, + /// \[\[IsExtensible\]\] + fn is_extensible(agent: &mut Agent, object: Self) -> JsResult; - /// [[PreventExtensions]] - pub prevent_extensions: PreventExtensions, + /// \[\[PreventExtensions\]\] + fn prevent_extensions(agent: &mut Agent, object: Self) -> JsResult; - /// [[GetOwnProperty]] - pub get_own_property: GetOwnProperty, + /// \[\[GetOwnProperty\]\] + fn get_own_property( + agent: &mut Agent, + object: Self, + property_key: PropertyKey, + ) -> JsResult>; - /// [[DefineOwnProperty]] - pub define_own_property: DefineOwnProperty, + /// \[\[DefineOwnProperty\]\] + fn define_own_property( + agent: &mut Agent, + object: Self, + property_key: PropertyKey, + property_descriptor: PropertyDescriptor, + ) -> JsResult; - /// [[HasProperty]] - pub has_property: HasProperty, + /// \[\[HasProperty\]\] + fn has_property(agent: &mut Agent, object: Self, property_key: PropertyKey) -> JsResult; - /// [[Get]] - pub get: Get, + /// \[\[Get\]\] + fn get( + agent: &mut Agent, + object: Self, + property_key: PropertyKey, + receiver: Value, + ) -> JsResult; - /// [[Set]] - pub set: Set, + /// \[\[Set\]\] + fn set( + agent: &mut Agent, + object: Self, + property_key: PropertyKey, + value: Value, + receiver: Value, + ) -> JsResult; - /// [[Delete]] - pub delete: Delete, + /// \[\[Delete\]\] + fn delete(agent: &mut Agent, object: Self, property_key: PropertyKey) -> JsResult; - /// [[OwnPropertyKeys]] - pub own_property_keys: OwnPropertyKeys, + /// \[\[OwnPropertyKeys\]\] + fn own_property_keys(agent: &mut Agent, object: Self) -> JsResult>; - /// [[Call]] - pub call: Option, + /// \[\[Call\]\] + fn call( + agent: &mut Agent, + object: Self, + this_value: Value, + arguments_list: &[Value], + ) -> JsResult { + unreachable!() + } - /// [[Construct]] - pub construct: Option, + /// \[\[Construct\]\] + fn construct(agent: &mut Agent, object: Self, arguments_list: &[Value]) -> JsResult { + unreachable!() + } } diff --git a/nova_vm/src/ecmascript/types/language/string.rs b/nova_vm/src/ecmascript/types/language/string.rs index e10a647ec..5261052ab 100644 --- a/nova_vm/src/ecmascript/types/language/string.rs +++ b/nova_vm/src/ecmascript/types/language/string.rs @@ -58,6 +58,19 @@ impl From for Value { } impl String { + pub fn from_str(agent: &mut Agent, message: &str) -> String { + if let Ok(ascii_string) = SmallString::try_from(message) { + String::SmallString(ascii_string) + } else { + String::String(agent.heap.alloc_string(message)) + } + } + + pub fn from_small_string(message: &'static str) -> String { + assert!(message.len() < 8 && !message.ends_with("\0")); + String::SmallString(SmallString::from_str_unchecked(message)) + } + pub fn into_value(self) -> Value { self.into() } diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index b3393c196..540e7b4a9 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -1,7 +1,10 @@ use std::mem::size_of; use crate::{ - ecmascript::execution::{Agent, JsResult}, + ecmascript::{ + abstract_operations::type_conversion::{to_int32, to_number, to_numeric, to_uint32}, + execution::{Agent, JsResult}, + }, heap::indexes::{ ArrayBufferIndex, ArrayIndex, BigIntIndex, DateIndex, ErrorIndex, FunctionIndex, NumberIndex, ObjectIndex, RegExpIndex, StringIndex, SymbolIndex, @@ -9,7 +12,7 @@ use crate::{ Heap, SmallInteger, SmallString, }; -use super::{BigInt, Number}; +use super::Number; /// 6.1 ECMAScript Language Types /// https://tc39.es/ecma262/#sec-ecmascript-language-types @@ -232,410 +235,20 @@ impl Value { } } - /// 7.1.1 ToPrimitive ( input [ , preferredType ] ) - /// https://tc39.es/ecma262/#sec-toprimitive - pub fn to_primitive( - self, - _agent: &mut Agent, - _preferred_type: Option, - ) -> JsResult { - let input = self; - - // 1. If input is an Object, then - if input.is_object() { - // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive). - // b. If exoticToPrim is not undefined, then - // i. If preferredType is not present, then - // 1. Let hint be "default". - // ii. Else if preferredType is string, then - // 1. Let hint be "string". - // iii. Else, - // 1. Assert: preferredType is number. - // 2. Let hint be "number". - // iv. Let result be ? Call(exoticToPrim, input, « hint »). - // v. If result is not an Object, return result. - // vi. Throw a TypeError exception. - // c. If preferredType is not present, let preferredType be number. - // d. Return ? OrdinaryToPrimitive(input, preferredType). - todo!(); - } - - // 2. Return input. - Ok(input) - } - - /// 7.1.1.1 OrdinaryToPrimitive ( O, hint ) - /// https://tc39.es/ecma262/#sec-ordinarytoprimitive - pub fn ordinary_to_primitive(self, _agent: &mut Agent, hint: PreferredType) -> JsResult { - // TODO: This takes in an object...so probably put it in Object. - let _o = self; - - // 1. If hint is string, then - let method_names = if matches!(hint, PreferredType::String) { - // a. Let methodNames be « "toString", "valueOf" ». - &["toString", "valueOf"] - } - // 2. Else, - else { - // a. Let methodNames be « "valueOf", "toString" ». - &["valueOf", "toString"] - }; - - // TODO: 3. For each element name of methodNames, do - for _name in method_names.iter() { - // a. Let method be ? Get(O, name). - // b. If IsCallable(method) is true, then - // i. Let result be ? Call(method, O). - // ii. If result is not an Object, return result. - // 4. Throw a TypeError exception. - } - - todo!() - } - - /// 7.1.2 ToBoolean ( argument ) - /// https://tc39.es/ecma262/#sec-toboolean - pub fn to_boolean(self, agent: &mut Agent) -> JsResult { - let argument = self; - - // 1. If argument is a Boolean, return argument. - if argument.is_boolean() { - return Ok(argument); - } - - // 2. If argument is one of undefined, null, +0𝔽, -0𝔽, NaN, 0ℤ, or the empty String, return false. - // TODO: checks for 0ℤ - if argument.is_undefined() - || argument.is_null() - || argument.is_pos_zero(agent) - || argument.is_neg_zero(agent) - || argument.is_nan(agent) - || argument.is_empty_string() - { - return Ok(false.into()); - } - - // 3. NOTE: This step is replaced in section B.3.6.1. - - // 4. Return true. - Ok(true.into()) - } - - /// 7.1.3 ToNumeric ( value ) - /// https://tc39.es/ecma262/#sec-tonumeric - pub fn to_numeric(self, agent: &mut Agent) -> JsResult { - let value = self; - - // 1. Let primValue be ? ToPrimitive(value, number). - let prim_value = value.to_primitive(agent, Some(PreferredType::Number))?; - - // 2. If primValue is a BigInt, return primValue. - if prim_value.is_bigint() { - return Ok(prim_value); - } - - // 3. Return ? ToNumber(primValue). - prim_value.to_number(agent).map(|n| n.into_value()) - } - - /// 7.1.4 ToNumber ( argument ) - /// https://tc39.es/ecma262/#sec-tonumber pub fn to_number(self, agent: &mut Agent) -> JsResult { - let argument = self; - - // 1. If argument is a Number, return argument. - if let Ok(argument) = Number::try_from(argument) { - return Ok(argument); - } - - // 2. If argument is either a Symbol or a BigInt, throw a TypeError exception. - if argument.is_symbol() || argument.is_bigint() { - todo!(); - } - - // 3. If argument is undefined, return NaN. - if argument.is_undefined() { - return Ok(Number::nan()); - } - - // 4. If argument is either null or false, return +0𝔽. - if argument.is_null() || argument.is_false() { - return Ok(Number::from(0)); - } - - // 5. If argument is true, return 1𝔽. - if argument.is_true() { - return Ok(Number::from(1)); - } - - // 6. If argument is a String, return StringToNumber(argument). - if argument.is_string() { - todo!(); - } - - // 7. Assert: argument is an Object. - debug_assert!(argument.is_object()); - - // 8. Let primValue be ? ToPrimitive(argument, number). - let prim_value = argument.to_primitive(agent, Some(PreferredType::Number))?; - - // 9. Assert: primValue is not an Object. - debug_assert!(!prim_value.is_object()); - - // 10. Return ? ToNumber(primValue). - prim_value.to_number(agent) + to_number(agent, self) } - /// 7.1.5 ToIntegerOrInfinity ( argument ) - /// https://tc39.es/ecma262/#sec-tointegerorinfinity - // TODO: Should we add another [`Value`] newtype for IntegerOrInfinity? - pub fn to_integer_or_infinty(self, agent: &mut Agent) -> JsResult { - let argument: Value = self; - - // 1. Let number be ? ToNumber(argument). - let number = argument.to_number(agent)?; - - // 2. If number is one of NaN, +0𝔽, or -0𝔽, return 0. - if number.is_nan(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { - return Ok(Number::pos_zero()); - } - - // 3. If number is +∞𝔽, return +∞. - if number.is_pos_infinity(agent) { - return Ok(Number::pos_inf()); - } - - // 4. If number is -∞𝔽, return -∞. - if number.is_neg_infinity(agent) { - return Ok(Number::neg_inf()); - } - - // 5. Return truncate(ℝ(number)). - Ok(number.truncate(agent)) + pub fn to_numeric(self, agent: &mut Agent) -> JsResult { + to_numeric(agent, self) } - /// 7.1.6 ToInt32 ( argument ) - /// https://tc39.es/ecma262/#sec-toint32 pub fn to_int32(self, agent: &mut Agent) -> JsResult { - let argument = self; - - // 1. Let number be ? ToNumber(argument). - let number = argument.to_number(agent)?; - - // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. - if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { - return Ok(0); - } - - // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent).into_f64(agent); - - // 4. Let int32bit be int modulo 2^32. - let int32bit = int % 2f64.powi(32); - - // 5. If int32bit ≥ 2^31, return 𝔽(int32bit - 2^32); otherwise return 𝔽(int32bit). - Ok(if int32bit >= 2f64.powi(32) { - int32bit - 2f64.powi(32) - } else { - int32bit - } as i32) + to_int32(agent, self) } - /// 7.1.7 ToUint32 ( argument ) - /// https://tc39.es/ecma262/#sec-touint32 pub fn to_uint32(self, agent: &mut Agent) -> JsResult { - let argument = self; - - // 1. Let number be ? ToNumber(argument). - let number = argument.to_number(agent)?; - - // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. - if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { - return Ok(0); - } - - // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent).into_f64(agent); - - // 4. Let int32bit be int modulo 2^32. - let int32bit = int % 2f64.powi(32); - - // 5. Return 𝔽(int32bit). - Ok(int32bit as u32) - } - - /// 7.1.8 ToInt16 ( argument ) - /// https://tc39.es/ecma262/#sec-toint16 - pub fn to_int16(self, agent: &mut Agent) -> JsResult { - let argument = self; - - // 1. Let number be ? ToNumber(argument). - let number = argument.to_number(agent)?; - - // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. - if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { - return Ok(0); - } - - // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent).into_f64(agent); - - // 4. Let int16bit be int modulo 2^16. - let int16bit = int % 2f64.powi(16); - - // 5. If int16bit ≥ 2^15, return 𝔽(int16bit - 2^16); otherwise return 𝔽(int16bit). - Ok(if int16bit >= 2f64.powi(15) { - int16bit - 2f64.powi(16) - } else { - int16bit - } as i16) - } - - /// 7.1.9 ToUint16 ( argument ) - /// https://tc39.es/ecma262/#sec-touint16 - pub fn to_uint16(self, agent: &mut Agent) -> JsResult { - let argument = self; - - // 1. Let number be ? ToNumber(argument). - let number = argument.to_number(agent)?; - - // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. - if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { - return Ok(0); - } - - // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent).into_f64(agent); - - // 4. Let int16bit be int modulo 2^16. - let int16bit = int % 2f64.powi(16); - - // Return 𝔽(int16bit). - Ok(int16bit as i16) - } - - /// 7.1.10 ToInt8 ( argument ) - /// https://tc39.es/ecma262/#sec-toint8 - pub fn to_int8(self, agent: &mut Agent) -> JsResult { - let argument = self; - - // 1. Let number be ? ToNumber(argument). - let number = argument.to_number(agent)?; - - // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. - if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { - return Ok(0); - } - - // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent).into_f64(agent); - - // 4. Let int8bit be int modulo 2^8. - let int8bit = int % 2f64.powi(8); - - // 5. If int8bit ≥ 2^7, return 𝔽(int8bit - 2^8); otherwise return 𝔽(int8bit). - Ok(if int8bit >= 2f64.powi(7) { - int8bit - 2f64.powi(8) - } else { - int8bit - } as i8) - } - - /// 7.1.11 ToUint8 ( argument ) - /// https://tc39.es/ecma262/#sec-touint8 - pub fn to_uint8(self, agent: &mut Agent) -> JsResult { - let argument = self; - - // 1. Let number be ? ToNumber(argument). - let number = argument.to_number(agent)?; - - // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. - if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { - return Ok(0); - } - - // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent).into_f64(agent); - - // 4. Let int8bit be int modulo 2^8. - let int8bit = int % 2f64.powi(8); - - // 5. Return 𝔽(int8bit). - Ok(int8bit as u8) - } - - /// 7.1.12 ToUint8Clamp ( argument ) - /// https://tc39.es/ecma262/#sec-touint8clamp - pub fn to_uint8_clamp(self, agent: &mut Agent) -> JsResult { - let argument = self; - - // 1. Let number be ? ToNumber(argument). - let number = argument.to_number(agent)?; - - // 2. If number is NaN, return +0𝔽. - if number.is_nan(agent) { - return Ok(0); - } - - // 3. Let mv be the extended mathematical value of number. - // TODO: Is there a better way? - let mv = number.into_f64(agent); - - // 4. Let clamped be the result of clamping mv between 0 and 255. - let clamped = mv.clamp(0.0, 255.0); - - // 5. Let f be floor(clamped). - let f = clamped.floor(); - - Ok( - // 6. If clamped < f + 0.5, return 𝔽(f). - if clamped < f + 0.5 { - f as u8 - } - // 7. If clamped > f + 0.5, return 𝔽(f + 1). - else if clamped > f + 0.5 { - f as u8 + 1 - } - // 8. If f is even, return 𝔽(f). Otherwise, return 𝔽(f + 1). - else if f % 2.0 == 0.0 { - f as u8 - } else { - f as u8 + 1 - }, - ) - } - - /// 7.1.13 ToBigInt ( argument ) - /// https://tc39.es/ecma262/#sec-tobigint - pub fn to_big_int(self, agent: &mut Agent) -> JsResult { - let argument = self; - - // 1. Let prim be ? ToPrimitive(argument, number). - let _prim = argument.to_primitive(agent, Some(PreferredType::Number))?; - - // 2. Return the value that prim corresponds to in Table 12. - todo!() - } - - /// 7.1.17 ToString ( argument ) - /// https://tc39.es/ecma262/#sec-tostring - pub fn to_string(self, _agent: &mut Agent) -> JsResult { - let _argument = self; - - // TODO: 1. If argument is a String, return argument. - // 2. If argument is a Symbol, throw a TypeError exception. - // 3. If argument is undefined, return "undefined". - // 4. If argument is null, return "null". - // 5. If argument is true, return "true". - // 6. If argument is false, return "false". - // 7. If argument is a Number, return Number::toString(argument, 10). - // 8. If argument is a BigInt, return BigInt::toString(argument, 10). - // 9. Assert: argument is an Object. - // 10. Let primValue be ? ToPrimitive(argument, string). - // 11. Assert: primValue is not an Object. - // 12. Return ? ToString(primValue). - - todo!() + to_uint32(agent, self) } fn is_same_type(self, y: Self) -> bool { diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index e7ef6b9a2..7c3cd8ea0 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -17,7 +17,7 @@ mod regexp; mod string; mod symbol; -pub use self::heap_constants::BuiltinObjectIndexes; +pub(crate) use self::heap_constants::{BuiltinObjectIndexes, WellKnownSymbolIndexes}; use self::{ array::initialize_array_heap, array_buffer::initialize_array_buffer_heap, diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index fa2b9470f..fb2d97d02 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -163,7 +163,7 @@ pub const fn get_constructor_index(object_index: BuiltinObjectIndexes) -> Functi #[derive(Debug, Clone, Copy)] #[repr(u32)] -pub enum WellKnownSymbolIndexes { +pub(crate) enum WellKnownSymbolIndexes { AsyncIterator, HasInstance, IsConcatSpreadable,