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(ecmascript): more builtins #43

Merged
merged 4 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
158 changes: 158 additions & 0 deletions nova_vm/src/ecmascript/abstract_operations/testing_and_comparison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use crate::ecmascript::{
types::{Number, Value},
};

use super::type_conversion::{to_primitive, PreferredType};

/// ### [7.2.1 RequireObjectCoercible ( argument )](https://tc39.es/ecma262/#sec-requireobjectcoercible)
///
/// The abstract operation RequireObjectCoercible takes argument argument (an
Expand Down Expand Up @@ -83,6 +85,37 @@ pub(crate) fn same_value<V1: Copy + Into<Value>, V2: Copy + Into<Value>>(
same_value_non_number(agent, x, y)
}

/// ### [7.2.11 SameValueZero ( x, y )](https://tc39.es/ecma262/#sec-samevaluezero)
///
/// The abstract operation SameValueZero takes arguments x (an ECMAScript
/// language value) and y (an ECMAScript language value) and returns a Boolean.
/// It determines whether or not the two arguments are the same value (ignoring
/// the difference between +0𝔽 and -0𝔽). It performs the following steps when
/// called:
pub(crate) fn same_value_zero(
agent: &mut Agent,
x: impl Copy + Into<Value>,
y: impl Copy + Into<Value>,
) -> bool {
let (x, y) = (x.into(), y.into());

// 1. If Type(x) is not Type(y), return false.
if !is_same_type(x, y) {
return false;
}

// 2. If x is a Number, then
// NOTE: We need to convert both to a number because we use number
// type-safety.
if let (Ok(x), Ok(y)) = (x.to_number(agent), y.to_number(agent)) {
// a. Return Number::sameValueZero(x, y).
return x.same_value_zero(agent, y);
}

// 3. Return SameValueNonNumber(x, y).
return same_value_non_number(agent, x, y);
}

