feat: works

This commit is contained in:
2022-07-17 10:11:20 +08:00
parent 4ba63b4c2e
commit 74a202f1ed
458 changed files with 125067 additions and 8 deletions

View File

@@ -0,0 +1,188 @@
use super::{Display, JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler};
impl From<&Self> for JsValue {
#[inline]
fn from(value: &Self) -> Self {
value.clone()
}
}
impl<T> From<T> for JsValue
where
T: Into<JsString>,
{
#[inline]
fn from(value: T) -> Self {
let _timer = Profiler::global().start_event("From<String>", "value");
Self::String(value.into())
}
}
impl From<char> for JsValue {
#[inline]
fn from(value: char) -> Self {
Self::new(value.to_string())
}
}
impl From<JsSymbol> for JsValue {
#[inline]
fn from(value: JsSymbol) -> Self {
Self::Symbol(value)
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct TryFromCharError;
impl Display for TryFromCharError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Could not convert value to a char type")
}
}
impl From<f32> for JsValue {
#[allow(clippy::float_cmp)]
#[inline]
fn from(value: f32) -> Self {
// if value as i32 as f64 == value {
// Self::Integer(value as i32)
// } else {
Self::Rational(value.into())
// }
}
}
impl From<f64> for JsValue {
#[allow(clippy::float_cmp)]
#[inline]
fn from(value: f64) -> Self {
// if value as i32 as f64 == value {
// Self::Integer(value as i32)
// } else {
Self::Rational(value)
// }
}
}
impl From<u8> for JsValue {
#[inline]
fn from(value: u8) -> Self {
Self::Integer(value.into())
}
}
impl From<i8> for JsValue {
#[inline]
fn from(value: i8) -> Self {
Self::Integer(value.into())
}
}
impl From<u16> for JsValue {
#[inline]
fn from(value: u16) -> Self {
Self::Integer(value.into())
}
}
impl From<i16> for JsValue {
#[inline]
fn from(value: i16) -> Self {
Self::Integer(value.into())
}
}
impl From<u32> for JsValue {
#[inline]
fn from(value: u32) -> Self {
if let Ok(integer) = i32::try_from(value) {
Self::Integer(integer)
} else {
Self::Rational(value.into())
}
}
}
impl From<i32> for JsValue {
#[inline]
fn from(value: i32) -> Self {
Self::Integer(value)
}
}
impl From<JsBigInt> for JsValue {
#[inline]
fn from(value: JsBigInt) -> Self {
Self::BigInt(value)
}
}
impl From<usize> for JsValue {
#[inline]
fn from(value: usize) -> Self {
if let Ok(value) = i32::try_from(value) {
Self::Integer(value)
} else {
Self::Rational(value as f64)
}
}
}
impl From<u64> for JsValue {
#[inline]
fn from(value: u64) -> Self {
if let Ok(value) = i32::try_from(value) {
Self::Integer(value)
} else {
Self::Rational(value as f64)
}
}
}
impl From<i64> for JsValue {
#[inline]
fn from(value: i64) -> Self {
if let Ok(value) = i32::try_from(value) {
Self::Integer(value)
} else {
Self::Rational(value as f64)
}
}
}
impl From<bool> for JsValue {
#[inline]
fn from(value: bool) -> Self {
Self::Boolean(value)
}
}
impl From<JsObject> for JsValue {
#[inline]
fn from(object: JsObject) -> Self {
let _timer = Profiler::global().start_event("From<JsObject>", "value");
Self::Object(object)
}
}
impl From<()> for JsValue {
#[inline]
fn from(_: ()) -> Self {
Self::null()
}
}
pub(crate) trait IntoOrUndefined {
fn into_or_undefined(self) -> JsValue;
}
impl<T> IntoOrUndefined for Option<T>
where
T: Into<JsValue>,
{
fn into_or_undefined(self) -> JsValue {
self.map_or_else(JsValue::undefined, Into::into)
}
}

View File

