-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ecmascript): Implement a smattering of abstract operations
- Loading branch information
Showing
16 changed files
with
875 additions
and
248 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
mod abstract_operations; | ||
pub mod builtins; | ||
pub mod execution; | ||
pub mod scripts_and_modules; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
mod operations_on_objects; | ||
mod testing_and_comparison; | ||
mod type_conversion; |
133 changes: 133 additions & 0 deletions
133
nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Value> { | ||
// 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<Value> { | ||
// 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<Option<Function>> { | ||
// 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<Value> { | ||
// 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<Value> { | ||
let arguments_list = arguments_list.unwrap_or(&[]); | ||
Function::call(agent, f, v, arguments_list) | ||
} |
50 changes: 50 additions & 0 deletions
50
nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Value> { | ||
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<bool> { | ||
// 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(_)) | ||
} |
167 changes: 167 additions & 0 deletions
167
nova_vm/src/ecmascript/abstract_operations/type_conversion.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
//! ## [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::{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<PreferredType>, | ||
) -> JsResult<Value> { | ||
// 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<Value> { | ||
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.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<Object> { | ||
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()), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.