/// 7.2.12 SameValueNonNumber ( x, y )
/// https://tc39.es/ecma262/#sec-samevaluenonnumber
pub(crate) fn same_value_non_number<T: Copy + Into<Value>>(_agent: &mut Agent, x: T, y: T) -> bool {
Expand Down Expand Up @@ -119,3 +152,128 @@ pub(crate) fn same_value_non_number<T: Copy + Into<Value>>(_agent: &mut Agent, x
// 7. If x is y, return true; otherwise, return false.
todo!()
}

/// [7.2.13 IsLessThan ( x, y, LeftFirst )](https://tc39.es/ecma262/#sec-islessthan)
///
/// The abstract operation IsLessThan takes arguments x (an ECMAScript language
/// value), y (an ECMAScript language value), and LeftFirst (a Boolean) and
/// returns either a normal completion containing either a Boolean or undefined,
/// or a throw completion. It provides the semantics for the comparison x < y,
/// returning true, false, or undefined (which indicates that at least one
/// operand is NaN). The LeftFirst flag is used to control the order in which
/// operations with potentially visible side-effects are performed upon x and y.
/// It is necessary because ECMAScript specifies left to right evaluation of
/// expressions. If LeftFirst is true, the x parameter corresponds to an
/// expression that occurs to the left of the y parameter's corresponding
/// expression. If LeftFirst is false, the reverse is the case and operations
/// must be performed upon y before x. It performs the following steps when
/// called:
pub(crate) fn is_less_than<const LEFT_FIRST: bool>(
agent: &mut Agent,
x: impl Into<Value> + Copy,
y: impl Into<Value> + Copy,
) -> JsResult<Value> {
sno2 marked this conversation as resolved.
Show resolved Hide resolved
// 1. If LeftFirst is true, then
let (px, py) = if LEFT_FIRST {
// a. Let px be ? ToPrimitive(x, NUMBER).
let px = to_primitive(agent, x.into(), Some(PreferredType::Number))?;

// b. Let py be ? ToPrimitive(y, NUMBER).
let py = to_primitive(agent, y.into(), Some(PreferredType::Number))?;

(px, py)
}
// 2. Else,
else {
// a. NOTE: The order of evaluation needs to be reversed to preserve left to right evaluation.
// b. Let py be ? ToPrimitive(y, NUMBER).
let py = to_primitive(agent, y.into(), Some(PreferredType::Number))?;

// c. Let px be ? ToPrimitive(x, NUMBER).
let px = to_primitive(agent, x.into(), Some(PreferredType::Number))?;

(px, py)
};

// 3. If px is a String and py is a String, then
if px.is_string() && py.is_string() {
todo!("Finish this")
// a. Let lx be the length of px.
// b. Let ly be the length of py.
// c. For each integer i such that 0 ≤ i < min(lx, ly), in ascending order, do
// i. Let cx be the numeric value of the code unit at index i within px.
// ii. Let cy be the numeric value of the code unit at index i within py.
// iii. If cx < cy, return true.
// iv. If cx > cy, return false.
// d. If lx < ly, return true. Otherwise, return false.
}
// 4. Else,
else {
// a. If px is a BigInt and py is a String, then
if px.is_bigint() && py.is_string() {
todo!("Finish this")
// i. Let ny be StringToBigInt(py).
// ii. If ny is undefined, return undefined.
// iii. Return BigInt::lessThan(px, ny).
}

// b. If px is a String and py is a BigInt, then
if px.is_string() && py.is_bigint() {
todo!("Finish this")
// i. Let nx be StringToBigInt(px).
// ii. If nx is undefined, return undefined.
// iii. Return BigInt::lessThan(nx, py).
}

// c. NOTE: Because px and py are primitive values, evaluation order is not important.
// d. Let nx be ? ToNumeric(px).
let nx = px.to_numeric(agent)?;

// e. Let ny be ? ToNumeric(py).
let ny = py.to_numeric(agent)?;

// f. If Type(nx) is Type(ny), then
if is_same_type(nx, ny) {
// i. If nx is a Number, then
if nx.is_number() {
// 1. Return Number::lessThan(nx, ny).
let nx = nx.to_number(agent)?;
let ny = ny.to_number(agent)?;
return Ok(nx.less_than(agent, ny).into());
}
// ii. Else,
else {
// 1. Assert: nx is a BigInt.
assert!(nx.is_bigint());

// 2. Return BigInt::lessThan(nx, ny).
let nx = nx.to_bigint(agent)?;
let ny = ny.to_bigint(agent)?;
return Ok(nx.less_than(agent, ny).into());
}
}

// g. Assert: nx is a BigInt and ny is a Number, or nx is a Number and ny is a BigInt.
assert!(nx.is_bigint() && ny.is_number() || nx.is_number() && ny.is_bigint());

// h. If nx or ny is NaN, return undefined.
if nx.is_nan(agent) || ny.is_nan(agent) {
return Ok(Value::Undefined);
}

// i. If nx is -∞𝔽 or ny is +∞𝔽, return true.
if nx.is_neg_infinity(agent) || ny.is_pos_infinity(agent) {
return Ok(Value::from(true));
}

// j. If nx is +∞𝔽 or ny is -∞𝔽, return false.
if nx.is_pos_infinity(agent) || ny.is_neg_infinity(agent) {
return Ok(Value::from(false));
}

// k. If ℝ(nx) < ℝ(ny), return true; otherwise return false.
let rnx = nx.to_real(agent)?;
let rny = nx.to_real(agent)?;
return Ok(Value::from(rnx < rny));
}
}
26 changes: 25 additions & 1 deletion nova_vm/src/ecmascript/types/language/bigint.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
mod data;

use super::value::{BIGINT_DISCRIMINANT, SMALL_BIGINT_DISCRIMINANT};
use crate::{heap::indexes::BigIntIndex, SmallInteger};
use crate::{
ecmascript::execution::Agent,
heap::{indexes::BigIntIndex, GetHeapData},
SmallInteger,
};

pub use data::BigIntHeapData;

Expand All @@ -11,3 +15,23 @@ pub enum BigInt {
BigInt(BigIntIndex) = BIGINT_DISCRIMINANT,
SmallBigInt(SmallInteger) = SMALL_BIGINT_DISCRIMINANT,
}

impl BigInt {
/// ### [6.1.6.2.12 BigInt::lessThan ( x, y )](https://tc39.es/ecma262/#sec-numeric-types-bigint-lessThan)
///
/// The abstract operation BigInt::lessThan takes arguments x (a BigInt) and
/// y (a BigInt) and returns a Boolean. It performs the following steps when
/// called:
pub(crate) fn less_than(self, agent: &mut Agent, y: BigInt) -> bool {
sno2 marked this conversation as resolved.
Show resolved Hide resolved
// 1. If ℝ(x) < ℝ(y), return true; otherwise return false.
match (self, y) {
(BigInt::BigInt(_), BigInt::SmallBigInt(_)) => false,
(BigInt::SmallBigInt(_), BigInt::BigInt(_)) => true,
(BigInt::BigInt(b1), BigInt::BigInt(b2)) => {
let (b1, b2) = (agent.heap.get(b1), agent.heap.get(b2));
b1.data < b2.data
}
(BigInt::SmallBigInt(b1), BigInt::SmallBigInt(b2)) => b1.into_i64() < b2.into_i64(),
}
}
}
2 changes: 1 addition & 1 deletion nova_vm/src/ecmascript/types/language/bigint/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use num_bigint_dig::BigInt;

#[derive(Debug, Clone)]
pub struct BigIntHeapData {
pub(super) data: BigInt,
pub(crate) data: BigInt,
}

impl TryInto<f64> for BigIntHeapData {
Expand Down
42 changes: 37 additions & 5 deletions nova_vm/src/ecmascript/types/language/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ use std::mem::size_of;

use crate::{
ecmascript::{
abstract_operations::type_conversion::{to_int32, to_number, to_numeric, to_uint32},
abstract_operations::type_conversion::{
to_big_int, to_int32, to_number, to_numeric, to_uint32,
},
execution::{Agent, JsResult},
},
heap::indexes::{
ArrayBufferIndex, ArrayIndex, BigIntIndex, DateIndex, ErrorIndex, FunctionIndex,
NumberIndex, ObjectIndex, RegExpIndex, StringIndex, SymbolIndex,
heap::{
indexes::{
ArrayBufferIndex, ArrayIndex, BigIntIndex, DateIndex, ErrorIndex, FunctionIndex,
NumberIndex, ObjectIndex, RegExpIndex, StringIndex, SymbolIndex,
},
GetHeapData,
},
Heap, SmallInteger, SmallString,
};

use super::Number;
use super::{BigInt, Number};

/// 6.1 ECMAScript Language Types
/// https://tc39.es/ecma262/#sec-ecmascript-language-types
Expand Down Expand Up @@ -208,6 +213,18 @@ impl Value {
.unwrap_or(false)
}

pub fn is_pos_infinity(self, agent: &mut Agent) -> bool {
Number::try_from(self)
.map(|n| n.is_pos_infinity(agent))
.unwrap_or(false)
}

pub fn is_neg_infinity(self, agent: &mut Agent) -> bool {
Number::try_from(self)
.map(|n| n.is_neg_infinity(agent))
.unwrap_or(false)
}

pub fn is_nan(self, agent: &mut Agent) -> bool {
Number::try_from(self)
.map(|n| n.is_nan(agent))
Expand Down Expand Up @@ -239,6 +256,10 @@ impl Value {
to_number(agent, self)
}

pub fn to_bigint(self, agent: &mut Agent) -> JsResult<BigInt> {
to_big_int(agent, self)
}

pub fn to_numeric(self, agent: &mut Agent) -> JsResult<Value> {
to_numeric(agent, self)
}
Expand All @@ -250,6 +271,17 @@ impl Value {
pub fn to_uint32(self, agent: &mut Agent) -> JsResult<u32> {
to_uint32(agent, self)
}

/// ### [ℝ](https://tc39.es/ecma262/#%E2%84%9D)
pub fn to_real(self, agent: &mut Agent) -> JsResult<f64> {
Ok(match self {
Value::Number(n) => *agent.heap.get(n),
Value::Integer(i) => i.into_i64() as f64,
Value::Float(f) => f as f64,
// NOTE: Converting to a number should give us a nice error message.
_ => to_number(agent, self)?.into_f64(agent),
})
}
}

impl From<bool> for Value {
Expand Down
1 change: 1 addition & 0 deletions nova_vm/src/heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ impl_heap_data!(functions, FunctionHeapData, FunctionHeapData);
impl_heap_data!(numbers, NumberHeapData, f64, data);
impl_heap_data!(objects, ObjectHeapData, ObjectHeapData);
impl_heap_data!(strings, StringHeapData, Wtf8Buf, data);
impl_heap_data!(bigints, BigIntHeapData, BigIntHeapData);

impl CreateHeapData<&str, String> for Heap<'_, '_> {
fn create(&mut self, data: &str) -> String {
Expand Down