feat: works
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
1083
__wasm/wit-bindgen-sample/engine/boa/boa_engine/src/value/mod.rs
Normal file
1083
__wasm/wit-bindgen-sample/engine/boa/boa_engine/src/value/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
1337
__wasm/wit-bindgen-sample/engine/boa/boa_engine/src/value/tests.rs
Normal file
1337
__wasm/wit-bindgen-sample/engine/boa/boa_engine/src/value/tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user