@@ -0,0 +1,313 @@
use crate::{object::ObjectKind, property::PropertyDescriptor};
use super::{fmt, Display, HashSet, JsValue, PropertyKey};
/// This object is used for displaying a `Value`.
#[derive(Debug, Clone, Copy)]
pub struct ValueDisplay<'value> {
pub(super) value: &'value JsValue,
pub(super) internals: bool,
}
impl ValueDisplay<'_> {
/// Display internal information about value.
///
/// By default this is `false`.
#[inline]
#[must_use]
pub fn internals(mut self, yes: bool) -> Self {
self.internals = yes;
self
}
}
/// A helper macro for printing objects
/// Can be used to print both properties and internal slots
/// All of the overloads take:
/// - The object to be printed
/// - The function with which to print
/// - The indentation for the current level (for nested objects)
/// - A `HashSet` with the addresses of the already printed objects for the current branch
/// (used to avoid infinite loops when there are cyclic deps)
macro_rules! print_obj_value {
(all of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => {
{
let mut internals = print_obj_value!(internals of $obj, $display_fn, $indent, $encounters);
let mut props = print_obj_value!(props of $obj, $display_fn, $indent, $encounters, true);
props.reserve(internals.len());
props.append(&mut internals);
props
}
};
(internals of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => {
{
let object = $obj.borrow();
if let Some(object) = object.prototype() {
vec![format!(
"{:>width$}: {}",
"__proto__",
$display_fn(&object.clone().into(), $encounters, $indent.wrapping_add(4), true),
width = $indent,
)]
} else {
vec![format!(
"{:>width$}: {}",
"__proto__",
JsValue::Null.display(),
width = $indent,
)]
}
}
};
(props of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr, $print_internals:expr) => {
print_obj_value!(impl $obj, |(key, val)| {
if val.is_data_descriptor() {
let v = &val.expect_value();
format!(
"{:>width$}: {}",
key,
$display_fn(v, $encounters, $indent.wrapping_add(4), $print_internals),
width = $indent,
)
} else {
let display = match (val.set().is_some(), val.get().is_some()) {
(true, true) => "Getter & Setter",
(true, false) => "Setter",
(false, true) => "Getter",
_ => "No Getter/Setter"
};
format!("{:>width$}: {}", key, display, width = $indent)
}
})
};
// A private overload of the macro
// DO NOT use directly
(impl $v:expr, $f:expr) => {
$v
.borrow()
.properties()
.iter()
.map($f)
.collect::<Vec<String>>()
};
}
pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children: bool) -> String {
match x {
// We don't want to print private (compiler) or prototype properties
JsValue::Object(ref v) => {
// Can use the private "type" field of an Object to match on
// which type of Object it represents for special printing
match v.borrow().kind() {
ObjectKind::String(ref string) => format!("String {{ \"{string}\" }}"),
ObjectKind::Boolean(boolean) => format!("Boolean {{ {boolean} }}"),
ObjectKind::Number(rational) => {
if rational.is_sign_negative() && *rational == 0.0 {
"Number { -0 }".to_string()
} else {
let mut buffer = ryu_js::Buffer::new();
format!("Number {{ {} }}", buffer.format(*rational))
}
}
ObjectKind::Array => {
let len = v
.borrow()
.properties()
.get(&PropertyKey::from("length"))
.expect("array object must have 'length' property")
// FIXME: handle accessor descriptors
.expect_value()
.as_number()
.map(|n| n as i32)
.unwrap_or_default();
if print_children {
if len == 0 {
return String::from("[]");
}
let arr = (0..len)
.map(|i| {
// Introduce recursive call to stringify any objects
// which are part of the Array
// FIXME: handle accessor descriptors
if let Some(value) = v
.borrow()
.properties()
.get(&i.into())
.and_then(|x| x.value().cloned())
{
log_string_from(&value, print_internals, false)
} else {
String::from("<empty>")
}
})
.collect::<Vec<String>>()
.join(", ");
format!("[ {arr} ]")
} else {
format!("Array({len})")
}
}
ObjectKind::Map(ref map) => {
let size = map.len();
if size == 0 {
return String::from("Map(0)");
}
if print_children {
let mappings = map
.iter()
.map(|(key, value)| {
let key = log_string_from(key, print_internals, false);
let value = log_string_from(value, print_internals, false);
format!("{key}{value}")
})
.collect::<Vec<String>>()
.join(", ");
format!("Map {{ {mappings} }}")
} else {
format!("Map({size})")
}
}
ObjectKind::Set(ref set) => {
let size = set.size();
if size == 0 {
return String::from("Set(0)");
}
if print_children {
let entries = set
.iter()
.map(|value| log_string_from(value, print_internals, false))
.collect::<Vec<String>>()
.join(", ");
format!("Set {{ {entries} }}")
} else {
format!("Set({size})")
}
}
_ => display_obj(x, print_internals),
}
}
JsValue::Symbol(ref symbol) => symbol.to_string(),
_ => x.display().to_string(),
}
}
/// A helper function for specifically printing object values
pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String {
// A simple helper for getting the address of a value
// TODO: Find a more general place for this, as it can be used in other situations as well
fn address_of<T>(t: &T) -> usize {
let my_ptr: *const T = t;
my_ptr as usize
}
fn display_obj_internal(
data: &JsValue,
encounters: &mut HashSet<usize>,
indent: usize,
print_internals: bool,
) -> String {
if let JsValue::Object(ref v) = *data {
// The in-memory address of the current object
let addr = address_of(v.as_ref());
// We need not continue if this object has already been
// printed up the current chain
if encounters.contains(&addr) {
return String::from("[Cycle]");
}
// Mark the current object as encountered
encounters.insert(addr);
let result = if print_internals {
print_obj_value!(all of v, display_obj_internal, indent, encounters).join(",\n")
} else {
print_obj_value!(props of v, display_obj_internal, indent, encounters, print_internals)
.join(",\n")
};
// If the current object is referenced in a different branch,
// it will not cause an infinite printing loop, so it is safe to be printed again
encounters.remove(&addr);
let closing_indent = String::from_utf8(vec![b' '; indent.wrapping_sub(4)])
.expect("Could not create the closing brace's indentation string");
format!("{{\n{result}\n{closing_indent}}}")
} else {
// Every other type of data is printed with the display method
data.display().to_string()
}
}
// We keep track of which objects we have encountered by keeping their
// in-memory address in this set
let mut encounters = HashSet::new();
if let JsValue::Object(object) = v {
if object.borrow().is_error() {
let name = v
.get_property("name")
.as_ref()
.and_then(PropertyDescriptor::value)
.unwrap_or(&JsValue::Undefined)
.display()
.to_string();
let message = v
.get_property("message")
.as_ref()
.and_then(PropertyDescriptor::value)
.unwrap_or(&JsValue::Undefined)
.display()
.to_string();
return format!("{name}: {message}");
}
}
display_obj_internal(v, &mut encounters, 4, print_internals)
}
impl Display for ValueDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.value {
JsValue::Null => write!(f, "null"),
JsValue::Undefined => write!(f, "undefined"),
JsValue::Boolean(v) => write!(f, "{v}"),
JsValue::Symbol(ref symbol) => match symbol.description() {
Some(description) => write!(f, "Symbol({description})"),
None => write!(f, "Symbol()"),
},
JsValue::String(ref v) => write!(f, "\"{v}\""),
JsValue::Rational(v) => format_rational(*v, f),
JsValue::Object(_) => {
write!(f, "{}", log_string_from(self.value, self.internals, true))
}
JsValue::Integer(v) => write!(f, "{v}"),
JsValue::BigInt(ref num) => write!(f, "{num}n"),
}
}
}
/// This is different from the ECMAScript compliant number to string, in the printing of `-0`.
///
/// This function prints `-0` as `-0` instead of positive `0` as the specification says.
/// This is done to make it easer for the user of the REPL to identify what is a `-0` vs `0`,
/// since the REPL is not bound to the ECMAScript specification we can do this.
fn format_rational(v: f64, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if v.is_sign_negative() && v == 0.0 {
f.write_str("-0")
} else {
let mut buffer = ryu_js::Buffer::new();
write!(f, "{}", buffer.format(v))
}
}

