Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vm): more abstract operations #50

Merged
merged 20 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 88 additions & 1 deletion nova_vm/src/ecmascript/abstract_operations/type_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use crate::{
execution::{agent::ExceptionType, agent::JsError, Agent, JsResult},
types::{BigInt, Number, Object, PropertyKey, String, Value},
},
heap::WellKnownSymbolIndexes,
heap::{CreateHeapData, GetHeapData, WellKnownSymbolIndexes},
SmallInteger,
};

use super::{
Expand Down Expand Up @@ -490,3 +491,89 @@ pub(crate) fn to_object(_agent: &mut Agent, argument: Value) -> JsResult<Object>
_ => Ok(Object::try_from(argument).unwrap()),
}
}

/// ### [7.1.19 ToPropertyKey ( argument )](https://tc39.es/ecma262/#sec-topropertykey)
pub(crate) fn to_property_key(agent: &mut Agent, argument: Value) -> JsResult<PropertyKey> {
// 1. Let key be ? ToPrimitive(argument, hint String).
let key = to_primitive(agent, argument, Some(PreferredType::String))?;

// 2. If Type(key) is Symbol, then
// a. Return key.
// NOTE: This handles Symbols and other primitives because we use niche
// specializations for PropertyKey (e.g. integer indexes for arrays).
if let Ok(property_key) = PropertyKey::try_from(key) {
return Ok(property_key);
}
aapoalas marked this conversation as resolved.
Show resolved Hide resolved

// 3. Return ! ToString(key).
Ok(to_string(agent, key).unwrap().into())
}

/// ### [7.1.20 ToLength ( argument )](https://tc39.es/ecma262/#sec-tolength)
pub(crate) fn to_length(agent: &mut Agent, argument: Value) -> JsResult<i64> {
// TODO: This can be heavily optimized by inlining `to_integer_or_infinity`.

// 1. Let len be ? ToIntegerOrInfinity(argument).
let len = to_integer_or_infinity(agent, argument)?;

// 2. If len ≤ 0, return +0𝔽.
if match len {
Number::Integer(n) => n.into_i64() <= 0,
Number::Float(n) => n <= 0.0,
Number::Number(n) => *agent.heap.get(n) <= 0.0,
} {
return Ok(0);
}

// 3. Return 𝔽(min(len, 2**53 - 1)).
lino-levan marked this conversation as resolved.
Show resolved Hide resolved
Ok(match len {
Number::Integer(n) => n.into_i64().min(SmallInteger::MAX_NUMBER),
Number::Float(n) => n.min(SmallInteger::MAX_NUMBER as f32) as i64,
Number::Number(n) => agent.heap.get(n).min(SmallInteger::MAX_NUMBER as f64) as i64,
})
}

/// ### [7.1.21 CanonicalNumericIndexString ( argument )](https://tc39.es/ecma262/#sec-canonicalnumericindexstring)
pub(crate) fn canonical_numeric_index_string(
agent: &mut Agent,
argument: String,
) -> Option<Number> {
// 1. If argument is "-0", return -0𝔽.
if argument == String::from_small_string("-0") {
return Some((-0.0).into());
}

// 2. Let n be ! ToNumber(argument).
let n = to_number(agent, argument.into()).unwrap();

// 3. If ! ToString(n) is argument, return n.
if to_string(agent, n.into()).unwrap() == argument {
return Some(n);
}
aapoalas marked this conversation as resolved.
Show resolved Hide resolved

// 4. Return undefined.
None
}

/// ### [7.1.22 ToIndex ( value )](https://tc39.es/ecma262/#sec-toindex)
pub(crate) fn to_index(agent: &mut Agent, argument: Value) -> JsResult<i64> {
// TODO: This can be heavily optimized by inlining `to_integer_or_infinity`.

// 1. Let integer be ? ToIntegerOrInfinity(value).
let integer = to_integer_or_infinity(agent, argument)?;

// 2. If integer is not in the inclusive interval from 0 to 2**53 - 1, throw a RangeError exception.
let integer = if let Number::Integer(n) = integer {
let integer = n.into_i64();
if !(0..=(SmallInteger::MAX_NUMBER)).contains(&integer) {
return Err(agent.throw_exception(ExceptionType::RangeError, "Result is out of range"));
}
integer
} else {
// to_integer_or_infinity returns +0, +Infinity, -Infinity, or an integer.
return Err(agent.throw_exception(ExceptionType::RangeError, "Result is out of range"));
};

// 3. Return integer.
Ok(integer)
}
17 changes: 17 additions & 0 deletions nova_vm/src/ecmascript/types/language/object/property_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,26 @@ impl TryFrom<Value> for PropertyKey {
fn try_from(value: Value) -> Result<Self, Self::Error> {
match value {
Value::Integer(x) => Ok(PropertyKey::Integer(x)),
Value::Float(x) => {
if x == -0.0f32 {
Ok(PropertyKey::Integer(0.into()))
} else if x.fract() == 0.0
&& (SmallInteger::MIN_NUMBER..=SmallInteger::MAX_NUMBER).contains(&(x as i64))
{
unreachable!("Value::Float should not contain safe integers");
} else {
Err(())
}
}
Value::SmallString(x) => Ok(PropertyKey::SmallString(x)),
Value::String(x) => Ok(PropertyKey::String(x)),
Value::Symbol(x) => Ok(PropertyKey::Symbol(x)),
Value::SmallBigInt(x)
if (SmallInteger::MIN_NUMBER..=SmallInteger::MAX_NUMBER)
.contains(&x.into_i64()) =>
{
Ok(PropertyKey::Integer(x))
}
_ => Err(()),
}
}
Expand Down