View File

@@ -0,0 +1,200 @@
use super::{JsBigInt, JsObject, JsResult, JsValue, PreferredType};
use crate::{builtins::Number, Context};
impl JsValue {
/// Strict equality comparison.
///
/// This method is executed when doing strict equality comparisons with the `===` operator.
/// For more information, check <https://tc39.es/ecma262/#sec-strict-equality-comparison>.
pub fn strict_equals(&self, other: &Self) -> bool {
// 1. If Type(x) is different from Type(y), return false.
if self.get_type() != other.get_type() {
return false;
}
match (self, other) {
// 2. If Type(x) is Number or BigInt, then
// a. Return ! Type(x)::equal(x, y).
(Self::BigInt(x), Self::BigInt(y)) => JsBigInt::equal(x, y),
(Self::Rational(x), Self::Rational(y)) => Number::equal(*x, *y),
(Self::Rational(x), Self::Integer(y)) => Number::equal(*x, f64::from(*y)),
(Self::Integer(x), Self::Rational(y)) => Number::equal(f64::from(*x), *y),
(Self::Integer(x), Self::Integer(y)) => x == y,
//Null has to be handled specially because "typeof null" returns object and if we managed
//this without a special case we would compare self and other as if they were actually
//objects which unfortunately fails
//Specification Link: https://tc39.es/ecma262/#sec-typeof-operator
(Self::Null, Self::Null) => true,
// 3. Return ! SameValueNonNumeric(x, y).
(_, _) => Self::same_value_non_numeric(self, other),
}
}
/// Abstract equality comparison.
///
/// This method is executed when doing abstract equality comparisons with the `==` operator.
/// For more information, check <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
#[allow(clippy::float_cmp)]
pub fn equals(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
// 1. If Type(x) is the same as Type(y), then
// a. Return the result of performing Strict Equality Comparison x === y.
if self.get_type() == other.get_type() {
return Ok(self.strict_equals(other));
}
Ok(match (self, other) {
// 2. If x is null and y is undefined, return true.
// 3. If x is undefined and y is null, return true.
(Self::Null, Self::Undefined) | (Self::Undefined, Self::Null) => true,
// 3. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y).
// 4. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y.
//
// https://github.com/rust-lang/rust/issues/54883
(Self::Integer(_) | Self::Rational(_), Self::String(_) | Self::Boolean(_))
| (Self::String(_), Self::Integer(_) | Self::Rational(_)) => {
let x = self.to_number(context)?;
let y = other.to_number(context)?;
Number::equal(x, y)
}
// 6. If Type(x) is BigInt and Type(y) is String, then
// a. Let n be ! StringToBigInt(y).
// b. If n is NaN, return false.
// c. Return the result of the comparison x == n.
(Self::BigInt(ref a), Self::String(ref b)) => match JsBigInt::from_string(b) {
Some(ref b) => a == b,
None => false,
},
// 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x.
(Self::String(ref a), Self::BigInt(ref b)) => match JsBigInt::from_string(a) {
Some(ref a) => a == b,
None => false,
},
// 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y.
(Self::Boolean(x), _) => return other.equals(&Self::new(i32::from(*x)), context),
// 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y).
(_, Self::Boolean(y)) => return self.equals(&Self::new(i32::from(*y)), context),
// 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result
// of the comparison x == ? ToPrimitive(y).
(
Self::Object(_),
Self::String(_)
| Self::Rational(_)
| Self::Integer(_)
| Self::BigInt(_)
| Self::Symbol(_),
) => {
let primitive = self.to_primitive(context, PreferredType::Default)?;
return Ok(primitive
.equals(other, context)
.expect("should not fail according to spec"));
}
// 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result
// of the comparison ? ToPrimitive(x) == y.
(
Self::String(_)
| Self::Rational(_)
| Self::Integer(_)
| Self::BigInt(_)
| Self::Symbol(_),
Self::Object(_),
) => {
let primitive = other.to_primitive(context, PreferredType::Default)?;
return Ok(primitive
.equals(self, context)
.expect("should not fail according to spec"));
}
// 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then
// a. If x or y are any of NaN, +∞, or -∞, return false.
// b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false.
(Self::BigInt(ref a), Self::Rational(ref b)) => a == b,
(Self::Rational(ref a), Self::BigInt(ref b)) => a == b,
(Self::BigInt(ref a), Self::Integer(ref b)) => a == b,
(Self::Integer(ref a), Self::BigInt(ref b)) => a == b,
// 13. Return false.
_ => false,
})
}
/// The internal comparison abstract operation SameValue(x, y),
/// where x and y are ECMAScript language values, produces true or false.
///
/// More information:
/// - [ECMAScript][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-samevalue
pub fn same_value(x: &Self, y: &Self) -> bool {
// 1. If Type(x) is different from Type(y), return false.
if x.get_type() != y.get_type() {
return false;
}
match (x, y) {
// 2. If Type(x) is Number or BigInt, then
// a. Return ! Type(x)::SameValue(x, y).
(Self::BigInt(x), Self::BigInt(y)) => JsBigInt::same_value(x, y),
(Self::Rational(x), Self::Rational(y)) => Number::same_value(*x, *y),
(Self::Rational(x), Self::Integer(y)) => Number::same_value(*x, f64::from(*y)),
(Self::Integer(x), Self::Rational(y)) => Number::same_value(f64::from(*x), *y),
(Self::Integer(x), Self::Integer(y)) => x == y,
// 3. Return ! SameValueNonNumeric(x, y).
(_, _) => Self::same_value_non_numeric(x, y),
}
}
/// The internal comparison abstract operation `SameValueZero(x, y)`,
/// where `x` and `y` are ECMAScript language values, produces `true` or `false`.
///
/// `SameValueZero` differs from `SameValue` only in its treatment of `+0` and `-0`.
///
/// More information:
/// - [ECMAScript][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-samevaluezero
pub fn same_value_zero(x: &Self, y: &Self) -> bool {
if x.get_type() != y.get_type() {
return false;
}
match (x, y) {
// 2. If Type(x) is Number or BigInt, then
// a. Return ! Type(x)::SameValueZero(x, y).
(JsValue::BigInt(x), JsValue::BigInt(y)) => JsBigInt::same_value_zero(x, y),
(JsValue::Rational(x), JsValue::Rational(y)) => Number::same_value_zero(*x, *y),
(JsValue::Rational(x), JsValue::Integer(y)) => {
Number::same_value_zero(*x, f64::from(*y))
}
(JsValue::Integer(x), JsValue::Rational(y)) => {
Number::same_value_zero(f64::from(*x), *y)
}
(JsValue::Integer(x), JsValue::Integer(y)) => x == y,
// 3. Return ! SameValueNonNumeric(x, y).
(_, _) => Self::same_value_non_numeric(x, y),
}
}
fn same_value_non_numeric(x: &Self, y: &Self) -> bool {
debug_assert!(x.get_type() == y.get_type());
match (x, y) {
(Self::Null, Self::Null) | (Self::Undefined, Self::Undefined) => true,
(Self::String(ref x), Self::String(ref y)) => x == y,
(Self::Boolean(x), Self::Boolean(y)) => x == y,
(Self::Object(ref x), Self::Object(ref y)) => JsObject::equals(x, y),
(Self::Symbol(ref x), Self::Symbol(ref y)) => x == y,
_ => false,
}
}
}

View File

@@ -0,0 +1,52 @@
use super::JsValue;
use crate::builtins::Number;
use std::hash::{Hash, Hasher};
impl PartialEq for JsValue {
fn eq(&self, other: &Self) -> bool {
Self::same_value_zero(self, other)
}
}
impl Eq for JsValue {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct UndefinedHashable;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct NullHashable;
#[derive(Debug, Clone, Copy)]
struct RationalHashable(f64);
impl PartialEq for RationalHashable {
#[inline]
fn eq(&self, other: &Self) -> bool {
Number::same_value(self.0, other.0)
}
}
impl Eq for RationalHashable {}
impl Hash for RationalHashable {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl Hash for JsValue {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Undefined => UndefinedHashable.hash(state),
Self::Null => NullHashable.hash(state),
Self::String(ref string) => string.hash(state),
Self::Boolean(boolean) => boolean.hash(state),
Self::Integer(integer) => RationalHashable(f64::from(*integer)).hash(state),
Self::BigInt(ref bigint) => bigint.hash(state),
Self::Rational(rational) => RationalHashable(*rational).hash(state),
Self::Symbol(ref symbol) => Hash::hash(symbol, state),
Self::Object(ref object) => std::ptr::hash(object.as_ref(), state),
}
}
}

View File

@@ -0,0 +1,69 @@
use std::cmp::Ordering;
/// Represents the result of `ToIntegerOrInfinity` operation
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum IntegerOrInfinity {
PositiveInfinity,
Integer(i64),
NegativeInfinity,
}
impl IntegerOrInfinity {
/// Clamps an `IntegerOrInfinity` between two `i64`, effectively converting
/// it to an i64.
pub fn clamp_finite(self, min: i64, max: i64) -> i64 {
assert!(min <= max);
match self {
Self::Integer(i) => i.clamp(min, max),
Self::PositiveInfinity => max,
Self::NegativeInfinity => min,
}
}
/// Gets the wrapped `i64` if the variant is an `Integer`.
pub fn as_integer(self) -> Option<i64> {
match self {
Self::Integer(i) => Some(i),
_ => None,
}
}
}
impl PartialEq<i64> for IntegerOrInfinity {
fn eq(&self, other: &i64) -> bool {
match self {
Self::Integer(i) => i == other,
_ => false,
}
}
}
impl PartialEq<IntegerOrInfinity> for i64 {
fn eq(&self, other: &IntegerOrInfinity) -> bool {
match other {
IntegerOrInfinity::Integer(i) => i == other,
_ => false,
}
}
}
impl PartialOrd<i64> for IntegerOrInfinity {
fn partial_cmp(&self, other: &i64) -> Option<Ordering> {
match self {
Self::PositiveInfinity => Some(Ordering::Greater),
Self::Integer(i) => i.partial_cmp(other),
Self::NegativeInfinity => Some(Ordering::Less),
}
}
}
impl PartialOrd<IntegerOrInfinity> for i64 {
fn partial_cmp(&self, other: &IntegerOrInfinity) -> Option<Ordering> {
match other {
IntegerOrInfinity::PositiveInfinity => Some(Ordering::Less),
IntegerOrInfinity::Integer(i) => self.partial_cmp(i),
IntegerOrInfinity::NegativeInfinity => Some(Ordering::Greater),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,675 @@
use super::{
Context, FromStr, JsBigInt, JsResult, JsString, JsValue, Numeric, PreferredType,
WellKnownSymbols,
};
use crate::builtins::number::{f64_to_int32, f64_to_uint32, Number};
impl JsValue {
#[inline]
pub fn add(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
// Numeric add
(Self::Integer(x), Self::Integer(y)) => x
.checked_add(*y)
.map_or_else(|| Self::new(f64::from(*x) + f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x + y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) + y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x + f64::from(*y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::add(x, y)),
// String concat
(Self::String(ref x), Self::String(ref y)) => Self::from(JsString::concat(x, y)),
(Self::String(ref x), y) => Self::from(JsString::concat(x, y.to_string(context)?)),
(x, Self::String(ref y)) => Self::from(JsString::concat(x.to_string(context)?, y)),
// Slow path:
(_, _) => match (
self.to_primitive(context, PreferredType::Default)?,
other.to_primitive(context, PreferredType::Default)?,
) {
(Self::String(ref x), ref y) => {
Self::from(JsString::concat(x, y.to_string(context)?))
}
(ref x, Self::String(ref y)) => {
Self::from(JsString::concat(x.to_string(context)?, y))
}
(x, y) => match (x.to_numeric(context)?, y.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::add(x, y))
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
)
}
},
},
})
}
#[inline]
pub fn sub(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => x
.checked_sub(*y)
.map_or_else(|| Self::new(f64::from(*x) - f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x - y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) - y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x - f64::from(*y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)),
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::new(a - b),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)),
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
);
}
},
})
}
#[inline]
pub fn mul(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => x
.checked_mul(*y)
.map_or_else(|| Self::new(f64::from(*x) * f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x * y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) * y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x * f64::from(*y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)),
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::new(a * b),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)),
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
);
}
},
})
}
#[inline]
pub fn div(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => x
.checked_div(*y)
.filter(|div| *y * div == *x)
.map_or_else(|| Self::new(f64::from(*x) / f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x / y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) / y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x / f64::from(*y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => {
if y.is_zero() {
return context.throw_range_error("BigInt division by zero");
}
Self::new(JsBigInt::div(x, y))
}
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::new(a / b),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
if y.is_zero() {
return context.throw_range_error("BigInt division by zero");
}
Self::new(JsBigInt::div(x, y))
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
);
}
},
})
}
#[inline]
pub fn rem(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => {
if *y == 0 {
Self::nan()
} else {
match x % *y {
rem if rem == 0 && *x < 0 => Self::new(-0.0),
rem => Self::new(rem),
}
}
}
(Self::Rational(x), Self::Rational(y)) => Self::new((x % y).copysign(*x)),
(Self::Integer(x), Self::Rational(y)) => {
let x = f64::from(*x);
Self::new((x % y).copysign(x))
}
(Self::Rational(x), Self::Integer(y)) => Self::new((x % f64::from(*y)).copysign(*x)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => {
if y.is_zero() {
return context.throw_range_error("BigInt division by zero");
}
Self::new(JsBigInt::rem(x, y))
}
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::new(a % b),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
if y.is_zero() {
return context.throw_range_error("BigInt division by zero");
}
Self::new(JsBigInt::rem(x, y))
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
);
}
},
})
}
#[inline]
pub fn pow(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => u32::try_from(*y)
.ok()
.and_then(|y| x.checked_pow(y))
.map_or_else(|| Self::new(f64::from(*x).powi(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x.powf(*y)),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x).powf(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(x.powi(*y)),
(Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::pow(a, b, context)?),
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::new(a.powf(b)),
(Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::new(JsBigInt::pow(a, b, context)?)
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
);
}
},
})
}
#[inline]
pub fn bitand(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x & y),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x) & f64_to_int32(*y))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x & f64_to_int32(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) & y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitand(x, y)),
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => {
Self::new(f64_to_int32(a) & f64_to_int32(b))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::bitand(x, y))
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
);
}
},
})
}
#[inline]
pub fn bitor(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x | y),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x) | f64_to_int32(*y))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x | f64_to_int32(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) | y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitor(x, y)),
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => {
Self::new(f64_to_int32(a) | f64_to_int32(b))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::bitor(x, y))
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
);
}
},
})
}
#[inline]
pub fn bitxor(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x ^ y),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x) ^ f64_to_int32(*y))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x ^ f64_to_int32(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) ^ y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitxor(x, y)),
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => {
Self::new(f64_to_int32(a) ^ f64_to_int32(b))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::bitxor(x, y))
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
);
}
},
})
}
#[inline]
pub fn shl(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shl(*y as u32)),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x).wrapping_shl(f64_to_uint32(*y)))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shl(f64_to_uint32(*y))),
(Self::Rational(x), Self::Integer(y)) => {
Self::new(f64_to_int32(*x).wrapping_shl(*y as u32))
}
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
Self::new(JsBigInt::shift_left(a, b, context)?)
}
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => {
Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y)))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::shift_left(x, y, context)?)
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
);
}
},
})
}
#[inline]
pub fn shr(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shr(*y as u32)),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x).wrapping_shr(f64_to_uint32(*y)))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shr(f64_to_uint32(*y))),
(Self::Rational(x), Self::Integer(y)) => {
Self::new(f64_to_int32(*x).wrapping_shr(*y as u32))
}
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
Self::new(JsBigInt::shift_right(a, b, context)?)
}
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => {
Self::new(f64_to_int32(x).wrapping_shr(f64_to_uint32(y)))
}
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::shift_right(x, y, context)?)
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
);
}
},
})
}
#[inline]
pub fn ushr(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new((*x as u32).wrapping_shr(*y as u32)),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_uint32(*x).wrapping_shr(f64_to_uint32(*y)))
}
(Self::Integer(x), Self::Rational(y)) => {
Self::new((*x as u32).wrapping_shr(f64_to_uint32(*y)))
}
(Self::Rational(x), Self::Integer(y)) => {
Self::new(f64_to_uint32(*x).wrapping_shr(*y as u32))
}
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => {
Self::new(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y)))
}
(Numeric::BigInt(_), Numeric::BigInt(_)) => {
return context
.throw_type_error("BigInts have no unsigned right shift, use >> instead");
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
);
}
},
})
}
/// Abstract operation `InstanceofOperator ( V, target )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-instanceofoperator
#[inline]
pub fn instance_of(&self, target: &Self, context: &mut Context) -> JsResult<bool> {
// 1. If Type(target) is not Object, throw a TypeError exception.
if !target.is_object() {
return context.throw_type_error(format!(
"right-hand side of 'instanceof' should be an object, got {}",
target.type_of()
));
}
// 2. Let instOfHandler be ? GetMethod(target, @@hasInstance).
match target.get_method(WellKnownSymbols::has_instance(), context)? {
// 3. If instOfHandler is not undefined, then
Some(instance_of_handler) => {
// a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)).
Ok(instance_of_handler
.call(target, std::slice::from_ref(self), context)?
.to_boolean())
}
None if target.is_callable() => {
// 5. Return ? OrdinaryHasInstance(target, V).
Self::ordinary_has_instance(target, self, context)
}
None => {
// 4. If IsCallable(target) is false, throw a TypeError exception.
context.throw_type_error("right-hand side of 'instanceof' is not callable")
}
}
}
#[inline]
pub fn neg(&self, context: &mut Context) -> JsResult<Self> {
Ok(match *self {
Self::Symbol(_) | Self::Undefined => Self::new(f64::NAN),
Self::Object(_) => Self::new(match self.to_numeric_number(context) {
Ok(num) => -num,
Err(_) => f64::NAN,
}),
Self::String(ref str) => Self::new(match f64::from_str(str) {
Ok(num) => -num,
Err(_) => f64::NAN,
}),
Self::Rational(num) => Self::new(-num),
Self::Integer(num) if num == 0 => Self::new(-f64::from(0)),
Self::Integer(num) => Self::new(-num),
Self::Boolean(true) => Self::new(1),
Self::Boolean(false) | Self::Null => Self::new(0),
Self::BigInt(ref x) => Self::new(JsBigInt::neg(x)),
})
}
#[inline]
pub fn not(&self, _: &mut Context) -> JsResult<bool> {
Ok(!self.to_boolean())
}
/// Abstract relational comparison
///
/// The comparison `x < y`, where `x` and `y` are values, produces `true`, `false`,
/// or `undefined` (which indicates that at least one operand is `NaN`).
///
/// In addition to `x` and `y` the algorithm takes a Boolean flag named `LeftFirst` as a parameter.
/// The 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. The default value of `LeftFirst` is `true` and indicates that 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`.
///
/// More Information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-abstract-relational-comparison
pub fn abstract_relation(
&self,
other: &Self,
left_first: bool,
context: &mut Context,
) -> JsResult<AbstractRelation> {
Ok(match (self, other) {
// Fast path (for some common operations):
(Self::Integer(x), Self::Integer(y)) => (x < y).into(),
(Self::Integer(x), Self::Rational(y)) => Number::less_than(f64::from(*x), *y),
(Self::Rational(x), Self::Integer(y)) => Number::less_than(*x, f64::from(*y)),
(Self::Rational(x), Self::Rational(y)) => Number::less_than(*x, *y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => (x < y).into(),
// Slow path:
(_, _) => {
let (px, py) = if left_first {
let px = self.to_primitive(context, PreferredType::Number)?;
let py = other.to_primitive(context, PreferredType::Number)?;
(px, py)
} else {
// NOTE: The order of evaluation needs to be reversed to preserve left to right evaluation.
let py = other.to_primitive(context, PreferredType::Number)?;
let px = self.to_primitive(context, PreferredType::Number)?;
(px, py)
};
match (px, py) {
(Self::String(ref x), Self::String(ref y)) => {
if x.starts_with(y.as_str()) {
return Ok(AbstractRelation::False);
}
if y.starts_with(x.as_str()) {
return Ok(AbstractRelation::True);
}
for (x, y) in x.chars().zip(y.chars()) {
if x != y {
return Ok((x < y).into());
}
}
unreachable!()
}
(Self::BigInt(ref x), Self::String(ref y)) => {
if let Some(y) = JsBigInt::from_string(y) {
(*x < y).into()
} else {
AbstractRelation::Undefined
}
}
(Self::String(ref x), Self::BigInt(ref y)) => {
if let Some(x) = JsBigInt::from_string(x) {
(x < *y).into()
} else {
AbstractRelation::Undefined
}
}
(px, py) => match (px.to_numeric(context)?, py.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(),
(Numeric::BigInt(ref x), Numeric::Number(y)) => {
if y.is_nan() {
return Ok(AbstractRelation::Undefined);
}
if y.is_infinite() {
return Ok(y.is_sign_positive().into());
}
if let Ok(y) = JsBigInt::try_from(y) {
return Ok((*x < y).into());
}
(x.to_f64() < y).into()
}
(Numeric::Number(x), Numeric::BigInt(ref y)) => {
if x.is_nan() {
return Ok(AbstractRelation::Undefined);
}
if x.is_infinite() {
return Ok(x.is_sign_negative().into());
}
if let Ok(x) = JsBigInt::try_from(x) {
return Ok((x < *y).into());
}
(x < y.to_f64()).into()
}
},
}
}
})
}
/// The less than operator (`<`) returns `true` if the left operand is less than the right operand,
/// and `false` otherwise.
///
/// More Information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than
/// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation
#[inline]
pub fn lt(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
match self.abstract_relation(other, true, context)? {
AbstractRelation::True => Ok(true),
AbstractRelation::False | AbstractRelation::Undefined => Ok(false),
}
}
/// The less than or equal operator (`<=`) returns `true` if the left operand is less than
/// or equal to the right operand, and `false` otherwise.
///
/// More Information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than_or_equal
/// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation
#[inline]
pub fn le(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
match other.abstract_relation(self, false, context)? {
AbstractRelation::False => Ok(true),
AbstractRelation::True | AbstractRelation::Undefined => Ok(false),
}
}
/// The greater than operator (`>`) returns `true` if the left operand is greater than
/// the right operand, and `false` otherwise.
///
/// More Information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than
/// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation
#[inline]
pub fn gt(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
match other.abstract_relation(self, false, context)? {
AbstractRelation::True => Ok(true),
AbstractRelation::False | AbstractRelation::Undefined => Ok(false),
}
}
/// The greater than or equal operator (`>=`) returns `true` if the left operand is greater than
/// or equal to the right operand, and `false` otherwise.
///
/// More Information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than_or_equal
/// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation
#[inline]
pub fn ge(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
match self.abstract_relation(other, true, context)? {
AbstractRelation::False => Ok(true),
AbstractRelation::True | AbstractRelation::Undefined => Ok(false),
}
}
}
/// The result of the [Abstract Relational Comparison][arc].
///
/// Comparison `x < y`, where `x` and `y` are values.
/// It produces `true`, `false`, or `undefined`
/// (which indicates that at least one operand is `NaN`).
///
/// [arc]: https://tc39.es/ecma262/#sec-abstract-relational-comparison
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AbstractRelation {
/// `x` is less than `y`
True,
/// `x` is **not** less than `y`
False,
/// Indicates that at least one operand is `NaN`
Undefined,
}
impl From<bool> for AbstractRelation {
#[inline]
fn from(value: bool) -> Self {
if value {
Self::True
} else {
Self::False
}
}
}

View File

@@ -0,0 +1,277 @@
//! This module implements the conversions from and into [`serde_json::Value`].
use super::JsValue;
use crate::{
builtins::Array,
property::{PropertyDescriptor, PropertyKey},
Context, JsResult,
};
use serde_json::{Map, Value};
impl JsValue {
/// Converts a [`serde_json::Value`] to a `JsValue`.
///
/// # Example
///
/// ```
/// use boa_engine::{Context, JsValue};
///
/// let data = r#"
/// {
/// "name": "John Doe",
/// "age": 43,
/// "phones": [
/// "+44 1234567",
/// "+44 2345678"
/// ]
/// }"#;
///
/// let json: serde_json::Value = serde_json::from_str(data).unwrap();
///
/// let mut context = Context::default();
/// let value = JsValue::from_json(&json, &mut context).unwrap();
/// #
/// # assert_eq!(json, value.to_json(&mut context).unwrap());
/// ```
pub fn from_json(json: &Value, context: &mut Context) -> JsResult<Self> {
/// Biggest possible integer, as i64.
const MAX_INT: i64 = i32::MAX as i64;
/// Smallest possible integer, as i64.
const MIN_INT: i64 = i32::MIN as i64;
match json {
Value::Null => Ok(Self::Null),
Value::Bool(b) => Ok(Self::Boolean(*b)),
Value::Number(num) => num
.as_i64()
.filter(|n| (MIN_INT..=MAX_INT).contains(n))
.map(|i| Self::Integer(i as i32))
.or_else(|| num.as_f64().map(Self::Rational))
.ok_or_else(|| {
context.construct_type_error(format!(
"could not convert JSON number {num} to JsValue"
))
}),
Value::String(string) => Ok(Self::from(string.as_str())),
Value::Array(vec) => {
let mut arr = Vec::with_capacity(vec.len());
for val in vec {
arr.push(Self::from_json(val, context)?);
}
Ok(Array::create_array_from_list(arr, context).into())
}
Value::Object(obj) => {
let js_obj = context.construct_object();
for (key, value) in obj {
let property = PropertyDescriptor::builder()
.value(Self::from_json(value, context)?)
.writable(true)
.enumerable(true)
.configurable(true);
js_obj.borrow_mut().insert(key.as_str(), property);
}
Ok(js_obj.into())
}
}
}
/// Converts the `JsValue` to a [`serde_json::Value`].
///
/// # Example
///
/// ```
/// use boa_engine::{Context, JsValue};
///
/// let data = r#"
/// {
/// "name": "John Doe",
/// "age": 43,
/// "phones": [
/// "+44 1234567",
/// "+44 2345678"
/// ]
/// }"#;
///
/// let json: serde_json::Value = serde_json::from_str(data).unwrap();
///
/// let mut context = Context::default();
/// let value = JsValue::from_json(&json, &mut context).unwrap();
///
/// let back_to_json = value.to_json(&mut context).unwrap();
/// #
/// # assert_eq!(json, back_to_json);
/// ```
pub fn to_json(&self, context: &mut Context) -> JsResult<Value> {
match self {
Self::Null => Ok(Value::Null),
Self::Undefined => todo!("undefined to JSON"),
&Self::Boolean(b) => Ok(b.into()),
Self::String(string) => Ok(string.as_str().into()),
&Self::Rational(rat) => Ok(rat.into()),
&Self::Integer(int) => Ok(int.into()),
Self::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"),
Self::Object(obj) => {
if obj.is_array() {
let len = obj.length_of_array_like(context)?;
let mut arr = Vec::with_capacity(len);
let obj = obj.borrow();
for k in 0..len as u32 {
let val = obj.properties().get(&k.into()).map_or(Self::Null, |desc| {
desc.value().cloned().unwrap_or(Self::Null)
});
arr.push(val.to_json(context)?);
}
Ok(Value::Array(arr))
} else {
let mut map = Map::new();
for (key, property) in obj.borrow().properties().iter() {
let key = match &key {
PropertyKey::String(string) => string.as_str().to_owned(),
PropertyKey::Index(i) => i.to_string(),
PropertyKey::Symbol(_sym) => {
return context.throw_type_error("cannot convert Symbol to JSON")
}
};
let value = match property.value() {
Some(val) => val.to_json(context)?,
None => Value::Null,
};
map.insert(key, value);
}
Ok(Value::Object(map))
}
}
Self::Symbol(_sym) => context.throw_type_error("cannot convert Symbol to JSON"),
}
}
}
#[cfg(test)]
mod tests {
use crate::object::JsArray;
use crate::{Context, JsValue};
#[test]
fn ut_json_conversions() {
let data = r#"
{
"name": "John Doe",
"age": 43,
"minor": false,
"adult": true,
"extra": {
"address": null
},
"phones": [
"+44 1234567",
-45,
{},
true
]
}"#;
let json: serde_json::Value = serde_json::from_str(data).unwrap();
assert!(json.is_object());
let mut context = Context::default();
let value = JsValue::from_json(&json, &mut context).unwrap();
let obj = value.as_object().unwrap();
assert_eq!(obj.get("name", &mut context).unwrap(), "John Doe".into());
assert_eq!(obj.get("age", &mut context).unwrap(), 43_i32.into());
assert_eq!(obj.get("minor", &mut context).unwrap(), false.into());
assert_eq!(obj.get("adult", &mut context).unwrap(), true.into());
{
let extra = obj.get("extra", &mut context).unwrap();
let extra = extra.as_object().unwrap();
assert!(extra.get("address", &mut context).unwrap().is_null());
}
{
let phones = obj.get("phones", &mut context).unwrap();
let phones = phones.as_object().unwrap();
let arr = JsArray::from_object(phones.clone(), &mut context).unwrap();
assert_eq!(arr.at(0, &mut context).unwrap(), "+44 1234567".into());
assert_eq!(arr.at(1, &mut context).unwrap(), JsValue::from(-45_i32));
assert!(arr.at(2, &mut context).unwrap().is_object());
assert_eq!(arr.at(3, &mut context).unwrap(), true.into());
}
assert_eq!(json, value.to_json(&mut context).unwrap());
}
#[test]
fn integer_ops_to_json() {
let mut context = Context::default();
let add = context
.eval(
r#"
1000000 + 500
"#,
)
.unwrap();
let add: u32 = serde_json::from_value(add.to_json(&mut context).unwrap()).unwrap();
assert_eq!(add, 1_000_500);
let sub = context
.eval(
r#"
1000000 - 500
"#,
)
.unwrap();
let sub: u32 = serde_json::from_value(sub.to_json(&mut context).unwrap()).unwrap();
assert_eq!(sub, 999_500);
let mult = context
.eval(
r#"
1000000 * 500
"#,
)
.unwrap();
let mult: u32 = serde_json::from_value(mult.to_json(&mut context).unwrap()).unwrap();
assert_eq!(mult, 500_000_000);
let div = context
.eval(
r#"
1000000 / 500
"#,
)
.unwrap();
let div: u32 = serde_json::from_value(div.to_json(&mut context).unwrap()).unwrap();
assert_eq!(div, 2000);
let rem = context
.eval(
r#"
233894 % 500
"#,
)
.unwrap();
let rem: u32 = serde_json::from_value(rem.to_json(&mut context).unwrap()).unwrap();
assert_eq!(rem, 394);
let pow = context
.eval(
r#"
36 ** 5
"#,
)
.unwrap();
let pow: u32 = serde_json::from_value(pow.to_json(&mut context).unwrap()).unwrap();
assert_eq!(pow, 60466176);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
use super::JsValue;
/// Possible types of values as defined at <https://tc39.es/ecma262/#sec-typeof-operator>.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Type {
Undefined,
Null,
Boolean,
Number,
String,
Symbol,
BigInt,
Object,
}
impl JsValue {
/// Get the type of a value
///
/// This is the abstract operation Type(v), as described in
/// <https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-ecmascript-language-types>.
///
/// Check [`JsValue::type_of`] if you need to call the `typeof` operator.
pub fn get_type(&self) -> Type {
match *self {
Self::Rational(_) | Self::Integer(_) => Type::Number,
Self::String(_) => Type::String,
Self::Boolean(_) => Type::Boolean,
Self::Symbol(_) => Type::Symbol,
Self::Null => Type::Null,
Self::Undefined => Type::Undefined,
Self::BigInt(_) => Type::BigInt,
Self::Object(_) => Type::Object,
}
}
}