feat: works
This commit is contained in:
75
__wasm/wit-bindgen-sample/engine/boa/boa_engine/Cargo.toml
Normal file
75
__wasm/wit-bindgen-sample/engine/boa/boa_engine/Cargo.toml
Normal file
@@ -0,0 +1,75 @@
|
||||
[package]
|
||||
name = "boa_engine"
|
||||
version = "0.15.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.60"
|
||||
authors = ["boa-dev"]
|
||||
description = "Boa is a Javascript lexer, parser and Just-in-Time compiler written in Rust. Currently, it has support for some of the language."
|
||||
repository = "https://github.com/boa-dev/boa"
|
||||
keywords = ["javascript", "js", "compiler", "lexer", "parser"]
|
||||
categories = ["parser-implementations", "compilers"]
|
||||
license = "Unlicense/MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
[features]
|
||||
profiler = ["boa_profiler/profiler"]
|
||||
deser = ["boa_interner/serde"]
|
||||
intl = [
|
||||
"dep:icu_locale_canonicalizer",
|
||||
"dep:icu_locid",
|
||||
"dep:icu_datetime",
|
||||
"dep:icu_plurals",
|
||||
"dep:icu_provider",
|
||||
"dep:icu_testdata",
|
||||
"dep:sys-locale"
|
||||
]
|
||||
|
||||
# Enable Boa's WHATWG console object implementation.
|
||||
console = []
|
||||
|
||||
[dependencies]
|
||||
boa_unicode = { path = "../boa_unicode", version = "0.15.0" }
|
||||
boa_interner = { path = "../boa_interner", version = "0.15.0" }
|
||||
boa_gc = { path = "../boa_gc", version = "0.15.0" }
|
||||
gc = "0.4.1"
|
||||
boa_profiler = { path = "../boa_profiler", version = "0.15.0" }
|
||||
serde = { version = "1.0.139", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.82"
|
||||
rand = "0.8.5"
|
||||
num-traits = "0.2.15"
|
||||
regress = "0.4.1"
|
||||
rustc-hash = "1.1.0"
|
||||
num-bigint = { version = "0.4.3", features = ["serde"] }
|
||||
num-integer = "0.1.45"
|
||||
bitflags = "1.3.2"
|
||||
indexmap = "1.9.1"
|
||||
ryu-js = "0.2.2"
|
||||
chrono = "0.4.19"
|
||||
fast-float = "0.2.0"
|
||||
unicode-normalization = "0.1.21"
|
||||
dyn-clone = "1.0.7"
|
||||
once_cell = "1.13.0"
|
||||
tap = "1.0.1"
|
||||
icu_locale_canonicalizer = { version = "0.6.0", features = ["serde"], optional = true }
|
||||
icu_locid = { version = "0.6.0", features = ["serde"], optional = true }
|
||||
icu_datetime = { version = "0.6.0", features = ["serde"], optional = true }
|
||||
icu_plurals = { version = "0.6.0", features = ["serde"], optional = true }
|
||||
icu_provider = { version = "0.6.0", optional = true }
|
||||
icu_testdata = { version = "0.6.0", optional = true }
|
||||
sys-locale = { version = "0.2.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.5"
|
||||
float-cmp = "0.9.0"
|
||||
|
||||
[target.x86_64-unknown-linux-gnu.dev-dependencies]
|
||||
jemallocator = "0.5.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "boa_engine"
|
||||
bench = false
|
||||
|
||||
[[bench]]
|
||||
name = "full"
|
||||
harness = false
|
||||
@@ -0,0 +1,10 @@
|
||||
# Boa Benchmarks
|
||||
|
||||
For each js script in the `bench_scripts` folder, we create three benchmarks:
|
||||
|
||||
- Parser => lexing and parsing of the source code
|
||||
- Compiler => compilation of the parsed statement list into bytecode
|
||||
- Execution => execution of the bytecode in the vm
|
||||
|
||||
The idea is to check the performance of Boa in different scenarios.
|
||||
Different parts of Boa are benchmarked separately to make the impact of local changes visible.
|
||||
@@ -0,0 +1 @@
|
||||
((2 + 2) ** 3 / 100 - 5 ** 3 * -1000) ** 2 + 100 - 8;
|
||||
@@ -0,0 +1,7 @@
|
||||
(function () {
|
||||
let testArr = [1, 2, 3, 4, 5];
|
||||
|
||||
let res = testArr[2];
|
||||
|
||||
return res;
|
||||
})();
|
||||
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let testArr = [];
|
||||
for (let a = 0; a <= 500; a++) {
|
||||
testArr[a] = "p" + a;
|
||||
}
|
||||
|
||||
return testArr;
|
||||
})();
|
||||
@@ -0,0 +1,22 @@
|
||||
(function () {
|
||||
let testArray = [
|
||||
83, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83,
|
||||
62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93,
|
||||
17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77,
|
||||
32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32,
|
||||
56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234,
|
||||
23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29,
|
||||
2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28,
|
||||
93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62,
|
||||
99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17,
|
||||
28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32,
|
||||
45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56,
|
||||
67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28,
|
||||
];
|
||||
|
||||
while (testArray.length > 0) {
|
||||
testArray.pop();
|
||||
}
|
||||
|
||||
return testArray;
|
||||
})();
|
||||
@@ -0,0 +1,7 @@
|
||||
new Boolean(
|
||||
!new Boolean(
|
||||
new Boolean(
|
||||
!new Boolean(false).valueOf() && new Boolean(true).valueOf()
|
||||
).valueOf()
|
||||
).valueOf()
|
||||
).valueOf();
|
||||
@@ -0,0 +1,15 @@
|
||||
!function () {
|
||||
var M = new Array();
|
||||
for (i = 0; i < 100; i++) {
|
||||
M.push(Math.floor(Math.random() * 100));
|
||||
}
|
||||
var test = [];
|
||||
for (i = 0; i < 100; i++) {
|
||||
if (M[i] > 50) {
|
||||
test.push(M[i]);
|
||||
}
|
||||
}
|
||||
test.forEach(elem => {
|
||||
0
|
||||
});
|
||||
}();
|
||||
@@ -0,0 +1,10 @@
|
||||
(function () {
|
||||
let num = 12;
|
||||
|
||||
function fib(n) {
|
||||
if (n <= 1) return 1;
|
||||
return fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
|
||||
return fib(num);
|
||||
})();
|
||||
@@ -0,0 +1,10 @@
|
||||
(function () {
|
||||
let b = "hello";
|
||||
for (let a = 10; a < 100; a += 5) {
|
||||
if (a < 50) {
|
||||
b += "world";
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
})();
|
||||
@@ -0,0 +1 @@
|
||||
!function(){var r=new Array();for(i=0;i<100;i++)r.push(Math.floor(100*Math.random()));var a=[];for(i=0;i<100;i++)r[i]>50&&a.push(r[i]);a.forEach(i=>{0})}();
|
||||
@@ -0,0 +1,5 @@
|
||||
new Number(
|
||||
new Number(
|
||||
new Number(new Number(100).valueOf() - 10.5).valueOf() + 100
|
||||
).valueOf() * 1.6
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let test = {
|
||||
my_prop: "hello",
|
||||
another: 65,
|
||||
};
|
||||
|
||||
return test;
|
||||
})();
|
||||
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let test = {
|
||||
my_prop: "hello",
|
||||
another: 65,
|
||||
};
|
||||
|
||||
return test.my_prop;
|
||||
})();
|
||||
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let test = {
|
||||
my_prop: "hello",
|
||||
another: 65,
|
||||
};
|
||||
|
||||
return test["my" + "_prop"];
|
||||
})();
|
||||
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = new RegExp("hello", "i");
|
||||
|
||||
return regExp.test("Hello World");
|
||||
})();
|
||||
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = new RegExp("hello", "i");
|
||||
|
||||
return regExp;
|
||||
})();
|
||||
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = /hello/i;
|
||||
|
||||
return regExp.test("Hello World");
|
||||
})();
|
||||
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = /hello/i;
|
||||
|
||||
return regExp;
|
||||
})();
|
||||
@@ -0,0 +1,9 @@
|
||||
(function () {
|
||||
var a = "hello";
|
||||
var b = "world";
|
||||
|
||||
var c = a == b;
|
||||
|
||||
var d = b;
|
||||
var e = d == b;
|
||||
})();
|
||||
@@ -0,0 +1,6 @@
|
||||
(function () {
|
||||
var a = "hello";
|
||||
var b = "world";
|
||||
|
||||
var c = a + b;
|
||||
})();
|
||||
@@ -0,0 +1,4 @@
|
||||
(function () {
|
||||
var a = "hello";
|
||||
var b = a;
|
||||
})();
|
||||
@@ -0,0 +1,7 @@
|
||||
new String(
|
||||
new String(
|
||||
new String(
|
||||
new String("Hello").valueOf() + new String(", world").valueOf()
|
||||
).valueOf() + "!"
|
||||
).valueOf()
|
||||
).valueOf();
|
||||
@@ -0,0 +1,3 @@
|
||||
(function () {
|
||||
return Symbol();
|
||||
})();
|
||||
@@ -0,0 +1,94 @@
|
||||
//! Benchmarks of the whole execution engine in Boa.
|
||||
|
||||
use boa_engine::{realm::Realm, Context};
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
|
||||
#[cfg_attr(
|
||||
all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"),
|
||||
global_allocator
|
||||
)]
|
||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||
|
||||
fn create_realm(c: &mut Criterion) {
|
||||
c.bench_function("Create Realm", move |b| b.iter(Realm::create));
|
||||
}
|
||||
|
||||
macro_rules! full_benchmarks {
|
||||
($({$id:literal, $name:ident}),*) => {
|
||||
fn bench_parser(c: &mut Criterion) {
|
||||
$(
|
||||
{
|
||||
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
|
||||
let mut context = Context::default();
|
||||
c.bench_function(concat!($id, " (Parser)"), move |b| {
|
||||
b.iter(|| context.parse(black_box(CODE)))
|
||||
});
|
||||
}
|
||||
)*
|
||||
}
|
||||
fn bench_compile(c: &mut Criterion) {
|
||||
$(
|
||||
{
|
||||
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
|
||||
let mut context = Context::default();
|
||||
let statement_list = context.parse(CODE).expect("parsing failed");
|
||||
c.bench_function(concat!($id, " (Compiler)"), move |b| {
|
||||
b.iter(|| {
|
||||
context.compile(black_box(&statement_list))
|
||||
})
|
||||
});
|
||||
}
|
||||
)*
|
||||
}
|
||||
fn bench_execution(c: &mut Criterion) {
|
||||
$(
|
||||
{
|
||||
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
|
||||
let mut context = Context::default();
|
||||
let statement_list = context.parse(CODE).expect("parsing failed");
|
||||
let code_block = context.compile(&statement_list).unwrap();
|
||||
c.bench_function(concat!($id, " (Execution)"), move |b| {
|
||||
b.iter(|| {
|
||||
context.execute(black_box(code_block.clone())).unwrap()
|
||||
})
|
||||
});
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
full_benchmarks!(
|
||||
{"Symbols", symbol_creation},
|
||||
{"For loop", for_loop},
|
||||
{"Fibonacci", fibonacci},
|
||||
{"Object Creation", object_creation},
|
||||
{"Static Object Property Access", object_prop_access_const},
|
||||
{"Dynamic Object Property Access", object_prop_access_dyn},
|
||||
{"RegExp Literal Creation", regexp_literal_creation},
|
||||
{"RegExp Creation", regexp_creation},
|
||||
{"RegExp Literal", regexp_literal},
|
||||
{"RegExp", regexp},
|
||||
{"Array access", array_access},
|
||||
{"Array creation", array_create},
|
||||
{"Array pop", array_pop},
|
||||
{"String concatenation", string_concat},
|
||||
{"String comparison", string_compare},
|
||||
{"String copy", string_copy},
|
||||
{"Number Object Access", number_object_access},
|
||||
{"Boolean Object Access", boolean_object_access},
|
||||
{"String Object Access", string_object_access},
|
||||
{"Arithmetic operations", arithmetic_operations},
|
||||
{"Clean js", clean_js},
|
||||
{"Mini js", mini_js}
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
create_realm,
|
||||
bench_parser,
|
||||
bench_compile,
|
||||
bench_execution,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
453
__wasm/wit-bindgen-sample/engine/boa/boa_engine/src/bigint.rs
Normal file
453
__wasm/wit-bindgen-sample/engine/boa/boa_engine/src/bigint.rs
Normal file
@@ -0,0 +1,453 @@
|
||||
//! This module implements the JavaScript bigint primitive rust type.
|
||||
|
||||
use crate::{builtins::Number, Context, JsValue};
|
||||
use boa_gc::{unsafe_empty_trace, Finalize, Trace};
|
||||
use num_integer::Integer;
|
||||
use num_traits::{pow::Pow, FromPrimitive, One, ToPrimitive, Zero};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// The raw bigint type.
|
||||
pub type RawBigInt = num_bigint::BigInt;
|
||||
|
||||
#[cfg(feature = "deser")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// JavaScript bigint primitive rust type.
|
||||
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Finalize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct JsBigInt {
|
||||
inner: Rc<RawBigInt>,
|
||||
}
|
||||
|
||||
// Safety: BigInt does not contain any objects which needs to be traced,
|
||||
// so this is safe.
|
||||
unsafe impl Trace for JsBigInt {
|
||||
unsafe_empty_trace!();
|
||||
}
|
||||
|
||||
impl JsBigInt {
|
||||
/// Create a new [`JsBigInt`].
|
||||
#[inline]
|
||||
pub fn new<T: Into<Self>>(value: T) -> Self {
|
||||
value.into()
|
||||
}
|
||||
|
||||
/// Create a [`JsBigInt`] with value `0`.
|
||||
#[inline]
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::zero()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if is zero.
|
||||
#[inline]
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.inner.is_zero()
|
||||
}
|
||||
|
||||
/// Create a [`JsBigInt`] with value `1`.
|
||||
#[inline]
|
||||
pub fn one() -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::one()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if is one.
|
||||
#[inline]
|
||||
pub fn is_one(&self) -> bool {
|
||||
self.inner.is_one()
|
||||
}
|
||||
|
||||
/// Convert bigint to string with radix.
|
||||
#[inline]
|
||||
pub fn to_string_radix(&self, radix: u32) -> String {
|
||||
self.inner.to_str_radix(radix)
|
||||
}
|
||||
|
||||
/// Converts the `BigInt` to a f64 type.
|
||||
///
|
||||
/// Returns `f64::INFINITY` if the `BigInt` is too big.
|
||||
#[inline]
|
||||
pub fn to_f64(&self) -> f64 {
|
||||
self.inner.to_f64().unwrap_or(f64::INFINITY)
|
||||
}
|
||||
|
||||
/// Converts a string to a `BigInt` with the specified radix.
|
||||
#[inline]
|
||||
pub fn from_string_radix(buf: &str, radix: u32) -> Option<Self> {
|
||||
Some(Self {
|
||||
inner: Rc::new(RawBigInt::parse_bytes(buf.as_bytes(), radix)?),
|
||||
})
|
||||
}
|
||||
|
||||
/// This function takes a string and converts it to `BigInt` type.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-stringtobigint
|
||||
#[inline]
|
||||
pub fn from_string(mut string: &str) -> Option<Self> {
|
||||
string = string.trim();
|
||||
|
||||
if string.is_empty() {
|
||||
return Some(Self::zero());
|
||||
}
|
||||
|
||||
let mut radix = 10;
|
||||
if string.starts_with("0b") || string.starts_with("0B") {
|
||||
radix = 2;
|
||||
string = &string[2..];
|
||||
} else if string.starts_with("0x") || string.starts_with("0X") {
|
||||
radix = 16;
|
||||
string = &string[2..];
|
||||
} else if string.starts_with("0o") || string.starts_with("0O") {
|
||||
radix = 8;
|
||||
string = &string[2..];
|
||||
}
|
||||
|
||||
Self::from_string_radix(string, radix)
|
||||
}
|
||||
|
||||
/// Checks for `SameValueZero` equality.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-equal
|
||||
#[inline]
|
||||
pub fn same_value_zero(x: &Self, y: &Self) -> bool {
|
||||
// Return BigInt::equal(x, y)
|
||||
Self::equal(x, y)
|
||||
}
|
||||
|
||||
/// Checks for `SameValue` equality.
|
||||
///
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValue
|
||||
#[inline]
|
||||
pub fn same_value(x: &Self, y: &Self) -> bool {
|
||||
// Return BigInt::equal(x, y)
|
||||
Self::equal(x, y)
|
||||
}
|
||||
|
||||
/// Checks for mathematical equality.
|
||||
///
|
||||
/// The abstract operation `BigInt::equal` takes arguments x (a `BigInt`) and y (a `BigInt`).
|
||||
/// It returns `true` if x and y have the same mathematical integer value and false otherwise.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValueZero
|
||||
#[inline]
|
||||
pub fn equal(x: &Self, y: &Self) -> bool {
|
||||
x == y
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pow(x: &Self, y: &Self, context: &mut Context) -> Result<Self, JsValue> {
|
||||
let y = if let Some(y) = y.inner.to_biguint() {
|
||||
y
|
||||
} else {
|
||||
return context.throw_range_error("BigInt negative exponent");
|
||||
};
|
||||
|
||||
let num_bits = (x.inner.bits() as f64
|
||||
* y.to_f64().expect("Unable to convert from BigUInt to f64"))
|
||||
.floor()
|
||||
+ 1f64;
|
||||
|
||||
if num_bits > 1_000_000_000f64 {
|
||||
return context.throw_range_error("Maximum BigInt size exceeded");
|
||||
}
|
||||
|
||||
Ok(Self::new(x.inner.as_ref().clone().pow(y)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_right(x: &Self, y: &Self, context: &mut Context) -> Result<Self, JsValue> {
|
||||
if let Some(n) = y.inner.to_i32() {
|
||||
let inner = if n > 0 {
|
||||
x.inner.as_ref().clone().shr(n as usize)
|
||||
} else {
|
||||
x.inner.as_ref().clone().shl(n.unsigned_abs())
|
||||
};
|
||||
|
||||
Ok(Self::new(inner))
|
||||
} else {
|
||||
context.throw_range_error("Maximum BigInt size exceeded")
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_left(x: &Self, y: &Self, context: &mut Context) -> Result<Self, JsValue> {
|
||||
if let Some(n) = y.inner.to_i32() {
|
||||
let inner = if n > 0 {
|
||||
x.inner.as_ref().clone().shl(n as usize)
|
||||
} else {
|
||||
x.inner.as_ref().clone().shr(n.unsigned_abs())
|
||||
};
|
||||
|
||||
Ok(Self::new(inner))
|
||||
} else {
|
||||
context.throw_range_error("Maximum BigInt size exceeded")
|
||||
}
|
||||
}
|
||||
|
||||
/// Floored integer modulo.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use num_integer::Integer;
|
||||
/// assert_eq!((8).mod_floor(&3), 2);
|
||||
/// assert_eq!((8).mod_floor(&-3), -1);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn mod_floor(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.mod_floor(&y.inner))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().add(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sub(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().sub(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mul(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().mul(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn div(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().div(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rem(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().rem(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bitand(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().bitand(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bitor(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().bitor(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bitxor(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().bitxor(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn neg(x: &Self) -> Self {
|
||||
Self::new(x.as_inner().neg())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn not(x: &Self) -> Self {
|
||||
Self::new(!x.as_inner())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn as_inner(&self) -> &RawBigInt {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for JsBigInt {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawBigInt> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: RawBigInt) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<RawBigInt>> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: Box<RawBigInt>) -> Self {
|
||||
Self {
|
||||
inner: value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i8> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: i8) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: u8) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: i16) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: u16) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: i32) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: u32) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: i64) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: u64) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<isize> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: isize) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TryFromF64Error;
|
||||
|
||||
impl Display for TryFromF64Error {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Could not convert f64 value to a BigInt type")
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<f64> for JsBigInt {
|
||||
type Error = TryFromF64Error;
|
||||
|
||||
#[inline]
|
||||
fn try_from(n: f64) -> Result<Self, Self::Error> {
|
||||
// If the truncated version of the number is not the
|
||||
// same as the non-truncated version then the floating-point
|
||||
// number conains a fractional part.
|
||||
if !Number::equal(n.trunc(), n) {
|
||||
return Err(TryFromF64Error);
|
||||
}
|
||||
match RawBigInt::from_f64(n) {
|
||||
Some(bigint) => Ok(Self::new(bigint)),
|
||||
None => Err(TryFromF64Error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<i32> for JsBigInt {
|
||||
#[inline]
|
||||
fn eq(&self, other: &i32) -> bool {
|
||||
self.inner.as_ref() == &RawBigInt::from(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<JsBigInt> for i32 {
|
||||
#[inline]
|
||||
fn eq(&self, other: &JsBigInt) -> bool {
|
||||
&RawBigInt::from(*self) == other.inner.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<f64> for JsBigInt {
|
||||
#[inline]
|
||||
fn eq(&self, other: &f64) -> bool {
|
||||
if other.fract() != 0.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.inner.as_ref() == &RawBigInt::from(*other as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<JsBigInt> for f64 {
|
||||
#[inline]
|
||||
fn eq(&self, other: &JsBigInt) -> bool {
|
||||
if self.fract() != 0.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
&RawBigInt::from(*self as i64) == other.inner.as_ref()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
use crate::{
|
||||
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue},
|
||||
object::{JsObject, ObjectData},
|
||||
property::{PropertyDescriptor, PropertyNameKind},
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// The Array Iterator object represents an iteration over an array. It implements the iterator protocol.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct ArrayIterator {
|
||||
array: JsObject,
|
||||
next_index: usize,
|
||||
kind: PropertyNameKind,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl ArrayIterator {
|
||||
pub(crate) const NAME: &'static str = "ArrayIterator";
|
||||
|
||||
fn new(array: JsObject, kind: PropertyNameKind) -> Self {
|
||||
Self {
|
||||
array,
|
||||
kind,
|
||||
next_index: 0,
|
||||
done: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// `CreateArrayIterator( array, kind )`
|
||||
///
|
||||
/// Creates a new iterator over the given array.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createarrayiterator
|
||||
pub(crate) fn create_array_iterator(
|
||||
array: JsObject,
|
||||
kind: PropertyNameKind,
|
||||
context: &Context,
|
||||
) -> JsValue {
|
||||
let array_iterator = JsObject::from_proto_and_data(
|
||||
context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.array_iterator(),
|
||||
ObjectData::array_iterator(Self::new(array, kind)),
|
||||
);
|
||||
array_iterator.into()
|
||||
}
|
||||
|
||||
/// %ArrayIteratorPrototype%.next( )
|
||||
///
|
||||
/// Gets the next result in the array.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next
|
||||
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let mut array_iterator = this.as_object().map(JsObject::borrow_mut);
|
||||
let array_iterator = array_iterator
|
||||
.as_mut()
|
||||
.and_then(|obj| obj.as_array_iterator_mut())
|
||||
.ok_or_else(|| context.construct_type_error("`this` is not an ArrayIterator"))?;
|
||||
let index = array_iterator.next_index;
|
||||
if array_iterator.done {
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
));
|
||||
}
|
||||
|
||||
let len = if let Some(f) = array_iterator.array.borrow().as_typed_array() {
|
||||
if f.is_detached() {
|
||||
return context.throw_type_error(
|
||||
"Cannot get value from typed array that has a detached array buffer",
|
||||
);
|
||||
}
|
||||
|
||||
f.array_length()
|
||||
} else {
|
||||
array_iterator.array.length_of_array_like(context)?
|
||||
};
|
||||
|
||||
if index >= len {
|
||||
array_iterator.done = true;
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
));
|
||||
}
|
||||
array_iterator.next_index = index + 1;
|
||||
match array_iterator.kind {
|
||||
PropertyNameKind::Key => Ok(create_iter_result_object(index.into(), false, context)),
|
||||
PropertyNameKind::Value => {
|
||||
let element_value = array_iterator.array.get(index, context)?;
|
||||
Ok(create_iter_result_object(element_value, false, context))
|
||||
}
|
||||
PropertyNameKind::KeyAndValue => {
|
||||
let element_value = array_iterator.array.get(index, context)?;
|
||||
let result = Array::create_array_from_list([index.into(), element_value], context);
|
||||
Ok(create_iter_result_object(result.into(), false, context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the `%ArrayIteratorPrototype%` object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
|
||||
pub(crate) fn create_prototype(
|
||||
iterator_prototype: JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsObject {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
// Create prototype
|
||||
let array_iterator =
|
||||
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
|
||||
make_builtin_fn(Self::next, "next", &array_iterator, 0, context);
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
let to_string_tag_property = PropertyDescriptor::builder()
|
||||
.value("Array Iterator")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
array_iterator.insert(to_string_tag, to_string_tag_property);
|
||||
array_iterator
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,818 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{
|
||||
builtins::{typed_array::TypedArrayKind, BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
|
||||
JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
symbol::WellKnownSymbols,
|
||||
value::{IntegerOrInfinity, Numeric},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
use num_traits::{Signed, ToPrimitive};
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct ArrayBuffer {
|
||||
pub array_buffer_data: Option<Vec<u8>>,
|
||||
pub array_buffer_byte_length: usize,
|
||||
pub array_buffer_detach_key: JsValue,
|
||||
}
|
||||
|
||||
impl ArrayBuffer {
|
||||
pub(crate) fn array_buffer_byte_length(&self) -> usize {
|
||||
self.array_buffer_byte_length
|
||||
}
|
||||
}
|
||||
|
||||
impl BuiltIn for ArrayBuffer {
|
||||
const NAME: &'static str = "ArrayBuffer";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
|
||||
|
||||
let get_species = FunctionBuilder::native(context, Self::get_species)
|
||||
.name("get [Symbol.species]")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let get_byte_length = FunctionBuilder::native(context, Self::get_byte_length)
|
||||
.name("get byteLength")
|
||||
.build();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().array_buffer().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.accessor("byteLength", Some(get_byte_length), None, flag_attributes)
|
||||
.static_accessor(
|
||||
WellKnownSymbols::species(),
|
||||
Some(get_species),
|
||||
None,
|
||||
Attribute::CONFIGURABLE,
|
||||
)
|
||||
.static_method(Self::is_view, "isView", 1)
|
||||
.method(Self::slice, "slice", 2)
|
||||
.property(
|
||||
WellKnownSymbols::to_string_tag(),
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrayBuffer {
|
||||
const LENGTH: usize = 1;
|
||||
|
||||
/// `25.1.3.1 ArrayBuffer ( length )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer-length
|
||||
fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if new_target.is_undefined() {
|
||||
return context
|
||||
.throw_type_error("ArrayBuffer.constructor called with undefined new target");
|
||||
}
|
||||
|
||||
// 2. Let byteLength be ? ToIndex(length).
|
||||
let byte_length = args.get_or_undefined(0).to_index(context)?;
|
||||
|
||||
// 3. Return ? AllocateArrayBuffer(NewTarget, byteLength).
|
||||
Ok(Self::allocate(new_target, byte_length, context)?.into())
|
||||
}
|
||||
|
||||
/// `25.1.4.3 get ArrayBuffer [ @@species ]`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer-@@species
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Return the this value.
|
||||
Ok(this.clone())
|
||||
}
|
||||
|
||||
/// `25.1.4.1 ArrayBuffer.isView ( arg )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.isview
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn is_view(_: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. If Type(arg) is not Object, return false.
|
||||
// 2. If arg has a [[ViewedArrayBuffer]] internal slot, return true.
|
||||
// 3. Return false.
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
.as_object()
|
||||
.map(|obj| obj.borrow().has_viewed_array_buffer())
|
||||
.unwrap_or_default()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// `25.1.5.1 get ArrayBuffer.prototype.byteLength`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength
|
||||
pub(crate) fn get_byte_length(
|
||||
this: &JsValue,
|
||||
_args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
let obj = if let Some(obj) = this.as_object() {
|
||||
obj
|
||||
} else {
|
||||
return context.throw_type_error("ArrayBuffer.byteLength called with non-object value");
|
||||
};
|
||||
let obj = obj.borrow();
|
||||
let o = if let Some(o) = obj.as_array_buffer() {
|
||||
o
|
||||
} else {
|
||||
return context.throw_type_error("ArrayBuffer.byteLength called with invalid object");
|
||||
};
|
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
|
||||
// 4. If IsDetachedBuffer(O) is true, return +0𝔽.
|
||||
if Self::is_detached_buffer(o) {
|
||||
return Ok(0.into());
|
||||
}
|
||||
|
||||
// 5. Let length be O.[[ArrayBufferByteLength]].
|
||||
// 6. Return 𝔽(length).
|
||||
Ok(o.array_buffer_byte_length.into())
|
||||
}
|
||||
|
||||
/// `25.1.5.3 ArrayBuffer.prototype.slice ( start, end )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice
|
||||
fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
let obj = if let Some(obj) = this.as_object() {
|
||||
obj
|
||||
} else {
|
||||
return context.throw_type_error("ArrayBuffer.slice called with non-object value");
|
||||
};
|
||||
let obj_borrow = obj.borrow();
|
||||
let o = if let Some(o) = obj_borrow.as_array_buffer() {
|
||||
o
|
||||
} else {
|
||||
return context.throw_type_error("ArrayBuffer.slice called with invalid object");
|
||||
};
|
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
|
||||
// 4. If IsDetachedBuffer(O) is true, throw a TypeError exception.
|
||||
if Self::is_detached_buffer(o) {
|
||||
return context.throw_type_error("ArrayBuffer.slice called with detached buffer");
|
||||
}
|
||||
|
||||
// 5. Let len be O.[[ArrayBufferByteLength]].
|
||||
let len = o.array_buffer_byte_length as i64;
|
||||
|
||||
// 6. Let relativeStart be ? ToIntegerOrInfinity(start).
|
||||
let relative_start = args.get_or_undefined(0).to_integer_or_infinity(context)?;
|
||||
|
||||
let first = match relative_start {
|
||||
// 7. If relativeStart is -∞, let first be 0.
|
||||
IntegerOrInfinity::NegativeInfinity => 0,
|
||||
// 8. Else if relativeStart < 0, let first be max(len + relativeStart, 0).
|
||||
IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0),
|
||||
// 9. Else, let first be min(relativeStart, len).
|
||||
IntegerOrInfinity::Integer(i) => std::cmp::min(i, len),
|
||||
IntegerOrInfinity::PositiveInfinity => len,
|
||||
};
|
||||
|
||||
// 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
|
||||
let end = args.get_or_undefined(1);
|
||||
let relative_end = if end.is_undefined() {
|
||||
IntegerOrInfinity::Integer(len)
|
||||
} else {
|
||||
end.to_integer_or_infinity(context)?
|
||||
};
|
||||
|
||||
let r#final = match relative_end {
|
||||
// 11. If relativeEnd is -∞, let final be 0.
|
||||
IntegerOrInfinity::NegativeInfinity => 0,
|
||||
// 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
|
||||
IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0),
|
||||
// 13. Else, let final be min(relativeEnd, len).
|
||||
IntegerOrInfinity::Integer(i) => std::cmp::min(i, len),
|
||||
IntegerOrInfinity::PositiveInfinity => len,
|
||||
};
|
||||
|
||||
// 14. Let newLen be max(final - first, 0).
|
||||
let new_len = std::cmp::max(r#final - first, 0) as usize;
|
||||
|
||||
// 15. Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%).
|
||||
let ctor = obj.species_constructor(StandardConstructors::array_buffer, context)?;
|
||||
|
||||
// 16. Let new be ? Construct(ctor, « 𝔽(newLen) »).
|
||||
let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?;
|
||||
|
||||
{
|
||||
let new_obj = new.borrow();
|
||||
// 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]).
|
||||
let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| {
|
||||
context.construct_type_error("ArrayBuffer constructor returned invalid object")
|
||||
})?;
|
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception.
|
||||
|
||||
// 19. If IsDetachedBuffer(new) is true, throw a TypeError exception.
|
||||
if new_array_buffer.is_detached_buffer() {
|
||||
return context
|
||||
.throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer");
|
||||
}
|
||||
}
|
||||
// 20. If SameValue(new, O) is true, throw a TypeError exception.
|
||||
if this
|
||||
.as_object()
|
||||
.map(|obj| JsObject::equals(obj, &new))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer");
|
||||
}
|
||||
|
||||
{
|
||||
let mut new_obj_borrow = new.borrow_mut();
|
||||
let new_array_buffer = new_obj_borrow
|
||||
.as_array_buffer_mut()
|
||||
.expect("Already checked that `new_obj` was an `ArrayBuffer`");
|
||||
|
||||
// 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception.
|
||||
if new_array_buffer.array_buffer_byte_length < new_len {
|
||||
return context.throw_type_error("New ArrayBuffer length too small");
|
||||
}
|
||||
|
||||
// 22. NOTE: Side-effects of the above steps may have detached O.
|
||||
// 23. If IsDetachedBuffer(O) is true, throw a TypeError exception.
|
||||
if Self::is_detached_buffer(o) {
|
||||
return context
|
||||
.throw_type_error("ArrayBuffer detached while ArrayBuffer.slice was running");
|
||||
}
|
||||
|
||||
// 24. Let fromBuf be O.[[ArrayBufferData]].
|
||||
let from_buf = o
|
||||
.array_buffer_data
|
||||
.as_ref()
|
||||
.expect("ArrayBuffer cannot be detached here");
|
||||
|
||||
// 25. Let toBuf be new.[[ArrayBufferData]].
|
||||
let to_buf = new_array_buffer
|
||||
.array_buffer_data
|
||||
.as_mut()
|
||||
.expect("ArrayBuffer cannot be detached here");
|
||||
|
||||
// 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
|
||||
copy_data_block_bytes(to_buf, 0, from_buf, first as usize, new_len);
|
||||
}
|
||||
|
||||
// 27. Return new.
|
||||
Ok(new.into())
|
||||
}
|
||||
|
||||
/// `25.1.2.1 AllocateArrayBuffer ( constructor, byteLength )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-allocatearraybuffer
|
||||
pub(crate) fn allocate(
|
||||
constructor: &JsValue,
|
||||
byte_length: usize,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject> {
|
||||
// 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »).
|
||||
let prototype = get_prototype_from_constructor(
|
||||
constructor,
|
||||
StandardConstructors::array_buffer,
|
||||
context,
|
||||
)?;
|
||||
let obj = context.construct_object();
|
||||
obj.set_prototype(prototype.into());
|
||||
|
||||
// 2. Let block be ? CreateByteDataBlock(byteLength).
|
||||
let block = create_byte_data_block(byte_length, context)?;
|
||||
|
||||
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||
obj.borrow_mut().data = ObjectData::array_buffer(Self {
|
||||
array_buffer_data: Some(block),
|
||||
array_buffer_byte_length: byte_length,
|
||||
array_buffer_detach_key: JsValue::Undefined,
|
||||
});
|
||||
|
||||
// 5. Return obj.
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
/// `25.1.2.2 IsDetachedBuffer ( arrayBuffer )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer
|
||||
pub(crate) fn is_detached_buffer(&self) -> bool {
|
||||
// 1. If arrayBuffer.[[ArrayBufferData]] is null, return true.
|
||||
// 2. Return false.
|
||||
self.array_buffer_data.is_none()
|
||||
}
|
||||
|
||||
/// `25.1.2.4 CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength, cloneConstructor )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-clonearraybuffer
|
||||
pub(crate) fn clone_array_buffer(
|
||||
&self,
|
||||
src_byte_offset: usize,
|
||||
src_length: usize,
|
||||
clone_constructor: &JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject> {
|
||||
// 1. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength).
|
||||
let target_buffer = Self::allocate(clone_constructor, src_length, context)?;
|
||||
|
||||
// 2. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception.
|
||||
// 3. Let srcBlock be srcBuffer.[[ArrayBufferData]].
|
||||
let src_block = if let Some(b) = &self.array_buffer_data {
|
||||
b
|
||||
} else {
|
||||
return context.throw_syntax_error("Cannot clone detached array buffer");
|
||||
};
|
||||
|
||||
{
|
||||
// 4. Let targetBlock be targetBuffer.[[ArrayBufferData]].
|
||||
let mut target_buffer_mut = target_buffer.borrow_mut();
|
||||
let target_block = target_buffer_mut
|
||||
.as_array_buffer_mut()
|
||||
.expect("This must be an ArrayBuffer");
|
||||
|
||||
// 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength).
|
||||
copy_data_block_bytes(
|
||||
target_block
|
||||
.array_buffer_data
|
||||
.as_mut()
|
||||
.expect("ArrayBuffer cannot me detached here"),
|
||||
0,
|
||||
src_block,
|
||||
src_byte_offset,
|
||||
src_length,
|
||||
);
|
||||
}
|
||||
|
||||
// 6. Return targetBuffer.
|
||||
Ok(target_buffer)
|
||||
}
|
||||
|
||||
/// `25.1.2.6 IsUnclampedIntegerElementType ( type )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isunclampedintegerelementtype
|
||||
fn is_unclamped_integer_element_type(t: TypedArrayKind) -> bool {
|
||||
// 1. If type is Int8, Uint8, Int16, Uint16, Int32, or Uint32, return true.
|
||||
// 2. Return false.
|
||||
matches!(
|
||||
t,
|
||||
TypedArrayKind::Int8
|
||||
| TypedArrayKind::Uint8
|
||||
| TypedArrayKind::Int16
|
||||
| TypedArrayKind::Uint16
|
||||
| TypedArrayKind::Int32
|
||||
| TypedArrayKind::Uint32
|
||||
)
|
||||
}
|
||||
|
||||
/// `25.1.2.7 IsBigIntElementType ( type )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isbigintelementtype
|
||||
fn is_big_int_element_type(t: TypedArrayKind) -> bool {
|
||||
// 1. If type is BigUint64 or BigInt64, return true.
|
||||
// 2. Return false.
|
||||
matches!(t, TypedArrayKind::BigUint64 | TypedArrayKind::BigInt64)
|
||||
}
|
||||
|
||||
/// `25.1.2.8 IsNoTearConfiguration ( type, order )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isnotearconfiguration
|
||||
// TODO: Allow unused function until shared array buffers are implemented.
|
||||
#[allow(dead_code)]
|
||||
fn is_no_tear_configuration(t: TypedArrayKind, order: SharedMemoryOrder) -> bool {
|
||||
// 1. If ! IsUnclampedIntegerElementType(type) is true, return true.
|
||||
if Self::is_unclamped_integer_element_type(t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. If ! IsBigIntElementType(type) is true and order is not Init or Unordered, return true.
|
||||
if Self::is_big_int_element_type(t)
|
||||
&& !matches!(
|
||||
order,
|
||||
SharedMemoryOrder::Init | SharedMemoryOrder::Unordered
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Return false.
|
||||
false
|
||||
}
|
||||
|
||||
/// `25.1.2.9 RawBytesToNumeric ( type, rawBytes, isLittleEndian )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-rawbytestonumeric
|
||||
fn raw_bytes_to_numeric(t: TypedArrayKind, bytes: &[u8], is_little_endian: bool) -> JsValue {
|
||||
let n: Numeric = match t {
|
||||
TypedArrayKind::Int8 => {
|
||||
if is_little_endian {
|
||||
i8::from_le_bytes(bytes.try_into().expect("slice with incorrect length")).into()
|
||||
} else {
|
||||
i8::from_be_bytes(bytes.try_into().expect("slice with incorrect length")).into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => {
|
||||
if is_little_endian {
|
||||
u8::from_le_bytes(bytes.try_into().expect("slice with incorrect length")).into()
|
||||
} else {
|
||||
u8::from_be_bytes(bytes.try_into().expect("slice with incorrect length")).into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Int16 => {
|
||||
if is_little_endian {
|
||||
i16::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
i16::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Uint16 => {
|
||||
if is_little_endian {
|
||||
u16::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
u16::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Int32 => {
|
||||
if is_little_endian {
|
||||
i32::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
i32::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Uint32 => {
|
||||
if is_little_endian {
|
||||
u32::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
u32::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::BigInt64 => {
|
||||
if is_little_endian {
|
||||
i64::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
i64::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::BigUint64 => {
|
||||
if is_little_endian {
|
||||
u64::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
u64::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Float32 => {
|
||||
if is_little_endian {
|
||||
f32::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
f32::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Float64 => {
|
||||
if is_little_endian {
|
||||
f64::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
f64::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
n.into()
|
||||
}
|
||||
|
||||
/// `25.1.2.10 GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-getvaluefrombuffer
|
||||
pub(crate) fn get_value_from_buffer(
|
||||
&self,
|
||||
byte_index: usize,
|
||||
t: TypedArrayKind,
|
||||
_is_typed_array: bool,
|
||||
_order: SharedMemoryOrder,
|
||||
is_little_endian: Option<bool>,
|
||||
) -> JsValue {
|
||||
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
|
||||
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
|
||||
// 3. Let block be arrayBuffer.[[ArrayBufferData]].
|
||||
let block = self
|
||||
.array_buffer_data
|
||||
.as_ref()
|
||||
.expect("ArrayBuffer cannot be detached here");
|
||||
|
||||
// 4. Let elementSize be the Element Size value specified in Table 73 for Element Type type.
|
||||
let element_size = t.element_size();
|
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 5. If IsSharedArrayBuffer(arrayBuffer) is true, then
|
||||
|
||||
// 6. Else, let rawValue be a List whose elements are bytes from block at indices byteIndex (inclusive) through byteIndex + elementSize (exclusive).
|
||||
// 7. Assert: The number of elements in rawValue is elementSize.
|
||||
let raw_value = &block[byte_index..byte_index + element_size];
|
||||
|
||||
// TODO: Agent Record [[LittleEndian]] filed
|
||||
// 8. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
|
||||
let is_little_endian = is_little_endian.unwrap_or(true);
|
||||
|
||||
// 9. Return RawBytesToNumeric(type, rawValue, isLittleEndian).
|
||||
Self::raw_bytes_to_numeric(t, raw_value, is_little_endian)
|
||||
}
|
||||
|
||||
/// `25.1.2.11 NumericToRawBytes ( type, value, isLittleEndian )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-numerictorawbytes
|
||||
fn numeric_to_raw_bytes(
|
||||
t: TypedArrayKind,
|
||||
value: &JsValue,
|
||||
is_little_endian: bool,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Vec<u8>> {
|
||||
Ok(match t {
|
||||
TypedArrayKind::Int8 if is_little_endian => {
|
||||
value.to_int8(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Int8 => value.to_int8(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Uint8 if is_little_endian => {
|
||||
value.to_uint8(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Uint8 => value.to_uint8(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Uint8Clamped if is_little_endian => {
|
||||
value.to_uint8_clamp(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Uint8Clamped => value.to_uint8_clamp(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Int16 if is_little_endian => {
|
||||
value.to_int16(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Int16 => value.to_int16(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Uint16 if is_little_endian => {
|
||||
value.to_uint16(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Uint16 => value.to_uint16(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Int32 if is_little_endian => {
|
||||
value.to_i32(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Int32 => value.to_i32(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Uint32 if is_little_endian => {
|
||||
value.to_u32(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Uint32 => value.to_u32(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::BigInt64 if is_little_endian => {
|
||||
let big_int = value.to_big_int64(context)?;
|
||||
big_int
|
||||
.to_i64()
|
||||
.unwrap_or_else(|| {
|
||||
if big_int.is_positive() {
|
||||
i64::MAX
|
||||
} else {
|
||||
i64::MIN
|
||||
}
|
||||
})
|
||||
.to_le_bytes()
|
||||
.to_vec()
|
||||
}
|
||||
TypedArrayKind::BigInt64 => {
|
||||
let big_int = value.to_big_int64(context)?;
|
||||
big_int
|
||||
.to_i64()
|
||||
.unwrap_or_else(|| {
|
||||
if big_int.is_positive() {
|
||||
i64::MAX
|
||||
} else {
|
||||
i64::MIN
|
||||
}
|
||||
})
|
||||
.to_be_bytes()
|
||||
.to_vec()
|
||||
}
|
||||
TypedArrayKind::BigUint64 if is_little_endian => value
|
||||
.to_big_uint64(context)?
|
||||
.to_u64()
|
||||
.unwrap_or(u64::MAX)
|
||||
.to_le_bytes()
|
||||
.to_vec(),
|
||||
TypedArrayKind::BigUint64 => value
|
||||
.to_big_uint64(context)?
|
||||
.to_u64()
|
||||
.unwrap_or(u64::MAX)
|
||||
.to_be_bytes()
|
||||
.to_vec(),
|
||||
TypedArrayKind::Float32 => match value.to_number(context)? {
|
||||
f if is_little_endian => (f as f32).to_le_bytes().to_vec(),
|
||||
f => (f as f32).to_be_bytes().to_vec(),
|
||||
},
|
||||
TypedArrayKind::Float64 => match value.to_number(context)? {
|
||||
f if is_little_endian => f.to_le_bytes().to_vec(),
|
||||
f => f.to_be_bytes().to_vec(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// `25.1.2.12 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-setvalueinbuffer
|
||||
pub(crate) fn set_value_in_buffer(
|
||||
&mut self,
|
||||
byte_index: usize,
|
||||
t: TypedArrayKind,
|
||||
value: &JsValue,
|
||||
_order: SharedMemoryOrder,
|
||||
is_little_endian: Option<bool>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
|
||||
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
|
||||
// 3. Assert: Type(value) is BigInt if ! IsBigIntElementType(type) is true; otherwise, Type(value) is Number.
|
||||
// 4. Let block be arrayBuffer.[[ArrayBufferData]].
|
||||
let block = self
|
||||
.array_buffer_data
|
||||
.as_mut()
|
||||
.expect("ArrayBuffer cannot be detached here");
|
||||
|
||||
// 5. Let elementSize be the Element Size value specified in Table 73 for Element Type type.
|
||||
|
||||
// TODO: Agent Record [[LittleEndian]] filed
|
||||
// 6. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
|
||||
let is_little_endian = is_little_endian.unwrap_or(true);
|
||||
|
||||
// 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian).
|
||||
let raw_bytes = Self::numeric_to_raw_bytes(t, value, is_little_endian, context)?;
|
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 8. If IsSharedArrayBuffer(arrayBuffer) is true, then
|
||||
|
||||
// 9. Else, store the individual bytes of rawBytes into block, starting at block[byteIndex].
|
||||
for (i, raw_byte) in raw_bytes.iter().enumerate() {
|
||||
block[byte_index + i] = *raw_byte;
|
||||
}
|
||||
|
||||
// 10. Return NormalCompletion(undefined).
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
}
|
||||
|
||||
/// `CreateByteDataBlock ( size )` abstract operation.
|
||||
///
|
||||
/// The abstract operation `CreateByteDataBlock` takes argument `size` (a non-negative
|
||||
/// integer). For more information, check the [spec][spec].
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createbytedatablock
|
||||
pub fn create_byte_data_block(size: usize, context: &mut Context) -> JsResult<Vec<u8>> {
|
||||
// 1. Let db be a new Data Block value consisting of size bytes. If it is impossible to
|
||||
// create such a Data Block, throw a RangeError exception.
|
||||
let mut data_block = Vec::new();
|
||||
data_block.try_reserve(size).map_err(|e| {
|
||||
context.construct_range_error(format!("couldn't allocate the data block: {e}"))
|
||||
})?;
|
||||
|
||||
// 2. Set all of the bytes of db to 0.
|
||||
data_block.resize(size, 0);
|
||||
|
||||
// 3. Return db.
|
||||
Ok(data_block)
|
||||
}
|
||||
|
||||
/// `6.2.8.3 CopyDataBlockBytes ( toBlock, toIndex, fromBlock, fromIndex, count )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-copydatablockbytes
|
||||
fn copy_data_block_bytes(
|
||||
to_block: &mut [u8],
|
||||
mut to_index: usize,
|
||||
from_block: &[u8],
|
||||
mut from_index: usize,
|
||||
mut count: usize,
|
||||
) {
|
||||
// 1. Assert: fromBlock and toBlock are distinct values.
|
||||
// 2. Let fromSize be the number of bytes in fromBlock.
|
||||
let from_size = from_block.len();
|
||||
|
||||
// 3. Assert: fromIndex + count ≤ fromSize.
|
||||
assert!(from_index + count <= from_size);
|
||||
|
||||
// 4. Let toSize be the number of bytes in toBlock.
|
||||
let to_size = to_block.len();
|
||||
|
||||
// 5. Assert: toIndex + count ≤ toSize.
|
||||
assert!(to_index + count <= to_size);
|
||||
|
||||
// 6. Repeat, while count > 0,
|
||||
while count > 0 {
|
||||
// a. If fromBlock is a Shared Data Block, then
|
||||
// TODO: Shared Data Block
|
||||
|
||||
// b. Else,
|
||||
// i. Assert: toBlock is not a Shared Data Block.
|
||||
// ii. Set toBlock[toIndex] to fromBlock[fromIndex].
|
||||
to_block[to_index] = from_block[from_index];
|
||||
|
||||
// c. Set toIndex to toIndex + 1.
|
||||
to_index += 1;
|
||||
|
||||
// d. Set fromIndex to fromIndex + 1.
|
||||
from_index += 1;
|
||||
|
||||
// e. Set count to count - 1.
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
// 7. Return NormalCompletion(empty).
|
||||
}
|
||||
|
||||
// TODO: Allow unused variants until shared array buffers are implemented.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub(crate) enum SharedMemoryOrder {
|
||||
Init,
|
||||
SeqCst,
|
||||
Unordered,
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ut_sunny_day_create_byte_data_block() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert!(create_byte_data_block(100, &mut context).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ut_rainy_day_create_byte_data_block() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert!(create_byte_data_block(usize::MAX, &mut context).is_err());
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
//! This module implements the global `AsyncFunction` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-async-function-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction
|
||||
|
||||
use crate::{
|
||||
builtins::{
|
||||
function::{ConstructorKind, Function},
|
||||
BuiltIn,
|
||||
},
|
||||
object::ObjectData,
|
||||
property::PropertyDescriptor,
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AsyncFunction;
|
||||
|
||||
impl BuiltIn for AsyncFunction {
|
||||
const NAME: &'static str = "AsyncFunction";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let prototype = &context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_function()
|
||||
.prototype;
|
||||
let constructor = &context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_function()
|
||||
.constructor;
|
||||
|
||||
constructor.set_prototype(Some(
|
||||
context.intrinsics().constructors().function().constructor(),
|
||||
));
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(1)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
constructor.borrow_mut().insert("length", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(Self::NAME)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
constructor.borrow_mut().insert("name", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(prototype.clone())
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(false);
|
||||
constructor.borrow_mut().insert("prototype", property);
|
||||
constructor.borrow_mut().data = ObjectData::function(Function::Native {
|
||||
function: Self::constructor,
|
||||
constructor: Some(ConstructorKind::Base),
|
||||
});
|
||||
|
||||
prototype.set_prototype(Some(
|
||||
context.intrinsics().constructors().function().prototype(),
|
||||
));
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(constructor.clone())
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype.borrow_mut().insert("constructor", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(Self::NAME)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype
|
||||
.borrow_mut()
|
||||
.insert(WellKnownSymbols::to_string_tag(), property);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncFunction {
|
||||
/// `AsyncFunction ( p1, p2, … , pn, body )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-async-function-constructor-arguments
|
||||
fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
crate::builtins::function::BuiltInFunctionObject::create_dynamic_function(
|
||||
new_target, args, true, false, context,
|
||||
)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
//! This module implements the global `BigInt` object.
|
||||
//!
|
||||
//! `BigInt` is a built-in object that provides a way to represent whole numbers larger
|
||||
//! than the largest number JavaScript can reliably represent with the Number primitive
|
||||
//! and represented by the `Number.MAX_SAFE_INTEGER` constant.
|
||||
//! `BigInt` can be used for arbitrarily large integers.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-bigint-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
object::ConstructorBuilder,
|
||||
property::Attribute,
|
||||
symbol::WellKnownSymbols,
|
||||
value::{IntegerOrInfinity, PreferredType},
|
||||
Context, JsBigInt, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use num_bigint::ToBigInt;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// `BigInt` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BigInt;
|
||||
|
||||
impl BuiltIn for BigInt {
|
||||
const NAME: &'static str = "BigInt";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().bigint_object().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.callable(true)
|
||||
.constructor(true)
|
||||
.method(Self::to_string, "toString", 0)
|
||||
.method(Self::value_of, "valueOf", 0)
|
||||
.static_method(Self::as_int_n, "asIntN", 2)
|
||||
.static_method(Self::as_uint_n, "asUintN", 2)
|
||||
.property(
|
||||
to_string_tag,
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl BigInt {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// `BigInt()`
|
||||
///
|
||||
/// The `BigInt()` constructor is used to create `BigInt` objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bigint-objects
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt
|
||||
fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is not undefined, throw a TypeError exception.
|
||||
if !new_target.is_undefined() {
|
||||
return context.throw_type_error("BigInt is not a constructor");
|
||||
}
|
||||
|
||||
let value = args.get_or_undefined(0);
|
||||
|
||||
// 2. Let prim be ? ToPrimitive(value, number).
|
||||
let prim = value.to_primitive(context, PreferredType::Number)?;
|
||||
|
||||
// 3. If Type(prim) is Number, return ? NumberToBigInt(prim).
|
||||
if let Some(number) = prim.as_number() {
|
||||
return Self::number_to_bigint(number, context);
|
||||
}
|
||||
|
||||
// 4. Otherwise, return ? ToBigInt(value).
|
||||
Ok(value.to_bigint(context)?.into())
|
||||
}
|
||||
|
||||
/// `NumberToBigInt ( number )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-numbertobigint
|
||||
#[inline]
|
||||
fn number_to_bigint(number: f64, context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. If IsIntegralNumber(number) is false, throw a RangeError exception.
|
||||
if number.is_nan() || number.is_infinite() || number.fract() != 0.0 {
|
||||
return context.throw_range_error(format!("Cannot convert {number} to BigInt"));
|
||||
}
|
||||
|
||||
// 2. Return the BigInt value that represents ℝ(number).
|
||||
Ok(JsBigInt::from(number.to_bigint().expect("This conversion must be safe")).into())
|
||||
}
|
||||
|
||||
/// The abstract operation `thisBigIntValue` takes argument value.
|
||||
///
|
||||
/// The phrase “this `BigInt` value” within the specification of a method refers to the
|
||||
/// result returned by calling the abstract operation `thisBigIntValue` with the `this` value
|
||||
/// of the method invocation passed as the argument.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-thisbigintvalue
|
||||
#[inline]
|
||||
fn this_bigint_value(value: &JsValue, context: &mut Context) -> JsResult<JsBigInt> {
|
||||
value
|
||||
// 1. If Type(value) is BigInt, return value.
|
||||
.as_bigint()
|
||||
.cloned()
|
||||
// 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then
|
||||
// a. Assert: Type(value.[[BigIntData]]) is BigInt.
|
||||
// b. Return value.[[BigIntData]].
|
||||
.or_else(|| {
|
||||
value
|
||||
.as_object()
|
||||
.and_then(|obj| obj.borrow().as_bigint().cloned())
|
||||
})
|
||||
// 3. Throw a TypeError exception.
|
||||
.ok_or_else(|| context.construct_type_error("'this' is not a BigInt"))
|
||||
}
|
||||
|
||||
/// `BigInt.prototype.toString( [radix] )`
|
||||
///
|
||||
/// The `toString()` method returns a string representing the specified `BigInt` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.tostring
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn to_string(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let x be ? thisBigIntValue(this value).
|
||||
let x = Self::this_bigint_value(this, context)?;
|
||||
|
||||
let radix = args.get_or_undefined(0);
|
||||
|
||||
// 2. If radix is undefined, let radixMV be 10.
|
||||
let radix_mv = if radix.is_undefined() {
|
||||
// 5. If radixMV = 10, return ! ToString(x).
|
||||
// Note: early return optimization.
|
||||
return Ok(x.to_string().into());
|
||||
// 3. Else, let radixMV be ? ToIntegerOrInfinity(radix).
|
||||
} else {
|
||||
radix.to_integer_or_infinity(context)?
|
||||
};
|
||||
|
||||
// 4. If radixMV < 2 or radixMV > 36, throw a RangeError exception.
|
||||
let radix_mv = match radix_mv {
|
||||
IntegerOrInfinity::Integer(i) if (2..=36).contains(&i) => i,
|
||||
_ => {
|
||||
return context.throw_range_error(
|
||||
"radix must be an integer at least 2 and no greater than 36",
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// 5. If radixMV = 10, return ! ToString(x).
|
||||
if radix_mv == 10 {
|
||||
return Ok(x.to_string().into());
|
||||
}
|
||||
|
||||
// 1. Let x be ? thisBigIntValue(this value).
|
||||
// 6. Return the String representation of this Number value using the radix specified by radixMV.
|
||||
// Letters a-z are used for digits with values 10 through 35.
|
||||
// The precise algorithm is implementation-defined, however the algorithm should be a generalization of that specified in 6.1.6.2.23.
|
||||
Ok(JsValue::new(x.to_string_radix(radix_mv as u32)))
|
||||
}
|
||||
|
||||
/// `BigInt.prototype.valueOf()`
|
||||
///
|
||||
/// The `valueOf()` method returns the wrapped primitive value of a Number object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.valueof
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/valueOf
|
||||
pub(crate) fn value_of(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
Ok(JsValue::new(Self::this_bigint_value(this, context)?))
|
||||
}
|
||||
|
||||
/// `BigInt.asIntN()`
|
||||
///
|
||||
/// The `BigInt.asIntN()` method wraps the value of a `BigInt` to a signed integer between `-2**(width - 1)` and `2**(width-1) - 1`.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bigint.asintn
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asIntN
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn as_int_n(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let (modulo, bits) = Self::calculate_as_uint_n(args, context)?;
|
||||
|
||||
if bits > 0
|
||||
&& modulo
|
||||
>= JsBigInt::pow(
|
||||
&JsBigInt::new(2),
|
||||
&JsBigInt::new(i64::from(bits) - 1),
|
||||
context,
|
||||
)?
|
||||
{
|
||||
Ok(JsValue::new(JsBigInt::sub(
|
||||
&modulo,
|
||||
&JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)), context)?,
|
||||
)))
|
||||
} else {
|
||||
Ok(JsValue::new(modulo))
|
||||
}
|
||||
}
|
||||
|
||||
/// `BigInt.asUintN()`
|
||||
///
|
||||
/// The `BigInt.asUintN()` method wraps the value of a `BigInt` to an unsigned integer between `0` and `2**(width) - 1`.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bigint.asuintn
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asUintN
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn as_uint_n(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let (modulo, _) = Self::calculate_as_uint_n(args, context)?;
|
||||
|
||||
Ok(JsValue::new(modulo))
|
||||
}
|
||||
|
||||
/// Helper function to wrap the value of a `BigInt` to an unsigned integer.
|
||||
///
|
||||
/// This function expects the same arguments as `as_uint_n` and wraps the value of a `BigInt`.
|
||||
/// Additionally to the wrapped unsigned value it returns the converted `bits` argument, so it
|
||||
/// can be reused from the `as_int_n` method.
|
||||
fn calculate_as_uint_n(args: &[JsValue], context: &mut Context) -> JsResult<(JsBigInt, u32)> {
|
||||
let bits_arg = args.get_or_undefined(0);
|
||||
let bigint_arg = args.get_or_undefined(1);
|
||||
|
||||
let bits = bits_arg.to_index(context)?;
|
||||
let bits = u32::try_from(bits).unwrap_or(u32::MAX);
|
||||
|
||||
let bigint = bigint_arg.to_bigint(context)?;
|
||||
|
||||
Ok((
|
||||
JsBigInt::mod_floor(
|
||||
&bigint,
|
||||
&JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)), context)?,
|
||||
),
|
||||
bits,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
use crate::{forward, Context};
|
||||
|
||||
#[test]
|
||||
fn equality() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "0n == 0n"), "true");
|
||||
assert_eq!(forward(&mut context, "1n == 0n"), "false");
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"1000000000000000000000000000000000n == 1000000000000000000000000000000000n"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
|
||||
assert_eq!(forward(&mut context, "0n == ''"), "true");
|
||||
assert_eq!(forward(&mut context, "100n == '100'"), "true");
|
||||
assert_eq!(forward(&mut context, "100n == '100.5'"), "false");
|
||||
assert_eq!(
|
||||
forward(&mut context, "10000000000000000n == '10000000000000000'"),
|
||||
"true"
|
||||
);
|
||||
|
||||
assert_eq!(forward(&mut context, "'' == 0n"), "true");
|
||||
assert_eq!(forward(&mut context, "'100' == 100n"), "true");
|
||||
assert_eq!(forward(&mut context, "'100.5' == 100n"), "false");
|
||||
assert_eq!(
|
||||
forward(&mut context, "'10000000000000000' == 10000000000000000n"),
|
||||
"true"
|
||||
);
|
||||
|
||||
assert_eq!(forward(&mut context, "0n == 0"), "true");
|
||||
assert_eq!(forward(&mut context, "0n == 0.0"), "true");
|
||||
assert_eq!(forward(&mut context, "100n == 100"), "true");
|
||||
assert_eq!(forward(&mut context, "100n == 100.0"), "true");
|
||||
assert_eq!(forward(&mut context, "100n == '100.5'"), "false");
|
||||
assert_eq!(forward(&mut context, "100n == '1005'"), "false");
|
||||
assert_eq!(
|
||||
forward(&mut context, "10000000000000000n == 10000000000000000"),
|
||||
"true"
|
||||
);
|
||||
|
||||
assert_eq!(forward(&mut context, "0 == 0n"), "true");
|
||||
assert_eq!(forward(&mut context, "0.0 == 0n"), "true");
|
||||
assert_eq!(forward(&mut context, "100 == 100n"), "true");
|
||||
assert_eq!(forward(&mut context, "100.0 == 100n"), "true");
|
||||
assert_eq!(forward(&mut context, "100.5 == 100n"), "false");
|
||||
assert_eq!(forward(&mut context, "1005 == 100n"), "false");
|
||||
assert_eq!(
|
||||
forward(&mut context, "10000000000000000 == 10000000000000000n"),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_integer() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt(1000)"), "1000n");
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt(20000000000000000)"),
|
||||
"20000000000000000n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt(1000000000000000000000000000000000)"),
|
||||
"999999999999999945575230987042816n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_rational() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt(0.0)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt(1.0)"), "1n");
|
||||
assert_eq!(forward(&mut context, "BigInt(10000.0)"), "10000n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_rational_with_fractional_part() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let scenario = r#"
|
||||
try {
|
||||
BigInt(0.1);
|
||||
} catch (e) {
|
||||
e.toString();
|
||||
}
|
||||
"#;
|
||||
assert_eq!(
|
||||
forward(&mut context, scenario),
|
||||
"\"RangeError: Cannot convert 0.1 to BigInt\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_null() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let scenario = r#"
|
||||
try {
|
||||
BigInt(null);
|
||||
} catch (e) {
|
||||
e.toString();
|
||||
}
|
||||
"#;
|
||||
assert_eq!(
|
||||
forward(&mut context, scenario),
|
||||
"\"TypeError: cannot convert null to a BigInt\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_undefined() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let scenario = r#"
|
||||
try {
|
||||
BigInt(undefined);
|
||||
} catch (e) {
|
||||
e.toString();
|
||||
}
|
||||
"#;
|
||||
assert_eq!(
|
||||
forward(&mut context, scenario),
|
||||
"\"TypeError: cannot convert undefined to a BigInt\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_string() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt('')"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt(' ')"), "0n");
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt('200000000000000000')"),
|
||||
"200000000000000000n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt('1000000000000000000000000000000000')"),
|
||||
"1000000000000000000000000000000000n"
|
||||
);
|
||||
assert_eq!(forward(&mut context, "BigInt('0b1111')"), "15n");
|
||||
assert_eq!(forward(&mut context, "BigInt('0o70')"), "56n");
|
||||
assert_eq!(forward(&mut context, "BigInt('0xFF')"), "255n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "10000n + 1000n"), "11000n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "10000n - 1000n"), "9000n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, "123456789n * 102030n"),
|
||||
"12596296181670n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "15000n / 50n"), "300n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_with_truncation() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "15001n / 50n"), "300n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn r#mod() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "15007n % 10n"), "7n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, "100n ** 10n"),
|
||||
"100000000000000000000n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_negative_exponent() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "10n ** (-10n)", "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shl() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "8n << 2n"), "32n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shl_out_of_range() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "1000n << 1000000000000000n", "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shr() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "8n >> 2n"), "2n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shr_out_of_range() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "1000n >> 1000000000000000n", "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "1000n.toString()"), "\"1000\"");
|
||||
assert_eq!(forward(&mut context, "1000n.toString(2)"), "\"1111101000\"");
|
||||
assert_eq!(forward(&mut context, "255n.toString(16)"), "\"ff\"");
|
||||
assert_eq!(forward(&mut context, "1000n.toString(36)"), "\"rs\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string_invalid_radix() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "10n.toString(null)", "RangeError");
|
||||
assert_throws(&mut context, "10n.toString(-1)", "RangeError");
|
||||
assert_throws(&mut context, "10n.toString(37)", "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_int_n() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN(0, 1n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN(1, 1n)"), "-1n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN(3, 10n)"), "2n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN({}, 1n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN(2, 0n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN(2, -0n)"), "0n");
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asIntN(2, -123456789012345678901n)"),
|
||||
"-1n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asIntN(2, -123456789012345678900n)"),
|
||||
"0n"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asIntN(2, 123456789012345678900n)"),
|
||||
"0n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asIntN(2, 123456789012345678901n)"),
|
||||
"1n"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asIntN(200, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)"
|
||||
),
|
||||
"-1n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asIntN(201, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)"
|
||||
),
|
||||
"1606938044258990275541962092341162602522202993782792835301375n"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asIntN(200, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)"
|
||||
),
|
||||
"-741470203160010616172516490008037905920749803227695190508460n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asIntN(201, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)"
|
||||
),
|
||||
"865467841098979659369445602333124696601453190555097644792916n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_int_n_errors() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "BigInt.asIntN(-1, 0n)", "RangeError");
|
||||
assert_throws(&mut context, "BigInt.asIntN(-2.5, 0n)", "RangeError");
|
||||
assert_throws(
|
||||
&mut context,
|
||||
"BigInt.asIntN(9007199254740992, 0n)",
|
||||
"RangeError",
|
||||
);
|
||||
assert_throws(&mut context, "BigInt.asIntN(0n, 0n)", "TypeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_uint_n() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(0, -2n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(0, -1n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(0, 0n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(0, 1n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(0, 2n)"), "0n");
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, -3n)"), "1n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, -2n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, -1n)"), "1n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 0n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 1n)"), "1n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 2n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 3n)"), "1n");
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asUintN(1, -123456789012345678901n)"),
|
||||
"1n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asUintN(1, -123456789012345678900n)"),
|
||||
"0n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asUintN(1, 123456789012345678900n)"),
|
||||
"0n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asUintN(1, 123456789012345678901n)"),
|
||||
"1n"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asUintN(200, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)"
|
||||
),
|
||||
"1606938044258990275541962092341162602522202993782792835301375n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asUintN(201, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)"
|
||||
),
|
||||
"3213876088517980551083924184682325205044405987565585670602751n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_uint_n_errors() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "BigInt.asUintN(-1, 0n)", "RangeError");
|
||||
assert_throws(&mut context, "BigInt.asUintN(-2.5, 0n)", "RangeError");
|
||||
assert_throws(
|
||||
&mut context,
|
||||
"BigInt.asUintN(9007199254740992, 0n)",
|
||||
"RangeError",
|
||||
);
|
||||
assert_throws(&mut context, "BigInt.asUintN(0n, 0n)", "TypeError");
|
||||
}
|
||||
|
||||
fn assert_throws(context: &mut Context, src: &str, error_type: &str) {
|
||||
let result = forward(context, src);
|
||||
assert!(result.contains(error_type));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn division_by_zero() {
|
||||
let mut context = Context::default();
|
||||
assert_throws(&mut context, "1n/0n", "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remainder_by_zero() {
|
||||
let mut context = Context::default();
|
||||
assert_throws(&mut context, "1n % 0n", "RangeError");
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//! This module implements the global `Boolean` object.
|
||||
//!
|
||||
//! The `Boolean` object is an object wrapper for a boolean value.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-boolean-object
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{
|
||||
builtins::BuiltIn,
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
/// Boolean implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Boolean;
|
||||
|
||||
impl BuiltIn for Boolean {
|
||||
/// The name of the object.
|
||||
const NAME: &'static str = "Boolean";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().boolean().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.method(Self::to_string, "toString", 0)
|
||||
.method(Self::value_of, "valueOf", 0)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Boolean {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// `[[Construct]]` Create a new boolean object
|
||||
///
|
||||
/// `[[Call]]` Creates a new boolean primitive
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// Get the argument, if any
|
||||
let data = args.get(0).map_or(false, JsValue::to_boolean);
|
||||
if new_target.is_undefined() {
|
||||
return Ok(JsValue::new(data));
|
||||
}
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::boolean, context)?;
|
||||
let boolean = JsObject::from_proto_and_data(prototype, ObjectData::boolean(data));
|
||||
|
||||
Ok(boolean.into())
|
||||
}
|
||||
|
||||
/// An Utility function used to get the internal `[[BooleanData]]`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue
|
||||
fn this_boolean_value(value: &JsValue, context: &mut Context) -> JsResult<bool> {
|
||||
value
|
||||
.as_boolean()
|
||||
.or_else(|| value.as_object().and_then(|obj| obj.borrow().as_boolean()))
|
||||
.ok_or_else(|| context.construct_type_error("'this' is not a boolean"))
|
||||
}
|
||||
|
||||
/// The `toString()` method returns a string representing the specified `Boolean` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-boolean-object
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn to_string(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let boolean = Self::this_boolean_value(this, context)?;
|
||||
Ok(JsValue::new(boolean.to_string()))
|
||||
}
|
||||
|
||||
/// The valueOf() method returns the primitive value of a `Boolean` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf
|
||||
#[inline]
|
||||
pub(crate) fn value_of(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
Ok(JsValue::new(Self::this_boolean_value(this, context)?))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use crate::{forward, forward_val, Context};
|
||||
|
||||
/// Test the correct type is returned from call and construct
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[test]
|
||||
fn construct_and_call() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var one = new Boolean(1);
|
||||
var zero = Boolean(0);
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let one = forward_val(&mut context, "one").unwrap();
|
||||
let zero = forward_val(&mut context, "zero").unwrap();
|
||||
|
||||
assert!(one.is_object());
|
||||
assert!(zero.is_boolean());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constructor_gives_true_instance() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var trueVal = new Boolean(true);
|
||||
var trueNum = new Boolean(1);
|
||||
var trueString = new Boolean("true");
|
||||
var trueBool = new Boolean(trueVal);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let true_val = forward_val(&mut context, "trueVal").expect("value expected");
|
||||
let true_num = forward_val(&mut context, "trueNum").expect("value expected");
|
||||
let true_string = forward_val(&mut context, "trueString").expect("value expected");
|
||||
let true_bool = forward_val(&mut context, "trueBool").expect("value expected");
|
||||
|
||||
// Values should all be objects
|
||||
assert!(true_val.is_object());
|
||||
assert!(true_num.is_object());
|
||||
assert!(true_string.is_object());
|
||||
assert!(true_bool.is_object());
|
||||
|
||||
// Values should all be truthy
|
||||
assert!(true_val.to_boolean());
|
||||
assert!(true_num.to_boolean());
|
||||
assert!(true_string.to_boolean());
|
||||
assert!(true_bool.to_boolean());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instances_have_correct_proto_set() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var boolInstance = new Boolean(true);
|
||||
var boolProto = Boolean.prototype;
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let bool_instance = forward_val(&mut context, "boolInstance").expect("value expected");
|
||||
let bool_prototype = forward_val(&mut context, "boolProto").expect("value expected");
|
||||
|
||||
assert_eq!(
|
||||
&*bool_instance.as_object().unwrap().prototype(),
|
||||
&bool_prototype.as_object().cloned()
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,577 @@
|
||||
//! This module implements the global `console` object.
|
||||
//!
|
||||
//! The `console` object can be accessed from any global object.
|
||||
//!
|
||||
//! The specifics of how it works varies from browser to browser, but there is a de facto set of features that are typically provided.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [WHATWG `console` specification][spec]
|
||||
//!
|
||||
//! [spec]: https://console.spec.whatwg.org/
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Console
|
||||
|
||||
#![allow(clippy::print_stdout)]
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
object::ObjectInitializer,
|
||||
value::{display::display_obj, JsValue, Numeric},
|
||||
Context, JsResult, JsString,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::time::SystemTime;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
/// This represents the different types of log messages.
|
||||
#[derive(Debug)]
|
||||
pub enum LogMessage {
|
||||
Log(String),
|
||||
Info(String),
|
||||
Warn(String),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
/// Helper function for logging messages.
|
||||
pub(crate) fn logger(msg: LogMessage, console_state: &Console) {
|
||||
let indent = 2 * console_state.groups.len();
|
||||
|
||||
match msg {
|
||||
LogMessage::Error(msg) => {
|
||||
eprintln!("{msg:>indent$}");
|
||||
}
|
||||
LogMessage::Log(msg) | LogMessage::Info(msg) | LogMessage::Warn(msg) => {
|
||||
println!("{msg:>indent$}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents the `console` formatter.
|
||||
pub fn formatter(data: &[JsValue], context: &mut Context) -> JsResult<String> {
|
||||
let target = data
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.to_string(context)?;
|
||||
|
||||
match data.len() {
|
||||
0 => Ok(String::new()),
|
||||
1 => Ok(target.to_string()),
|
||||
_ => {
|
||||
let mut formatted = String::new();
|
||||
let mut arg_index = 1;
|
||||
let mut chars = target.chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if c == '%' {
|
||||
let fmt = chars.next().unwrap_or('%');
|
||||
match fmt {
|
||||
/* integer */
|
||||
'd' | 'i' => {
|
||||
let arg = match data.get_or_undefined(arg_index).to_numeric(context)? {
|
||||
Numeric::Number(r) => (r.floor() + 0.0).to_string(),
|
||||
Numeric::BigInt(int) => int.to_string(),
|
||||
};
|
||||
formatted.push_str(&arg);
|
||||
arg_index += 1;
|
||||
}
|
||||
/* float */
|
||||
'f' => {
|
||||
let arg = data.get_or_undefined(arg_index).to_number(context)?;
|
||||
formatted.push_str(&format!("{arg:.6}"));
|
||||
arg_index += 1;
|
||||
}
|
||||
/* object, FIXME: how to render this properly? */
|
||||
'o' | 'O' => {
|
||||
let arg = data.get_or_undefined(arg_index);
|
||||
formatted.push_str(&arg.display().to_string());
|
||||
arg_index += 1;
|
||||
}
|
||||
/* string */
|
||||
's' => {
|
||||
let arg = data
|
||||
.get(arg_index)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.to_string(context)?;
|
||||
formatted.push_str(&arg);
|
||||
arg_index += 1;
|
||||
}
|
||||
'%' => formatted.push('%'),
|
||||
/* TODO: %c is not implemented */
|
||||
c => {
|
||||
formatted.push('%');
|
||||
formatted.push(c);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
formatted.push(c);
|
||||
};
|
||||
}
|
||||
|
||||
/* unformatted data */
|
||||
for rest in data.iter().skip(arg_index) {
|
||||
formatted.push_str(&format!(" {}", rest.to_string(context)?));
|
||||
}
|
||||
|
||||
Ok(formatted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the internal console object state.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Console {
|
||||
count_map: FxHashMap<JsString, u32>,
|
||||
timer_map: FxHashMap<JsString, u128>,
|
||||
groups: Vec<String>,
|
||||
}
|
||||
|
||||
impl BuiltIn for Console {
|
||||
const NAME: &'static str = "console";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
ObjectInitializer::new(context)
|
||||
.function(Self::assert, "assert", 0)
|
||||
.function(Self::clear, "clear", 0)
|
||||
.function(Self::debug, "debug", 0)
|
||||
.function(Self::error, "error", 0)
|
||||
.function(Self::info, "info", 0)
|
||||
.function(Self::log, "log", 0)
|
||||
.function(Self::trace, "trace", 0)
|
||||
.function(Self::warn, "warn", 0)
|
||||
.function(Self::error, "exception", 0)
|
||||
.function(Self::count, "count", 0)
|
||||
.function(Self::count_reset, "countReset", 0)
|
||||
.function(Self::group, "group", 0)
|
||||
.function(Self::group, "groupCollapsed", 0)
|
||||
.function(Self::group_end, "groupEnd", 0)
|
||||
.function(Self::time, "time", 0)
|
||||
.function(Self::time_log, "timeLog", 0)
|
||||
.function(Self::time_end, "timeEnd", 0)
|
||||
.function(Self::dir, "dir", 0)
|
||||
.function(Self::dir, "dirxml", 0)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Console {
|
||||
/// The name of the object.
|
||||
pub(crate) const NAME: &'static str = "console";
|
||||
|
||||
/// `console.assert(condition, ...data)`
|
||||
///
|
||||
/// Prints a JavaScript value to the standard error if first argument evaluates to `false` or there
|
||||
/// were no arguments.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#assert
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/assert
|
||||
pub(crate) fn assert(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let assertion = args.get(0).map_or(false, JsValue::to_boolean);
|
||||
|
||||
if !assertion {
|
||||
let mut args: Vec<JsValue> = args.iter().skip(1).cloned().collect();
|
||||
let message = "Assertion failed".to_string();
|
||||
if args.is_empty() {
|
||||
args.push(JsValue::new(message));
|
||||
} else if !args[0].is_string() {
|
||||
args.insert(0, JsValue::new(message));
|
||||
} else {
|
||||
let concat = format!("{message}: {}", args[0].display());
|
||||
args[0] = JsValue::new(concat);
|
||||
}
|
||||
|
||||
logger(
|
||||
LogMessage::Error(formatter(&args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.clear()`
|
||||
///
|
||||
/// Removes all groups and clears console if possible.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#clear
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/clear
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn clear(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
context.console_mut().groups.clear();
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.debug(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "debug" logLevel.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#debug
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/debug
|
||||
pub(crate) fn debug(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Log(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.error(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "error" logLevel.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#error
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/error
|
||||
pub(crate) fn error(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Error(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.info(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "info" logLevel.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#info
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/info
|
||||
pub(crate) fn info(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Info(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.log(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "log" logLevel.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#log
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/log
|
||||
pub(crate) fn log(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Log(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
fn get_stack_trace(context: &mut Context) -> Vec<String> {
|
||||
let mut stack_trace: Vec<String> = vec![];
|
||||
|
||||
for frame in context.vm.frames.iter().rev() {
|
||||
stack_trace.push(
|
||||
context
|
||||
.interner()
|
||||
.resolve_expect(frame.code.name)
|
||||
.to_owned(),
|
||||
);
|
||||
}
|
||||
|
||||
stack_trace
|
||||
}
|
||||
|
||||
/// `console.trace(...data)`
|
||||
///
|
||||
/// Prints a stack trace with "trace" logLevel, optionally labelled by data.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#trace
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/trace
|
||||
pub(crate) fn trace(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
if !args.is_empty() {
|
||||
logger(
|
||||
LogMessage::Log(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
|
||||
let stack_trace_dump = Self::get_stack_trace(context).join("\n");
|
||||
logger(LogMessage::Log(stack_trace_dump), context.console());
|
||||
}
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.warn(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "warn" logLevel.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#warn
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/warn
|
||||
pub(crate) fn warn(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Warn(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.count(label)`
|
||||
///
|
||||
/// Prints number of times the function was called with that particular label.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#count
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/count
|
||||
pub(crate) fn count(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let label = match args.get(0) {
|
||||
Some(value) => value.to_string(context)?,
|
||||
None => "default".into(),
|
||||
};
|
||||
|
||||
let msg = format!("count {label}:");
|
||||
let c = context.console_mut().count_map.entry(label).or_insert(0);
|
||||
*c += 1;
|
||||
|
||||
logger(LogMessage::Info(format!("{msg} {c}")), context.console());
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.countReset(label)`
|
||||
///
|
||||
/// Resets the counter for label.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#countreset
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/countReset
|
||||
pub(crate) fn count_reset(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let label = match args.get(0) {
|
||||
Some(value) => value.to_string(context)?,
|
||||
None => "default".into(),
|
||||
};
|
||||
|
||||
context.console_mut().count_map.remove(&label);
|
||||
|
||||
logger(
|
||||
LogMessage::Warn(format!("countReset {label}")),
|
||||
context.console(),
|
||||
);
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// Returns current system time in ms.
|
||||
fn system_time_in_ms() -> u128 {
|
||||
let now = SystemTime::now();
|
||||
now.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.expect("negative duration")
|
||||
.as_millis()
|
||||
}
|
||||
|
||||
/// `console.time(label)`
|
||||
///
|
||||
/// Starts the timer for given label.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#time
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/time
|
||||
pub(crate) fn time(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let label = match args.get(0) {
|
||||
Some(value) => value.to_string(context)?,
|
||||
None => "default".into(),
|
||||
};
|
||||
|
||||
if context.console().timer_map.get(&label).is_some() {
|
||||
logger(
|
||||
LogMessage::Warn(format!("Timer '{label}' already exist")),
|
||||
context.console(),
|
||||
);
|
||||
} else {
|
||||
let time = Self::system_time_in_ms();
|
||||
context.console_mut().timer_map.insert(label, time);
|
||||
}
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.timeLog(label, ...data)`
|
||||
///
|
||||
/// Prints elapsed time for timer with given label.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#timelog
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeLog
|
||||
pub(crate) fn time_log(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let label = match args.get(0) {
|
||||
Some(value) => value.to_string(context)?,
|
||||
None => "default".into(),
|
||||
};
|
||||
|
||||
if let Some(t) = context.console().timer_map.get(&label) {
|
||||
let time = Self::system_time_in_ms();
|
||||
let mut concat = format!("{label}: {} ms", time - t);
|
||||
for msg in args.iter().skip(1) {
|
||||
concat = concat + " " + &msg.display().to_string();
|
||||
}
|
||||
logger(LogMessage::Log(concat), context.console());
|
||||
} else {
|
||||
logger(
|
||||
LogMessage::Warn(format!("Timer '{label}' doesn't exist")),
|
||||
context.console(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.timeEnd(label)`
|
||||
///
|
||||
/// Removes the timer with given label.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#timeend
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeEnd
|
||||
pub(crate) fn time_end(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let label = match args.get(0) {
|
||||
Some(value) => value.to_string(context)?,
|
||||
None => "default".into(),
|
||||
};
|
||||
|
||||
if let Some(t) = context.console_mut().timer_map.remove(label.as_str()) {
|
||||
let time = Self::system_time_in_ms();
|
||||
logger(
|
||||
LogMessage::Info(format!("{label}: {} ms - timer removed", time - t)),
|
||||
context.console(),
|
||||
);
|
||||
} else {
|
||||
logger(
|
||||
LogMessage::Warn(format!("Timer '{label}' doesn't exist")),
|
||||
context.console(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.group(...data)`
|
||||
///
|
||||
/// Adds new group with name from formatted data to stack.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#group
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/group
|
||||
pub(crate) fn group(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let group_label = formatter(args, context)?;
|
||||
|
||||
logger(
|
||||
LogMessage::Info(format!("group: {group_label}")),
|
||||
context.console(),
|
||||
);
|
||||
context.console_mut().groups.push(group_label);
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.groupEnd(label)`
|
||||
///
|
||||
/// Removes the last group from the stack.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#groupend
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/groupEnd
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn group_end(
|
||||
_: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
context.console_mut().groups.pop();
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.dir(item, options)`
|
||||
///
|
||||
/// Prints info about item
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#dir
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/dir
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn dir(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Info(display_obj(args.get_or_undefined(0), true)),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use crate::{builtins::console::formatter, Context, JsValue};
|
||||
|
||||
#[test]
|
||||
fn formatter_no_args_is_empty_string() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(formatter(&[], &mut context).unwrap(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatter_empty_format_string_is_empty_string() {
|
||||
let mut context = Context::default();
|
||||
let val = JsValue::new("");
|
||||
assert_eq!(formatter(&[val], &mut context).unwrap(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatter_format_without_args_renders_verbatim() {
|
||||
let mut context = Context::default();
|
||||
let val = [JsValue::new("%d %s %% %f")];
|
||||
let res = formatter(&val, &mut context).unwrap();
|
||||
assert_eq!(res, "%d %s %% %f");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatter_empty_format_string_concatenates_rest_of_args() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let val = [
|
||||
JsValue::new(""),
|
||||
JsValue::new("to powinno zostać"),
|
||||
JsValue::new("połączone"),
|
||||
];
|
||||
let res = formatter(&val, &mut context).unwrap();
|
||||
assert_eq!(res, " to powinno zostać połączone");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatter_utf_8_checks() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let val = [
|
||||
JsValue::new("Są takie chwile %dą %są tu%sów %привет%ź".to_string()),
|
||||
JsValue::new(123),
|
||||
JsValue::new(1.23),
|
||||
JsValue::new("ł"),
|
||||
];
|
||||
let res = formatter(&val, &mut context).unwrap();
|
||||
assert_eq!(res, "Są takie chwile 123ą 1.23ą tułów %привет%ź");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatter_trailing_format_leader_renders() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let val = [JsValue::new("%%%%%"), JsValue::new("|")];
|
||||
let res = formatter(&val, &mut context).unwrap();
|
||||
assert_eq!(res, "%%% |");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::approx_constant)]
|
||||
fn formatter_float_format_works() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let val = [JsValue::new("%f"), JsValue::new(3.1415)];
|
||||
let res = formatter(&val, &mut context).unwrap();
|
||||
assert_eq!(res, "3.141500");
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,116 @@
|
||||
//! This module implements the global `AggregateError` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-aggregate-error
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError
|
||||
|
||||
use crate::{
|
||||
builtins::{iterable::iterable_to_list, Array, BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::{Attribute, PropertyDescriptorBuilder},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct AggregateError;
|
||||
|
||||
impl BuiltIn for AggregateError {
|
||||
const NAME: &'static str = "AggregateError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.aggregate_error()
|
||||
.clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl AggregateError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 2;
|
||||
|
||||
/// Create a new aggregate error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%", « [[ErrorData]] »).
|
||||
let prototype = get_prototype_from_constructor(
|
||||
new_target,
|
||||
StandardConstructors::aggregate_error,
|
||||
context,
|
||||
)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(1);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(2), context)?;
|
||||
|
||||
// 5. Let errorsList be ? IterableToList(errors).
|
||||
let errors = args.get_or_undefined(0);
|
||||
let errors_list = iterable_to_list(context, errors, None)?;
|
||||
// 6. Perform ! DefinePropertyOrThrow(O, "errors",
|
||||
// PropertyDescriptor {
|
||||
// [[Configurable]]: true,
|
||||
// [[Enumerable]]: false,
|
||||
// [[Writable]]: true,
|
||||
// [[Value]]: CreateArrayFromList(errorsList)
|
||||
// }).
|
||||
o.define_property_or_throw(
|
||||
"errors",
|
||||
PropertyDescriptorBuilder::new()
|
||||
.configurable(true)
|
||||
.enumerable(false)
|
||||
.writable(true)
|
||||
.value(Array::create_array_from_list(errors_list, context))
|
||||
.build(),
|
||||
context,
|
||||
)
|
||||
.expect("should not fail according to spec");
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//! This module implements the global `EvalError` object.
|
||||
//!
|
||||
//! Indicates an error regarding the global `eval()` function.
|
||||
//! This exception is not thrown by JavaScript anymore, however
|
||||
//! the `EvalError` object remains for compatibility.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// JavaScript `EvalError` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct EvalError;
|
||||
|
||||
impl BuiltIn for EvalError {
|
||||
const NAME: &'static str = "EvalError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().eval_error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl EvalError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::eval_error, context)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
//! This module implements the global `Error` object.
|
||||
//!
|
||||
//! Error objects are thrown when runtime errors occur.
|
||||
//! The Error object can also be used as a base object for user-defined exceptions.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-error-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
|
||||
use crate::{
|
||||
builtins::BuiltIn,
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
pub(crate) mod aggregate;
|
||||
pub(crate) mod eval;
|
||||
pub(crate) mod range;
|
||||
pub(crate) mod reference;
|
||||
pub(crate) mod syntax;
|
||||
pub(crate) mod r#type;
|
||||
pub(crate) mod uri;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub(crate) use self::aggregate::AggregateError;
|
||||
pub(crate) use self::eval::EvalError;
|
||||
pub(crate) use self::r#type::TypeError;
|
||||
pub(crate) use self::range::RangeError;
|
||||
pub(crate) use self::reference::ReferenceError;
|
||||
pub(crate) use self::syntax::SyntaxError;
|
||||
pub(crate) use self::uri::UriError;
|
||||
|
||||
use super::JsArgs;
|
||||
|
||||
/// Built-in `Error` object.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Error;
|
||||
|
||||
impl BuiltIn for Error {
|
||||
const NAME: &'static str = "Error";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.method(Self::to_string, "toString", 0)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
pub(crate) fn install_error_cause(
|
||||
o: &JsObject,
|
||||
options: &JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<()> {
|
||||
// 1. If Type(options) is Object and ? HasProperty(options, "cause") is true, then
|
||||
if let Some(options) = options.as_object() {
|
||||
if options.has_property("cause", context)? {
|
||||
// a. Let cause be ? Get(options, "cause").
|
||||
let cause = options.get("cause", context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause).
|
||||
o.create_non_enumerable_data_property_or_throw("cause", cause, context);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Return unused.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `Error( message [ , options ] )`
|
||||
///
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%", « [[ErrorData]] »).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::error, context)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Self::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
|
||||
/// `Error.prototype.toString()`
|
||||
///
|
||||
/// The toString() method returns a string representing the specified Error object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn to_string(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let O be the this value.
|
||||
let o = if let Some(o) = this.as_object() {
|
||||
o
|
||||
// 2. If Type(O) is not Object, throw a TypeError exception.
|
||||
} else {
|
||||
return context.throw_type_error("'this' is not an Object");
|
||||
};
|
||||
|
||||
// 3. Let name be ? Get(O, "name").
|
||||
let name = o.get("name", context)?;
|
||||
|
||||
// 4. If name is undefined, set name to "Error"; otherwise set name to ? ToString(name).
|
||||
let name = if name.is_undefined() {
|
||||
JsString::new("Error")
|
||||
} else {
|
||||
name.to_string(context)?
|
||||
};
|
||||
|
||||
// 5. Let msg be ? Get(O, "message").
|
||||
let msg = o.get("message", context)?;
|
||||
|
||||
// 6. If msg is undefined, set msg to the empty String; otherwise set msg to ? ToString(msg).
|
||||
let msg = if msg.is_undefined() {
|
||||
JsString::empty()
|
||||
} else {
|
||||
msg.to_string(context)?
|
||||
};
|
||||
|
||||
// 7. If name is the empty String, return msg.
|
||||
if name.is_empty() {
|
||||
return Ok(msg.into());
|
||||
}
|
||||
|
||||
// 8. If msg is the empty String, return name.
|
||||
if msg.is_empty() {
|
||||
return Ok(name.into());
|
||||
}
|
||||
|
||||
// 9. Return the string-concatenation of name, the code unit 0x003A (COLON),
|
||||
// the code unit 0x0020 (SPACE), and msg.
|
||||
Ok(format!("{name}: {msg}").into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//! This module implements the global `RangeError` object.
|
||||
//!
|
||||
//! Indicates a value that is not in the set or range of allowable values.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// JavaScript `RangeError` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct RangeError;
|
||||
|
||||
impl BuiltIn for RangeError {
|
||||
const NAME: &'static str = "RangeError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().range_error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl RangeError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::range_error, context)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//! This module implements the global `ReferenceError` object.
|
||||
//!
|
||||
//! Indicates an error that occurs when de-referencing an invalid reference
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct ReferenceError;
|
||||
|
||||
impl BuiltIn for ReferenceError {
|
||||
const NAME: &'static str = "ReferenceError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.reference_error()
|
||||
.clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReferenceError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype = get_prototype_from_constructor(
|
||||
new_target,
|
||||
StandardConstructors::reference_error,
|
||||
context,
|
||||
)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//! This module implements the global `SyntaxError` object.
|
||||
//!
|
||||
//! The `SyntaxError` object represents an error when trying to interpret syntactically invalid code.
|
||||
//! It is thrown when the JavaScript context encounters tokens or token order that does not conform
|
||||
//! to the syntax of the language when parsing code.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// JavaScript `SyntaxError` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct SyntaxError;
|
||||
|
||||
impl BuiltIn for SyntaxError {
|
||||
const NAME: &'static str = "SyntaxError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().syntax_error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl SyntaxError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype = get_prototype_from_constructor(
|
||||
new_target,
|
||||
StandardConstructors::syntax_error,
|
||||
context,
|
||||
)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use crate::{forward, Context};
|
||||
|
||||
#[test]
|
||||
fn error_to_string() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let e = new Error('1');
|
||||
let name = new Error();
|
||||
let message = new Error('message');
|
||||
message.name = '';
|
||||
let range_e = new RangeError('2');
|
||||
let ref_e = new ReferenceError('3');
|
||||
let syntax_e = new SyntaxError('4');
|
||||
let type_e = new TypeError('5');
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
assert_eq!(forward(&mut context, "e.toString()"), "\"Error: 1\"");
|
||||
assert_eq!(forward(&mut context, "name.toString()"), "\"Error\"");
|
||||
assert_eq!(forward(&mut context, "message.toString()"), "\"message\"");
|
||||
assert_eq!(
|
||||
forward(&mut context, "range_e.toString()"),
|
||||
"\"RangeError: 2\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "ref_e.toString()"),
|
||||
"\"ReferenceError: 3\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "syntax_e.toString()"),
|
||||
"\"SyntaxError: 4\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "type_e.toString()"),
|
||||
"\"TypeError: 5\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_error_name() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(forward(&mut context, "EvalError.name"), "\"EvalError\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_error_length() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(forward(&mut context, "EvalError.length"), "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_error_to_string() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(
|
||||
forward(&mut context, "new EvalError('hello').toString()"),
|
||||
"\"EvalError: hello\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "new EvalError().toString()"),
|
||||
"\"EvalError\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_error_name() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(forward(&mut context, "URIError.name"), "\"URIError\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_error_length() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(forward(&mut context, "URIError.length"), "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_error_to_string() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(
|
||||
forward(&mut context, "new URIError('hello').toString()"),
|
||||
"\"URIError: hello\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "new URIError().toString()"),
|
||||
"\"URIError\""
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
//! This module implements the global `TypeError` object.
|
||||
//!
|
||||
//! The `TypeError` object represents an error when an operation could not be performed,
|
||||
//! typically (but not exclusively) when a value is not of the expected type.
|
||||
//!
|
||||
//! A `TypeError` may be thrown when:
|
||||
//! - an operand or argument passed to a function is incompatible with the type expected by that operator or function.
|
||||
//! - when attempting to modify a value that cannot be changed.
|
||||
//! - when attempting to use a value in an inappropriate way.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-typeerror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
|
||||
|
||||
use crate::{
|
||||
builtins::{function::Function, BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::{Attribute, PropertyDescriptor},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// JavaScript `TypeError` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct TypeError;
|
||||
|
||||
impl BuiltIn for TypeError {
|
||||
const NAME: &'static str = "TypeError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().type_error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::type_error, context)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_throw_type_error(context: &mut Context) -> JsObject {
|
||||
fn throw_type_error(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
context.throw_type_error("'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")
|
||||
}
|
||||
|
||||
let function = JsObject::from_proto_and_data(
|
||||
context.intrinsics().constructors().function().prototype(),
|
||||
ObjectData::function(Function::Native {
|
||||
function: throw_type_error,
|
||||
constructor: None,
|
||||
}),
|
||||
);
|
||||
|
||||
let property = PropertyDescriptor::builder()
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(false);
|
||||
function.insert_property("name", property.clone().value("ThrowTypeError"));
|
||||
function.insert_property("length", property.value(0));
|
||||
|
||||
function
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//! This module implements the global `URIError` object.
|
||||
//!
|
||||
//! The `URIError` object represents an error when a global URI handling
|
||||
//! function was used in a wrong way.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// JavaScript `URIError` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct UriError;
|
||||
|
||||
impl BuiltIn for UriError {
|
||||
const NAME: &'static str = "URIError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().uri_error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl UriError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::uri_error, context)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
//! This module implements the global `eval` function.
|
||||
//!
|
||||
//! The `eval()` function evaluates JavaScript code represented as a string.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-eval-x
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
object::FunctionBuilder,
|
||||
property::Attribute,
|
||||
Context, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Eval;
|
||||
|
||||
impl BuiltIn for Eval {
|
||||
const NAME: &'static str = "eval";
|
||||
|
||||
const ATTRIBUTE: Attribute = Attribute::CONFIGURABLE
|
||||
.union(Attribute::NON_ENUMERABLE)
|
||||
.union(Attribute::WRITABLE);
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let object = FunctionBuilder::native(context, Self::eval)
|
||||
.name("eval")
|
||||
.length(1)
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
Some(object.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval {
|
||||
/// `19.2.1 eval ( x )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-eval-x
|
||||
fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result<JsValue, JsValue> {
|
||||
// 1. Return ? PerformEval(x, false, false).
|
||||
Self::perform_eval(args.get_or_undefined(0), false, false, context)
|
||||
}
|
||||
|
||||
/// `19.2.1.1 PerformEval ( x, strictCaller, direct )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-performeval
|
||||
pub(crate) fn perform_eval(
|
||||
x: &JsValue,
|
||||
direct: bool,
|
||||
strict: bool,
|
||||
context: &mut Context,
|
||||
) -> Result<JsValue, JsValue> {
|
||||
// 1. Assert: If direct is false, then strictCaller is also false.
|
||||
if !direct {
|
||||
debug_assert!(!strict);
|
||||
}
|
||||
|
||||
// 2. If Type(x) is not String, return x.
|
||||
let x = if let Some(x) = x.as_string() {
|
||||
x.clone()
|
||||
} else {
|
||||
return Ok(x.clone());
|
||||
};
|
||||
|
||||
// Because of implementation details the following code differs from the spec.
|
||||
|
||||
// Parse the script body and handle early errors (6 - 11)
|
||||
let body = match context.parse_eval(x.as_bytes(), direct, strict) {
|
||||
Ok(body) => body,
|
||||
Err(e) => return context.throw_syntax_error(e.to_string()),
|
||||
};
|
||||
|
||||
// 12 - 13 are implicit in the call of `Context::compile_with_new_declarative`.
|
||||
|
||||
// Because our environment model does not map directly to the spec this section looks very different.
|
||||
// 14 - 33 are in the following section, together with EvalDeclarationInstantiation.
|
||||
if direct {
|
||||
// If the call to eval is direct, the code is executed in the current environment.
|
||||
|
||||
// Poison the current environment, because it may contain new declarations after/during eval.
|
||||
context.realm.environments.poison_current();
|
||||
|
||||
// Set the compile time environment to the current running environment and save the number of current environments.
|
||||
context.realm.compile_env = context.realm.environments.current_compile_environment();
|
||||
let environments_len = context.realm.environments.len();
|
||||
|
||||
// Error if any var declaration in the eval code already exists as a let/const declaration in the current running environment.
|
||||
let mut vars = FxHashSet::default();
|
||||
body.var_declared_names_new(&mut vars);
|
||||
if let Some(name) = context
|
||||
.realm
|
||||
.environments
|
||||
.has_lex_binding_until_function_environment(&vars)
|
||||
{
|
||||
let name = context.interner().resolve_expect(name);
|
||||
let msg = format!("variable declaration {name} in eval function already exists as lexically declaration");
|
||||
return context.throw_syntax_error(msg);
|
||||
}
|
||||
|
||||
// Compile and execute the eval statement list.
|
||||
let code_block = context.compile_with_new_declarative(&body, strict)?;
|
||||
context
|
||||
.realm
|
||||
.environments
|
||||
.extend_outer_function_environment();
|
||||
let result = context.execute(code_block);
|
||||
|
||||
// Pop any added runtime environments that where not removed during the eval execution.
|
||||
context.realm.environments.truncate(environments_len);
|
||||
|
||||
result
|
||||
} else {
|
||||
// If the call to eval is indirect, the code is executed in the global environment.
|
||||
|
||||
// Poison all environments, because the global environment may contain new declarations after/during eval.
|
||||
context.realm.environments.poison_all();
|
||||
|
||||
// Pop all environments before the eval execution.
|
||||
let environments = context.realm.environments.pop_to_global();
|
||||
let environments_len = context.realm.environments.len();
|
||||
context.realm.compile_env = context.realm.environments.current_compile_environment();
|
||||
|
||||
// Compile and execute the eval statement list.
|
||||
let code_block = context.compile_with_new_declarative(&body, false)?;
|
||||
let result = context.execute(code_block);
|
||||
|
||||
// Restore all environments to the state from before the eval execution.
|
||||
context.realm.environments.truncate(environments_len);
|
||||
context.realm.environments.extend(environments);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
use crate::{
|
||||
builtins::Array,
|
||||
environments::DeclarativeEnvironment,
|
||||
object::{JsObject, ObjectData},
|
||||
property::PropertyDescriptor,
|
||||
symbol::{self, WellKnownSymbols},
|
||||
syntax::ast::node::FormalParameterList,
|
||||
Context, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Gc, Trace};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
/// `ParameterMap` represents the `[[ParameterMap]]` internal slot on a Arguments exotic object.
|
||||
///
|
||||
/// This struct stores all the data to access mapped function parameters in their environment.
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct ParameterMap {
|
||||
binding_indices: Vec<Option<usize>>,
|
||||
environment: Gc<DeclarativeEnvironment>,
|
||||
}
|
||||
|
||||
impl ParameterMap {
|
||||
/// Deletes the binding with the given index from the parameter map.
|
||||
pub(crate) fn delete(&mut self, index: usize) {
|
||||
if let Some(binding) = self.binding_indices.get_mut(index) {
|
||||
*binding = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value of the binding at the given index from the function environment.
|
||||
///
|
||||
/// Note: This function is the abstract getter closure described in 10.4.4.7.1 `MakeArgGetter ( name, env )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-makearggetter
|
||||
pub(crate) fn get(&self, index: usize) -> Option<JsValue> {
|
||||
if let Some(Some(binding_index)) = self.binding_indices.get(index) {
|
||||
return Some(self.environment.get(*binding_index));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Set the value of the binding at the given index in the function environment.
|
||||
///
|
||||
/// Note: This function is the abstract setter closure described in 10.4.4.7.2 `MakeArgSetter ( name, env )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-makeargsetter
|
||||
pub(crate) fn set(&self, index: usize, value: &JsValue) {
|
||||
if let Some(Some(binding_index)) = self.binding_indices.get(index) {
|
||||
self.environment.set(*binding_index, value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub enum Arguments {
|
||||
Unmapped,
|
||||
Mapped(ParameterMap),
|
||||
}
|
||||
|
||||
impl Arguments {
|
||||
/// Creates a new unmapped Arguments ordinary object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createunmappedargumentsobject
|
||||
pub(crate) fn create_unmapped_arguments_object(
|
||||
arguments_list: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsObject {
|
||||
// 1. Let len be the number of elements in argumentsList.
|
||||
let len = arguments_list.len();
|
||||
|
||||
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »).
|
||||
let obj = context.construct_object();
|
||||
|
||||
// 3. Set obj.[[ParameterMap]] to undefined.
|
||||
// skipped because the `Arguments` enum ensures ordinary argument objects don't have a `[[ParameterMap]]`
|
||||
obj.borrow_mut().data = ObjectData::arguments(Self::Unmapped);
|
||||
|
||||
// 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len),
|
||||
// [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
|
||||
obj.define_property_or_throw(
|
||||
"length",
|
||||
PropertyDescriptor::builder()
|
||||
.value(len)
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// 5. Let index be 0.
|
||||
// 6. Repeat, while index < len,
|
||||
for (index, value) in arguments_list.iter().cloned().enumerate() {
|
||||
// a. Let val be argumentsList[index].
|
||||
// b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val).
|
||||
obj.create_data_property_or_throw(index, value, context)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// c. Set index to index + 1.
|
||||
}
|
||||
|
||||
// 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor {
|
||||
// [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false,
|
||||
// [[Configurable]]: true }).
|
||||
obj.define_property_or_throw(
|
||||
symbol::WellKnownSymbols::iterator(),
|
||||
PropertyDescriptor::builder()
|
||||
.value(Array::values_intrinsic(context))
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
let throw_type_error = context.intrinsics().objects().throw_type_error();
|
||||
|
||||
// 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor {
|
||||
// [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false,
|
||||
// [[Configurable]]: false }).
|
||||
obj.define_property_or_throw(
|
||||
"callee",
|
||||
PropertyDescriptor::builder()
|
||||
.get(throw_type_error.clone())
|
||||
.set(throw_type_error)
|
||||
.enumerable(false)
|
||||
.configurable(false),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// 9. Return obj.
|
||||
obj
|
||||
}
|
||||
|
||||
/// Creates a new mapped Arguments exotic object.
|
||||
///
|
||||
/// <https://tc39.es/ecma262/#sec-createmappedargumentsobject>
|
||||
pub(crate) fn create_mapped_arguments_object(
|
||||
func: &JsObject,
|
||||
formals: &FormalParameterList,
|
||||
arguments_list: &[JsValue],
|
||||
env: &Gc<DeclarativeEnvironment>,
|
||||
context: &mut Context,
|
||||
) -> JsObject {
|
||||
// 1. Assert: formals does not contain a rest parameter, any binding patterns, or any initializers.
|
||||
// It may contain duplicate identifiers.
|
||||
// 2. Let len be the number of elements in argumentsList.
|
||||
let len = arguments_list.len();
|
||||
|
||||
// 3. Let obj be ! MakeBasicObject(« [[Prototype]], [[Extensible]], [[ParameterMap]] »).
|
||||
// 4. Set obj.[[GetOwnProperty]] as specified in 10.4.4.1.
|
||||
// 5. Set obj.[[DefineOwnProperty]] as specified in 10.4.4.2.
|
||||
// 6. Set obj.[[Get]] as specified in 10.4.4.3.
|
||||
// 7. Set obj.[[Set]] as specified in 10.4.4.4.
|
||||
// 8. Set obj.[[Delete]] as specified in 10.4.4.5.
|
||||
// 9. Set obj.[[Prototype]] to %Object.prototype%.
|
||||
|
||||
// Section 17-19 are done first, for easier object creation in 11.
|
||||
//
|
||||
// The section 17-19 differs from the spec, due to the way the runtime environments work.
|
||||
//
|
||||
// This section creates getters and setters for all mapped arguments.
|
||||
// Getting and setting values on the `arguments` object will actually access the bindings in the environment:
|
||||
// ```
|
||||
// function f(a) {console.log(a); arguments[0] = 1; console.log(a)};
|
||||
// f(0) // 0, 1
|
||||
// ```
|
||||
//
|
||||
// The spec assumes, that identifiers are used at runtime to reference bindings in the environment.
|
||||
// We use indices to access environment bindings at runtime.
|
||||
// To map to function parameters to binding indices, we use the fact, that bindings in a
|
||||
// function environment start with all of the arguments in order:
|
||||
// `function f (a,b,c)`
|
||||
// | binding index | `arguments` property key | identifier |
|
||||
// | 0 | 0 | a |
|
||||
// | 1 | 1 | b |
|
||||
// | 2 | 2 | c |
|
||||
//
|
||||
// Notice that the binding index does not correspond to the argument index:
|
||||
// `function f (a,a,b)` => binding indices 0 (a), 1 (b), 2 (c)
|
||||
// | binding index | `arguments` property key | identifier |
|
||||
// | - | 0 | - |
|
||||
// | 0 | 1 | a |
|
||||
// | 1 | 2 | b |
|
||||
// While the `arguments` object contains all arguments, they must not be all bound.
|
||||
// In the case of duplicate parameter names, the last one is bound as the environment binding.
|
||||
//
|
||||
// The following logic implements the steps 17-19 adjusted for our environment structure.
|
||||
|
||||
let mut bindings = FxHashMap::default();
|
||||
let mut property_index = 0;
|
||||
'outer: for formal in formals.parameters.iter() {
|
||||
for name in formal.names() {
|
||||
if property_index >= len {
|
||||
break 'outer;
|
||||
}
|
||||
let binding_index = bindings.len() + 1;
|
||||
let entry = bindings
|
||||
.entry(name)
|
||||
.or_insert((binding_index, property_index));
|
||||
entry.1 = property_index;
|
||||
property_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut map = ParameterMap {
|
||||
binding_indices: vec![None; property_index],
|
||||
environment: env.clone(),
|
||||
};
|
||||
|
||||
for (binding_index, property_index) in bindings.values() {
|
||||
map.binding_indices[*property_index] = Some(*binding_index);
|
||||
}
|
||||
|
||||
// 11. Set obj.[[ParameterMap]] to map.
|
||||
let obj = JsObject::from_proto_and_data(
|
||||
context.intrinsics().constructors().object().prototype(),
|
||||
ObjectData::arguments(Self::Mapped(map)),
|
||||
);
|
||||
|
||||
// 14. Let index be 0.
|
||||
// 15. Repeat, while index < len,
|
||||
for (index, val) in arguments_list.iter().cloned().enumerate() {
|
||||
// a. Let val be argumentsList[index].
|
||||
// b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val).
|
||||
// Note: Insert is used here because `CreateDataPropertyOrThrow` would cause a panic while executing
|
||||
// exotic argument object set methods before the variables in the environment are initialized.
|
||||
obj.insert(
|
||||
index,
|
||||
PropertyDescriptor::builder()
|
||||
.value(val)
|
||||
.writable(true)
|
||||
.enumerable(true)
|
||||
.configurable(true)
|
||||
.build(),
|
||||
);
|
||||
// c. Set index to index + 1.
|
||||
}
|
||||
|
||||
// 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len),
|
||||
// [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
|
||||
obj.define_property_or_throw(
|
||||
"length",
|
||||
PropertyDescriptor::builder()
|
||||
.value(len)
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// 20. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor {
|
||||
// [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false,
|
||||
// [[Configurable]]: true }).
|
||||
obj.define_property_or_throw(
|
||||
WellKnownSymbols::iterator(),
|
||||
PropertyDescriptor::builder()
|
||||
.value(Array::values_intrinsic(context))
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor {
|
||||
// [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
|
||||
obj.define_property_or_throw(
|
||||
"callee",
|
||||
PropertyDescriptor::builder()
|
||||
.value(func.clone())
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// 22. Return obj.
|
||||
obj
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,262 @@
|
||||
use crate::{
|
||||
forward, forward_val,
|
||||
object::FunctionBuilder,
|
||||
property::{Attribute, PropertyDescriptor},
|
||||
Context, JsString,
|
||||
};
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
#[test]
|
||||
fn arguments_object() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
function jason(a, b) {
|
||||
return arguments[0];
|
||||
}
|
||||
var val = jason(100, 6);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let return_val = forward_val(&mut context, "val").expect("value expected");
|
||||
assert!(return_val.is_integer());
|
||||
assert_eq!(
|
||||
return_val
|
||||
.to_i32(&mut context)
|
||||
.expect("Could not convert value to i32"),
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_mutating_function_when_calling() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
function x() {
|
||||
x.y = 3;
|
||||
}
|
||||
x();
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, func));
|
||||
let y = forward_val(&mut context, "x.y").expect("value expected");
|
||||
assert!(y.is_integer());
|
||||
assert_eq!(
|
||||
y.to_i32(&mut context)
|
||||
.expect("Could not convert value to i32"),
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_mutating_function_when_constructing() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
function x() {
|
||||
x.y = 3;
|
||||
}
|
||||
new x();
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, func));
|
||||
let y = forward_val(&mut context, "x.y").expect("value expected");
|
||||
assert!(y.is_integer());
|
||||
assert_eq!(
|
||||
y.to_i32(&mut context)
|
||||
.expect("Could not convert value to i32"),
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_function_prototype() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
Function.prototype()
|
||||
"#;
|
||||
let value = forward_val(&mut context, func).unwrap();
|
||||
assert!(value.is_undefined());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_function_prototype_with_arguments() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
Function.prototype(1, "", new String(""))
|
||||
"#;
|
||||
let value = forward_val(&mut context, func).unwrap();
|
||||
assert!(value.is_undefined());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_function_prototype_with_new() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
new Function.prototype()
|
||||
"#;
|
||||
let value = forward_val(&mut context, func);
|
||||
assert!(value.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_name() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
Function.prototype.name
|
||||
"#;
|
||||
let value = forward_val(&mut context, func).unwrap();
|
||||
assert!(value.is_string());
|
||||
assert!(value.as_string().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)]
|
||||
fn function_prototype_length() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
Function.prototype.length
|
||||
"#;
|
||||
let value = forward_val(&mut context, func).unwrap();
|
||||
assert!(value.is_number());
|
||||
assert_eq!(value.as_number().unwrap(), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_call() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
let e = new Error()
|
||||
Object.prototype.toString.call(e)
|
||||
"#;
|
||||
let value = forward_val(&mut context, func).unwrap();
|
||||
assert!(value.is_string());
|
||||
assert_eq!(value.as_string().unwrap(), "[object Error]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_call_throw() {
|
||||
let mut context = Context::default();
|
||||
let throw = r#"
|
||||
let call = Function.prototype.call;
|
||||
call(call)
|
||||
"#;
|
||||
let value = forward_val(&mut context, throw).unwrap_err();
|
||||
assert!(value.is_object());
|
||||
let string = value.to_string(&mut context).unwrap();
|
||||
assert!(string.starts_with("TypeError"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_call_multiple_args() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
function f(a, b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
let o = {a: 0, b: 0};
|
||||
f.call(o, 1, 2);
|
||||
"#;
|
||||
forward_val(&mut context, init).unwrap();
|
||||
let boolean = forward_val(&mut context, "o.a == 1")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
let boolean = forward_val(&mut context, "o.b == 2")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_apply() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
const numbers = [6, 7, 3, 4, 2];
|
||||
const max = Math.max.apply(null, numbers);
|
||||
const min = Math.min.apply(null, numbers);
|
||||
"#;
|
||||
forward_val(&mut context, init).unwrap();
|
||||
|
||||
let boolean = forward_val(&mut context, "max == 7")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
|
||||
let boolean = forward_val(&mut context, "min == 2")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_apply_on_object() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
function f(a, b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
let o = {a: 0, b: 0};
|
||||
f.apply(o, [1, 2]);
|
||||
"#;
|
||||
forward_val(&mut context, init).unwrap();
|
||||
|
||||
let boolean = forward_val(&mut context, "o.a == 1")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
|
||||
let boolean = forward_val(&mut context, "o.b == 2")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_capture_clone() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let string = JsString::from("Hello");
|
||||
let object = context.construct_object();
|
||||
object
|
||||
.define_property_or_throw(
|
||||
"key",
|
||||
PropertyDescriptor::builder()
|
||||
.value(" world!")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(false),
|
||||
&mut context,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let func = FunctionBuilder::closure_with_captures(
|
||||
&mut context,
|
||||
|_, _, captures, context| {
|
||||
let (string, object) = &captures;
|
||||
|
||||
let hw = JsString::concat(
|
||||
string,
|
||||
object
|
||||
.__get_own_property__(&"key".into(), context)?
|
||||
.and_then(|prop| prop.value().cloned())
|
||||
.and_then(|val| val.as_string().cloned())
|
||||
.ok_or_else(|| context.construct_type_error("invalid `key` property"))?,
|
||||
);
|
||||
Ok(hw.into())
|
||||
},
|
||||
(string, object),
|
||||
)
|
||||
.name("closure")
|
||||
.build();
|
||||
|
||||
context.register_global_property("closure", func, Attribute::default());
|
||||
|
||||
assert_eq!(forward(&mut context, "closure()"), "\"Hello world!\"");
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
//! This module implements the global `Generator` object.
|
||||
//!
|
||||
//! A Generator is an instance of a generator function and conforms to both the Iterator and Iterable interfaces.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-generator-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator
|
||||
|
||||
use crate::{
|
||||
builtins::{iterable::create_iter_result_object, BuiltIn, JsArgs},
|
||||
environments::DeclarativeEnvironmentStack,
|
||||
object::{ConstructorBuilder, JsObject, ObjectData},
|
||||
property::{Attribute, PropertyDescriptor},
|
||||
symbol::WellKnownSymbols,
|
||||
value::JsValue,
|
||||
vm::{CallFrame, GeneratorResumeKind, ReturnType},
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_gc::{Cell, Finalize, Gc, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// Indicates the state of a generator.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) enum GeneratorState {
|
||||
Undefined,
|
||||
SuspendedStart,
|
||||
SuspendedYield,
|
||||
Executing,
|
||||
Completed,
|
||||
}
|
||||
|
||||
/// Holds all information that a generator needs to continue it's execution.
|
||||
///
|
||||
/// All of the fields must be changed with those that are currently present in the
|
||||
/// context/vm before the generator execution starts/resumes and after it has ended/yielded.
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub(crate) struct GeneratorContext {
|
||||
pub(crate) environments: DeclarativeEnvironmentStack,
|
||||
pub(crate) call_frame: CallFrame,
|
||||
pub(crate) stack: Vec<JsValue>,
|
||||
}
|
||||
|
||||
/// The internal representation on a `Generator` object.
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct Generator {
|
||||
/// The `[[GeneratorState]]` internal slot.
|
||||
#[unsafe_ignore_trace]
|
||||
pub(crate) state: GeneratorState,
|
||||
|
||||
/// The `[[GeneratorContext]]` internal slot.
|
||||
pub(crate) context: Option<Gc<Cell<GeneratorContext>>>,
|
||||
}
|
||||
|
||||
impl BuiltIn for Generator {
|
||||
const NAME: &'static str = "Generator";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let iterator_prototype = context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.iterator_prototype();
|
||||
|
||||
let generator_function_prototype = context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator_function()
|
||||
.prototype();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().generator().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.property(
|
||||
WellKnownSymbols::to_string_tag(),
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.method(Self::next, "next", 1)
|
||||
.method(Self::r#return, "return", 1)
|
||||
.method(Self::throw, "throw", 1)
|
||||
.inherit(iterator_prototype)
|
||||
.build();
|
||||
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator()
|
||||
.prototype
|
||||
.insert_property(
|
||||
"constructor",
|
||||
PropertyDescriptor::builder()
|
||||
.value(generator_function_prototype)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Generator {
|
||||
pub(crate) const LENGTH: usize = 0;
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn constructor(
|
||||
_: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let prototype = context.intrinsics().constructors().generator().prototype();
|
||||
|
||||
let this = JsObject::from_proto_and_data(
|
||||
prototype,
|
||||
ObjectData::generator(Self {
|
||||
state: GeneratorState::Undefined,
|
||||
context: None,
|
||||
}),
|
||||
);
|
||||
|
||||
Ok(this.into())
|
||||
}
|
||||
|
||||
/// `Generator.prototype.next ( value )`
|
||||
///
|
||||
/// The `next()` method returns an object with two properties done and value.
|
||||
/// You can also provide a parameter to the next method to send a value to the generator.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.next
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next
|
||||
pub(crate) fn next(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Return ? GeneratorResume(this value, value, empty).
|
||||
match this.as_object() {
|
||||
Some(obj) if obj.is_generator() => {
|
||||
Self::generator_resume(obj, args.get_or_undefined(0), context)
|
||||
}
|
||||
_ => context.throw_type_error("Generator.prototype.next called on non generator"),
|
||||
}
|
||||
}
|
||||
|
||||
/// `Generator.prototype.return ( value )`
|
||||
///
|
||||
/// The `return()` method returns the given value and finishes the generator.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.return
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return
|
||||
pub(crate) fn r#return(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let g be the this value.
|
||||
// 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
|
||||
// 3. Return ? GeneratorResumeAbrupt(g, C, empty).
|
||||
Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0).clone()), context)
|
||||
}
|
||||
|
||||
/// `Generator.prototype.throw ( exception )`
|
||||
///
|
||||
/// The `throw()` method resumes the execution of a generator by throwing an error into it
|
||||
/// and returns an object with two properties done and value.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.throw
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/throw
|
||||
pub(crate) fn throw(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let g be the this value.
|
||||
// 2. Let C be ThrowCompletion(exception).
|
||||
// 3. Return ? GeneratorResumeAbrupt(g, C, empty).
|
||||
Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0).clone()), context)
|
||||
}
|
||||
|
||||
/// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generatorresume
|
||||
pub(crate) fn generator_resume(
|
||||
generator_obj: &JsObject,
|
||||
value: &JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let state be ? GeneratorValidate(generator, generatorBrand).
|
||||
let mut generator_obj_mut = generator_obj.borrow_mut();
|
||||
let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| {
|
||||
context.construct_type_error("generator resumed on non generator object")
|
||||
})?;
|
||||
let state = generator.state;
|
||||
|
||||
if state == GeneratorState::Executing {
|
||||
return Err(context.construct_type_error("Generator should not be executing"));
|
||||
}
|
||||
|
||||
// 2. If state is completed, return CreateIterResultObject(undefined, true).
|
||||
if state == GeneratorState::Completed {
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
));
|
||||
}
|
||||
|
||||
// 3. Assert: state is either suspendedStart or suspendedYield.
|
||||
assert!(matches!(
|
||||
state,
|
||||
GeneratorState::SuspendedStart | GeneratorState::SuspendedYield
|
||||
));
|
||||
|
||||
// 4. Let genContext be generator.[[GeneratorContext]].
|
||||
// 5. Let methodContext be the running execution context.
|
||||
// 6. Suspend methodContext.
|
||||
// 7. Set generator.[[GeneratorState]] to executing.
|
||||
generator.state = GeneratorState::Executing;
|
||||
let first_execution = matches!(state, GeneratorState::SuspendedStart);
|
||||
|
||||
let generator_context_cell = generator
|
||||
.context
|
||||
.take()
|
||||
.expect("generator context cannot be empty here");
|
||||
let mut generator_context = generator_context_cell.borrow_mut();
|
||||
drop(generator_obj_mut);
|
||||
|
||||
std::mem::swap(
|
||||
&mut context.realm.environments,
|
||||
&mut generator_context.environments,
|
||||
);
|
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
|
||||
context.vm.push_frame(generator_context.call_frame.clone());
|
||||
if !first_execution {
|
||||
context.vm.push(value);
|
||||
}
|
||||
|
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal;
|
||||
|
||||
let result = context.run();
|
||||
|
||||
generator_context.call_frame = context
|
||||
.vm
|
||||
.pop_frame()
|
||||
.expect("generator call frame must exist");
|
||||
std::mem::swap(
|
||||
&mut context.realm.environments,
|
||||
&mut generator_context.environments,
|
||||
);
|
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
|
||||
|
||||
let mut generator_obj_mut = generator_obj.borrow_mut();
|
||||
let generator = generator_obj_mut
|
||||
.as_generator_mut()
|
||||
.expect("already checked this object type");
|
||||
|
||||
match result {
|
||||
Ok((value, ReturnType::Yield)) => {
|
||||
generator.state = GeneratorState::SuspendedYield;
|
||||
drop(generator_context);
|
||||
generator.context = Some(generator_context_cell);
|
||||
Ok(create_iter_result_object(value, false, context))
|
||||
}
|
||||
Ok((value, _)) => {
|
||||
generator.state = GeneratorState::Completed;
|
||||
Ok(create_iter_result_object(value, true, context))
|
||||
}
|
||||
Err(value) => {
|
||||
generator.state = GeneratorState::Completed;
|
||||
Err(value)
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Push genContext onto the execution context stack; genContext is now the running execution context.
|
||||
// 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the result of the operation that suspended it. Let result be the value returned by the resumed computation.
|
||||
// 10. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context.
|
||||
// 11. Return Completion(result).
|
||||
}
|
||||
|
||||
/// `27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generatorresumeabrupt
|
||||
pub(crate) fn generator_resume_abrupt(
|
||||
this: &JsValue,
|
||||
abrupt_completion: JsResult<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let state be ? GeneratorValidate(generator, generatorBrand).
|
||||
let generator_obj = this.as_object().ok_or_else(|| {
|
||||
context.construct_type_error("generator resumed on non generator object")
|
||||
})?;
|
||||
let mut generator_obj_mut = generator_obj.borrow_mut();
|
||||
let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| {
|
||||
context.construct_type_error("generator resumed on non generator object")
|
||||
})?;
|
||||
let mut state = generator.state;
|
||||
|
||||
if state == GeneratorState::Executing {
|
||||
return Err(context.construct_type_error("Generator should not be executing"));
|
||||
}
|
||||
|
||||
// 2. If state is suspendedStart, then
|
||||
if state == GeneratorState::SuspendedStart {
|
||||
// a. Set generator.[[GeneratorState]] to completed.
|
||||
generator.state = GeneratorState::Completed;
|
||||
// b. Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with generator can be discarded at this point.
|
||||
generator.context = None;
|
||||
// c. Set state to completed.
|
||||
state = GeneratorState::Completed;
|
||||
}
|
||||
|
||||
// 3. If state is completed, then
|
||||
if state == GeneratorState::Completed {
|
||||
// a. If abruptCompletion.[[Type]] is return, then
|
||||
if let Ok(value) = abrupt_completion {
|
||||
// i. Return CreateIterResultObject(abruptCompletion.[[Value]], true).
|
||||
return Ok(create_iter_result_object(value, true, context));
|
||||
}
|
||||
// b. Return Completion(abruptCompletion).
|
||||
return abrupt_completion;
|
||||
}
|
||||
|
||||
// 4. Assert: state is suspendedYield.
|
||||
// 5. Let genContext be generator.[[GeneratorContext]].
|
||||
// 6. Let methodContext be the running execution context.
|
||||
// 7. Suspend methodContext.
|
||||
// 8. Set generator.[[GeneratorState]] to executing.
|
||||
// 9. Push genContext onto the execution context stack; genContext is now the running execution context.
|
||||
// 10. Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation.
|
||||
// 11. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context.
|
||||
// 12. Return Completion(result).
|
||||
let generator_context_cell = generator
|
||||
.context
|
||||
.take()
|
||||
.expect("generator context cannot be empty here");
|
||||
let mut generator_context = generator_context_cell.borrow_mut();
|
||||
|
||||
generator.state = GeneratorState::Executing;
|
||||
drop(generator_obj_mut);
|
||||
|
||||
std::mem::swap(
|
||||
&mut context.realm.environments,
|
||||
&mut generator_context.environments,
|
||||
);
|
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
|
||||
context.vm.push_frame(generator_context.call_frame.clone());
|
||||
|
||||
let result = match abrupt_completion {
|
||||
Ok(value) => {
|
||||
context.vm.push(value);
|
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return;
|
||||
context.run()
|
||||
}
|
||||
Err(value) => {
|
||||
context.vm.push(value);
|
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw;
|
||||
context.run()
|
||||
}
|
||||
};
|
||||
generator_context.call_frame = context
|
||||
.vm
|
||||
.pop_frame()
|
||||
.expect("generator call frame must exist");
|
||||
std::mem::swap(
|
||||
&mut context.realm.environments,
|
||||
&mut generator_context.environments,
|
||||
);
|
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
|
||||
|
||||
let mut generator_obj_mut = generator_obj.borrow_mut();
|
||||
let generator = generator_obj_mut
|
||||
.as_generator_mut()
|
||||
.expect("already checked this object type");
|
||||
|
||||
match result {
|
||||
Ok((value, ReturnType::Yield)) => {
|
||||
generator.state = GeneratorState::SuspendedYield;
|
||||
drop(generator_context);
|
||||
generator.context = Some(generator_context_cell);
|
||||
Ok(create_iter_result_object(value, false, context))
|
||||
}
|
||||
Ok((value, _)) => {
|
||||
generator.state = GeneratorState::Completed;
|
||||
Ok(create_iter_result_object(value, true, context))
|
||||
}
|
||||
Err(value) => {
|
||||
generator.state = GeneratorState::Completed;
|
||||
Err(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
//! This module implements the global `GeneratorFunction` object.
|
||||
//!
|
||||
//! The `GeneratorFunction` constructor creates a new generator function object.
|
||||
//! In JavaScript, every generator function is actually a `GeneratorFunction` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-generatorfunction-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/GeneratorFunction
|
||||
|
||||
use crate::{
|
||||
builtins::{
|
||||
function::{BuiltInFunctionObject, ConstructorKind, Function},
|
||||
BuiltIn,
|
||||
},
|
||||
object::ObjectData,
|
||||
property::PropertyDescriptor,
|
||||
symbol::WellKnownSymbols,
|
||||
value::JsValue,
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// The internal representation on a `Generator` object.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GeneratorFunction;
|
||||
|
||||
impl BuiltIn for GeneratorFunction {
|
||||
const NAME: &'static str = "GeneratorFunction";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let prototype = &context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator_function()
|
||||
.prototype;
|
||||
let constructor = &context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator_function()
|
||||
.constructor;
|
||||
|
||||
constructor.set_prototype(Some(
|
||||
context.intrinsics().constructors().function().constructor(),
|
||||
));
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(1)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
constructor.borrow_mut().insert("length", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value("GeneratorFunction")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
constructor.borrow_mut().insert("name", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator_function()
|
||||
.prototype(),
|
||||
)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(false);
|
||||
constructor.borrow_mut().insert("prototype", property);
|
||||
constructor.borrow_mut().data = ObjectData::function(Function::Native {
|
||||
function: Self::constructor,
|
||||
constructor: Some(ConstructorKind::Base),
|
||||
});
|
||||
|
||||
prototype.set_prototype(Some(
|
||||
context.intrinsics().constructors().function().prototype(),
|
||||
));
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator_function()
|
||||
.constructor(),
|
||||
)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype.borrow_mut().insert("constructor", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(context.intrinsics().constructors().generator().prototype())
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype.borrow_mut().insert("prototype", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value("GeneratorFunction")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype
|
||||
.borrow_mut()
|
||||
.insert(WellKnownSymbols::to_string_tag(), property);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl GeneratorFunction {
|
||||
/// `GeneratorFunction ( p1, p2, … , pn, body )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generatorfunction
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
BuiltInFunctionObject::create_dynamic_function(new_target, args, false, true, context)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//! This module implements the global `globalThis` property.
|
||||
//!
|
||||
//! The global globalThis property contains the global this value,
|
||||
//! which is akin to the global object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-globalthis
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis
|
||||
|
||||
use crate::{builtins::BuiltIn, Context, JsValue};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// The JavaScript `globalThis`.
|
||||
pub(crate) struct GlobalThis;
|
||||
|
||||
impl BuiltIn for GlobalThis {
|
||||
const NAME: &'static str = "globalThis";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
Some(context.global_object().clone().into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
use crate::exec;
|
||||
|
||||
#[test]
|
||||
fn global_this_exists_on_global_object_and_evaluates_to_an_object() {
|
||||
let scenario = r#"
|
||||
typeof globalThis;
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "\"object\"");
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//! This module implements the global `Infinity` property.
|
||||
//!
|
||||
//! The global property `Infinity` is a numeric value representing infinity.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-value-properties-of-the-global-object-infinity
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{builtins::BuiltIn, property::Attribute, Context, JsValue};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// JavaScript global `Infinity` property.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) struct Infinity;
|
||||
|
||||
impl BuiltIn for Infinity {
|
||||
const NAME: &'static str = "Infinity";
|
||||
|
||||
const ATTRIBUTE: Attribute = Attribute::READONLY
|
||||
.union(Attribute::NON_ENUMERABLE)
|
||||
.union(Attribute::PERMANENT);
|
||||
|
||||
fn init(_: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
Some(f64::INFINITY.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use crate::exec;
|
||||
|
||||
#[test]
|
||||
fn infinity_exists_on_global_object_and_evaluates_to_infinity_value() {
|
||||
let scenario = r#"
|
||||
Infinity;
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "Infinity");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infinity_exists_and_equals_to_number_positive_infinity_value() {
|
||||
let scenario = r#"
|
||||
Number.POSITIVE_INFINITY === Infinity;
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "true");
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
//! This module implements the global `Intl.DateTimeFormat` object.
|
||||
//!
|
||||
//! `Intl.DateTimeFormat` is a built-in object that has properties and methods for date and time i18n.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma402/#datetimeformat-objects
|
||||
|
||||
use crate::{
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsFunction, JsObject,
|
||||
ObjectData,
|
||||
},
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// JavaScript `Intl.DateTimeFormat` object.
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct DateTimeFormat {
|
||||
initialized_date_time_format: bool,
|
||||
locale: JsString,
|
||||
calendar: JsString,
|
||||
numbering_system: JsString,
|
||||
time_zone: JsString,
|
||||
weekday: JsString,
|
||||
era: JsString,
|
||||
year: JsString,
|
||||
month: JsString,
|
||||
day: JsString,
|
||||
day_period: JsString,
|
||||
hour: JsString,
|
||||
minute: JsString,
|
||||
second: JsString,
|
||||
fractional_second_digits: JsString,
|
||||
time_zone_name: JsString,
|
||||
hour_cycle: JsString,
|
||||
pattern: JsString,
|
||||
bound_format: JsString,
|
||||
}
|
||||
|
||||
impl DateTimeFormat {
|
||||
const NAME: &'static str = "DateTimeFormat";
|
||||
|
||||
pub(super) fn init(context: &mut Context) -> JsFunction {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
ConstructorBuilder::new(context, Self::constructor)
|
||||
.name(Self::NAME)
|
||||
.length(0)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl DateTimeFormat {
|
||||
/// The `Intl.DateTimeFormat` constructor is the `%DateTimeFormat%` intrinsic object and a standard built-in property of the `Intl` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#datetimeformat-objects
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
_args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
|
||||
let prototype = get_prototype_from_constructor(
|
||||
new_target,
|
||||
StandardConstructors::date_time_format,
|
||||
context,
|
||||
)?;
|
||||
// 2. Let dateTimeFormat be ? OrdinaryCreateFromConstructor(newTarget, "%DateTimeFormat.prototype%",
|
||||
// « [[InitializedDateTimeFormat]], [[Locale]], [[Calendar]], [[NumberingSystem]], [[TimeZone]], [[Weekday]],
|
||||
// [[Era]], [[Year]], [[Month]], [[Day]], [[DayPeriod]], [[Hour]], [[Minute]], [[Second]],
|
||||
// [[FractionalSecondDigits]], [[TimeZoneName]], [[HourCycle]], [[Pattern]], [[BoundFormat]] »).
|
||||
let date_time_format = JsObject::from_proto_and_data(
|
||||
prototype,
|
||||
ObjectData::date_time_format(Box::new(Self {
|
||||
initialized_date_time_format: true,
|
||||
locale: JsString::from("en-US"),
|
||||
calendar: JsString::from("gregory"),
|
||||
numbering_system: JsString::from("arab"),
|
||||
time_zone: JsString::from("UTC"),
|
||||
weekday: JsString::from("narrow"),
|
||||
era: JsString::from("narrow"),
|
||||
year: JsString::from("numeric"),
|
||||
month: JsString::from("narrow"),
|
||||
day: JsString::from("numeric"),
|
||||
day_period: JsString::from("narrow"),
|
||||
hour: JsString::from("numeric"),
|
||||
minute: JsString::from("numeric"),
|
||||
second: JsString::from("numeric"),
|
||||
fractional_second_digits: JsString::from(""),
|
||||
time_zone_name: JsString::from(""),
|
||||
hour_cycle: JsString::from("h24"),
|
||||
pattern: JsString::from("{hour}:{minute}"),
|
||||
bound_format: JsString::from("undefined"),
|
||||
})),
|
||||
);
|
||||
|
||||
// TODO 3. Perform ? InitializeDateTimeFormat(dateTimeFormat, locales, options).
|
||||
// TODO 4. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then
|
||||
// TODO a. Let this be the this value.
|
||||
// TODO b. Return ? ChainDateTimeFormat(dateTimeFormat, NewTarget, this).
|
||||
|
||||
// 5. Return dateTimeFormat.
|
||||
Ok(date_time_format.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the `required` and `defaults` arguments in the abstract operation
|
||||
/// `toDateTimeOptions`.
|
||||
///
|
||||
/// Since `required` and `defaults` differ only in the `any` and `all` variants,
|
||||
/// we combine both in a single variant `AnyAll`.
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum DateTimeReqs {
|
||||
Date,
|
||||
Time,
|
||||
AnyAll,
|
||||
}
|
||||
|
||||
/// The abstract operation `toDateTimeOptions` is called with arguments `options`, `required` and
|
||||
/// `defaults`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-todatetimeoptions
|
||||
#[allow(unused)]
|
||||
pub(crate) fn to_date_time_options(
|
||||
options: &JsValue,
|
||||
required: &DateTimeReqs,
|
||||
defaults: &DateTimeReqs,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject> {
|
||||
// 1. If options is undefined, let options be null;
|
||||
// otherwise let options be ? ToObject(options).
|
||||
// 2. Let options be ! OrdinaryObjectCreate(options).
|
||||
let options = if options.is_undefined() {
|
||||
None
|
||||
} else {
|
||||
Some(options.to_object(context)?)
|
||||
};
|
||||
let options = JsObject::from_proto_and_data(options, ObjectData::ordinary());
|
||||
|
||||
// 3. Let needDefaults be true.
|
||||
let mut need_defaults = true;
|
||||
|
||||
// 4. If required is "date" or "any", then
|
||||
if [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(required) {
|
||||
// a. For each property name prop of « "weekday", "year", "month", "day" », do
|
||||
for property in ["weekday", "year", "month", "day"] {
|
||||
// i. Let value be ? Get(options, prop).
|
||||
let value = options.get(property, context)?;
|
||||
|
||||
// ii. If value is not undefined, let needDefaults be false.
|
||||
if !value.is_undefined() {
|
||||
need_defaults = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. If required is "time" or "any", then
|
||||
if [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(required) {
|
||||
// a. For each property name prop of « "dayPeriod", "hour", "minute", "second",
|
||||
// "fractionalSecondDigits" », do
|
||||
for property in [
|
||||
"dayPeriod",
|
||||
"hour",
|
||||
"minute",
|
||||
"second",
|
||||
"fractionalSecondDigits",
|
||||
] {
|
||||
// i. Let value be ? Get(options, prop).
|
||||
let value = options.get(property, context)?;
|
||||
|
||||
// ii. If value is not undefined, let needDefaults be false.
|
||||
if !value.is_undefined() {
|
||||
need_defaults = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Let dateStyle be ? Get(options, "dateStyle").
|
||||
let date_style = options.get("dateStyle", context)?;
|
||||
|
||||
// 7. Let timeStyle be ? Get(options, "timeStyle").
|
||||
let time_style = options.get("timeStyle", context)?;
|
||||
|
||||
// 8. If dateStyle is not undefined or timeStyle is not undefined, let needDefaults be false.
|
||||
if !date_style.is_undefined() || !time_style.is_undefined() {
|
||||
need_defaults = false;
|
||||
}
|
||||
|
||||
// 9. If required is "date" and timeStyle is not undefined, then
|
||||
if required == &DateTimeReqs::Date && !time_style.is_undefined() {
|
||||
// a. Throw a TypeError exception.
|
||||
return context.throw_type_error("'date' is required, but timeStyle was defined");
|
||||
}
|
||||
|
||||
// 10. If required is "time" and dateStyle is not undefined, then
|
||||
if required == &DateTimeReqs::Time && !date_style.is_undefined() {
|
||||
// a. Throw a TypeError exception.
|
||||
return context.throw_type_error("'time' is required, but dateStyle was defined");
|
||||
}
|
||||
|
||||
// 11. If needDefaults is true and defaults is either "date" or "all", then
|
||||
if need_defaults && [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(defaults) {
|
||||
// a. For each property name prop of « "year", "month", "day" », do
|
||||
for property in ["year", "month", "day"] {
|
||||
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
|
||||
options.create_data_property_or_throw(property, "numeric", context)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 12. If needDefaults is true and defaults is either "time" or "all", then
|
||||
if need_defaults && [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(defaults) {
|
||||
// a. For each property name prop of « "hour", "minute", "second" », do
|
||||
for property in ["hour", "minute", "second"] {
|
||||
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
|
||||
options.create_data_property_or_throw(property, "numeric", context)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 13. Return options.
|
||||
Ok(options)
|
||||
}
|
||||
@@ -0,0 +1,836 @@
|
||||
//! This module implements the global `Intl` object.
|
||||
//!
|
||||
//! `Intl` is a built-in object that has properties and methods for i18n. It's not a function object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma402/#intl-object
|
||||
|
||||
use crate::{
|
||||
builtins::intl::date_time_format::DateTimeFormat,
|
||||
builtins::{Array, BuiltIn, JsArgs},
|
||||
object::{JsObject, ObjectInitializer},
|
||||
property::Attribute,
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
|
||||
pub mod date_time_format;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use boa_profiler::Profiler;
|
||||
use icu_locale_canonicalizer::LocaleCanonicalizer;
|
||||
use icu_locid::{locale, Locale};
|
||||
use indexmap::IndexSet;
|
||||
use rustc_hash::FxHashMap;
|
||||
use tap::{Conv, Pipe, TapOptional};
|
||||
|
||||
/// JavaScript `Intl` object.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) struct Intl;
|
||||
|
||||
impl BuiltIn for Intl {
|
||||
const NAME: &'static str = "Intl";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let string_tag = WellKnownSymbols::to_string_tag();
|
||||
let date_time_format = DateTimeFormat::init(context);
|
||||
ObjectInitializer::new(context)
|
||||
.function(Self::get_canonical_locales, "getCanonicalLocales", 1)
|
||||
.property(
|
||||
string_tag,
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
"DateTimeFormat",
|
||||
date_time_format,
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Intl {
|
||||
/// `Intl.getCanonicalLocales ( locales )`
|
||||
///
|
||||
/// Returns an array containing the canonical locale names.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN docs][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-intl.getcanonicallocales
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/getCanonicalLocales
|
||||
pub(crate) fn get_canonical_locales(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let ll be ? CanonicalizeLocaleList(locales).
|
||||
let ll = canonicalize_locale_list(args, context)?;
|
||||
|
||||
// 2. Return CreateArrayFromList(ll).
|
||||
Ok(JsValue::Object(Array::create_array_from_list(
|
||||
ll.into_iter().map(|loc| loc.to_string().into()),
|
||||
context,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// `MatcherRecord` type aggregates unicode `locale` string and unicode locale `extension`.
|
||||
///
|
||||
/// This is a return value for `lookup_matcher` and `best_fit_matcher` subroutines.
|
||||
#[derive(Debug)]
|
||||
struct MatcherRecord {
|
||||
locale: JsString,
|
||||
extension: JsString,
|
||||
}
|
||||
|
||||
/// Abstract operation `DefaultLocale ( )`
|
||||
///
|
||||
/// Returns a String value representing the structurally valid and canonicalized
|
||||
/// Unicode BCP 47 locale identifier for the host environment's current locale.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-defaultlocale
|
||||
fn default_locale(canonicalizer: &LocaleCanonicalizer) -> Locale {
|
||||
sys_locale::get_locale()
|
||||
.and_then(|loc| loc.parse::<Locale>().ok())
|
||||
.tap_some_mut(|loc| canonicalize_unicode_locale_id(loc, canonicalizer))
|
||||
.unwrap_or(locale!("en-US"))
|
||||
}
|
||||
|
||||
/// Abstract operation `BestAvailableLocale ( availableLocales, locale )`
|
||||
///
|
||||
/// Compares the provided argument `locale`, which must be a String value with a
|
||||
/// structurally valid and canonicalized Unicode BCP 47 locale identifier, against
|
||||
/// the locales in `availableLocales` and returns either the longest non-empty prefix
|
||||
/// of `locale` that is an element of `availableLocales`, or undefined if there is no
|
||||
/// such element.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-bestavailablelocale
|
||||
fn best_available_locale(available_locales: &[JsString], locale: &JsString) -> Option<JsString> {
|
||||
// 1. Let candidate be locale.
|
||||
let mut candidate = locale.clone();
|
||||
// 2. Repeat
|
||||
loop {
|
||||
// a. If availableLocales contains an element equal to candidate, return candidate.
|
||||
if available_locales.contains(&candidate) {
|
||||
return Some(candidate);
|
||||
}
|
||||
|
||||
// b. Let pos be the character index of the last occurrence of "-" (U+002D) within candidate. If that character does not occur, return undefined.
|
||||
let pos = candidate.rfind('-');
|
||||
match pos {
|
||||
Some(ind) => {
|
||||
// c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, decrease pos by 2.
|
||||
let tmp_candidate = candidate[..ind].to_string();
|
||||
let prev_dash = tmp_candidate.rfind('-').unwrap_or(ind);
|
||||
let trim_ind = if ind >= 2 && prev_dash == ind - 2 {
|
||||
ind - 2
|
||||
} else {
|
||||
ind
|
||||
};
|
||||
// d. Let candidate be the substring of candidate from position 0, inclusive, to position pos, exclusive.
|
||||
candidate = JsString::new(&candidate[..trim_ind]);
|
||||
}
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract operation `LookupMatcher ( availableLocales, requestedLocales )`
|
||||
///
|
||||
/// Compares `requestedLocales`, which must be a `List` as returned by `CanonicalizeLocaleList`,
|
||||
/// against the locales in `availableLocales` and determines the best available language to
|
||||
/// meet the request.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-lookupmatcher
|
||||
fn lookup_matcher(
|
||||
available_locales: &[JsString],
|
||||
requested_locales: &[JsString],
|
||||
canonicalizer: &LocaleCanonicalizer,
|
||||
) -> MatcherRecord {
|
||||
// 1. Let result be a new Record.
|
||||
// 2. For each element locale of requestedLocales, do
|
||||
for locale_str in requested_locales {
|
||||
// a. Let noExtensionsLocale be the String value that is locale with any Unicode locale
|
||||
// extension sequences removed.
|
||||
let parsed_locale =
|
||||
Locale::from_bytes(locale_str.as_bytes()).expect("Locale parsing failed");
|
||||
let no_extensions_locale = JsString::new(parsed_locale.id.to_string());
|
||||
|
||||
// b. Let availableLocale be ! BestAvailableLocale(availableLocales, noExtensionsLocale).
|
||||
let available_locale = best_available_locale(available_locales, &no_extensions_locale);
|
||||
|
||||
// c. If availableLocale is not undefined, then
|
||||
if let Some(available_locale) = available_locale {
|
||||
// i. Set result.[[locale]] to availableLocale.
|
||||
// Assignment deferred. See return statement below.
|
||||
// ii. If locale and noExtensionsLocale are not the same String value, then
|
||||
let maybe_ext = if locale_str.eq(&no_extensions_locale) {
|
||||
JsString::empty()
|
||||
} else {
|
||||
// 1. Let extension be the String value consisting of the substring of the Unicode
|
||||
// locale extension sequence within locale.
|
||||
// 2. Set result.[[extension]] to extension.
|
||||
JsString::new(parsed_locale.extensions.to_string())
|
||||
};
|
||||
|
||||
// iii. Return result.
|
||||
return MatcherRecord {
|
||||
locale: available_locale,
|
||||
extension: maybe_ext,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Let defLocale be ! DefaultLocale().
|
||||
// 4. Set result.[[locale]] to defLocale.
|
||||
// 5. Return result.
|
||||
MatcherRecord {
|
||||
locale: default_locale(canonicalizer).to_string().into(),
|
||||
extension: JsString::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract operation `BestFitMatcher ( availableLocales, requestedLocales )`
|
||||
///
|
||||
/// Compares `requestedLocales`, which must be a `List` as returned by `CanonicalizeLocaleList`,
|
||||
/// against the locales in `availableLocales` and determines the best available language to
|
||||
/// meet the request. The algorithm is implementation dependent, but should produce results
|
||||
/// that a typical user of the requested locales would perceive as at least as good as those
|
||||
/// produced by the `LookupMatcher` abstract operation.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-bestfitmatcher
|
||||
fn best_fit_matcher(
|
||||
available_locales: &[JsString],
|
||||
requested_locales: &[JsString],
|
||||
canonicalizer: &LocaleCanonicalizer,
|
||||
) -> MatcherRecord {
|
||||
lookup_matcher(available_locales, requested_locales, canonicalizer)
|
||||
}
|
||||
|
||||
/// `Keyword` structure is a pair of keyword key and keyword value.
|
||||
#[derive(Debug)]
|
||||
struct Keyword {
|
||||
key: JsString,
|
||||
value: JsString,
|
||||
}
|
||||
|
||||
/// `UniExtRecord` structure represents unicode extension records.
|
||||
///
|
||||
/// It contains the list of unicode `extension` attributes and the list of `keywords`.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// - `-u-nu-thai` has no attributes and the list of keywords contains `(nu:thai)` pair.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
struct UniExtRecord {
|
||||
attributes: Vec<JsString>, // never read at this point
|
||||
keywords: Vec<Keyword>,
|
||||
}
|
||||
|
||||
/// Abstract operation `UnicodeExtensionComponents ( extension )`
|
||||
///
|
||||
/// Returns the attributes and keywords from `extension`, which must be a String
|
||||
/// value whose contents are a `Unicode locale extension` sequence.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-unicode-extension-components
|
||||
fn unicode_extension_components(extension: &JsString) -> UniExtRecord {
|
||||
// 1. Let attributes be a new empty List.
|
||||
let mut attributes = Vec::<JsString>::new();
|
||||
|
||||
// 2. Let keywords be a new empty List.
|
||||
let mut keywords = Vec::<Keyword>::new();
|
||||
|
||||
// 3. Let keyword be undefined.
|
||||
let mut keyword: Option<Keyword> = None;
|
||||
|
||||
// 4. Let size be the length of extension.
|
||||
let size = extension.len();
|
||||
|
||||
// 5. Let k be 3.
|
||||
let mut k = 3;
|
||||
|
||||
// 6. Repeat, while k < size,
|
||||
while k < size {
|
||||
// a. Let e be ! StringIndexOf(extension, "-", k).
|
||||
let e = extension.index_of(&JsString::new("-"), k);
|
||||
|
||||
// b. If e = -1, let len be size - k; else let len be e - k.
|
||||
let len = match e {
|
||||
Some(pos) => pos - k,
|
||||
None => size - k,
|
||||
};
|
||||
|
||||
// c. Let subtag be the String value equal to the substring of extension consisting of the
|
||||
// code units at indices k (inclusive) through k + len (exclusive).
|
||||
let subtag = JsString::new(&extension[k..k + len]);
|
||||
|
||||
// d. If keyword is undefined and len ≠ 2, then
|
||||
if keyword.is_none() && len != 2 {
|
||||
// i. If subtag is not an element of attributes, then
|
||||
if !attributes.contains(&subtag) {
|
||||
// 1. Append subtag to attributes.
|
||||
attributes.push(subtag);
|
||||
}
|
||||
// e. Else if len = 2, then
|
||||
} else if len == 2 {
|
||||
// i. If keyword is not undefined and keywords does not contain an element
|
||||
// whose [[Key]] is the same as keyword.[[Key]], then
|
||||
// 1. Append keyword to keywords.
|
||||
if let Some(keyword_val) = keyword {
|
||||
let has_key = keywords.iter().any(|elem| elem.key == keyword_val.key);
|
||||
if !has_key {
|
||||
keywords.push(keyword_val);
|
||||
}
|
||||
};
|
||||
|
||||
// ii. Set keyword to the Record { [[Key]]: subtag, [[Value]]: "" }.
|
||||
keyword = Some(Keyword {
|
||||
key: subtag,
|
||||
value: JsString::empty(),
|
||||
});
|
||||
// f. Else,
|
||||
} else {
|
||||
// i. If keyword.[[Value]] is the empty String, then
|
||||
// 1. Set keyword.[[Value]] to subtag.
|
||||
// ii. Else,
|
||||
// 1. Set keyword.[[Value]] to the string-concatenation of keyword.[[Value]], "-", and subtag.
|
||||
if let Some(keyword_val) = keyword {
|
||||
let new_keyword_val = if keyword_val.value.is_empty() {
|
||||
subtag
|
||||
} else {
|
||||
JsString::new(format!("{}-{subtag}", keyword_val.value))
|
||||
};
|
||||
|
||||
keyword = Some(Keyword {
|
||||
key: keyword_val.key,
|
||||
value: new_keyword_val,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// g. Let k be k + len + 1.
|
||||
k = k + len + 1;
|
||||
}
|
||||
|
||||
// 7. If keyword is not undefined and keywords does not contain an element whose [[Key]] is
|
||||
// the same as keyword.[[Key]], then
|
||||
// a. Append keyword to keywords.
|
||||
if let Some(keyword_val) = keyword {
|
||||
let has_key = keywords.iter().any(|elem| elem.key == keyword_val.key);
|
||||
if !has_key {
|
||||
keywords.push(keyword_val);
|
||||
}
|
||||
};
|
||||
|
||||
// 8. Return the Record { [[Attributes]]: attributes, [[Keywords]]: keywords }.
|
||||
UniExtRecord {
|
||||
attributes,
|
||||
keywords,
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract operation `InsertUnicodeExtensionAndCanonicalize ( locale, extension )`
|
||||
///
|
||||
/// Inserts `extension`, which must be a Unicode locale extension sequence, into
|
||||
/// `locale`, which must be a String value with a structurally valid and canonicalized
|
||||
/// Unicode BCP 47 locale identifier.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-insert-unicode-extension-and-canonicalize
|
||||
fn insert_unicode_extension_and_canonicalize(
|
||||
locale: &str,
|
||||
extension: &str,
|
||||
canonicalizer: &LocaleCanonicalizer,
|
||||
) -> JsString {
|
||||
// TODO 1. Assert: locale does not contain a substring that is a Unicode locale extension sequence.
|
||||
// TODO 2. Assert: extension is a Unicode locale extension sequence.
|
||||
// TODO 3. Assert: tag matches the unicode_locale_id production.
|
||||
// 4. Let privateIndex be ! StringIndexOf(locale, "-x-", 0).
|
||||
let private_index = locale.find("-x-");
|
||||
let new_locale = match private_index {
|
||||
// 5. If privateIndex = -1, then
|
||||
None => {
|
||||
// a. Let locale be the string-concatenation of locale and extension.
|
||||
locale.to_owned() + extension
|
||||
}
|
||||
// 6. Else,
|
||||
Some(idx) => {
|
||||
// a. Let preExtension be the substring of locale from position 0, inclusive,
|
||||
// to position privateIndex, exclusive.
|
||||
let pre_extension = &locale[0..idx];
|
||||
|
||||
// b. Let postExtension be the substring of locale from position privateIndex to
|
||||
// the end of the string.
|
||||
let post_extension = &locale[idx..];
|
||||
|
||||
// c. Let locale be the string-concatenation of preExtension, extension,
|
||||
// and postExtension.
|
||||
pre_extension.to_owned() + extension + post_extension
|
||||
}
|
||||
};
|
||||
|
||||
// 7. Assert: ! IsStructurallyValidLanguageTag(locale) is true.
|
||||
let mut new_locale = new_locale
|
||||
.parse()
|
||||
.expect("Assert: ! IsStructurallyValidLanguageTag(locale) is true.");
|
||||
|
||||
// 8. Return ! CanonicalizeUnicodeLocaleId(locale).
|
||||
canonicalize_unicode_locale_id(&mut new_locale, canonicalizer);
|
||||
new_locale.to_string().into()
|
||||
}
|
||||
|
||||
/// Abstract operation `CanonicalizeLocaleList ( locales )`
|
||||
///
|
||||
/// Converts an array of [`JsValue`]s containing structurally valid
|
||||
/// [Unicode BCP 47 locale identifiers][bcp-47] into their [canonical form][canon].
|
||||
///
|
||||
/// For efficiency, this returns a [`Vec`] of [`Locale`]s instead of a [`Vec`] of
|
||||
/// [`String`]s, since [`Locale`] allows us to modify individual parts of the locale
|
||||
/// without scanning the whole string again.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-canonicalizelocalelist
|
||||
/// [bcp-47]: https://unicode.org/reports/tr35/#Unicode_locale_identifier
|
||||
/// [canon]: https://unicode.org/reports/tr35/#LocaleId_Canonicalization
|
||||
fn canonicalize_locale_list(args: &[JsValue], context: &mut Context) -> JsResult<Vec<Locale>> {
|
||||
// 1. If locales is undefined, then
|
||||
let locales = args.get_or_undefined(0);
|
||||
if locales.is_undefined() {
|
||||
// a. Return a new empty List.
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
// 2. Let seen be a new empty List.
|
||||
let mut seen = IndexSet::new();
|
||||
|
||||
// 3. If Type(locales) is String or Type(locales) is Object and locales has an [[InitializedLocale]] internal slot, then
|
||||
// TODO: check if Type(locales) is object and handle the internal slots
|
||||
let o = if locales.is_string() {
|
||||
// a. Let O be CreateArrayFromList(« locales »).
|
||||
Array::create_array_from_list([locales.clone()], context)
|
||||
} else {
|
||||
// 4. Else,
|
||||
// a. Let O be ? ToObject(locales).
|
||||
locales.to_object(context)?
|
||||
};
|
||||
|
||||
// 5. Let len be ? ToLength(? Get(O, "length")).
|
||||
let len = o.length_of_array_like(context)?;
|
||||
|
||||
// 6 Let k be 0.
|
||||
// 7. Repeat, while k < len,
|
||||
for k in 0..len {
|
||||
// a. Let Pk be ToString(k).
|
||||
// b. Let kPresent be ? HasProperty(O, Pk).
|
||||
let k_present = o.has_property(k, context)?;
|
||||
// c. If kPresent is true, then
|
||||
if k_present {
|
||||
// i. Let kValue be ? Get(O, Pk).
|
||||
let k_value = o.get(k, context)?;
|
||||
// ii. If Type(kValue) is not String or Object, throw a TypeError exception.
|
||||
if !(k_value.is_object() || k_value.is_string()) {
|
||||
return context.throw_type_error("locale should be a String or Object");
|
||||
}
|
||||
// iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
|
||||
// TODO: handle checks for InitializedLocale internal slot (there should be an if statement here)
|
||||
// 1. Let tag be kValue.[[Locale]].
|
||||
// iv. Else,
|
||||
// 1. Let tag be ? ToString(kValue).
|
||||
let tag = k_value.to_string(context)?;
|
||||
// v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
|
||||
let mut tag = tag.parse().map_err(|_| {
|
||||
context.construct_range_error("locale is not a structurally valid language tag")
|
||||
})?;
|
||||
|
||||
// vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
|
||||
canonicalize_unicode_locale_id(&mut tag, &*context.icu().locale_canonicalizer());
|
||||
seen.insert(tag);
|
||||
// vii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
|
||||
}
|
||||
// d. Increase k by 1.
|
||||
}
|
||||
|
||||
// 8. Return seen.
|
||||
Ok(seen.into_iter().collect())
|
||||
}
|
||||
|
||||
/// `LocaleDataRecord` is the type of `locale_data` argument in `resolve_locale` subroutine.
|
||||
///
|
||||
/// It is an alias for a map where key is a string and value is another map.
|
||||
///
|
||||
/// Value of that inner map is a vector of strings representing locale parameters.
|
||||
type LocaleDataRecord = FxHashMap<JsString, FxHashMap<JsString, Vec<JsString>>>;
|
||||
|
||||
/// `DateTimeFormatRecord` type aggregates `locale_matcher` selector and `properties` map.
|
||||
///
|
||||
/// It is used as a type of `options` parameter in `resolve_locale` subroutine.
|
||||
#[derive(Debug)]
|
||||
struct DateTimeFormatRecord {
|
||||
pub(crate) locale_matcher: JsString,
|
||||
pub(crate) properties: FxHashMap<JsString, JsValue>,
|
||||
}
|
||||
|
||||
/// `ResolveLocaleRecord` type consists of unicode `locale` string, `data_locale` string and `properties` map.
|
||||
///
|
||||
/// This is a return value for `resolve_locale` subroutine.
|
||||
#[derive(Debug)]
|
||||
struct ResolveLocaleRecord {
|
||||
pub(crate) locale: JsString,
|
||||
pub(crate) properties: FxHashMap<JsString, JsValue>,
|
||||
pub(crate) data_locale: JsString,
|
||||
}
|
||||
|
||||
/// Abstract operation `ResolveLocale ( availableLocales, requestedLocales, options, relevantExtensionKeys, localeData )`
|
||||
///
|
||||
/// Compares a BCP 47 language priority list `requestedLocales` against the locales
|
||||
/// in `availableLocales` and determines the best available language to meet the request.
|
||||
/// `availableLocales`, `requestedLocales`, and `relevantExtensionKeys` must be provided as
|
||||
/// `List` values, options and `localeData` as Records.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-resolvelocale
|
||||
#[allow(dead_code)]
|
||||
fn resolve_locale(
|
||||
available_locales: &[JsString],
|
||||
requested_locales: &[JsString],
|
||||
options: &DateTimeFormatRecord,
|
||||
relevant_extension_keys: &[JsString],
|
||||
locale_data: &LocaleDataRecord,
|
||||
context: &mut Context,
|
||||
) -> ResolveLocaleRecord {
|
||||
// 1. Let matcher be options.[[localeMatcher]].
|
||||
let matcher = &options.locale_matcher;
|
||||
// 2. If matcher is "lookup", then
|
||||
// a. Let r be ! LookupMatcher(availableLocales, requestedLocales).
|
||||
// 3. Else,
|
||||
// a. Let r be ! BestFitMatcher(availableLocales, requestedLocales).
|
||||
let r = if matcher.eq(&JsString::new("lookup")) {
|
||||
lookup_matcher(
|
||||
available_locales,
|
||||
requested_locales,
|
||||
context.icu().locale_canonicalizer(),
|
||||
)
|
||||
} else {
|
||||
best_fit_matcher(
|
||||
available_locales,
|
||||
requested_locales,
|
||||
context.icu().locale_canonicalizer(),
|
||||
)
|
||||
};
|
||||
|
||||
// 4. Let foundLocale be r.[[locale]].
|
||||
let mut found_locale = r.locale;
|
||||
|
||||
// 5. Let result be a new Record.
|
||||
let mut result = ResolveLocaleRecord {
|
||||
locale: JsString::empty(),
|
||||
properties: FxHashMap::default(),
|
||||
data_locale: JsString::empty(),
|
||||
};
|
||||
|
||||
// 6. Set result.[[dataLocale]] to foundLocale.
|
||||
result.data_locale = found_locale.clone();
|
||||
|
||||
// 7. If r has an [[extension]] field, then
|
||||
let keywords = if r.extension.is_empty() {
|
||||
Vec::<Keyword>::new()
|
||||
} else {
|
||||
// a. Let components be ! UnicodeExtensionComponents(r.[[extension]]).
|
||||
let components = unicode_extension_components(&r.extension);
|
||||
// b. Let keywords be components.[[Keywords]].
|
||||
components.keywords
|
||||
};
|
||||
|
||||
// 8. Let supportedExtension be "-u".
|
||||
let mut supported_extension = JsString::new("-u");
|
||||
|
||||
// 9. For each element key of relevantExtensionKeys, do
|
||||
for key in relevant_extension_keys {
|
||||
// a. Let foundLocaleData be localeData.[[<foundLocale>]].
|
||||
// TODO b. Assert: Type(foundLocaleData) is Record.
|
||||
let found_locale_data = match locale_data.get(&found_locale) {
|
||||
Some(locale_value) => locale_value.clone(),
|
||||
None => FxHashMap::default(),
|
||||
};
|
||||
|
||||
// c. Let keyLocaleData be foundLocaleData.[[<key>]].
|
||||
// TODO d. Assert: Type(keyLocaleData) is List.
|
||||
let key_locale_data = match found_locale_data.get(key) {
|
||||
Some(locale_vec) => locale_vec.clone(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
// e. Let value be keyLocaleData[0].
|
||||
// TODO f. Assert: Type(value) is either String or Null.
|
||||
let mut value = match key_locale_data.get(0) {
|
||||
Some(first_elt) => JsValue::String(first_elt.clone()),
|
||||
None => JsValue::null(),
|
||||
};
|
||||
|
||||
// g. Let supportedExtensionAddition be "".
|
||||
let mut supported_extension_addition = JsString::empty();
|
||||
|
||||
// h. If r has an [[extension]] field, then
|
||||
if !r.extension.is_empty() {
|
||||
// i. If keywords contains an element whose [[Key]] is the same as key, then
|
||||
// 1. Let entry be the element of keywords whose [[Key]] is the same as key.
|
||||
let maybe_entry = keywords.iter().find(|elem| key.eq(&elem.key));
|
||||
if let Some(entry) = maybe_entry {
|
||||
// 2. Let requestedValue be entry.[[Value]].
|
||||
let requested_value = &entry.value;
|
||||
|
||||
// 3. If requestedValue is not the empty String, then
|
||||
if !requested_value.is_empty() {
|
||||
// a. If keyLocaleData contains requestedValue, then
|
||||
if key_locale_data.contains(requested_value) {
|
||||
// i. Let value be requestedValue.
|
||||
value = JsValue::String(JsString::new(requested_value));
|
||||
// ii. Let supportedExtensionAddition be the string-concatenation
|
||||
// of "-", key, "-", and value.
|
||||
supported_extension_addition =
|
||||
JsString::concat_array(&["-", key, "-", requested_value]);
|
||||
}
|
||||
// 4. Else if keyLocaleData contains "true", then
|
||||
} else if key_locale_data.contains(&JsString::new("true")) {
|
||||
// a. Let value be "true".
|
||||
value = JsValue::String(JsString::new("true"));
|
||||
// b. Let supportedExtensionAddition be the string-concatenation of "-" and key.
|
||||
supported_extension_addition = JsString::concat_array(&["-", key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// i. If options has a field [[<key>]], then
|
||||
if options.properties.contains_key(key) {
|
||||
// i. Let optionsValue be options.[[<key>]].
|
||||
// TODO ii. Assert: Type(optionsValue) is either String, Undefined, or Null.
|
||||
let mut options_value = options
|
||||
.properties
|
||||
.get(key)
|
||||
.unwrap_or(&JsValue::undefined())
|
||||
.clone();
|
||||
|
||||
// iii. If Type(optionsValue) is String, then
|
||||
if options_value.is_string() {
|
||||
// TODO 1. Let optionsValue be the string optionsValue after performing the
|
||||
// algorithm steps to transform Unicode extension values to canonical syntax
|
||||
// per Unicode Technical Standard #35 LDML § 3.2.1 Canonical Unicode Locale
|
||||
// Identifiers, treating key as ukey and optionsValue as uvalue productions.
|
||||
|
||||
// TODO 2. Let optionsValue be the string optionsValue after performing the
|
||||
// algorithm steps to replace Unicode extension values with their canonical
|
||||
// form per Unicode Technical Standard #35 LDML § 3.2.1 Canonical Unicode
|
||||
// Locale Identifiers, treating key as ukey and optionsValue as uvalue
|
||||
// productions.
|
||||
|
||||
// 3. If optionsValue is the empty String, then
|
||||
if let Some(options_val_str) = options_value.as_string() {
|
||||
if options_val_str.is_empty() {
|
||||
// a. Let optionsValue be "true".
|
||||
options_value = JsValue::String(JsString::new("true"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// iv. If keyLocaleData contains optionsValue, then
|
||||
let options_val_str = options_value
|
||||
.to_string(context)
|
||||
.unwrap_or_else(|_| JsString::empty());
|
||||
if key_locale_data.contains(&options_val_str) {
|
||||
// 1. If SameValue(optionsValue, value) is false, then
|
||||
if !options_value.eq(&value) {
|
||||
// a. Let value be optionsValue.
|
||||
value = options_value;
|
||||
|
||||
// b. Let supportedExtensionAddition be "".
|
||||
supported_extension_addition = JsString::empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// j. Set result.[[<key>]] to value.
|
||||
result.properties.insert(key.clone(), value);
|
||||
|
||||
// k. Append supportedExtensionAddition to supportedExtension.
|
||||
supported_extension = JsString::concat(supported_extension, &supported_extension_addition);
|
||||
}
|
||||
|
||||
// 10. If the number of elements in supportedExtension is greater than 2, then
|
||||
if supported_extension.len() > 2 {
|
||||
// a. Let foundLocale be InsertUnicodeExtensionAndCanonicalize(foundLocale, supportedExtension).
|
||||
found_locale = insert_unicode_extension_and_canonicalize(
|
||||
&found_locale,
|
||||
&supported_extension,
|
||||
context.icu().locale_canonicalizer(),
|
||||
);
|
||||
}
|
||||
|
||||
// 11. Set result.[[locale]] to foundLocale.
|
||||
result.locale = found_locale;
|
||||
|
||||
// 12. Return result.
|
||||
result
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) enum GetOptionType {
|
||||
String,
|
||||
Boolean,
|
||||
}
|
||||
|
||||
/// Abstract operation `GetOption ( options, property, type, values, fallback )`
|
||||
///
|
||||
/// Extracts the value of the property named `property` from the provided `options` object,
|
||||
/// converts it to the required `type`, checks whether it is one of a `List` of allowed
|
||||
/// `values`, and fills in a `fallback` value if necessary. If `values` is
|
||||
/// undefined, there is no fixed set of values and any is permitted.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-getoption
|
||||
#[allow(unused)]
|
||||
pub(crate) fn get_option(
|
||||
options: &JsObject,
|
||||
property: &str,
|
||||
r#type: &GetOptionType,
|
||||
values: &[JsString],
|
||||
fallback: &JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Assert: Type(options) is Object.
|
||||
// 2. Let value be ? Get(options, property).
|
||||
let mut value = options.get(property, context)?;
|
||||
|
||||
// 3. If value is undefined, return fallback.
|
||||
if value.is_undefined() {
|
||||
return Ok(fallback.clone());
|
||||
}
|
||||
|
||||
// 4. Assert: type is "boolean" or "string".
|
||||
// 5. If type is "boolean", then
|
||||
// a. Set value to ! ToBoolean(value).
|
||||
// 6. If type is "string", then
|
||||
// a. Set value to ? ToString(value).
|
||||
// 7. If values is not undefined and values does not contain an element equal to value,
|
||||
// throw a RangeError exception.
|
||||
value = match r#type {
|
||||
GetOptionType::Boolean => JsValue::Boolean(value.to_boolean()),
|
||||
GetOptionType::String => {
|
||||
let string_value = value.to_string(context)?;
|
||||
if !values.is_empty() && !values.contains(&string_value) {
|
||||
return context.throw_range_error("GetOption: values array does not contain value");
|
||||
}
|
||||
JsValue::String(string_value)
|
||||
}
|
||||
};
|
||||
|
||||
// 8. Return value.
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Abstract operation `GetNumberOption ( options, property, minimum, maximum, fallback )`
|
||||
///
|
||||
/// Extracts the value of the property named `property` from the provided `options`
|
||||
/// object, converts it to a `Number value`, checks whether it is in the allowed range,
|
||||
/// and fills in a `fallback` value if necessary.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-getnumberoption
|
||||
#[allow(unused)]
|
||||
pub(crate) fn get_number_option(
|
||||
options: &JsObject,
|
||||
property: &str,
|
||||
minimum: f64,
|
||||
maximum: f64,
|
||||
fallback: Option<f64>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Option<f64>> {
|
||||
// 1. Assert: Type(options) is Object.
|
||||
// 2. Let value be ? Get(options, property).
|
||||
let value = options.get(property, context)?;
|
||||
|
||||
// 3. Return ? DefaultNumberOption(value, minimum, maximum, fallback).
|
||||
default_number_option(&value, minimum, maximum, fallback, context)
|
||||
}
|
||||
|
||||
/// Abstract operation `DefaultNumberOption ( value, minimum, maximum, fallback )`
|
||||
///
|
||||
/// Converts `value` to a `Number value`, checks whether it is in the allowed range,
|
||||
/// and fills in a `fallback` value if necessary.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-defaultnumberoption
|
||||
#[allow(unused)]
|
||||
pub(crate) fn default_number_option(
|
||||
value: &JsValue,
|
||||
minimum: f64,
|
||||
maximum: f64,
|
||||
fallback: Option<f64>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Option<f64>> {
|
||||
// 1. If value is undefined, return fallback.
|
||||
if value.is_undefined() {
|
||||
return Ok(fallback);
|
||||
}
|
||||
|
||||
// 2. Set value to ? ToNumber(value).
|
||||
let value = value.to_number(context)?;
|
||||
|
||||
// 3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
|
||||
if value.is_nan() || value < minimum || value > maximum {
|
||||
return context.throw_range_error("DefaultNumberOption: value is out of range.");
|
||||
}
|
||||
|
||||
// 4. Return floor(value).
|
||||
Ok(Some(value.floor()))
|
||||
}
|
||||
|
||||
/// Abstract operation `CanonicalizeUnicodeLocaleId ( locale )`.
|
||||
///
|
||||
/// This function differs slightly from the specification by modifying in-place
|
||||
/// the provided [`Locale`] instead of creating a new canonicalized copy.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-canonicalizeunicodelocaleid
|
||||
fn canonicalize_unicode_locale_id(locale: &mut Locale, canonicalizer: &LocaleCanonicalizer) {
|
||||
canonicalizer.canonicalize(locale);
|
||||
}
|
||||
@@ -0,0 +1,545 @@
|
||||
use crate::{
|
||||
builtins::intl::date_time_format::{to_date_time_options, DateTimeReqs},
|
||||
builtins::intl::{
|
||||
best_available_locale, best_fit_matcher, default_locale, default_number_option,
|
||||
get_number_option, get_option, insert_unicode_extension_and_canonicalize, lookup_matcher,
|
||||
resolve_locale, unicode_extension_components, DateTimeFormatRecord, GetOptionType,
|
||||
},
|
||||
object::JsObject,
|
||||
Context, JsString, JsValue,
|
||||
};
|
||||
|
||||
use icu_locale_canonicalizer::LocaleCanonicalizer;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
#[test]
|
||||
fn best_avail_loc() {
|
||||
let no_extensions_locale = JsString::new("en-US");
|
||||
let available_locales = Vec::<JsString>::new();
|
||||
assert_eq!(
|
||||
best_available_locale(&available_locales, &no_extensions_locale,),
|
||||
None
|
||||
);
|
||||
|
||||
let no_extensions_locale = JsString::new("de-DE");
|
||||
let available_locales = vec![no_extensions_locale.clone()];
|
||||
assert_eq!(
|
||||
best_available_locale(&available_locales, &no_extensions_locale,),
|
||||
Some(no_extensions_locale)
|
||||
);
|
||||
|
||||
let locale_part = "fr".to_string();
|
||||
let no_extensions_locale = JsString::new(locale_part.clone() + "-CA");
|
||||
let available_locales = vec![JsString::new(locale_part.clone())];
|
||||
assert_eq!(
|
||||
best_available_locale(&available_locales, &no_extensions_locale,),
|
||||
Some(JsString::new(locale_part))
|
||||
);
|
||||
|
||||
let ja_kana_t = JsString::new("ja-Kana-JP-t");
|
||||
let ja_kana = JsString::new("ja-Kana-JP");
|
||||
let no_extensions_locale = JsString::new("ja-Kana-JP-t-it-latn-it");
|
||||
let available_locales = vec![ja_kana_t, ja_kana.clone()];
|
||||
assert_eq!(
|
||||
best_available_locale(&available_locales, &no_extensions_locale,),
|
||||
Some(ja_kana)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_match() {
|
||||
let provider = icu_testdata::get_provider();
|
||||
let canonicalizer =
|
||||
LocaleCanonicalizer::new(&provider).expect("Could not create canonicalizer");
|
||||
// available: [], requested: []
|
||||
let available_locales = Vec::<JsString>::new();
|
||||
let requested_locales = Vec::<JsString>::new();
|
||||
|
||||
let matcher = lookup_matcher(&available_locales, &requested_locales, &canonicalizer);
|
||||
assert_eq!(
|
||||
matcher.locale,
|
||||
default_locale(&canonicalizer).to_string().as_str()
|
||||
);
|
||||
assert_eq!(matcher.extension, "");
|
||||
|
||||
// available: [de-DE], requested: []
|
||||
let available_locales = vec![JsString::new("de-DE")];
|
||||
let requested_locales = Vec::<JsString>::new();
|
||||
|
||||
let matcher = lookup_matcher(&available_locales, &requested_locales, &canonicalizer);
|
||||
assert_eq!(
|
||||
matcher.locale,
|
||||
default_locale(&canonicalizer).to_string().as_str()
|
||||
);
|
||||
assert_eq!(matcher.extension, "");
|
||||
|
||||
// available: [fr-FR], requested: [fr-FR-u-hc-h12]
|
||||
let available_locales = vec![JsString::new("fr-FR")];
|
||||
let requested_locales = vec![JsString::new("fr-FR-u-hc-h12")];
|
||||
|
||||
let matcher = lookup_matcher(&available_locales, &requested_locales, &canonicalizer);
|
||||
assert_eq!(matcher.locale, "fr-FR");
|
||||
assert_eq!(matcher.extension, "u-hc-h12");
|
||||
|
||||
// available: [es-ES], requested: [es-ES]
|
||||
let available_locales = vec![JsString::new("es-ES")];
|
||||
let requested_locales = vec![JsString::new("es-ES")];
|
||||
|
||||
let matcher = best_fit_matcher(&available_locales, &requested_locales, &canonicalizer);
|
||||
assert_eq!(matcher.locale, "es-ES");
|
||||
assert_eq!(matcher.extension, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_unicode_ext() {
|
||||
let provider = icu_testdata::get_provider();
|
||||
let canonicalizer =
|
||||
LocaleCanonicalizer::new(&provider).expect("Could not create canonicalizer");
|
||||
let locale = JsString::new("hu-HU");
|
||||
let ext = JsString::empty();
|
||||
assert_eq!(
|
||||
insert_unicode_extension_and_canonicalize(&locale, &ext, &canonicalizer),
|
||||
locale
|
||||
);
|
||||
|
||||
let locale = JsString::new("hu-HU");
|
||||
let ext = JsString::new("-u-hc-h12");
|
||||
assert_eq!(
|
||||
insert_unicode_extension_and_canonicalize(&locale, &ext, &canonicalizer),
|
||||
JsString::new("hu-HU-u-hc-h12")
|
||||
);
|
||||
|
||||
let locale = JsString::new("hu-HU-x-PRIVATE");
|
||||
let ext = JsString::new("-u-hc-h12");
|
||||
assert_eq!(
|
||||
insert_unicode_extension_and_canonicalize(&locale, &ext, &canonicalizer),
|
||||
JsString::new("hu-HU-u-hc-h12-x-private")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uni_ext_comp() {
|
||||
let ext = JsString::new("-u-ca-japanese-hc-h12");
|
||||
let components = unicode_extension_components(&ext);
|
||||
assert!(components.attributes.is_empty());
|
||||
assert_eq!(components.keywords.len(), 2);
|
||||
assert_eq!(components.keywords[0].key, "ca");
|
||||
assert_eq!(components.keywords[0].value, "japanese");
|
||||
assert_eq!(components.keywords[1].key, "hc");
|
||||
assert_eq!(components.keywords[1].value, "h12");
|
||||
|
||||
let ext = JsString::new("-u-alias-co-phonebk-ka-shifted");
|
||||
let components = unicode_extension_components(&ext);
|
||||
assert_eq!(components.attributes, vec![JsString::new("alias")]);
|
||||
assert_eq!(components.keywords.len(), 2);
|
||||
assert_eq!(components.keywords[0].key, "co");
|
||||
assert_eq!(components.keywords[0].value, "phonebk");
|
||||
assert_eq!(components.keywords[1].key, "ka");
|
||||
assert_eq!(components.keywords[1].value, "shifted");
|
||||
|
||||
let ext = JsString::new("-u-ca-buddhist-kk-nu-thai");
|
||||
let components = unicode_extension_components(&ext);
|
||||
assert!(components.attributes.is_empty());
|
||||
assert_eq!(components.keywords.len(), 3);
|
||||
assert_eq!(components.keywords[0].key, "ca");
|
||||
assert_eq!(components.keywords[0].value, "buddhist");
|
||||
assert_eq!(components.keywords[1].key, "kk");
|
||||
assert_eq!(components.keywords[1].value, "");
|
||||
assert_eq!(components.keywords[2].key, "nu");
|
||||
assert_eq!(components.keywords[2].value, "thai");
|
||||
|
||||
let ext = JsString::new("-u-ca-islamic-civil");
|
||||
let components = unicode_extension_components(&ext);
|
||||
assert!(components.attributes.is_empty());
|
||||
assert_eq!(components.keywords.len(), 1);
|
||||
assert_eq!(components.keywords[0].key, "ca");
|
||||
assert_eq!(components.keywords[0].value, "islamic-civil");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locale_resolution() {
|
||||
let mut context = Context::default();
|
||||
|
||||
// test lookup
|
||||
let available_locales = Vec::<JsString>::new();
|
||||
let requested_locales = Vec::<JsString>::new();
|
||||
let relevant_extension_keys = Vec::<JsString>::new();
|
||||
let locale_data = FxHashMap::default();
|
||||
let options = DateTimeFormatRecord {
|
||||
locale_matcher: JsString::new("lookup"),
|
||||
properties: FxHashMap::default(),
|
||||
};
|
||||
|
||||
let locale_record = resolve_locale(
|
||||
&available_locales,
|
||||
&requested_locales,
|
||||
&options,
|
||||
&relevant_extension_keys,
|
||||
&locale_data,
|
||||
&mut context,
|
||||
);
|
||||
assert_eq!(
|
||||
locale_record.locale,
|
||||
default_locale(context.icu().locale_canonicalizer())
|
||||
.to_string()
|
||||
.as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
locale_record.data_locale,
|
||||
default_locale(context.icu().locale_canonicalizer())
|
||||
.to_string()
|
||||
.as_str()
|
||||
);
|
||||
assert!(locale_record.properties.is_empty());
|
||||
|
||||
// test best fit
|
||||
let available_locales = Vec::<JsString>::new();
|
||||
let requested_locales = Vec::<JsString>::new();
|
||||
let relevant_extension_keys = Vec::<JsString>::new();
|
||||
let locale_data = FxHashMap::default();
|
||||
let options = DateTimeFormatRecord {
|
||||
locale_matcher: JsString::new("best-fit"),
|
||||
properties: FxHashMap::default(),
|
||||
};
|
||||
|
||||
let locale_record = resolve_locale(
|
||||
&available_locales,
|
||||
&requested_locales,
|
||||
&options,
|
||||
&relevant_extension_keys,
|
||||
&locale_data,
|
||||
&mut context,
|
||||
);
|
||||
assert_eq!(
|
||||
locale_record.locale,
|
||||
default_locale(context.icu().locale_canonicalizer())
|
||||
.to_string()
|
||||
.as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
locale_record.data_locale,
|
||||
default_locale(context.icu().locale_canonicalizer())
|
||||
.to_string()
|
||||
.as_str()
|
||||
);
|
||||
assert!(locale_record.properties.is_empty());
|
||||
|
||||
// available: [es-ES], requested: [es-ES]
|
||||
let available_locales = vec![JsString::new("es-ES")];
|
||||
let requested_locales = vec![JsString::new("es-ES")];
|
||||
let relevant_extension_keys = Vec::<JsString>::new();
|
||||
let locale_data = FxHashMap::default();
|
||||
let options = DateTimeFormatRecord {
|
||||
locale_matcher: JsString::new("lookup"),
|
||||
properties: FxHashMap::default(),
|
||||
};
|
||||
|
||||
let locale_record = resolve_locale(
|
||||
&available_locales,
|
||||
&requested_locales,
|
||||
&options,
|
||||
&relevant_extension_keys,
|
||||
&locale_data,
|
||||
&mut context,
|
||||
);
|
||||
assert_eq!(locale_record.locale, "es-ES");
|
||||
assert_eq!(locale_record.data_locale, "es-ES");
|
||||
assert!(locale_record.properties.is_empty());
|
||||
|
||||
// available: [zh-CN], requested: []
|
||||
let available_locales = vec![JsString::new("zh-CN")];
|
||||
let requested_locales = Vec::<JsString>::new();
|
||||
let relevant_extension_keys = Vec::<JsString>::new();
|
||||
let locale_data = FxHashMap::default();
|
||||
let options = DateTimeFormatRecord {
|
||||
locale_matcher: JsString::new("lookup"),
|
||||
properties: FxHashMap::default(),
|
||||
};
|
||||
|
||||
let locale_record = resolve_locale(
|
||||
&available_locales,
|
||||
&requested_locales,
|
||||
&options,
|
||||
&relevant_extension_keys,
|
||||
&locale_data,
|
||||
&mut context,
|
||||
);
|
||||
assert_eq!(
|
||||
locale_record.locale,
|
||||
default_locale(context.icu().locale_canonicalizer())
|
||||
.to_string()
|
||||
.as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
locale_record.data_locale,
|
||||
default_locale(context.icu().locale_canonicalizer())
|
||||
.to_string()
|
||||
.as_str()
|
||||
);
|
||||
assert!(locale_record.properties.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_opt() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let values = Vec::<JsString>::new();
|
||||
let fallback = JsValue::String(JsString::new("fallback"));
|
||||
let options_obj = JsObject::empty();
|
||||
let option_type = GetOptionType::String;
|
||||
let get_option_result = get_option(
|
||||
&options_obj,
|
||||
"",
|
||||
&option_type,
|
||||
&values,
|
||||
&fallback,
|
||||
&mut context,
|
||||
)
|
||||
.expect("GetOption should not fail on fallback test");
|
||||
assert_eq!(get_option_result, fallback);
|
||||
|
||||
let values = Vec::<JsString>::new();
|
||||
let fallback = JsValue::String(JsString::new("fallback"));
|
||||
let options_obj = JsObject::empty();
|
||||
let locale_value = JsValue::String(JsString::new("en-US"));
|
||||
options_obj
|
||||
.set("Locale", locale_value.clone(), true, &mut context)
|
||||
.expect("Setting a property should not fail");
|
||||
let option_type = GetOptionType::String;
|
||||
let get_option_result = get_option(
|
||||
&options_obj,
|
||||
"Locale",
|
||||
&option_type,
|
||||
&values,
|
||||
&fallback,
|
||||
&mut context,
|
||||
)
|
||||
.expect("GetOption should not fail on string test");
|
||||
assert_eq!(get_option_result, locale_value);
|
||||
|
||||
let fallback = JsValue::String(JsString::new("fallback"));
|
||||
let options_obj = JsObject::empty();
|
||||
let locale_string = JsString::new("en-US");
|
||||
let locale_value = JsValue::String(locale_string.clone());
|
||||
let values = vec![locale_string];
|
||||
options_obj
|
||||
.set("Locale", locale_value.clone(), true, &mut context)
|
||||
.expect("Setting a property should not fail");
|
||||
let option_type = GetOptionType::String;
|
||||
let get_option_result = get_option(
|
||||
&options_obj,
|
||||
"Locale",
|
||||
&option_type,
|
||||
&values,
|
||||
&fallback,
|
||||
&mut context,
|
||||
)
|
||||
.expect("GetOption should not fail on values test");
|
||||
assert_eq!(get_option_result, locale_value);
|
||||
|
||||
let fallback = JsValue::new(false);
|
||||
let options_obj = JsObject::empty();
|
||||
let boolean_value = JsValue::new(true);
|
||||
let values = Vec::<JsString>::new();
|
||||
options_obj
|
||||
.set("boolean_val", boolean_value.clone(), true, &mut context)
|
||||
.expect("Setting a property should not fail");
|
||||
let option_type = GetOptionType::Boolean;
|
||||
let get_option_result = get_option(
|
||||
&options_obj,
|
||||
"boolean_val",
|
||||
&option_type,
|
||||
&values,
|
||||
&fallback,
|
||||
&mut context,
|
||||
)
|
||||
.expect("GetOption should not fail on boolean test");
|
||||
assert_eq!(get_option_result, boolean_value);
|
||||
|
||||
let fallback = JsValue::String(JsString::new("fallback"));
|
||||
let options_obj = JsObject::empty();
|
||||
let locale_value = JsValue::String(JsString::new("en-US"));
|
||||
let other_locale_str = JsString::new("de-DE");
|
||||
let values = vec![other_locale_str];
|
||||
options_obj
|
||||
.set("Locale", locale_value, true, &mut context)
|
||||
.expect("Setting a property should not fail");
|
||||
let option_type = GetOptionType::String;
|
||||
let get_option_result = get_option(
|
||||
&options_obj,
|
||||
"Locale",
|
||||
&option_type,
|
||||
&values,
|
||||
&fallback,
|
||||
&mut context,
|
||||
);
|
||||
assert!(get_option_result.is_err());
|
||||
|
||||
let value = JsValue::undefined();
|
||||
let minimum = 1.0;
|
||||
let maximum = 10.0;
|
||||
let fallback_val = 5.0;
|
||||
let fallback = Some(fallback_val);
|
||||
let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context);
|
||||
assert_eq!(get_option_result, Ok(fallback));
|
||||
|
||||
let value = JsValue::nan();
|
||||
let minimum = 1.0;
|
||||
let maximum = 10.0;
|
||||
let fallback = Some(5.0);
|
||||
let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context);
|
||||
assert!(get_option_result.is_err());
|
||||
|
||||
let value = JsValue::new(0);
|
||||
let minimum = 1.0;
|
||||
let maximum = 10.0;
|
||||
let fallback = Some(5.0);
|
||||
let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context);
|
||||
assert!(get_option_result.is_err());
|
||||
|
||||
let value = JsValue::new(11);
|
||||
let minimum = 1.0;
|
||||
let maximum = 10.0;
|
||||
let fallback = Some(5.0);
|
||||
let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context);
|
||||
assert!(get_option_result.is_err());
|
||||
|
||||
let value_f64 = 7.0;
|
||||
let value = JsValue::new(value_f64);
|
||||
let minimum = 1.0;
|
||||
let maximum = 10.0;
|
||||
let fallback = Some(5.0);
|
||||
let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context);
|
||||
assert_eq!(get_option_result, Ok(Some(value_f64)));
|
||||
|
||||
let options = JsObject::empty();
|
||||
let property = "fractionalSecondDigits";
|
||||
let minimum = 1.0;
|
||||
let maximum = 10.0;
|
||||
let fallback_val = 5.0;
|
||||
let fallback = Some(fallback_val);
|
||||
let get_option_result =
|
||||
get_number_option(&options, property, minimum, maximum, fallback, &mut context);
|
||||
assert_eq!(get_option_result, Ok(fallback));
|
||||
|
||||
let options = JsObject::empty();
|
||||
let value_f64 = 8.0;
|
||||
let value = JsValue::new(value_f64);
|
||||
let property = "fractionalSecondDigits";
|
||||
options
|
||||
.set(property, value, true, &mut context)
|
||||
.expect("Setting a property should not fail");
|
||||
let minimum = 1.0;
|
||||
let maximum = 10.0;
|
||||
let fallback = Some(5.0);
|
||||
let get_option_result =
|
||||
get_number_option(&options, property, minimum, maximum, fallback, &mut context);
|
||||
assert_eq!(get_option_result, Ok(Some(value_f64)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_date_time_opts() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let options_obj = JsObject::empty();
|
||||
options_obj
|
||||
.set("timeStyle", JsObject::empty(), true, &mut context)
|
||||
.expect("Setting a property should not fail");
|
||||
let date_time_opts = to_date_time_options(
|
||||
&JsValue::new(options_obj),
|
||||
&DateTimeReqs::Date,
|
||||
&DateTimeReqs::Date,
|
||||
&mut context,
|
||||
);
|
||||
assert!(date_time_opts.is_err());
|
||||
|
||||
let options_obj = JsObject::empty();
|
||||
options_obj
|
||||
.set("dateStyle", JsObject::empty(), true, &mut context)
|
||||
.expect("Setting a property should not fail");
|
||||
let date_time_opts = to_date_time_options(
|
||||
&JsValue::new(options_obj),
|
||||
&DateTimeReqs::Time,
|
||||
&DateTimeReqs::Time,
|
||||
&mut context,
|
||||
);
|
||||
assert!(date_time_opts.is_err());
|
||||
|
||||
let date_time_opts = to_date_time_options(
|
||||
&JsValue::undefined(),
|
||||
&DateTimeReqs::Date,
|
||||
&DateTimeReqs::Date,
|
||||
&mut context,
|
||||
)
|
||||
.expect("toDateTimeOptions should not fail in date test");
|
||||
|
||||
let numeric_jsstring = JsValue::String(JsString::new("numeric"));
|
||||
assert_eq!(
|
||||
date_time_opts.get("year", &mut context),
|
||||
Ok(numeric_jsstring.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
date_time_opts.get("month", &mut context),
|
||||
Ok(numeric_jsstring.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
date_time_opts.get("day", &mut context),
|
||||
Ok(numeric_jsstring)
|
||||
);
|
||||
|
||||
let date_time_opts = to_date_time_options(
|
||||
&JsValue::undefined(),
|
||||
&DateTimeReqs::Time,
|
||||
&DateTimeReqs::Time,
|
||||
&mut context,
|
||||
)
|
||||
.expect("toDateTimeOptions should not fail in time test");
|
||||
|
||||
let numeric_jsstring = JsValue::String(JsString::new("numeric"));
|
||||
assert_eq!(
|
||||
date_time_opts.get("hour", &mut context),
|
||||
Ok(numeric_jsstring.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
date_time_opts.get("minute", &mut context),
|
||||
Ok(numeric_jsstring.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
date_time_opts.get("second", &mut context),
|
||||
Ok(numeric_jsstring)
|
||||
);
|
||||
|
||||
let date_time_opts = to_date_time_options(
|
||||
&JsValue::undefined(),
|
||||
&DateTimeReqs::AnyAll,
|
||||
&DateTimeReqs::AnyAll,
|
||||
&mut context,
|
||||
)
|
||||
.expect("toDateTimeOptions should not fail when testing required = 'any'");
|
||||
|
||||
let numeric_jsstring = JsValue::String(JsString::new("numeric"));
|
||||
assert_eq!(
|
||||
date_time_opts.get("year", &mut context),
|
||||
Ok(numeric_jsstring.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
date_time_opts.get("month", &mut context),
|
||||
Ok(numeric_jsstring.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
date_time_opts.get("day", &mut context),
|
||||
Ok(numeric_jsstring.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
date_time_opts.get("hour", &mut context),
|
||||
Ok(numeric_jsstring.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
date_time_opts.get("minute", &mut context),
|
||||
Ok(numeric_jsstring.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
date_time_opts.get("second", &mut context),
|
||||
Ok(numeric_jsstring)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
use crate::{
|
||||
builtins::{
|
||||
regexp::regexp_string_iterator::RegExpStringIterator,
|
||||
string::string_iterator::StringIterator, ArrayIterator, ForInIterator, MapIterator,
|
||||
SetIterator,
|
||||
},
|
||||
object::{JsObject, ObjectInitializer},
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IteratorPrototypes {
|
||||
/// %IteratorPrototype%
|
||||
iterator_prototype: JsObject,
|
||||
/// %MapIteratorPrototype%
|
||||
array_iterator: JsObject,
|
||||
/// %SetIteratorPrototype%
|
||||
set_iterator: JsObject,
|
||||
/// %StringIteratorPrototype%
|
||||
string_iterator: JsObject,
|
||||
/// %RegExpStringIteratorPrototype%
|
||||
regexp_string_iterator: JsObject,
|
||||
/// %MapIteratorPrototype%
|
||||
map_iterator: JsObject,
|
||||
/// %ForInIteratorPrototype%
|
||||
for_in_iterator: JsObject,
|
||||
}
|
||||
|
||||
impl IteratorPrototypes {
|
||||
pub(crate) fn init(context: &mut Context) -> Self {
|
||||
let _timer = Profiler::global().start_event("IteratorPrototypes::init", "init");
|
||||
|
||||
let iterator_prototype = create_iterator_prototype(context);
|
||||
Self {
|
||||
array_iterator: ArrayIterator::create_prototype(iterator_prototype.clone(), context),
|
||||
set_iterator: SetIterator::create_prototype(iterator_prototype.clone(), context),
|
||||
string_iterator: StringIterator::create_prototype(iterator_prototype.clone(), context),
|
||||
regexp_string_iterator: RegExpStringIterator::create_prototype(
|
||||
iterator_prototype.clone(),
|
||||
context,
|
||||
),
|
||||
map_iterator: MapIterator::create_prototype(iterator_prototype.clone(), context),
|
||||
for_in_iterator: ForInIterator::create_prototype(iterator_prototype.clone(), context),
|
||||
iterator_prototype,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn array_iterator(&self) -> JsObject {
|
||||
self.array_iterator.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn iterator_prototype(&self) -> JsObject {
|
||||
self.iterator_prototype.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_iterator(&self) -> JsObject {
|
||||
self.set_iterator.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn string_iterator(&self) -> JsObject {
|
||||
self.string_iterator.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn regexp_string_iterator(&self) -> JsObject {
|
||||
self.regexp_string_iterator.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map_iterator(&self) -> JsObject {
|
||||
self.map_iterator.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn for_in_iterator(&self) -> JsObject {
|
||||
self.for_in_iterator.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// `CreateIterResultObject( value, done )`
|
||||
///
|
||||
/// Generates an object supporting the `IteratorResult` interface.
|
||||
#[inline]
|
||||
pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Context) -> JsValue {
|
||||
let _timer = Profiler::global().start_event("create_iter_result_object", "init");
|
||||
|
||||
// 1. Assert: Type(done) is Boolean.
|
||||
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
|
||||
let obj = context.construct_object();
|
||||
|
||||
// 3. Perform ! CreateDataPropertyOrThrow(obj, "value", value).
|
||||
obj.create_data_property_or_throw("value", value, context)
|
||||
.expect("this CreateDataPropertyOrThrow call must not fail");
|
||||
// 4. Perform ! CreateDataPropertyOrThrow(obj, "done", done).
|
||||
obj.create_data_property_or_throw("done", done, context)
|
||||
.expect("this CreateDataPropertyOrThrow call must not fail");
|
||||
// 5. Return obj.
|
||||
obj.into()
|
||||
}
|
||||
|
||||
/// Iterator hint for `GetIterator`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum IteratorHint {
|
||||
Sync,
|
||||
Async,
|
||||
}
|
||||
|
||||
impl JsValue {
|
||||
/// `GetIterator ( obj [ , hint [ , method ] ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-getiterator
|
||||
#[inline]
|
||||
pub fn get_iterator(
|
||||
&self,
|
||||
context: &mut Context,
|
||||
hint: Option<IteratorHint>,
|
||||
method: Option<Self>,
|
||||
) -> JsResult<IteratorRecord> {
|
||||
// 1. If hint is not present, set hint to sync.
|
||||
let hint = hint.unwrap_or(IteratorHint::Sync);
|
||||
|
||||
// 2. If method is not present, then
|
||||
let method = if let Some(method) = method {
|
||||
method
|
||||
} else {
|
||||
// a. If hint is async, then
|
||||
if hint == IteratorHint::Async {
|
||||
// i. Set method to ? GetMethod(obj, @@asyncIterator).
|
||||
if let Some(method) =
|
||||
self.get_method(WellKnownSymbols::async_iterator(), context)?
|
||||
{
|
||||
method.into()
|
||||
} else {
|
||||
// ii. If method is undefined, then
|
||||
// 1. Let syncMethod be ? GetMethod(obj, @@iterator).
|
||||
let sync_method = self
|
||||
.get_method(WellKnownSymbols::iterator(), context)?
|
||||
.map_or(Self::Undefined, Self::from);
|
||||
// 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod).
|
||||
let _sync_iterator_record =
|
||||
self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method));
|
||||
// 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord).
|
||||
todo!("CreateAsyncFromSyncIterator");
|
||||
}
|
||||
} else {
|
||||
// b. Otherwise, set method to ? GetMethod(obj, @@iterator).
|
||||
self.get_method(WellKnownSymbols::iterator(), context)?
|
||||
.map_or(Self::Undefined, Self::from)
|
||||
}
|
||||
};
|
||||
|
||||
// 3. Let iterator be ? Call(method, obj).
|
||||
let iterator = context.call(&method, self, &[])?;
|
||||
|
||||
// 4. If Type(iterator) is not Object, throw a TypeError exception.
|
||||
let iterator_obj = iterator
|
||||
.as_object()
|
||||
.ok_or_else(|| context.construct_type_error("the iterator is not an object"))?;
|
||||
|
||||
// 5. Let nextMethod be ? GetV(iterator, "next").
|
||||
let next_method = iterator.get_v("next", context)?;
|
||||
|
||||
// 6. Let iteratorRecord be the Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
|
||||
// 7. Return iteratorRecord.
|
||||
Ok(IteratorRecord::new(
|
||||
iterator_obj.clone(),
|
||||
next_method,
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the `%IteratorPrototype%` object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%iteratorprototype%-object
|
||||
#[inline]
|
||||
fn create_iterator_prototype(context: &mut Context) -> JsObject {
|
||||
let _timer = Profiler::global().start_event("Iterator Prototype", "init");
|
||||
|
||||
let symbol_iterator = WellKnownSymbols::iterator();
|
||||
let iterator_prototype = ObjectInitializer::new(context)
|
||||
.function(
|
||||
|v, _, _| Ok(v.clone()),
|
||||
(symbol_iterator, "[Symbol.iterator]"),
|
||||
0,
|
||||
)
|
||||
.build();
|
||||
iterator_prototype
|
||||
}
|
||||
|
||||
/// The result of the iteration process.
|
||||
#[derive(Debug)]
|
||||
pub struct IteratorResult {
|
||||
object: JsObject,
|
||||
}
|
||||
|
||||
impl IteratorResult {
|
||||
/// `IteratorComplete ( iterResult )`
|
||||
///
|
||||
/// The abstract operation `IteratorComplete` takes argument `iterResult` (an `Object`) and
|
||||
/// returns either a normal completion containing a `Boolean` or a throw completion.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-iteratorcomplete
|
||||
#[inline]
|
||||
pub fn complete(&self, context: &mut Context) -> JsResult<bool> {
|
||||
// 1. Return ToBoolean(? Get(iterResult, "done")).
|
||||
Ok(self.object.get("done", context)?.to_boolean())
|
||||
}
|
||||
|
||||
/// `IteratorValue ( iterResult )`
|
||||
///
|
||||
/// The abstract operation `IteratorValue` takes argument `iterResult` (an `Object`) and
|
||||
/// returns either a normal completion containing an ECMAScript language value or a throw
|
||||
/// completion.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-iteratorvalue
|
||||
#[inline]
|
||||
pub fn value(&self, context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Return ? Get(iterResult, "value").
|
||||
self.object.get("value", context)
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator Record
|
||||
///
|
||||
/// An Iterator Record is a Record value used to encapsulate an
|
||||
/// `Iterator` or `AsyncIterator` along with the `next` method.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-iterator-records
|
||||
#[derive(Debug)]
|
||||
pub struct IteratorRecord {
|
||||
/// `[[Iterator]]`
|
||||
///
|
||||
/// An object that conforms to the `Iterator` or `AsyncIterator` interface.
|
||||
iterator: JsObject,
|
||||
|
||||
/// `[[NextMethod]]`
|
||||
///
|
||||
/// The `next` method of the `[[Iterator]]` object.
|
||||
next_method: JsValue,
|
||||
|
||||
/// `[[Done]]`
|
||||
///
|
||||
/// Whether the iterator has been closed.
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl IteratorRecord {
|
||||
/// Creates a new `IteratorRecord` with the given iterator object, next method and `done` flag.
|
||||
#[inline]
|
||||
pub fn new(iterator: JsObject, next_method: JsValue, done: bool) -> Self {
|
||||
Self {
|
||||
iterator,
|
||||
next_method,
|
||||
done,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `[[Iterator]]` field of the `IteratorRecord`.
|
||||
#[inline]
|
||||
pub(crate) fn iterator(&self) -> &JsObject {
|
||||
&self.iterator
|
||||
}
|
||||
|
||||
/// Get the `[[NextMethod]]` field of the `IteratorRecord`.
|
||||
#[inline]
|
||||
pub(crate) fn next_method(&self) -> &JsValue {
|
||||
&self.next_method
|
||||
}
|
||||
|
||||
/// Get the `[[Done]]` field of the `IteratorRecord`.
|
||||
#[inline]
|
||||
pub(crate) fn done(&self) -> bool {
|
||||
self.done
|
||||
}
|
||||
|
||||
/// Sets the `[[Done]]` field of the `IteratorRecord`.
|
||||
#[inline]
|
||||
pub(crate) fn set_done(&mut self, done: bool) {
|
||||
self.done = done;
|
||||
}
|
||||
|
||||
/// `IteratorNext ( iteratorRecord [ , value ] )`
|
||||
///
|
||||
/// The abstract operation `IteratorNext` takes argument `iteratorRecord` (an `Iterator`
|
||||
/// Record) and optional argument `value` (an ECMAScript language value) and returns either a
|
||||
/// normal completion containing an `Object` or a throw completion.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-iteratornext
|
||||
#[inline]
|
||||
pub(crate) fn next(
|
||||
&self,
|
||||
value: Option<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<IteratorResult> {
|
||||
let _timer = Profiler::global().start_event("IteratorRecord::next", "iterator");
|
||||
|
||||
// Note: We check if iteratorRecord.[[NextMethod]] is callable here.
|
||||
// This check would happen in `Call` according to the spec, but we do not implement call for `JsValue`.
|
||||
let next_method = if let Some(next_method) = self.next_method.as_callable() {
|
||||
next_method
|
||||
} else {
|
||||
return context.throw_type_error("iterable next method not a function");
|
||||
};
|
||||
|
||||
let result = if let Some(value) = value {
|
||||
// 2. Else,
|
||||
// a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »).
|
||||
next_method.call(&self.iterator.clone().into(), &[value], context)?
|
||||
} else {
|
||||
// 1. If value is not present, then
|
||||
// a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
|
||||
next_method.call(&self.iterator.clone().into(), &[], context)?
|
||||
};
|
||||
|
||||
// 3. If Type(result) is not Object, throw a TypeError exception.
|
||||
// 4. Return result.
|
||||
if let Some(o) = result.as_object() {
|
||||
Ok(IteratorResult { object: o.clone() })
|
||||
} else {
|
||||
context.throw_type_error("next value should be an object")
|
||||
}
|
||||
}
|
||||
|
||||
/// `IteratorStep ( iteratorRecord )`
|
||||
///
|
||||
/// The abstract operation `IteratorStep` takes argument `iteratorRecord` (an `Iterator`
|
||||
/// Record) and returns either a normal completion containing either an `Object` or `false`, or
|
||||
/// a throw completion. It requests the next value from `iteratorRecord.[[Iterator]]` by
|
||||
/// calling `iteratorRecord.[[NextMethod]]` and returns either `false` indicating that the
|
||||
/// iterator has reached its end or the `IteratorResult` object if a next value is available.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-iteratorstep
|
||||
pub(crate) fn step(&self, context: &mut Context) -> JsResult<Option<IteratorResult>> {
|
||||
let _timer = Profiler::global().start_event("IteratorRecord::step", "iterator");
|
||||
|
||||
// 1. Let result be ? IteratorNext(iteratorRecord).
|
||||
let result = self.next(None, context)?;
|
||||
|
||||
// 2. Let done be ? IteratorComplete(result).
|
||||
let done = result.complete(context)?;
|
||||
|
||||
// 3. If done is true, return false.
|
||||
if done {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 4. Return result.
|
||||
Ok(Some(result))
|
||||
}
|
||||
|
||||
/// `IteratorClose ( iteratorRecord, completion )`
|
||||
///
|
||||
/// The abstract operation `IteratorClose` takes arguments `iteratorRecord` (an
|
||||
/// [Iterator Record][Self]) and `completion` (a `Completion` Record) and returns a
|
||||
/// `Completion` Record. It is used to notify an iterator that it should perform any actions it
|
||||
/// would normally perform when it has reached its completed state.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-iteratorclose
|
||||
#[inline]
|
||||
pub(crate) fn close(
|
||||
&self,
|
||||
completion: JsResult<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let _timer = Profiler::global().start_event("IteratorRecord::close", "iterator");
|
||||
|
||||
// 1. Assert: Type(iteratorRecord.[[Iterator]]) is Object.
|
||||
|
||||
// 2. Let iterator be iteratorRecord.[[Iterator]].
|
||||
let iterator = &self.iterator;
|
||||
|
||||
// 3. Let innerResult be Completion(GetMethod(iterator, "return")).
|
||||
let inner_result = iterator.get_method("return", context);
|
||||
|
||||
// 4. If innerResult.[[Type]] is normal, then
|
||||
let inner_result = match inner_result {
|
||||
Ok(inner_result) => {
|
||||
// a. Let return be innerResult.[[Value]].
|
||||
let r#return = inner_result;
|
||||
|
||||
if let Some(r#return) = r#return {
|
||||
// c. Set innerResult to Completion(Call(return, iterator)).
|
||||
r#return.call(&iterator.clone().into(), &[], context)
|
||||
} else {
|
||||
// b. If return is undefined, return ? completion.
|
||||
return completion;
|
||||
}
|
||||
}
|
||||
Err(inner_result) => {
|
||||
// 5. If completion.[[Type]] is throw, return ? completion.
|
||||
completion?;
|
||||
|
||||
// 6. If innerResult.[[Type]] is throw, return ? innerResult.
|
||||
return Err(inner_result);
|
||||
}
|
||||
};
|
||||
|
||||
// 5. If completion.[[Type]] is throw, return ? completion.
|
||||
let completion = completion?;
|
||||
|
||||
// 6. If innerResult.[[Type]] is throw, return ? innerResult.
|
||||
let inner_result = inner_result?;
|
||||
|
||||
if inner_result.is_object() {
|
||||
// 8. Return ? completion.
|
||||
Ok(completion)
|
||||
} else {
|
||||
// 7. If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception.
|
||||
context.throw_type_error("inner result was not an object")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `IterableToList ( items [ , method ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-iterabletolist
|
||||
pub(crate) fn iterable_to_list(
|
||||
context: &mut Context,
|
||||
items: &JsValue,
|
||||
method: Option<JsValue>,
|
||||
) -> JsResult<Vec<JsValue>> {
|
||||
let _timer = Profiler::global().start_event("iterable_to_list", "iterator");
|
||||
|
||||
// 1. If method is present, then
|
||||
let iterator_record = if let Some(method) = method {
|
||||
// a. Let iteratorRecord be ? GetIterator(items, sync, method).
|
||||
items.get_iterator(context, Some(IteratorHint::Sync), Some(method))?
|
||||
} else {
|
||||
// 2. Else,
|
||||
|
||||
// a. Let iteratorRecord be ? GetIterator(items, sync).
|
||||
items.get_iterator(context, Some(IteratorHint::Sync), None)?
|
||||
};
|
||||
|
||||
// 3. Let values be a new empty List.
|
||||
let mut values = Vec::new();
|
||||
|
||||
// 4. Let next be true.
|
||||
// 5. Repeat, while next is not false,
|
||||
// a. Set next to ? IteratorStep(iteratorRecord).
|
||||
// b. If next is not false, then
|
||||
// i. Let nextValue be ? IteratorValue(next).
|
||||
// ii. Append nextValue to the end of the List values.
|
||||
while let Some(next) = iterator_record.step(context)? {
|
||||
let next_value = next.value(context)?;
|
||||
values.push(next_value);
|
||||
}
|
||||
|
||||
// 6. Return values.
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
/// `IfAbruptCloseIterator ( value, iteratorRecord )`
|
||||
///
|
||||
/// `IfAbruptCloseIterator` is a shorthand for a sequence of algorithm steps that use an `Iterator`
|
||||
/// Record.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ifabruptcloseiterator
|
||||
macro_rules! if_abrupt_close_iterator {
|
||||
($value:expr, $iterator_record:expr, $context:expr) => {
|
||||
match $value {
|
||||
// 1. If value is an abrupt completion, return ? IteratorClose(iteratorRecord, value).
|
||||
Err(err) => return $iterator_record.close(Err(err), $context),
|
||||
// 2. Else if value is a Completion Record, set value to value.
|
||||
Ok(value) => value,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Export macro to crate level
|
||||
pub(crate) use if_abrupt_close_iterator;
|
||||
@@ -0,0 +1,739 @@
|
||||
//! This module implements the global `JSON` object.
|
||||
//!
|
||||
//! The `JSON` object contains methods for parsing [JavaScript Object Notation (JSON)][spec]
|
||||
//! and converting values to JSON. It can't be called or constructed, and aside from its
|
||||
//! two method properties, it has no interesting functionality of its own.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [JSON specification][json]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-json
|
||||
//! [json]: https://www.json.org/json-en.html
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
|
||||
|
||||
use super::JsArgs;
|
||||
use crate::{
|
||||
builtins::{
|
||||
string::{is_leading_surrogate, is_trailing_surrogate},
|
||||
BuiltIn,
|
||||
},
|
||||
object::{JsObject, ObjectInitializer, RecursionLimiter},
|
||||
property::{Attribute, PropertyNameKind},
|
||||
symbol::WellKnownSymbols,
|
||||
value::IntegerOrInfinity,
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use serde_json::{self, Value as JSONValue};
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// JavaScript `JSON` global object.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) struct Json;
|
||||
|
||||
impl BuiltIn for Json {
|
||||
const NAME: &'static str = "JSON";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
|
||||
ObjectInitializer::new(context)
|
||||
.function(Self::parse, "parse", 2)
|
||||
.function(Self::stringify, "stringify", 3)
|
||||
.property(to_string_tag, Self::NAME, attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Json {
|
||||
/// `JSON.parse( text[, reviver] )`
|
||||
///
|
||||
/// This `JSON` method parses a JSON string, constructing the JavaScript value or object described by the string.
|
||||
///
|
||||
/// An optional `reviver` function can be provided to perform a transformation on the resulting object before it is returned.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-json.parse
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
|
||||
pub(crate) fn parse(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let jsonString be ? ToString(text).
|
||||
let json_string = args
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.to_string(context)?;
|
||||
|
||||
// 2. Parse ! StringToCodePoints(jsonString) as a JSON text as specified in ECMA-404.
|
||||
// Throw a SyntaxError exception if it is not a valid JSON text as defined in that specification.
|
||||
if let Err(e) = serde_json::from_str::<JSONValue>(&json_string) {
|
||||
return context.throw_syntax_error(e.to_string());
|
||||
}
|
||||
|
||||
// 3. Let scriptString be the string-concatenation of "(", jsonString, and ");".
|
||||
let script_string = JsString::concat_array(&["(", json_string.as_str(), ");"]);
|
||||
|
||||
// 4. Let script be ParseText(! StringToCodePoints(scriptString), Script).
|
||||
// 5. NOTE: The early error rules defined in 13.2.5.1 have special handling for the above invocation of ParseText.
|
||||
// 6. Assert: script is a Parse Node.
|
||||
// 7. Let completion be the result of evaluating script.
|
||||
// 8. NOTE: The PropertyDefinitionEvaluation semantics defined in 13.2.5.5 have special handling for the above evaluation.
|
||||
// 9. Let unfiltered be completion.[[Value]].
|
||||
// 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral.
|
||||
let unfiltered = context.eval(script_string.as_bytes())?;
|
||||
|
||||
// 11. If IsCallable(reviver) is true, then
|
||||
if let Some(obj) = args.get_or_undefined(1).as_callable() {
|
||||
// a. Let root be ! OrdinaryObjectCreate(%Object.prototype%).
|
||||
let root = context.construct_object();
|
||||
|
||||
// b. Let rootName be the empty String.
|
||||
// c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered).
|
||||
root.create_data_property_or_throw("", unfiltered, context)
|
||||
.expect("CreateDataPropertyOrThrow should never throw here");
|
||||
|
||||
// d. Return ? InternalizeJSONProperty(root, rootName, reviver).
|
||||
Self::internalize_json_property(&root, "".into(), obj, context)
|
||||
} else {
|
||||
// 12. Else,
|
||||
// a. Return unfiltered.
|
||||
Ok(unfiltered)
|
||||
}
|
||||
}
|
||||
|
||||
/// `25.5.1.1 InternalizeJSONProperty ( holder, name, reviver )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-internalizejsonproperty
|
||||
fn internalize_json_property(
|
||||
holder: &JsObject,
|
||||
name: JsString,
|
||||
reviver: &JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let val be ? Get(holder, name).
|
||||
let val = holder.get(name.clone(), context)?;
|
||||
|
||||
// 2. If Type(val) is Object, then
|
||||
if let Some(obj) = val.as_object() {
|
||||
// a. Let isArray be ? IsArray(val).
|
||||
// b. If isArray is true, then
|
||||
if obj.is_array_abstract(context)? {
|
||||
// i. Let I be 0.
|
||||
// ii. Let len be ? LengthOfArrayLike(val).
|
||||
// iii. Repeat, while I < len,
|
||||
let len = obj.length_of_array_like(context)? as i64;
|
||||
for i in 0..len {
|
||||
// 1. Let prop be ! ToString(𝔽(I)).
|
||||
// 2. Let newElement be ? InternalizeJSONProperty(val, prop, reviver).
|
||||
let new_element = Self::internalize_json_property(
|
||||
obj,
|
||||
i.to_string().into(),
|
||||
reviver,
|
||||
context,
|
||||
)?;
|
||||
|
||||
// 3. If newElement is undefined, then
|
||||
if new_element.is_undefined() {
|
||||
// a. Perform ? val.[[Delete]](prop).
|
||||
obj.__delete__(&i.into(), context)?;
|
||||
}
|
||||
// 4. Else,
|
||||
else {
|
||||
// a. Perform ? CreateDataProperty(val, prop, newElement).
|
||||
obj.create_data_property(i, new_element, context)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// c. Else,
|
||||
else {
|
||||
// i. Let keys be ? EnumerableOwnPropertyNames(val, key).
|
||||
let keys = obj.enumerable_own_property_names(PropertyNameKind::Key, context)?;
|
||||
|
||||
// ii. For each String P of keys, do
|
||||
for p in keys {
|
||||
// This is safe, because EnumerableOwnPropertyNames with 'key' type only returns strings.
|
||||
let p = p
|
||||
.as_string()
|
||||
.expect("EnumerableOwnPropertyNames only returns strings");
|
||||
|
||||
// 1. Let newElement be ? InternalizeJSONProperty(val, P, reviver).
|
||||
let new_element =
|
||||
Self::internalize_json_property(obj, p.clone(), reviver, context)?;
|
||||
|
||||
// 2. If newElement is undefined, then
|
||||
if new_element.is_undefined() {
|
||||
// a. Perform ? val.[[Delete]](P).
|
||||
obj.__delete__(&p.clone().into(), context)?;
|
||||
}
|
||||
// 3. Else,
|
||||
else {
|
||||
// a. Perform ? CreateDataProperty(val, P, newElement).
|
||||
obj.create_data_property(p.as_str(), new_element, context)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return ? Call(reviver, holder, « name, val »).
|
||||
reviver.call(&holder.clone().into(), &[name.into(), val], context)
|
||||
}
|
||||
|
||||
/// `JSON.stringify( value[, replacer[, space]] )`
|
||||
///
|
||||
/// This `JSON` method converts a JavaScript object or value to a JSON string.
|
||||
///
|
||||
/// This method optionally replaces values if a `replacer` function is specified or
|
||||
/// optionally including only the specified properties if a replacer array is specified.
|
||||
///
|
||||
/// An optional `space` argument can be supplied of type `String` or `Number` that's used to insert
|
||||
/// white space into the output JSON string for readability purposes.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-json.stringify
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
|
||||
pub(crate) fn stringify(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let stack be a new empty List.
|
||||
let stack = Vec::new();
|
||||
|
||||
// 2. Let indent be the empty String.
|
||||
let indent = JsString::new("");
|
||||
|
||||
// 3. Let PropertyList and ReplacerFunction be undefined.
|
||||
let mut property_list = None;
|
||||
let mut replacer_function = None;
|
||||
|
||||
let replacer = args.get_or_undefined(1);
|
||||
|
||||
// 4. If Type(replacer) is Object, then
|
||||
if let Some(replacer_obj) = replacer.as_object() {
|
||||
// a. If IsCallable(replacer) is true, then
|
||||
if replacer_obj.is_callable() {
|
||||
// i. Set ReplacerFunction to replacer.
|
||||
replacer_function = Some(replacer_obj.clone());
|
||||
// b. Else,
|
||||
} else {
|
||||
// i. Let isArray be ? IsArray(replacer).
|
||||
// ii. If isArray is true, then
|
||||
if replacer_obj.is_array_abstract(context)? {
|
||||
// 1. Set PropertyList to a new empty List.
|
||||
let mut property_set = indexmap::IndexSet::new();
|
||||
|
||||
// 2. Let len be ? LengthOfArrayLike(replacer).
|
||||
let len = replacer_obj.length_of_array_like(context)?;
|
||||
|
||||
// 3. Let k be 0.
|
||||
let mut k = 0;
|
||||
|
||||
// 4. Repeat, while k < len,
|
||||
while k < len {
|
||||
// a. Let prop be ! ToString(𝔽(k)).
|
||||
// b. Let v be ? Get(replacer, prop).
|
||||
let v = replacer_obj.get(k, context)?;
|
||||
|
||||
// c. Let item be undefined.
|
||||
// d. If Type(v) is String, set item to v.
|
||||
// e. Else if Type(v) is Number, set item to ! ToString(v).
|
||||
// f. Else if Type(v) is Object, then
|
||||
// g. If item is not undefined and item is not currently an element of PropertyList, then
|
||||
// i. Append item to the end of PropertyList.
|
||||
if let Some(s) = v.as_string() {
|
||||
property_set.insert(s.clone());
|
||||
} else if v.is_number() {
|
||||
property_set.insert(
|
||||
v.to_string(context)
|
||||
.expect("ToString cannot fail on number value"),
|
||||
);
|
||||
} else if let Some(obj) = v.as_object() {
|
||||
// i. If v has a [[StringData]] or [[NumberData]] internal slot, set item to ? ToString(v).
|
||||
if obj.is_string() || obj.is_number() {
|
||||
property_set.insert(v.to_string(context)?);
|
||||
}
|
||||
}
|
||||
|
||||
// h. Set k to k + 1.
|
||||
k += 1;
|
||||
}
|
||||
property_list = Some(property_set.into_iter().collect());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut space = args.get_or_undefined(2).clone();
|
||||
|
||||
// 5. If Type(space) is Object, then
|
||||
if let Some(space_obj) = space.as_object() {
|
||||
// a. If space has a [[NumberData]] internal slot, then
|
||||
if space_obj.is_number() {
|
||||
// i. Set space to ? ToNumber(space).
|
||||
space = space.to_number(context)?.into();
|
||||
}
|
||||
// b. Else if space has a [[StringData]] internal slot, then
|
||||
else if space_obj.is_string() {
|
||||
// i. Set space to ? ToString(space).
|
||||
space = space.to_string(context)?.into();
|
||||
}
|
||||
}
|
||||
|
||||
// 6. If Type(space) is Number, then
|
||||
let gap = if space.is_number() {
|
||||
// a. Let spaceMV be ! ToIntegerOrInfinity(space).
|
||||
// b. Set spaceMV to min(10, spaceMV).
|
||||
// c. If spaceMV < 1, let gap be the empty String; otherwise let gap be the String value containing spaceMV occurrences of the code unit 0x0020 (SPACE).
|
||||
match space
|
||||
.to_integer_or_infinity(context)
|
||||
.expect("ToIntegerOrInfinity cannot fail on number")
|
||||
{
|
||||
IntegerOrInfinity::PositiveInfinity => JsString::new(" "),
|
||||
IntegerOrInfinity::NegativeInfinity => JsString::new(""),
|
||||
IntegerOrInfinity::Integer(i) if i < 1 => JsString::new(""),
|
||||
IntegerOrInfinity::Integer(i) => {
|
||||
let mut s = String::new();
|
||||
let i = std::cmp::min(10, i);
|
||||
for _ in 0..i {
|
||||
s.push(' ');
|
||||
}
|
||||
s.into()
|
||||
}
|
||||
}
|
||||
// 7. Else if Type(space) is String, then
|
||||
} else if let Some(s) = space.as_string() {
|
||||
// a. If the length of space is 10 or less, let gap be space; otherwise let gap be the substring of space from 0 to 10.
|
||||
String::from_utf16_lossy(&s.encode_utf16().take(10).collect::<Vec<u16>>()).into()
|
||||
// 8. Else,
|
||||
} else {
|
||||
// a. Let gap be the empty String.
|
||||
JsString::new("")
|
||||
};
|
||||
|
||||
// 9. Let wrapper be ! OrdinaryObjectCreate(%Object.prototype%).
|
||||
let wrapper = context.construct_object();
|
||||
|
||||
// 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value).
|
||||
wrapper
|
||||
.create_data_property_or_throw("", args.get_or_undefined(0).clone(), context)
|
||||
.expect("CreateDataPropertyOrThrow should never fail here");
|
||||
|
||||
// 11. Let state be the Record { [[ReplacerFunction]]: ReplacerFunction, [[Stack]]: stack, [[Indent]]: indent, [[Gap]]: gap, [[PropertyList]]: PropertyList }.
|
||||
let mut state = StateRecord {
|
||||
replacer_function,
|
||||
stack,
|
||||
indent,
|
||||
gap,
|
||||
property_list,
|
||||
};
|
||||
|
||||
// 12. Return ? SerializeJSONProperty(state, the empty String, wrapper).
|
||||
Ok(
|
||||
Self::serialize_json_property(&mut state, JsString::new(""), &wrapper, context)?
|
||||
.map(Into::into)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
/// `25.5.2.1 SerializeJSONProperty ( state, key, holder )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-serializejsonproperty
|
||||
fn serialize_json_property(
|
||||
state: &mut StateRecord,
|
||||
key: JsString,
|
||||
holder: &JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Option<JsString>> {
|
||||
// 1. Let value be ? Get(holder, key).
|
||||
let mut value = holder.get(key.clone(), context)?;
|
||||
|
||||
// 2. If Type(value) is Object or BigInt, then
|
||||
if value.is_object() || value.is_bigint() {
|
||||
// a. Let toJSON be ? GetV(value, "toJSON").
|
||||
let to_json = value.get_v("toJSON", context)?;
|
||||
|
||||
// b. If IsCallable(toJSON) is true, then
|
||||
if let Some(obj) = to_json.as_object() {
|
||||
if obj.is_callable() {
|
||||
// i. Set value to ? Call(toJSON, value, « key »).
|
||||
value = obj.call(&value, &[key.clone().into()], context)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. If state.[[ReplacerFunction]] is not undefined, then
|
||||
if let Some(obj) = &state.replacer_function {
|
||||
// a. Set value to ? Call(state.[[ReplacerFunction]], holder, « key, value »).
|
||||
value = obj.call(&holder.clone().into(), &[key.into(), value], context)?;
|
||||
}
|
||||
|
||||
// 4. If Type(value) is Object, then
|
||||
if let Some(obj) = value.as_object().cloned() {
|
||||
// a. If value has a [[NumberData]] internal slot, then
|
||||
if obj.is_number() {
|
||||
// i. Set value to ? ToNumber(value).
|
||||
value = value.to_number(context)?.into();
|
||||
}
|
||||
// b. Else if value has a [[StringData]] internal slot, then
|
||||
else if obj.is_string() {
|
||||
// i. Set value to ? ToString(value).
|
||||
value = value.to_string(context)?.into();
|
||||
}
|
||||
// c. Else if value has a [[BooleanData]] internal slot, then
|
||||
else if let Some(boolean) = obj.borrow().as_boolean() {
|
||||
// i. Set value to value.[[BooleanData]].
|
||||
value = boolean.into();
|
||||
}
|
||||
// d. Else if value has a [[BigIntData]] internal slot, then
|
||||
else if let Some(bigint) = obj.borrow().as_bigint() {
|
||||
// i. Set value to value.[[BigIntData]].
|
||||
value = bigint.clone().into();
|
||||
}
|
||||
}
|
||||
|
||||
// 5. If value is null, return "null".
|
||||
if value.is_null() {
|
||||
return Ok(Some(JsString::new("null")));
|
||||
}
|
||||
|
||||
// 6. If value is true, return "true".
|
||||
// 7. If value is false, return "false".
|
||||
if value.is_boolean() {
|
||||
return Ok(Some(JsString::new(if value.to_boolean() {
|
||||
"true"
|
||||
} else {
|
||||
"false"
|
||||
})));
|
||||
}
|
||||
|
||||
// 8. If Type(value) is String, return QuoteJSONString(value).
|
||||
if let Some(s) = value.as_string() {
|
||||
return Ok(Some(Self::quote_json_string(s)));
|
||||
}
|
||||
|
||||
// 9. If Type(value) is Number, then
|
||||
if let Some(n) = value.as_number() {
|
||||
// a. If value is finite, return ! ToString(value).
|
||||
if n.is_finite() {
|
||||
return Ok(Some(
|
||||
value
|
||||
.to_string(context)
|
||||
.expect("ToString should never fail here"),
|
||||
));
|
||||
}
|
||||
|
||||
// b. Return "null".
|
||||
return Ok(Some(JsString::new("null")));
|
||||
}
|
||||
|
||||
// 10. If Type(value) is BigInt, throw a TypeError exception.
|
||||
if value.is_bigint() {
|
||||
return context.throw_type_error("cannot serialize bigint to JSON");
|
||||
}
|
||||
|
||||
// 11. If Type(value) is Object and IsCallable(value) is false, then
|
||||
if let Some(obj) = value.as_object() {
|
||||
if !obj.is_callable() {
|
||||
// a. Let isArray be ? IsArray(value).
|
||||
// b. If isArray is true, return ? SerializeJSONArray(state, value).
|
||||
// c. Return ? SerializeJSONObject(state, value).
|
||||
return if obj.is_array_abstract(context)? {
|
||||
Ok(Some(Self::serialize_json_array(state, obj, context)?))
|
||||
} else {
|
||||
Ok(Some(Self::serialize_json_object(state, obj, context)?))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Return undefined.
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// `25.5.2.2 QuoteJSONString ( value )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-quotejsonstring
|
||||
fn quote_json_string(value: &JsString) -> JsString {
|
||||
// 1. Let product be the String value consisting solely of the code unit 0x0022 (QUOTATION MARK).
|
||||
let mut product = String::from('"');
|
||||
|
||||
// 2. For each code point C of ! StringToCodePoints(value), do
|
||||
for code_point in value.encode_utf16() {
|
||||
match code_point {
|
||||
// a. If C is listed in the “Code Point” column of Table 73, then
|
||||
// i. Set product to the string-concatenation of product and the escape sequence for C as specified in the “Escape Sequence” column of the corresponding row.
|
||||
0x8 => product.push_str("\\b"),
|
||||
0x9 => product.push_str("\\t"),
|
||||
0xA => product.push_str("\\n"),
|
||||
0xC => product.push_str("\\f"),
|
||||
0xD => product.push_str("\\r"),
|
||||
0x22 => product.push_str("\\\""),
|
||||
0x5C => product.push_str("\\\\"),
|
||||
// b. Else if C has a numeric value less than 0x0020 (SPACE), or if C has the same numeric value as a leading surrogate or trailing surrogate, then
|
||||
code_point
|
||||
if is_leading_surrogate(code_point) || is_trailing_surrogate(code_point) =>
|
||||
{
|
||||
// i. Let unit be the code unit whose numeric value is that of C.
|
||||
// ii. Set product to the string-concatenation of product and UnicodeEscape(unit).
|
||||
product.push_str(&format!("\\\\uAA{code_point:x}"));
|
||||
}
|
||||
// c. Else,
|
||||
code_point => {
|
||||
// i. Set product to the string-concatenation of product and ! UTF16EncodeCodePoint(C).
|
||||
product.push(
|
||||
char::from_u32(u32::from(code_point))
|
||||
.expect("char from code point cannot fail here"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK).
|
||||
product.push('"');
|
||||
|
||||
// 4. Return product.
|
||||
product.into()
|
||||
}
|
||||
|
||||
/// `25.5.2.4 SerializeJSONObject ( state, value )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-serializejsonobject
|
||||
fn serialize_json_object(
|
||||
state: &mut StateRecord,
|
||||
value: &JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsString> {
|
||||
// 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
|
||||
let limiter = RecursionLimiter::new(value);
|
||||
if limiter.live {
|
||||
return context.throw_type_error("cyclic object value");
|
||||
}
|
||||
|
||||
// 2. Append value to state.[[Stack]].
|
||||
state.stack.push(value.clone().into());
|
||||
|
||||
// 3. Let stepback be state.[[Indent]].
|
||||
let stepback = state.indent.clone();
|
||||
|
||||
// 4. Set state.[[Indent]] to the string-concatenation of state.[[Indent]] and state.[[Gap]].
|
||||
state.indent = JsString::concat(&state.indent, &state.gap);
|
||||
|
||||
// 5. If state.[[PropertyList]] is not undefined, then
|
||||
let k = if let Some(p) = &state.property_list {
|
||||
// a. Let K be state.[[PropertyList]].
|
||||
p.clone()
|
||||
// 6. Else,
|
||||
} else {
|
||||
// a. Let K be ? EnumerableOwnPropertyNames(value, key).
|
||||
let keys = value.enumerable_own_property_names(PropertyNameKind::Key, context)?;
|
||||
// Unwrap is safe, because EnumerableOwnPropertyNames with kind "key" only returns string values.
|
||||
keys.iter()
|
||||
.map(|v| {
|
||||
v.to_string(context)
|
||||
.expect("EnumerableOwnPropertyNames only returns strings")
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
// 7. Let partial be a new empty List.
|
||||
let mut partial = Vec::new();
|
||||
|
||||
// 8. For each element P of K, do
|
||||
for p in &k {
|
||||
// a. Let strP be ? SerializeJSONProperty(state, P, value).
|
||||
let str_p = Self::serialize_json_property(state, p.clone(), value, context)?;
|
||||
|
||||
// b. If strP is not undefined, then
|
||||
if let Some(str_p) = str_p {
|
||||
// i. Let member be QuoteJSONString(P).
|
||||
// ii. Set member to the string-concatenation of member and ":".
|
||||
// iii. If state.[[Gap]] is not the empty String, then
|
||||
// 1. Set member to the string-concatenation of member and the code unit 0x0020 (SPACE).
|
||||
// iv. Set member to the string-concatenation of member and strP.
|
||||
let member = if state.gap.is_empty() {
|
||||
format!("{}:{}", Self::quote_json_string(p).as_str(), str_p.as_str())
|
||||
} else {
|
||||
format!(
|
||||
"{}: {}",
|
||||
Self::quote_json_string(p).as_str(),
|
||||
str_p.as_str()
|
||||
)
|
||||
};
|
||||
|
||||
// v. Append member to partial.
|
||||
partial.push(member);
|
||||
}
|
||||
}
|
||||
|
||||
// 9. If partial is empty, then
|
||||
let r#final = if partial.is_empty() {
|
||||
// a. Let final be "{}".
|
||||
JsString::new("{}")
|
||||
// 10. Else,
|
||||
} else {
|
||||
// a. If state.[[Gap]] is the empty String, then
|
||||
if state.gap.is_empty() {
|
||||
// i. Let properties be the String value formed by concatenating all the element Strings of partial
|
||||
// with each adjacent pair of Strings separated with the code unit 0x002C (COMMA).
|
||||
// A comma is not inserted either before the first String or after the last String.
|
||||
// ii. Let final be the string-concatenation of "{", properties, and "}".
|
||||
format!("{{{}}}", partial.join(",")).into()
|
||||
// b. Else,
|
||||
} else {
|
||||
// i. Let separator be the string-concatenation of the code unit 0x002C (COMMA),
|
||||
// the code unit 0x000A (LINE FEED), and state.[[Indent]].
|
||||
let separator = format!(",\n{}", state.indent.as_str());
|
||||
// ii. Let properties be the String value formed by concatenating all the element Strings of partial
|
||||
// with each adjacent pair of Strings separated with separator.
|
||||
// The separator String is not inserted either before the first String or after the last String.
|
||||
let properties = partial.join(&separator);
|
||||
// iii. Let final be the string-concatenation of "{", the code unit 0x000A (LINE FEED), state.[[Indent]], properties, the code unit 0x000A (LINE FEED), stepback, and "}".
|
||||
format!(
|
||||
"{{\n{}{properties}\n{}}}",
|
||||
state.indent.as_str(),
|
||||
stepback.as_str()
|
||||
)
|
||||
.into()
|
||||
}
|
||||
};
|
||||
|
||||
// 11. Remove the last element of state.[[Stack]].
|
||||
state.stack.pop();
|
||||
|
||||
// 12. Set state.[[Indent]] to stepback.
|
||||
state.indent = stepback;
|
||||
|
||||
// 13. Return final.
|
||||
Ok(r#final)
|
||||
}
|
||||
|
||||
/// `25.5.2.5 SerializeJSONArray ( state, value )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-serializejsonarray
|
||||
fn serialize_json_array(
|
||||
state: &mut StateRecord,
|
||||
value: &JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsString> {
|
||||
// 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
|
||||
let limiter = RecursionLimiter::new(value);
|
||||
if limiter.live {
|
||||
return context.throw_type_error("cyclic object value");
|
||||
}
|
||||
|
||||
// 2. Append value to state.[[Stack]].
|
||||
state.stack.push(value.clone().into());
|
||||
|
||||
// 3. Let stepback be state.[[Indent]].
|
||||
let stepback = state.indent.clone();
|
||||
|
||||
// 4. Set state.[[Indent]] to the string-concatenation of state.[[Indent]] and state.[[Gap]].
|
||||
state.indent = JsString::concat(&state.indent, &state.gap);
|
||||
|
||||
// 5. Let partial be a new empty List.
|
||||
let mut partial = Vec::new();
|
||||
|
||||
// 6. Let len be ? LengthOfArrayLike(value).
|
||||
let len = value.length_of_array_like(context)?;
|
||||
|
||||
// 7. Let index be 0.
|
||||
let mut index = 0;
|
||||
|
||||
// 8. Repeat, while index < len,
|
||||
while index < len {
|
||||
// a. Let strP be ? SerializeJSONProperty(state, ! ToString(𝔽(index)), value).
|
||||
let str_p =
|
||||
Self::serialize_json_property(state, index.to_string().into(), value, context)?;
|
||||
|
||||
// b. If strP is undefined, then
|
||||
if let Some(str_p) = str_p {
|
||||
// i. Append strP to partial.
|
||||
partial.push(str_p);
|
||||
// c. Else,
|
||||
} else {
|
||||
// i. Append "null" to partial.
|
||||
partial.push("null".into());
|
||||
}
|
||||
|
||||
// d. Set index to index + 1.
|
||||
index += 1;
|
||||
}
|
||||
|
||||
// 9. If partial is empty, then
|
||||
let r#final = if partial.is_empty() {
|
||||
// a. Let final be "[]".
|
||||
JsString::from("[]")
|
||||
// 10. Else,
|
||||
} else {
|
||||
// a. If state.[[Gap]] is the empty String, then
|
||||
if state.gap.is_empty() {
|
||||
// i. Let properties be the String value formed by concatenating all the element Strings of partial
|
||||
// with each adjacent pair of Strings separated with the code unit 0x002C (COMMA).
|
||||
// A comma is not inserted either before the first String or after the last String.
|
||||
// ii. Let final be the string-concatenation of "[", properties, and "]".
|
||||
format!("[{}]", partial.join(",")).into()
|
||||
// b. Else,
|
||||
} else {
|
||||
// i. Let separator be the string-concatenation of the code unit 0x002C (COMMA),
|
||||
// the code unit 0x000A (LINE FEED), and state.[[Indent]].
|
||||
let separator = format!(",\n{}", state.indent.as_str());
|
||||
// ii. Let properties be the String value formed by concatenating all the element Strings of partial
|
||||
// with each adjacent pair of Strings separated with separator.
|
||||
// The separator String is not inserted either before the first String or after the last String.
|
||||
let properties = partial.join(&separator);
|
||||
// iii. Let final be the string-concatenation of "[", the code unit 0x000A (LINE FEED), state.[[Indent]], properties, the code unit 0x000A (LINE FEED), stepback, and "]".
|
||||
format!(
|
||||
"[\n{}{properties}\n{}]",
|
||||
state.indent.as_str(),
|
||||
stepback.as_str()
|
||||
)
|
||||
.into()
|
||||
}
|
||||
};
|
||||
|
||||
// 11. Remove the last element of state.[[Stack]].
|
||||
state.stack.pop();
|
||||
|
||||
// 12. Set state.[[Indent]] to stepback.
|
||||
state.indent = stepback;
|
||||
|
||||
// 13. Return final.
|
||||
Ok(r#final)
|
||||
}
|
||||
}
|
||||
|
||||
struct StateRecord {
|
||||
replacer_function: Option<JsObject>,
|
||||
stack: Vec<JsValue>,
|
||||
indent: JsString,
|
||||
gap: JsString,
|
||||
property_list: Option<Vec<JsString>>,
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
use crate::{forward, forward_val, Context};
|
||||
|
||||
#[test]
|
||||
fn json_sanity() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(
|
||||
forward(&mut context, r#"JSON.parse('{"aaa":"bbb"}').aaa == 'bbb'"#),
|
||||
"true"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({aaa: 'bbb'}) == '{"aaa":"bbb"}'"#
|
||||
),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_remove_undefined_values_from_objects() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({ aaa: undefined, bbb: 'ccc' })"#,
|
||||
);
|
||||
let expected = r#""{"bbb":"ccc"}""#;
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_remove_function_values_from_objects() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({ aaa: () => {}, bbb: 'ccc' })"#,
|
||||
);
|
||||
let expected = r#""{"bbb":"ccc"}""#;
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_remove_symbols_from_objects() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({ aaa: Symbol(), bbb: 'ccc' })"#,
|
||||
);
|
||||
let expected = r#""{"bbb":"ccc"}""#;
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_replacer_array_strings() {
|
||||
let mut context = Context::default();
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({aaa: 'bbb', bbb: 'ccc', ccc: 'ddd'}, ['aaa', 'bbb'])"#,
|
||||
);
|
||||
let expected = forward(&mut context, r#"'{"aaa":"bbb","bbb":"ccc"}'"#);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_replacer_array_numbers() {
|
||||
let mut context = Context::default();
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({ 0: 'aaa', 1: 'bbb', 2: 'ccc'}, [1, 2])"#,
|
||||
);
|
||||
let expected = forward(&mut context, r#"'{"1":"bbb","2":"ccc"}'"#);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_replacer_function() {
|
||||
let mut context = Context::default();
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({ aaa: 1, bbb: 2}, (key, value) => {
|
||||
if (key === 'aaa') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return value;
|
||||
})"#,
|
||||
);
|
||||
let expected = forward(&mut context, r#"'{"bbb":2}'"#);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_arrays() {
|
||||
let mut context = Context::default();
|
||||
let actual = forward(&mut context, r#"JSON.stringify(['a', 'b'])"#);
|
||||
let expected = forward(&mut context, r#"'["a","b"]'"#);
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_object_array() {
|
||||
let mut context = Context::default();
|
||||
let actual = forward(&mut context, r#"JSON.stringify([{a: 'b'}, {b: 'c'}])"#);
|
||||
let expected = forward(&mut context, r#"'[{"a":"b"},{"b":"c"}]'"#);
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_array_converts_undefined_to_null() {
|
||||
let mut context = Context::default();
|
||||
let actual = forward(&mut context, r#"JSON.stringify([undefined])"#);
|
||||
let expected = forward(&mut context, r#"'[null]'"#);
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_array_converts_function_to_null() {
|
||||
let mut context = Context::default();
|
||||
let actual = forward(&mut context, r#"JSON.stringify([() => {}])"#);
|
||||
let expected = forward(&mut context, r#"'[null]'"#);
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_array_converts_symbol_to_null() {
|
||||
let mut context = Context::default();
|
||||
let actual = forward(&mut context, r#"JSON.stringify([Symbol()])"#);
|
||||
let expected = forward(&mut context, r#"'[null]'"#);
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
#[test]
|
||||
fn json_stringify_function_replacer_propagate_error() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"
|
||||
let thrown = 0;
|
||||
try {
|
||||
JSON.stringify({x: 1}, (key, value) => { throw 1 })
|
||||
} catch (err) {
|
||||
thrown = err;
|
||||
}
|
||||
thrown
|
||||
"#,
|
||||
);
|
||||
let expected = forward(&mut context, "1");
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_function() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual_function = forward(&mut context, r#"JSON.stringify(() => {})"#);
|
||||
let expected = forward(&mut context, r#"undefined"#);
|
||||
|
||||
assert_eq!(actual_function, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_undefined() {
|
||||
let mut context = Context::default();
|
||||
let actual_undefined = forward(&mut context, r#"JSON.stringify(undefined)"#);
|
||||
let expected = forward(&mut context, r#"undefined"#);
|
||||
|
||||
assert_eq!(actual_undefined, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_symbol() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual_symbol = forward(&mut context, r#"JSON.stringify(Symbol())"#);
|
||||
let expected = forward(&mut context, r#"undefined"#);
|
||||
|
||||
assert_eq!(actual_symbol, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_no_args() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual_no_args = forward(&mut context, r#"JSON.stringify()"#);
|
||||
let expected = forward(&mut context, r#"undefined"#);
|
||||
|
||||
assert_eq!(actual_no_args, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_fractional_numbers() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(&mut context, r#"JSON.stringify(Math.round(1.0))"#);
|
||||
let expected = forward(&mut context, r#""1""#);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_pretty_print() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({a: "b", b: "c"}, undefined, 4)"#,
|
||||
);
|
||||
let expected = forward(
|
||||
&mut context,
|
||||
r#"'{\n'
|
||||
+' "a": "b",\n'
|
||||
+' "b": "c"\n'
|
||||
+'}'"#,
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_pretty_print_four_spaces() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({a: "b", b: "c"}, undefined, 4.3)"#,
|
||||
);
|
||||
let expected = forward(
|
||||
&mut context,
|
||||
r#"'{\n'
|
||||
+' "a": "b",\n'
|
||||
+' "b": "c"\n'
|
||||
+'}'"#,
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_pretty_print_twenty_spaces() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({a: "b", b: "c"}, ["a", "b"], 20)"#,
|
||||
);
|
||||
let expected = forward(
|
||||
&mut context,
|
||||
r#"'{\n'
|
||||
+' "a": "b",\n'
|
||||
+' "b": "c"\n'
|
||||
+'}'"#,
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_pretty_print_with_number_object() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({a: "b", b: "c"}, undefined, new Number(10))"#,
|
||||
);
|
||||
let expected = forward(
|
||||
&mut context,
|
||||
r#"'{\n'
|
||||
+' "a": "b",\n'
|
||||
+' "b": "c"\n'
|
||||
+'}'"#,
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_pretty_print_bad_space_argument() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({a: "b", b: "c"}, ["a", "b"], [])"#,
|
||||
);
|
||||
let expected = forward(&mut context, r#"'{"a":"b","b":"c"}'"#);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_pretty_print_with_too_long_string() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({a: "b", b: "c"}, undefined, "abcdefghijklmn")"#,
|
||||
);
|
||||
let expected = forward(
|
||||
&mut context,
|
||||
r#"'{\n'
|
||||
+'abcdefghij"a": "b",\n'
|
||||
+'abcdefghij"b": "c"\n'
|
||||
+'}'"#,
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_stringify_pretty_print_with_string_object() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let actual = forward(
|
||||
&mut context,
|
||||
r#"JSON.stringify({a: "b", b: "c"}, undefined, new String("abcd"))"#,
|
||||
);
|
||||
let expected = forward(
|
||||
&mut context,
|
||||
r#"'{\n'
|
||||
+'abcd"a": "b",\n'
|
||||
+'abcd"b": "c"\n'
|
||||
+'}'"#,
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_parse_array_with_reviver() {
|
||||
let mut context = Context::default();
|
||||
let result = forward_val(
|
||||
&mut context,
|
||||
r#"JSON.parse('[1,2,3,4]', function(k, v){
|
||||
if (typeof v == 'number') {
|
||||
return v * 2;
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
})"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
result
|
||||
.get_v("0", &mut context)
|
||||
.unwrap()
|
||||
.to_number(&mut context)
|
||||
.unwrap() as u8,
|
||||
2u8
|
||||
);
|
||||
assert_eq!(
|
||||
result
|
||||
.get_v("1", &mut context)
|
||||
.unwrap()
|
||||
.to_number(&mut context)
|
||||
.unwrap() as u8,
|
||||
4u8
|
||||
);
|
||||
assert_eq!(
|
||||
result
|
||||
.get_v("2", &mut context)
|
||||
.unwrap()
|
||||
.to_number(&mut context)
|
||||
.unwrap() as u8,
|
||||
6u8
|
||||
);
|
||||
assert_eq!(
|
||||
result
|
||||
.get_v("3", &mut context)
|
||||
.unwrap()
|
||||
.to_number(&mut context)
|
||||
.unwrap() as u8,
|
||||
8u8
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_parse_object_with_reviver() {
|
||||
let mut context = Context::default();
|
||||
let result = forward(
|
||||
&mut context,
|
||||
r#"
|
||||
var myObj = new Object();
|
||||
myObj.firstname = "boa";
|
||||
myObj.lastname = "snake";
|
||||
var jsonString = JSON.stringify(myObj);
|
||||
|
||||
function dataReviver(key, value) {
|
||||
if (key == 'lastname') {
|
||||
return 'interpreter';
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
var jsonObj = JSON.parse(jsonString, dataReviver);
|
||||
|
||||
JSON.stringify(jsonObj);"#,
|
||||
);
|
||||
assert_eq!(result, r#""{"firstname":"boa","lastname":"interpreter"}""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_parse_sets_prototypes() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
const jsonString = "{\"ob\":{\"ject\":1},\"arr\": [0,1]}";
|
||||
const jsonObj = JSON.parse(jsonString);
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let object_prototype = forward_val(&mut context, r#"jsonObj.ob"#)
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.prototype()
|
||||
.clone();
|
||||
let array_prototype = forward_val(&mut context, r#"jsonObj.arr"#)
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.prototype()
|
||||
.clone();
|
||||
let global_object_prototype = context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.object()
|
||||
.prototype()
|
||||
.into();
|
||||
let global_array_prototype = context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.array()
|
||||
.prototype()
|
||||
.into();
|
||||
assert_eq!(object_prototype, global_object_prototype);
|
||||
assert_eq!(array_prototype, global_array_prototype);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_fields_should_be_enumerable() {
|
||||
let mut context = Context::default();
|
||||
let actual_object = forward(
|
||||
&mut context,
|
||||
r#"
|
||||
var a = JSON.parse('{"x":0}');
|
||||
a.propertyIsEnumerable('x');
|
||||
"#,
|
||||
);
|
||||
let actual_array_index = forward(
|
||||
&mut context,
|
||||
r#"
|
||||
var b = JSON.parse('[0, 1]');
|
||||
b.propertyIsEnumerable('0');
|
||||
"#,
|
||||
);
|
||||
let expected = forward(&mut context, r#"true"#);
|
||||
|
||||
assert_eq!(actual_object, expected);
|
||||
assert_eq!(actual_array_index, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_parse_with_no_args_throws_syntax_error() {
|
||||
let mut context = Context::default();
|
||||
let result = forward(&mut context, "JSON.parse();");
|
||||
assert!(result.contains("SyntaxError"));
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
use super::ordered_map::MapLock;
|
||||
use crate::{
|
||||
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue},
|
||||
object::{JsObject, ObjectData},
|
||||
property::{PropertyDescriptor, PropertyNameKind},
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// The Map Iterator object represents an iteration over a map. It implements the iterator protocol.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct MapIterator {
|
||||
iterated_map: Option<JsObject>,
|
||||
map_next_index: usize,
|
||||
map_iteration_kind: PropertyNameKind,
|
||||
lock: MapLock,
|
||||
}
|
||||
|
||||
impl MapIterator {
|
||||
pub(crate) const NAME: &'static str = "MapIterator";
|
||||
|
||||
/// Abstract operation `CreateMapIterator( map, kind )`
|
||||
///
|
||||
/// Creates a new iterator over the given map.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createmapiterator
|
||||
pub(crate) fn create_map_iterator(
|
||||
map: &JsValue,
|
||||
kind: PropertyNameKind,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
if let Some(map_obj) = map.as_object() {
|
||||
if let Some(map) = map_obj.borrow_mut().as_map_mut() {
|
||||
let lock = map.lock(map_obj.clone());
|
||||
let iter = Self {
|
||||
iterated_map: Some(map_obj.clone()),
|
||||
map_next_index: 0,
|
||||
map_iteration_kind: kind,
|
||||
lock,
|
||||
};
|
||||
let map_iterator = JsObject::from_proto_and_data(
|
||||
context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.map_iterator(),
|
||||
ObjectData::map_iterator(iter),
|
||||
);
|
||||
return Ok(map_iterator.into());
|
||||
}
|
||||
}
|
||||
context.throw_type_error("`this` is not a Map")
|
||||
}
|
||||
|
||||
/// %MapIteratorPrototype%.next( )
|
||||
///
|
||||
/// Advances the iterator and gets the next result in the map.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next
|
||||
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let mut map_iterator = this.as_object().map(JsObject::borrow_mut);
|
||||
let map_iterator = map_iterator
|
||||
.as_mut()
|
||||
.and_then(|obj| obj.as_map_iterator_mut())
|
||||
.ok_or_else(|| context.construct_type_error("`this` is not a MapIterator"))?;
|
||||
|
||||
let item_kind = map_iterator.map_iteration_kind;
|
||||
|
||||
if let Some(obj) = map_iterator.iterated_map.take() {
|
||||
let e = {
|
||||
let map = obj.borrow();
|
||||
let entries = map.as_map_ref().expect("iterator should only iterate maps");
|
||||
let len = entries.full_len();
|
||||
loop {
|
||||
let element = entries
|
||||
.get_index(map_iterator.map_next_index)
|
||||
.map(|(v, k)| (v.clone(), k.clone()));
|
||||
map_iterator.map_next_index += 1;
|
||||
if element.is_some() || map_iterator.map_next_index >= len {
|
||||
break element;
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some((key, value)) = e {
|
||||
let item = match item_kind {
|
||||
PropertyNameKind::Key => Ok(create_iter_result_object(key, false, context)),
|
||||
PropertyNameKind::Value => Ok(create_iter_result_object(value, false, context)),
|
||||
PropertyNameKind::KeyAndValue => {
|
||||
let result = Array::create_array_from_list([key, value], context);
|
||||
Ok(create_iter_result_object(result.into(), false, context))
|
||||
}
|
||||
};
|
||||
map_iterator.iterated_map = Some(obj);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
))
|
||||
}
|
||||
|
||||
/// Create the `%MapIteratorPrototype%` object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%-object
|
||||
pub(crate) fn create_prototype(
|
||||
iterator_prototype: JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsObject {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
// Create prototype
|
||||
let map_iterator =
|
||||
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
|
||||
make_builtin_fn(Self::next, "next", &map_iterator, 0, context);
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
let to_string_tag_property = PropertyDescriptor::builder()
|
||||
.value("Map Iterator")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
map_iterator.insert(to_string_tag, to_string_tag_property);
|
||||
map_iterator
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
//! This module implements the global `Map` object.
|
||||
//!
|
||||
//! The JavaScript `Map` class is a global object that is used in the construction of maps; which
|
||||
//! are high-level, key-value stores.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-map-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
|
||||
|
||||
#![allow(clippy::mutable_key_type)]
|
||||
|
||||
use self::{map_iterator::MapIterator, ordered_map::OrderedMap};
|
||||
use super::JsArgs;
|
||||
use crate::{
|
||||
builtins::BuiltIn,
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
|
||||
JsObject, ObjectData,
|
||||
},
|
||||
property::{Attribute, PropertyNameKind},
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use num_traits::Zero;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
pub mod map_iterator;
|
||||
pub mod ordered_map;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Map(OrderedMap<JsValue>);
|
||||
|
||||
impl BuiltIn for Map {
|
||||
const NAME: &'static str = "Map";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let get_species = FunctionBuilder::native(context, Self::get_species)
|
||||
.name("get [Symbol.species]")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let get_size = FunctionBuilder::native(context, Self::get_size)
|
||||
.name("get size")
|
||||
.length(0)
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let entries_function = FunctionBuilder::native(context, Self::entries)
|
||||
.name("entries")
|
||||
.length(0)
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().map().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.static_accessor(
|
||||
WellKnownSymbols::species(),
|
||||
Some(get_species),
|
||||
None,
|
||||
Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
"entries",
|
||||
entries_function.clone(),
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
WellKnownSymbols::iterator(),
|
||||
entries_function,
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
WellKnownSymbols::to_string_tag(),
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.method(Self::clear, "clear", 0)
|
||||
.method(Self::delete, "delete", 1)
|
||||
.method(Self::for_each, "forEach", 1)
|
||||
.method(Self::get, "get", 1)
|
||||
.method(Self::has, "has", 1)
|
||||
.method(Self::keys, "keys", 0)
|
||||
.method(Self::set, "set", 2)
|
||||
.method(Self::values, "values", 0)
|
||||
.accessor("size", Some(get_size), None, Attribute::CONFIGURABLE)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Map {
|
||||
pub(crate) const LENGTH: usize = 0;
|
||||
|
||||
/// `Map ( [ iterable ] )`
|
||||
///
|
||||
/// Constructor for `Map` objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map-iterable
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if new_target.is_undefined() {
|
||||
return context
|
||||
.throw_type_error("calling a builtin Map constructor without new is forbidden");
|
||||
}
|
||||
|
||||
// 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%Map.prototype%", « [[MapData]] »).
|
||||
// 3. Set map.[[MapData]] to a new empty List.
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::map, context)?;
|
||||
let map = JsObject::from_proto_and_data(prototype, ObjectData::map(OrderedMap::new()));
|
||||
|
||||
// 4. If iterable is either undefined or null, return map.
|
||||
let iterable = match args.get_or_undefined(0) {
|
||||
val if !val.is_null_or_undefined() => val,
|
||||
_ => return Ok(map.into()),
|
||||
};
|
||||
|
||||
// 5. Let adder be ? Get(map, "set").
|
||||
let adder = map.get("set", context)?;
|
||||
|
||||
// 6. Return ? AddEntriesFromIterable(map, iterable, adder).
|
||||
add_entries_from_iterable(&map, iterable, &adder, context)
|
||||
}
|
||||
|
||||
/// `get Map [ @@species ]`
|
||||
///
|
||||
/// The `Map [ @@species ]` accessor property returns the Map constructor.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-map-@@species
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/@@species
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Return the this value.
|
||||
Ok(this.clone())
|
||||
}
|
||||
|
||||
/// `Map.prototype.entries()`
|
||||
///
|
||||
/// Returns a new Iterator object that contains the [key, value] pairs for each element in the Map object in insertion order.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.entries
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries
|
||||
pub(crate) fn entries(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let M be the this value.
|
||||
// 2. Return ? CreateMapIterator(M, key+value).
|
||||
MapIterator::create_map_iterator(this, PropertyNameKind::KeyAndValue, context)
|
||||
}
|
||||
|
||||
/// `Map.prototype.keys()`
|
||||
///
|
||||
/// Returns a new Iterator object that contains the keys for each element in the Map object in insertion order.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.keys
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys
|
||||
pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let M be the this value.
|
||||
// 2. Return ? CreateMapIterator(M, key).
|
||||
MapIterator::create_map_iterator(this, PropertyNameKind::Key, context)
|
||||
}
|
||||
|
||||
/// `Map.prototype.set( key, value )`
|
||||
///
|
||||
/// Inserts a new entry in the Map object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.set
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set
|
||||
pub(crate) fn set(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let key = args.get_or_undefined(0);
|
||||
let value = args.get_or_undefined(1);
|
||||
|
||||
// 1. Let M be the this value.
|
||||
if let Some(object) = this.as_object() {
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
// 3. Let entries be the List that is M.[[MapData]].
|
||||
if let Some(map) = object.borrow_mut().as_map_mut() {
|
||||
let key = match key {
|
||||
JsValue::Rational(r) => {
|
||||
// 5. If key is -0𝔽, set key to +0𝔽.
|
||||
if r.is_zero() {
|
||||
JsValue::Rational(0f64)
|
||||
} else {
|
||||
key.clone()
|
||||
}
|
||||
}
|
||||
_ => key.clone(),
|
||||
};
|
||||
// 4. For each Record { [[Key]], [[Value]] } p of entries, do
|
||||
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then
|
||||
// i. Set p.[[Value]] to value.
|
||||
// 6. Let p be the Record { [[Key]]: key, [[Value]]: value }.
|
||||
// 7. Append p as the last element of entries.
|
||||
map.insert(key, value.clone());
|
||||
// ii. Return M.
|
||||
// 8. Return M.
|
||||
return Ok(this.clone());
|
||||
}
|
||||
}
|
||||
context.throw_type_error("'this' is not a Map")
|
||||
}
|
||||
|
||||
/// `get Map.prototype.size`
|
||||
///
|
||||
/// Obtains the size of the map, filtering empty keys to ensure it updates
|
||||
/// while iterating.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-map.prototype.size
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size
|
||||
pub(crate) fn get_size(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let M be the this value.
|
||||
if let Some(object) = this.as_object() {
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
// 3. Let entries be the List that is M.[[MapData]].
|
||||
if let Some(map) = object.borrow_mut().as_map_mut() {
|
||||
// 4. Let count be 0.
|
||||
// 5. For each Record { [[Key]], [[Value]] } p of entries, do
|
||||
// a. If p.[[Key]] is not empty, set count to count + 1.
|
||||
// 6. Return 𝔽(count).
|
||||
return Ok(map.len().into());
|
||||
}
|
||||
}
|
||||
context.throw_type_error("'this' is not a Map")
|
||||
}
|
||||
|
||||
/// `Map.prototype.delete( key )`
|
||||
///
|
||||
/// Removes the element associated with the key, if it exists.
|
||||
/// Returns true if there was an element, and false otherwise.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.delete
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete
|
||||
pub(crate) fn delete(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let key = args.get_or_undefined(0);
|
||||
|
||||
// 1. Let M be the this value.
|
||||
if let Some(object) = this.as_object() {
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
// 3. Let entries be the List that is M.[[MapData]].
|
||||
if let Some(map) = object.borrow_mut().as_map_mut() {
|
||||
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then
|
||||
// i. Set p.[[Key]] to empty.
|
||||
// ii. Set p.[[Value]] to empty.
|
||||
// iii. Return true.
|
||||
// 5. Return false.
|
||||
return Ok(map.remove(key).is_some().into());
|
||||
}
|
||||
}
|
||||
context.throw_type_error("'this' is not a Map")
|
||||
}
|
||||
|
||||
/// `Map.prototype.get( key )`
|
||||
///
|
||||
/// Returns the value associated with the key, or undefined if there is none.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.get
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get
|
||||
pub(crate) fn get(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
const JS_ZERO: &JsValue = &JsValue::Rational(0f64);
|
||||
|
||||
let key = args.get_or_undefined(0);
|
||||
let key = match key {
|
||||
JsValue::Rational(r) => {
|
||||
if r.is_zero() {
|
||||
JS_ZERO
|
||||
} else {
|
||||
key
|
||||
}
|
||||
}
|
||||
_ => key,
|
||||
};
|
||||
|
||||
// 1. Let M be the this value.
|
||||
if let JsValue::Object(ref object) = this {
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
// 3. Let entries be the List that is M.[[MapData]].
|
||||
if let Some(map) = object.borrow().as_map_ref() {
|
||||
// 4. For each Record { [[Key]], [[Value]] } p of entries, do
|
||||
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return p.[[Value]].
|
||||
// 5. Return undefined.
|
||||
return Ok(map.get(key).cloned().unwrap_or_default());
|
||||
}
|
||||
}
|
||||
|
||||
context.throw_type_error("'this' is not a Map")
|
||||
}
|
||||
|
||||
/// `Map.prototype.clear( )`
|
||||
///
|
||||
/// Removes all entries from the map.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.clear
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear
|
||||
pub(crate) fn clear(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let M be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
if let Some(object) = this.as_object() {
|
||||
// 3. Let entries be the List that is M.[[MapData]].
|
||||
if let Some(map) = object.borrow_mut().as_map_mut() {
|
||||
// 4. For each Record { [[Key]], [[Value]] } p of entries, do
|
||||
// a. Set p.[[Key]] to empty.
|
||||
// b. Set p.[[Value]] to empty.
|
||||
map.clear();
|
||||
|
||||
// 5. Return undefined.
|
||||
return Ok(JsValue::undefined());
|
||||
}
|
||||
}
|
||||
context.throw_type_error("'this' is not a Map")
|
||||
}
|
||||
|
||||
/// `Map.prototype.has( key )`
|
||||
///
|
||||
/// Checks if the map contains an entry with the given key.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.has
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has
|
||||
pub(crate) fn has(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
const JS_ZERO: &JsValue = &JsValue::Rational(0f64);
|
||||
|
||||
let key = args.get_or_undefined(0);
|
||||
let key = match key {
|
||||
JsValue::Rational(r) => {
|
||||
if r.is_zero() {
|
||||
JS_ZERO
|
||||
} else {
|
||||
key
|
||||
}
|
||||
}
|
||||
_ => key,
|
||||
};
|
||||
|
||||
// 1. Let M be the this value.
|
||||
if let JsValue::Object(ref object) = this {
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
// 3. Let entries be the List that is M.[[MapData]].
|
||||
if let Some(map) = object.borrow().as_map_ref() {
|
||||
// 4. For each Record { [[Key]], [[Value]] } p of entries, do
|
||||
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return true.
|
||||
// 5. Return false.
|
||||
return Ok(map.contains_key(key).into());
|
||||
}
|
||||
}
|
||||
|
||||
context.throw_type_error("'this' is not a Map")
|
||||
}
|
||||
|
||||
/// `Map.prototype.forEach( callbackFn [ , thisArg ] )`
|
||||
///
|
||||
/// Executes the provided callback function for each key-value pair in the map.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.foreach
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach
|
||||
pub(crate) fn for_each(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let M be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
let map = this
|
||||
.as_object()
|
||||
.filter(|obj| obj.is_map())
|
||||
.ok_or_else(|| context.construct_type_error("`this` is not a Map"))?;
|
||||
|
||||
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
|
||||
let callback = args.get_or_undefined(0);
|
||||
let callback = callback.as_callable().ok_or_else(|| {
|
||||
context.construct_type_error(format!("{} is not a function", callback.display()))
|
||||
})?;
|
||||
|
||||
let this_arg = args.get_or_undefined(1);
|
||||
|
||||
// NOTE:
|
||||
//
|
||||
// forEach does not directly mutate the object on which it is called but
|
||||
// the object may be mutated by the calls to callbackfn. Each entry of a
|
||||
// map's [[MapData]] is only visited once. New keys added after the call
|
||||
// to forEach begins are visited. A key will be revisited if it is deleted
|
||||
// after it has been visited and then re-added before the forEach call completes.
|
||||
// Keys that are deleted after the call to forEach begins and before being visited
|
||||
// are not visited unless the key is added again before the forEach call completes.
|
||||
let _lock = map
|
||||
.borrow_mut()
|
||||
.as_map_mut()
|
||||
.expect("checked that `this` was a map")
|
||||
.lock(map.clone());
|
||||
|
||||
// 4. Let entries be the List that is M.[[MapData]].
|
||||
// 5. For each Record { [[Key]], [[Value]] } e of entries, do
|
||||
let mut index = 0;
|
||||
loop {
|
||||
let arguments = {
|
||||
let map = map.borrow();
|
||||
let map = map.as_map_ref().expect("checked that `this` was a map");
|
||||
if index < map.full_len() {
|
||||
map.get_index(index)
|
||||
.map(|(k, v)| [v.clone(), k.clone(), this.clone()])
|
||||
} else {
|
||||
// 6. Return undefined.
|
||||
return Ok(JsValue::undefined());
|
||||
}
|
||||
};
|
||||
|
||||
// a. If e.[[Key]] is not empty, then
|
||||
if let Some(arguments) = arguments {
|
||||
// i. Perform ? Call(callbackfn, thisArg, « e.[[Value]], e.[[Key]], M »).
|
||||
callback.call(this_arg, &arguments, context)?;
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// `Map.prototype.values()`
|
||||
///
|
||||
/// Returns a new Iterator object that contains the values for each element in the Map object in insertion order.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.values
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values
|
||||
pub(crate) fn values(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let M be the this value.
|
||||
// 2. Return ? CreateMapIterator(M, value).
|
||||
MapIterator::create_map_iterator(this, PropertyNameKind::Value, context)
|
||||
}
|
||||
}
|
||||
|
||||
/// `AddEntriesFromIterable`
|
||||
///
|
||||
/// Allows adding entries to a map from any object that has a `@@Iterator` field.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-add-entries-from-iterable
|
||||
pub(crate) fn add_entries_from_iterable(
|
||||
target: &JsObject,
|
||||
iterable: &JsValue,
|
||||
adder: &JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If IsCallable(adder) is false, throw a TypeError exception.
|
||||
let adder = adder.as_callable().ok_or_else(|| {
|
||||
context.construct_type_error("property `set` of `NewTarget` is not callable")
|
||||
})?;
|
||||
|
||||
// 2. Let iteratorRecord be ? GetIterator(iterable).
|
||||
let iterator_record = iterable.get_iterator(context, None, None)?;
|
||||
|
||||
// 3. Repeat,
|
||||
loop {
|
||||
// a. Let next be ? IteratorStep(iteratorRecord).
|
||||
let next = iterator_record.step(context)?;
|
||||
|
||||
// b. If next is false, return target.
|
||||
// c. Let nextItem be ? IteratorValue(next).
|
||||
let next_item = if let Some(next) = next {
|
||||
next.value(context)?
|
||||
} else {
|
||||
return Ok(target.clone().into());
|
||||
};
|
||||
|
||||
let next_item = if let Some(obj) = next_item.as_object() {
|
||||
obj
|
||||
// d. If Type(nextItem) is not Object, then
|
||||
} else {
|
||||
// i. Let error be ThrowCompletion(a newly created TypeError object).
|
||||
let err = context
|
||||
.throw_type_error("cannot get key and value from primitive item of `iterable`");
|
||||
|
||||
// ii. Return ? IteratorClose(iteratorRecord, error).
|
||||
return iterator_record.close(err, context);
|
||||
};
|
||||
|
||||
// e. Let k be Get(nextItem, "0").
|
||||
// f. IfAbruptCloseIterator(k, iteratorRecord).
|
||||
let key = match next_item.get(0, context) {
|
||||
Ok(val) => val,
|
||||
err => return iterator_record.close(err, context),
|
||||
};
|
||||
|
||||
// g. Let v be Get(nextItem, "1").
|
||||
// h. IfAbruptCloseIterator(v, iteratorRecord).
|
||||
let value = match next_item.get(1, context) {
|
||||
Ok(val) => val,
|
||||
err => return iterator_record.close(err, context),
|
||||
};
|
||||
|
||||
// i. Let status be Call(adder, target, « k, v »).
|
||||
let status = adder.call(&target.clone().into(), &[key, value], context);
|
||||
|
||||
// j. IfAbruptCloseIterator(status, iteratorRecord).
|
||||
if status.is_err() {
|
||||
return iterator_record.close(status, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
use crate::{object::JsObject, JsValue};
|
||||
use boa_gc::{custom_trace, Finalize, Trace};
|
||||
use indexmap::{Equivalent, IndexMap};
|
||||
use std::{
|
||||
collections::hash_map::RandomState,
|
||||
fmt::Debug,
|
||||
hash::{BuildHasher, Hash, Hasher},
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
enum MapKey {
|
||||
Key(JsValue),
|
||||
Empty(usize), // Necessary to ensure empty keys are still unique.
|
||||
}
|
||||
|
||||
// This ensures that a MapKey::Key(value) hashes to the same as value. The derived PartialEq implementation still holds.
|
||||
#[allow(clippy::derive_hash_xor_eq)]
|
||||
impl Hash for MapKey {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
MapKey::Key(v) => v.hash(state),
|
||||
MapKey::Empty(e) => e.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Equivalent<MapKey> for JsValue {
|
||||
fn equivalent(&self, key: &MapKey) -> bool {
|
||||
match key {
|
||||
MapKey::Key(v) => v == self,
|
||||
MapKey::Empty(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure wrapping `indexmap::IndexMap`.
|
||||
#[derive(Clone)]
|
||||
pub struct OrderedMap<V, S = RandomState> {
|
||||
map: IndexMap<MapKey, Option<V>, S>,
|
||||
lock: u32,
|
||||
empty_count: usize,
|
||||
}
|
||||
|
||||
impl<V: Trace, S: BuildHasher> Finalize for OrderedMap<V, S> {}
|
||||
unsafe impl<V: Trace, S: BuildHasher> Trace for OrderedMap<V, S> {
|
||||
custom_trace!(this, {
|
||||
for (k, v) in this.map.iter() {
|
||||
if let MapKey::Key(key) = k {
|
||||
mark(key);
|
||||
}
|
||||
mark(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl<V: Debug> Debug for OrderedMap<V> {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
self.map.fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Default for OrderedMap<V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> OrderedMap<V> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: IndexMap::new(),
|
||||
lock: 0,
|
||||
empty_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
map: IndexMap::with_capacity(capacity),
|
||||
lock: 0,
|
||||
empty_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of key-value pairs in the map, including empty values.
|
||||
///
|
||||
/// Computes in **O(1)** time.
|
||||
pub fn full_len(&self) -> usize {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
/// Gets the number of key-value pairs in the map, not including empty values.
|
||||
///
|
||||
/// Computes in **O(1)** time.
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.len() - self.empty_count
|
||||
}
|
||||
|
||||
/// Returns true if the map contains no elements.
|
||||
///
|
||||
/// Computes in **O(1)** time.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Insert a key-value pair in the map.
|
||||
///
|
||||
/// If an equivalent key already exists in the map: the key remains and
|
||||
/// retains in its place in the order, its corresponding value is updated
|
||||
/// with `value` and the older value is returned inside `Some(_)`.
|
||||
///
|
||||
/// If no equivalent key existed in the map: the new key-value pair is
|
||||
/// inserted, last in order, and `None` is returned.
|
||||
///
|
||||
/// Computes in **O(1)** time (amortized average).
|
||||
pub fn insert(&mut self, key: JsValue, value: V) -> Option<V> {
|
||||
self.map.insert(MapKey::Key(key), Some(value)).flatten()
|
||||
}
|
||||
|
||||
/// Remove the key-value pair equivalent to `key` and return
|
||||
/// its value.
|
||||
///
|
||||
/// Like `Vec::remove`, the pair is removed by shifting all of the
|
||||
/// elements that follow it, preserving their relative order.
|
||||
/// **This perturbs the index of all of those elements!**
|
||||
///
|
||||
/// Return `None` if `key` is not in map.
|
||||
///
|
||||
/// Computes in **O(n)** time (average).
|
||||
pub fn remove(&mut self, key: &JsValue) -> Option<V> {
|
||||
if self.lock == 0 {
|
||||
self.map.shift_remove(key).flatten()
|
||||
} else if self.map.contains_key(key) {
|
||||
self.map.insert(MapKey::Empty(self.empty_count), None);
|
||||
self.empty_count += 1;
|
||||
self.map.swap_remove(key).flatten()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes all elements from the map and resets the counter of
|
||||
/// empty entries.
|
||||
pub fn clear(&mut self) {
|
||||
self.map.clear();
|
||||
self.map.shrink_to_fit();
|
||||
self.empty_count = 0;
|
||||
}
|
||||
|
||||
/// Return a reference to the value stored for `key`, if it is present,
|
||||
/// else `None`.
|
||||
///
|
||||
/// Computes in **O(1)** time (average).
|
||||
pub fn get(&self, key: &JsValue) -> Option<&V> {
|
||||
self.map.get(key).and_then(Option::as_ref)
|
||||
}
|
||||
|
||||
/// Get a key-value pair by index.
|
||||
///
|
||||
/// Valid indices are `0 <= index < self.full_len()`.
|
||||
///
|
||||
/// Computes in O(1) time.
|
||||
pub fn get_index(&self, index: usize) -> Option<(&JsValue, &V)> {
|
||||
if let (MapKey::Key(key), Some(value)) = self.map.get_index(index)? {
|
||||
Some((key, value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an iterator over the key-value pairs of the map, in their order
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&JsValue, &V)> {
|
||||
self.map.iter().filter_map(|o| {
|
||||
if let (MapKey::Key(key), Some(value)) = o {
|
||||
Some((key, value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if an equivalent to `key` exists in the map.
|
||||
///
|
||||
/// Computes in **O(1)** time (average).
|
||||
pub fn contains_key(&self, key: &JsValue) -> bool {
|
||||
self.map.contains_key(key)
|
||||
}
|
||||
|
||||
/// Increases the lock counter and returns a lock object that will decrement the counter when dropped.
|
||||
///
|
||||
/// This allows objects to be removed from the map during iteration without affecting the indexes until the iteration has completed.
|
||||
pub(crate) fn lock(&mut self, map: JsObject) -> MapLock {
|
||||
self.lock += 1;
|
||||
MapLock(map)
|
||||
}
|
||||
|
||||
/// Decreases the lock counter and, if 0, removes all empty entries.
|
||||
fn unlock(&mut self) {
|
||||
self.lock -= 1;
|
||||
if self.lock == 0 {
|
||||
self.map.retain(|k, _| matches!(k, MapKey::Key(_)));
|
||||
self.empty_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Increases the lock count of the map for the lifetime of the guard. This should not be dropped until iteration has completed.
|
||||
#[derive(Debug, Trace)]
|
||||
pub(crate) struct MapLock(JsObject);
|
||||
|
||||
impl Clone for MapLock {
|
||||
fn clone(&self) -> Self {
|
||||
let mut map = self.0.borrow_mut();
|
||||
let map = map.as_map_mut().expect("MapLock does not point to a map");
|
||||
map.lock(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Finalize for MapLock {
|
||||
fn finalize(&self) {
|
||||
let mut map = self.0.borrow_mut();
|
||||
let map = map.as_map_mut().expect("MapLock does not point to a map");
|
||||
map.unlock();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
use crate::{forward, Context};
|
||||
|
||||
#[test]
|
||||
fn construct_empty() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var empty = new Map();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "empty.size");
|
||||
assert_eq!(result, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_from_array() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let map = new Map([["1", "one"], ["2", "two"]]);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "map.size");
|
||||
assert_eq!(result, "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let original = new Map([["1", "one"], ["2", "two"]]);
|
||||
let clone = new Map(original);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "clone.size");
|
||||
assert_eq!(result, "2");
|
||||
let result = forward(
|
||||
&mut context,
|
||||
r#"
|
||||
original.set("3", "three");
|
||||
original.size"#,
|
||||
);
|
||||
assert_eq!(result, "3");
|
||||
let result = forward(&mut context, "clone.size");
|
||||
assert_eq!(result, "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_iterator() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
const map1 = new Map();
|
||||
map1.set('0', 'foo');
|
||||
map1.set(1, 'bar');
|
||||
const iterator = map1[Symbol.iterator]();
|
||||
let item1 = iterator.next();
|
||||
let item2 = iterator.next();
|
||||
let item3 = iterator.next();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "item1.value.length");
|
||||
assert_eq!(result, "2");
|
||||
let result = forward(&mut context, "item1.value[0]");
|
||||
assert_eq!(result, "\"0\"");
|
||||
let result = forward(&mut context, "item1.value[1]");
|
||||
assert_eq!(result, "\"foo\"");
|
||||
let result = forward(&mut context, "item1.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item2.value.length");
|
||||
assert_eq!(result, "2");
|
||||
let result = forward(&mut context, "item2.value[0]");
|
||||
assert_eq!(result, "1");
|
||||
let result = forward(&mut context, "item2.value[1]");
|
||||
assert_eq!(result, "\"bar\"");
|
||||
let result = forward(&mut context, "item2.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item3.value");
|
||||
assert_eq!(result, "undefined");
|
||||
let result = forward(&mut context, "item3.done");
|
||||
assert_eq!(result, "true");
|
||||
}
|
||||
|
||||
// Should behave the same as symbol_iterator
|
||||
#[test]
|
||||
fn entries() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
const map1 = new Map();
|
||||
map1.set('0', 'foo');
|
||||
map1.set(1, 'bar');
|
||||
const entriesIterator = map1.entries();
|
||||
let item1 = entriesIterator.next();
|
||||
let item2 = entriesIterator.next();
|
||||
let item3 = entriesIterator.next();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "item1.value.length");
|
||||
assert_eq!(result, "2");
|
||||
let result = forward(&mut context, "item1.value[0]");
|
||||
assert_eq!(result, "\"0\"");
|
||||
let result = forward(&mut context, "item1.value[1]");
|
||||
assert_eq!(result, "\"foo\"");
|
||||
let result = forward(&mut context, "item1.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item2.value.length");
|
||||
assert_eq!(result, "2");
|
||||
let result = forward(&mut context, "item2.value[0]");
|
||||
assert_eq!(result, "1");
|
||||
let result = forward(&mut context, "item2.value[1]");
|
||||
assert_eq!(result, "\"bar\"");
|
||||
let result = forward(&mut context, "item2.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item3.value");
|
||||
assert_eq!(result, "undefined");
|
||||
let result = forward(&mut context, "item3.done");
|
||||
assert_eq!(result, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let first = new Map([["1", "one"], ["2", "two"]]);
|
||||
let second = new Map([["2", "second two"], ["3", "three"]]);
|
||||
let third = new Map([["4", "four"], ["5", "five"]]);
|
||||
let merged1 = new Map([...first, ...second]);
|
||||
let merged2 = new Map([...second, ...third]);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "merged1.size");
|
||||
assert_eq!(result, "3");
|
||||
let result = forward(&mut context, "merged1.get('2')");
|
||||
assert_eq!(result, "\"second two\"");
|
||||
let result = forward(&mut context, "merged2.size");
|
||||
assert_eq!(result, "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let map = new Map([["1", "one"], ["2", "two"]]);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "map.get('1')");
|
||||
assert_eq!(result, "\"one\"");
|
||||
let result = forward(&mut context, "map.get('2')");
|
||||
assert_eq!(result, "\"two\"");
|
||||
let result = forward(&mut context, "map.get('3')");
|
||||
assert_eq!(result, "undefined");
|
||||
let result = forward(&mut context, "map.get()");
|
||||
assert_eq!(result, "undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let map = new Map();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "map.set()");
|
||||
assert_eq!(result, "Map { undefined → undefined }");
|
||||
let result = forward(&mut context, "map.set('1', 'one')");
|
||||
assert_eq!(result, "Map { undefined → undefined, \"1\" → \"one\" }");
|
||||
let result = forward(&mut context, "map.set('2')");
|
||||
assert_eq!(
|
||||
result,
|
||||
"Map { undefined → undefined, \"1\" → \"one\", \"2\" → undefined }"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let map = new Map([["1", "one"], ["2", "two"]]);
|
||||
map.clear();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "map.size");
|
||||
assert_eq!(result, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let map = new Map([["1", "one"], ["2", "two"]]);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "map.delete('1')");
|
||||
assert_eq!(result, "true");
|
||||
let result = forward(&mut context, "map.size");
|
||||
assert_eq!(result, "1");
|
||||
let result = forward(&mut context, "map.delete('1')");
|
||||
assert_eq!(result, "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let map = new Map([["1", "one"]]);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "map.has()");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "map.has('1')");
|
||||
assert_eq!(result, "true");
|
||||
let result = forward(&mut context, "map.has('2')");
|
||||
assert_eq!(result, "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
const map1 = new Map();
|
||||
map1.set('0', 'foo');
|
||||
map1.set(1, 'bar');
|
||||
const keysIterator = map1.keys();
|
||||
let item1 = keysIterator.next();
|
||||
let item2 = keysIterator.next();
|
||||
let item3 = keysIterator.next();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "item1.value");
|
||||
assert_eq!(result, "\"0\"");
|
||||
let result = forward(&mut context, "item1.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item2.value");
|
||||
assert_eq!(result, "1");
|
||||
let result = forward(&mut context, "item2.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item3.value");
|
||||
assert_eq!(result, "undefined");
|
||||
let result = forward(&mut context, "item3.done");
|
||||
assert_eq!(result, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_each() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let map = new Map([[1, 5], [2, 10], [3, 15]]);
|
||||
let valueSum = 0;
|
||||
let keySum = 0;
|
||||
let sizeSum = 0;
|
||||
function callingCallback(value, key, map) {
|
||||
valueSum += value;
|
||||
keySum += key;
|
||||
sizeSum += map.size;
|
||||
}
|
||||
map.forEach(callingCallback);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
assert_eq!(forward(&mut context, "valueSum"), "30");
|
||||
assert_eq!(forward(&mut context, "keySum"), "6");
|
||||
assert_eq!(forward(&mut context, "sizeSum"), "9");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn values() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
const map1 = new Map();
|
||||
map1.set('0', 'foo');
|
||||
map1.set(1, 'bar');
|
||||
const valuesIterator = map1.values();
|
||||
let item1 = valuesIterator.next();
|
||||
let item2 = valuesIterator.next();
|
||||
let item3 = valuesIterator.next();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "item1.value");
|
||||
assert_eq!(result, "\"foo\"");
|
||||
let result = forward(&mut context, "item1.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item2.value");
|
||||
assert_eq!(result, "\"bar\"");
|
||||
let result = forward(&mut context, "item2.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item3.value");
|
||||
assert_eq!(result, "undefined");
|
||||
let result = forward(&mut context, "item3.done");
|
||||
assert_eq!(result, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modify_key() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let obj = new Object();
|
||||
let map = new Map([[obj, "one"]]);
|
||||
obj.field = "Value";
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "map.get(obj)");
|
||||
assert_eq!(result, "\"one\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn order() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let map = new Map([[1, "one"]]);
|
||||
map.set(2, "two");
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "map");
|
||||
assert_eq!(result, "Map { 1 → \"one\", 2 → \"two\" }");
|
||||
let result = forward(&mut context, "map.set(1, \"five\");map");
|
||||
assert_eq!(result, "Map { 1 → \"five\", 2 → \"two\" }");
|
||||
let result = forward(&mut context, "map.set();map");
|
||||
assert_eq!(
|
||||
result,
|
||||
"Map { 1 → \"five\", 2 → \"two\", undefined → undefined }"
|
||||
);
|
||||
let result = forward(&mut context, "map.delete(2);map");
|
||||
assert_eq!(result, "Map { 1 → \"five\", undefined → undefined }");
|
||||
let result = forward(&mut context, "map.set(2, \"two\");map");
|
||||
assert_eq!(
|
||||
result,
|
||||
"Map { 1 → \"five\", undefined → undefined, 2 → \"two\" }"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_display() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let map = new Map();
|
||||
let array = new Array([map]);
|
||||
map.set("y", map);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "map");
|
||||
assert_eq!(result, "Map { \"y\" → Map(1) }");
|
||||
let result = forward(&mut context, "map.set(\"z\", array)");
|
||||
assert_eq!(result, "Map { \"y\" → Map(2), \"z\" → Array(1) }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_a_function() {
|
||||
let mut context = Context::default();
|
||||
let init = r"
|
||||
try {
|
||||
let map = Map()
|
||||
} catch(e) {
|
||||
e.toString()
|
||||
}
|
||||
";
|
||||
assert_eq!(
|
||||
forward(&mut context, init),
|
||||
"\"TypeError: calling a builtin Map constructor without new is forbidden\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_each_delete() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let map = new Map([[0, "a"], [1, "b"], [2, "c"]]);
|
||||
let result = [];
|
||||
map.forEach(function(value, key) {
|
||||
if (key === 0) {
|
||||
map.delete(0);
|
||||
map.set(3, "d");
|
||||
}
|
||||
result.push([key, value]);
|
||||
})
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
assert_eq!(forward(&mut context, "result[0][0]"), "0");
|
||||
assert_eq!(forward(&mut context, "result[0][1]"), "\"a\"");
|
||||
assert_eq!(forward(&mut context, "result[1][0]"), "1");
|
||||
assert_eq!(forward(&mut context, "result[1][1]"), "\"b\"");
|
||||
assert_eq!(forward(&mut context, "result[2][0]"), "2");
|
||||
assert_eq!(forward(&mut context, "result[2][1]"), "\"c\"");
|
||||
assert_eq!(forward(&mut context, "result[3][0]"), "3");
|
||||
assert_eq!(forward(&mut context, "result[3][1]"), "\"d\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_delete() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let map = new Map([[0, "a"], [1, "b"], [2, "c"]]);
|
||||
let result = [];
|
||||
for (a of map) {
|
||||
if (a[0] === 0) {
|
||||
map.delete(0);
|
||||
map.set(3, "d");
|
||||
}
|
||||
result.push([a[0], a[1]]);
|
||||
}
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
assert_eq!(forward(&mut context, "result[0][0]"), "0");
|
||||
assert_eq!(forward(&mut context, "result[0][1]"), "\"a\"");
|
||||
assert_eq!(forward(&mut context, "result[1][0]"), "1");
|
||||
assert_eq!(forward(&mut context, "result[1][1]"), "\"b\"");
|
||||
assert_eq!(forward(&mut context, "result[2][0]"), "2");
|
||||
assert_eq!(forward(&mut context, "result[2][1]"), "\"c\"");
|
||||
assert_eq!(forward(&mut context, "result[3][0]"), "3");
|
||||
assert_eq!(forward(&mut context, "result[3][1]"), "\"d\"");
|
||||
}
|
||||
@@ -0,0 +1,889 @@
|
||||
//! This module implements the global `Math` object.
|
||||
//!
|
||||
//! `Math` is a built-in object that has properties and methods for mathematical constants and functions. It’s not a function object.
|
||||
//!
|
||||
//! `Math` works with the `Number` type. It doesn't work with `BigInt`.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-math-object
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math
|
||||
|
||||
use super::JsArgs;
|
||||
use crate::{
|
||||
builtins::BuiltIn, object::ObjectInitializer, property::Attribute, symbol::WellKnownSymbols,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Javascript `Math` object.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) struct Math;
|
||||
|
||||
impl BuiltIn for Math {
|
||||
const NAME: &'static str = "Math";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
|
||||
let string_tag = WellKnownSymbols::to_string_tag();
|
||||
ObjectInitializer::new(context)
|
||||
.property("E", std::f64::consts::E, attribute)
|
||||
.property("LN10", std::f64::consts::LN_10, attribute)
|
||||
.property("LN2", std::f64::consts::LN_2, attribute)
|
||||
.property("LOG10E", std::f64::consts::LOG10_E, attribute)
|
||||
.property("LOG2E", std::f64::consts::LOG2_E, attribute)
|
||||
.property("PI", std::f64::consts::PI, attribute)
|
||||
.property("SQRT1_2", std::f64::consts::FRAC_1_SQRT_2, attribute)
|
||||
.property("SQRT2", std::f64::consts::SQRT_2, attribute)
|
||||
.function(Self::abs, "abs", 1)
|
||||
.function(Self::acos, "acos", 1)
|
||||
.function(Self::acosh, "acosh", 1)
|
||||
.function(Self::asin, "asin", 1)
|
||||
.function(Self::asinh, "asinh", 1)
|
||||
.function(Self::atan, "atan", 1)
|
||||
.function(Self::atanh, "atanh", 1)
|
||||
.function(Self::atan2, "atan2", 2)
|
||||
.function(Self::cbrt, "cbrt", 1)
|
||||
.function(Self::ceil, "ceil", 1)
|
||||
.function(Self::clz32, "clz32", 1)
|
||||
.function(Self::cos, "cos", 1)
|
||||
.function(Self::cosh, "cosh", 1)
|
||||
.function(Self::exp, "exp", 1)
|
||||
.function(Self::expm1, "expm1", 1)
|
||||
.function(Self::floor, "floor", 1)
|
||||
.function(Self::fround, "fround", 1)
|
||||
.function(Self::hypot, "hypot", 2)
|
||||
.function(Self::imul, "imul", 2)
|
||||
.function(Self::log, "log", 1)
|
||||
.function(Self::log1p, "log1p", 1)
|
||||
.function(Self::log10, "log10", 1)
|
||||
.function(Self::log2, "log2", 1)
|
||||
.function(Self::max, "max", 2)
|
||||
.function(Self::min, "min", 2)
|
||||
.function(Self::pow, "pow", 2)
|
||||
.function(Self::random, "random", 0)
|
||||
.function(Self::round, "round", 1)
|
||||
.function(Self::sign, "sign", 1)
|
||||
.function(Self::sin, "sin", 1)
|
||||
.function(Self::sinh, "sinh", 1)
|
||||
.function(Self::sqrt, "sqrt", 1)
|
||||
.function(Self::tan, "tan", 1)
|
||||
.function(Self::tanh, "tanh", 1)
|
||||
.function(Self::trunc, "trunc", 1)
|
||||
.property(
|
||||
string_tag,
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Math {
|
||||
/// Get the absolute value of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.abs
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs
|
||||
pub(crate) fn abs(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 3. If n is -0𝔽, return +0𝔽.
|
||||
// 2. If n is NaN, return NaN.
|
||||
// 4. If n is -∞𝔽, return +∞𝔽.
|
||||
// 5. If n < +0𝔽, return -n.
|
||||
// 6. Return n.
|
||||
.abs()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the arccos of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.acos
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos
|
||||
pub(crate) fn acos(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n > 1𝔽, or n < -1𝔽, return NaN.
|
||||
// 3. If n is 1𝔽, return +0𝔽.
|
||||
// 4. Return an implementation-approximated value representing the result of the inverse cosine of ℝ(n).
|
||||
.acos()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the hyperbolic arccos of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.acosh
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh
|
||||
pub(crate) fn acosh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 4. If n < 1𝔽, return NaN.
|
||||
// 2. If n is NaN or n is +∞𝔽, return n.
|
||||
// 3. If n is 1𝔽, return +0𝔽.
|
||||
// 5. Return an implementation-approximated value representing the result of the inverse hyperbolic cosine of ℝ(n).
|
||||
.acosh()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the arcsine of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.asin
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin
|
||||
pub(crate) fn asin(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n.
|
||||
// 3. If n > 1𝔽 or n < -1𝔽, return NaN.
|
||||
// 4. Return an implementation-approximated value representing the result of the inverse sine of ℝ(n).
|
||||
.asin()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the hyperbolic arcsine of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.asinh
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asinh
|
||||
pub(crate) fn asinh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n.
|
||||
// 3. Return an implementation-approximated value representing the result of the inverse hyperbolic sine of ℝ(n).
|
||||
.asinh()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the arctangent of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.atan
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan
|
||||
pub(crate) fn atan(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n.
|
||||
// 3. If n is +∞𝔽, return an implementation-approximated value representing π / 2.
|
||||
// 4. If n is -∞𝔽, return an implementation-approximated value representing -π / 2.
|
||||
// 5. Return an implementation-approximated value representing the result of the inverse tangent of ℝ(n).
|
||||
.atan()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the hyperbolic arctangent of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.atanh
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh
|
||||
pub(crate) fn atanh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n.
|
||||
// 3. If n > 1𝔽 or n < -1𝔽, return NaN.
|
||||
// 4. If n is 1𝔽, return +∞𝔽.
|
||||
// 5. If n is -1𝔽, return -∞𝔽.
|
||||
// 6. Return an implementation-approximated value representing the result of the inverse hyperbolic tangent of ℝ(n).
|
||||
.atanh()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the four quadrant arctangent of the quotient y / x.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.atan2
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2
|
||||
pub(crate) fn atan2(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let ny be ? ToNumber(y).
|
||||
let y = args.get_or_undefined(0).to_number(context)?;
|
||||
|
||||
// 2. Let nx be ? ToNumber(x).
|
||||
let x = args.get_or_undefined(1).to_number(context)?;
|
||||
|
||||
// 4. If ny is +∞𝔽, then
|
||||
// a. If nx is +∞𝔽, return an implementation-approximated value representing π / 4.
|
||||
// b. If nx is -∞𝔽, return an implementation-approximated value representing 3π / 4.
|
||||
// c. Return an implementation-approximated value representing π / 2.
|
||||
// 5. If ny is -∞𝔽, then
|
||||
// a. If nx is +∞𝔽, return an implementation-approximated value representing -π / 4.
|
||||
// b. If nx is -∞𝔽, return an implementation-approximated value representing -3π / 4.
|
||||
// c. Return an implementation-approximated value representing -π / 2.
|
||||
// 6. If ny is +0𝔽, then
|
||||
// a. If nx > +0𝔽 or nx is +0𝔽, return +0𝔽.
|
||||
// b. Return an implementation-approximated value representing π.
|
||||
// 7. If ny is -0𝔽, then
|
||||
// a. If nx > +0𝔽 or nx is +0𝔽, return -0𝔽.
|
||||
// b. Return an implementation-approximated value representing -π.
|
||||
// 8. Assert: ny is finite and is neither +0𝔽 nor -0𝔽.
|
||||
// 9. If ny > +0𝔽, then
|
||||
// a. If nx is +∞𝔽, return +0𝔽.
|
||||
// b. If nx is -∞𝔽, return an implementation-approximated value representing π.
|
||||
// c. If nx is +0𝔽 or nx is -0𝔽, return an implementation-approximated value representing π / 2.
|
||||
// 10. If ny < +0𝔽, then
|
||||
// a. If nx is +∞𝔽, return -0𝔽.
|
||||
// b. If nx is -∞𝔽, return an implementation-approximated value representing -π.
|
||||
// c. If nx is +0𝔽 or nx is -0𝔽, return an implementation-approximated value representing -π / 2.
|
||||
// 11. Assert: nx is finite and is neither +0𝔽 nor -0𝔽.
|
||||
// 12. Return an implementation-approximated value representing the result of the inverse tangent of the quotient ℝ(ny) / ℝ(nx).
|
||||
Ok(y.atan2(x).into())
|
||||
}
|
||||
|
||||
/// Get the cubic root of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.cbrt
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt
|
||||
pub(crate) fn cbrt(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n.
|
||||
// 3. Return an implementation-approximated value representing the result of the cube root of ℝ(n).
|
||||
.cbrt()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get lowest integer above a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.ceil
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil
|
||||
pub(crate) fn ceil(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n.
|
||||
// 3. If n < +0𝔽 and n > -1𝔽, return -0𝔽.
|
||||
// 4. If n is an integral Number, return n.
|
||||
// 5. Return the smallest (closest to -∞) integral Number value that is not less than n.
|
||||
.ceil()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the number of leading zeros in the 32 bit representation of a number
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.clz32
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32
|
||||
pub(crate) fn clz32(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToUint32(x).
|
||||
.to_u32(context)?
|
||||
// 2. Let p be the number of leading zero bits in the unsigned 32-bit binary representation of n.
|
||||
// 3. Return 𝔽(p).
|
||||
.leading_zeros()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the cosine of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.cos
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos
|
||||
pub(crate) fn cos(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +∞𝔽, or n is -∞𝔽, return NaN.
|
||||
// 3. If n is +0𝔽 or n is -0𝔽, return 1𝔽.
|
||||
// 4. Return an implementation-approximated value representing the result of the cosine of ℝ(n).
|
||||
.cos()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the hyperbolic cosine of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.cosh
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh
|
||||
pub(crate) fn cosh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, return NaN.
|
||||
// 3. If n is +∞𝔽 or n is -∞𝔽, return +∞𝔽.
|
||||
// 4. If n is +0𝔽 or n is -0𝔽, return 1𝔽.
|
||||
// 5. Return an implementation-approximated value representing the result of the hyperbolic cosine of ℝ(n).
|
||||
.cosh()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the power to raise the natural logarithm to get the number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.exp
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp
|
||||
pub(crate) fn exp(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN or n is +∞𝔽, return n.
|
||||
// 3. If n is +0𝔽 or n is -0𝔽, return 1𝔽.
|
||||
// 4. If n is -∞𝔽, return +0𝔽.
|
||||
// 5. Return an implementation-approximated value representing the result of the exponential function of ℝ(n).
|
||||
.exp()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// The Math.expm1() function returns e^x - 1, where x is the argument, and e the base of
|
||||
/// the natural logarithms. The result is computed in a way that is accurate even when the
|
||||
/// value of x is close 0
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.expm1
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/expm1
|
||||
pub(crate) fn expm1(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, n is -0𝔽, or n is +∞𝔽, return n.
|
||||
// 3. If n is -∞𝔽, return -1𝔽.
|
||||
// 4. Return an implementation-approximated value representing the result of subtracting 1 from the exponential function of ℝ(n).
|
||||
.exp_m1()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the highest integer below a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.floor
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor
|
||||
pub(crate) fn floor(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n.
|
||||
// 3. If n < 1𝔽 and n > +0𝔽, return +0𝔽.
|
||||
// 4. If n is an integral Number, return n.
|
||||
// 5. Return the greatest (closest to +∞) integral Number value that is not greater than n.
|
||||
.floor()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the nearest 32-bit single precision float representation of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.fround
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround
|
||||
pub(crate) fn fround(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
let x = args.get_or_undefined(0).to_number(context)?;
|
||||
|
||||
// 2. If n is NaN, return NaN.
|
||||
// 3. If n is one of +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return n.
|
||||
// 4. Let n32 be the result of converting n to a value in IEEE 754-2019 binary32 format using roundTiesToEven mode.
|
||||
// 5. Let n64 be the result of converting n32 to a value in IEEE 754-2019 binary64 format.
|
||||
// 6. Return the ECMAScript Number value corresponding to n64.
|
||||
Ok(f64::from(x as f32).into())
|
||||
}
|
||||
|
||||
/// Get an approximation of the square root of the sum of squares of all arguments.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.hypot
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot
|
||||
pub(crate) fn hypot(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let coerced be a new empty List.
|
||||
// 2. For each element arg of args, do
|
||||
// a. Let n be ? ToNumber(arg).
|
||||
// b. Append n to coerced.
|
||||
// 3. For each element number of coerced, do
|
||||
// 5. For each element number of coerced, do
|
||||
let mut result = 0f64;
|
||||
for arg in args {
|
||||
let num = arg.to_number(context)?;
|
||||
// a. If number is +∞𝔽 or number is -∞𝔽, return +∞𝔽.
|
||||
// 4. Let onlyZero be true.
|
||||
// a. If number is NaN, return NaN.
|
||||
// b. If number is neither +0𝔽 nor -0𝔽, set onlyZero to false.
|
||||
// 6. If onlyZero is true, return +0𝔽.
|
||||
// 7. Return an implementation-approximated value representing the square root of the sum of squares of the mathematical values of the elements of coerced.
|
||||
result = result.hypot(num);
|
||||
}
|
||||
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
/// Get the result of the C-like 32-bit multiplication of the two parameters.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.imul
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul
|
||||
pub(crate) fn imul(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let a be ℝ(? ToUint32(x)).
|
||||
let x = args.get_or_undefined(0).to_u32(context)?;
|
||||
|
||||
// 2. Let b be ℝ(? ToUint32(y)).
|
||||
let y = args.get_or_undefined(1).to_u32(context)?;
|
||||
|
||||
// 3. Let product be (a × b) modulo 2^32.
|
||||
// 4. If product ≥ 2^31, return 𝔽(product - 2^32); otherwise return 𝔽(product).
|
||||
Ok((x.wrapping_mul(y) as i32).into())
|
||||
}
|
||||
|
||||
/// Get the natural logarithm of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.log
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log
|
||||
pub(crate) fn log(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN or n is +∞𝔽, return n.
|
||||
// 3. If n is 1𝔽, return +0𝔽.
|
||||
// 4. If n is +0𝔽 or n is -0𝔽, return -∞𝔽.
|
||||
// 5. If n < +0𝔽, return NaN.
|
||||
// 6. Return an implementation-approximated value representing the result of the natural logarithm of ℝ(n).
|
||||
.ln()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get approximation to the natural logarithm of 1 + x.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.log1p
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log1p
|
||||
pub(crate) fn log1p(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, n is -0𝔽, or n is +∞𝔽, return n.
|
||||
// 3. If n is -1𝔽, return -∞𝔽.
|
||||
// 4. If n < -1𝔽, return NaN.
|
||||
// 5. Return an implementation-approximated value representing the result of the natural logarithm of 1 + ℝ(n).
|
||||
.ln_1p()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the base 10 logarithm of the number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.log10
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10
|
||||
pub(crate) fn log10(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN or n is +∞𝔽, return n.
|
||||
// 3. If n is 1𝔽, return +0𝔽.
|
||||
// 4. If n is +0𝔽 or n is -0𝔽, return -∞𝔽.
|
||||
// 5. If n < +0𝔽, return NaN.
|
||||
// 6. Return an implementation-approximated value representing the result of the base 10 logarithm of ℝ(n).
|
||||
.log10()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the base 2 logarithm of the number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.log2
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2
|
||||
pub(crate) fn log2(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN or n is +∞𝔽, return n.
|
||||
// 3. If n is 1𝔽, return +0𝔽.
|
||||
// 4. If n is +0𝔽 or n is -0𝔽, return -∞𝔽.
|
||||
// 5. If n < +0𝔽, return NaN.
|
||||
// 6. Return an implementation-approximated value representing the result of the base 2 logarithm of ℝ(n).
|
||||
.log2()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the maximum of several numbers.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.max
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max
|
||||
pub(crate) fn max(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let coerced be a new empty List.
|
||||
// 2. For each element arg of args, do
|
||||
// b. Append n to coerced.
|
||||
// 3. Let highest be -∞𝔽.
|
||||
let mut highest = f64::NEG_INFINITY;
|
||||
|
||||
// 4. For each element number of coerced, do
|
||||
for arg in args {
|
||||
// a. Let n be ? ToNumber(arg).
|
||||
let num = arg.to_number(context)?;
|
||||
|
||||
highest = if highest.is_nan() {
|
||||
continue;
|
||||
} else if num.is_nan() {
|
||||
// a. If number is NaN, return NaN.
|
||||
f64::NAN
|
||||
} else {
|
||||
match (highest, num) {
|
||||
// b. When x and y are +0𝔽 -0𝔽, return +0𝔽.
|
||||
(x, y) if x == 0f64 && y == 0f64 && x.signum() != y.signum() => 0f64,
|
||||
// c. Otherwise, return the maximum value.
|
||||
(x, y) => x.max(y),
|
||||
}
|
||||
};
|
||||
}
|
||||
// 5. Return highest.
|
||||
Ok(highest.into())
|
||||
}
|
||||
|
||||
/// Get the minimum of several numbers.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.min
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min
|
||||
pub(crate) fn min(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let coerced be a new empty List.
|
||||
// 2. For each element arg of args, do
|
||||
// b. Append n to coerced.
|
||||
// 3. Let lowest be +∞𝔽.
|
||||
let mut lowest = f64::INFINITY;
|
||||
|
||||
// 4. For each element number of coerced, do
|
||||
for arg in args {
|
||||
// a. Let n be ? ToNumber(arg).
|
||||
let num = arg.to_number(context)?;
|
||||
|
||||
lowest = if lowest.is_nan() {
|
||||
continue;
|
||||
} else if num.is_nan() {
|
||||
// a. If number is NaN, return NaN.
|
||||
f64::NAN
|
||||
} else {
|
||||
match (lowest, num) {
|
||||
// b. When x and y are +0𝔽 -0𝔽, return -0𝔽.
|
||||
(x, y) if x == 0f64 && y == 0f64 && x.signum() != y.signum() => -0f64,
|
||||
// c. Otherwise, return the minimum value.
|
||||
(x, y) => x.min(y),
|
||||
}
|
||||
};
|
||||
}
|
||||
// 5. Return lowest.
|
||||
Ok(lowest.into())
|
||||
}
|
||||
|
||||
/// Raise a number to a power.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.pow
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow
|
||||
#[allow(clippy::float_cmp)]
|
||||
pub(crate) fn pow(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Set base to ? ToNumber(base).
|
||||
let x = args.get_or_undefined(0).to_number(context)?;
|
||||
|
||||
// 2. Set exponent to ? ToNumber(exponent).
|
||||
let y = args.get_or_undefined(1).to_number(context)?;
|
||||
|
||||
// 3. If |x| = 1 and the exponent is infinite, return NaN.
|
||||
if f64::abs(x) == 1f64 && y.is_infinite() {
|
||||
return Ok(f64::NAN.into());
|
||||
}
|
||||
|
||||
// 4. Return ! Number::exponentiate(base, exponent).
|
||||
Ok(x.powf(y).into())
|
||||
}
|
||||
|
||||
/// Generate a random floating-point number between `0` and `1`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.random
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn random(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
|
||||
// NOTE: Each Math.random function created for distinct realms must produce a distinct sequence of values from successive calls.
|
||||
Ok(rand::random::<f64>().into())
|
||||
}
|
||||
|
||||
/// Round a number to the nearest integer.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.round
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
|
||||
#[allow(clippy::float_cmp)]
|
||||
pub(crate) fn round(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let num = args
|
||||
.get_or_undefined(0)
|
||||
//1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?;
|
||||
|
||||
//2. If n is NaN, +∞𝔽, -∞𝔽, or an integral Number, return n.
|
||||
//3. If n < 0.5𝔽 and n > +0𝔽, return +0𝔽.
|
||||
//4. If n < +0𝔽 and n ≥ -0.5𝔽, return -0𝔽.
|
||||
//5. Return the integral Number closest to n, preferring the Number closer to +∞ in the case of a tie.
|
||||
|
||||
if num.fract() == -0.5 {
|
||||
Ok(num.ceil().into())
|
||||
} else {
|
||||
Ok(num.round().into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the sign of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.sign
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign
|
||||
pub(crate) fn sign(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
let n = args.get_or_undefined(0).to_number(context)?;
|
||||
|
||||
// 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n.
|
||||
if n == 0f64 {
|
||||
return Ok(n.into());
|
||||
}
|
||||
// 3. If n < +0𝔽, return -1𝔽.
|
||||
// 4. Return 1𝔽.
|
||||
Ok(n.signum().into())
|
||||
}
|
||||
|
||||
/// Get the sine of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.sin
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin
|
||||
pub(crate) fn sin(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n.
|
||||
// 3. If n is +∞𝔽 or n is -∞𝔽, return NaN.
|
||||
// 4. Return an implementation-approximated value representing the result of the sine of ℝ(n).
|
||||
.sin()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the hyperbolic sine of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.sinh
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh
|
||||
pub(crate) fn sinh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n.
|
||||
// 3. Return an implementation-approximated value representing the result of the hyperbolic sine of ℝ(n).
|
||||
.sinh()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the square root of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.sqrt
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt
|
||||
pub(crate) fn sqrt(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, n is -0𝔽, or n is +∞𝔽, return n.
|
||||
// 3. If n < +0𝔽, return NaN.
|
||||
// 4. Return an implementation-approximated value representing the result of the square root of ℝ(n).
|
||||
.sqrt()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the tangent of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.tan
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan
|
||||
pub(crate) fn tan(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n.
|
||||
// 3. If n is +∞𝔽, or n is -∞𝔽, return NaN.
|
||||
// 4. Return an implementation-approximated value representing the result of the tangent of ℝ(n).
|
||||
.tan()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the hyperbolic tangent of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.tanh
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh
|
||||
pub(crate) fn tanh(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, or n is -0𝔽, return n.
|
||||
// 3. If n is +∞𝔽, return 1𝔽.
|
||||
// 4. If n is -∞𝔽, return -1𝔽.
|
||||
// 5. Return an implementation-approximated value representing the result of the hyperbolic tangent of ℝ(n).
|
||||
.tanh()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Get the integer part of a number.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-math.trunc
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc
|
||||
pub(crate) fn trunc(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
// 1. Let n be ? ToNumber(x).
|
||||
.to_number(context)?
|
||||
// 2. If n is NaN, n is +0𝔽, n is -0𝔽, n is +∞𝔽, or n is -∞𝔽, return n.
|
||||
// 3. If n < 1𝔽 and n > +0𝔽, return +0𝔽.
|
||||
// 4. If n < +0𝔽 and n > -1𝔽, return -0𝔽.
|
||||
// 5. Return the integral Number nearest n in the direction of +0𝔽.
|
||||
.trunc()
|
||||
.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,729 @@
|
||||
#![allow(clippy::float_cmp)]
|
||||
|
||||
use crate::{forward, forward_val, Context};
|
||||
use std::f64;
|
||||
|
||||
#[test]
|
||||
fn abs() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.abs(3 - 5);
|
||||
var b = Math.abs(1.23456 - 7.89012);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 2.0);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 6.655_559_999_999_999_5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn acos() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.acos(8 / 10);
|
||||
var b = Math.acos(5 / 3);
|
||||
var c = Math.acos(1);
|
||||
var d = Math.acos(2);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward(&mut context, "b");
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
let d = forward(&mut context, "d");
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 0.643_501_108_793_284_3);
|
||||
assert_eq!(b, "NaN");
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(d, "NaN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn acosh() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.acosh(2);
|
||||
var b = Math.acosh(-1);
|
||||
var c = Math.acosh(0.5);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward(&mut context, "b");
|
||||
let c = forward(&mut context, "c");
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 1.316_957_896_924_816_6);
|
||||
assert_eq!(b, "NaN");
|
||||
assert_eq!(c, "NaN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asin() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.asin(6 / 10);
|
||||
var b = Math.asin(5 / 3);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward(&mut context, "b");
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 0.643_501_108_793_284_4);
|
||||
assert_eq!(b, String::from("NaN"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asinh() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.asinh(1);
|
||||
var b = Math.asinh(0);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 0.881_373_587_019_542_9);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 0_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atan() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.atan(1);
|
||||
var b = Math.atan(0);
|
||||
var c = Math.atan(-0);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), f64::consts::FRAC_PI_4);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), f64::from(-0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atan2() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.atan2(90, 15);
|
||||
var b = Math.atan2(15, 90);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 1.405_647_649_380_269_9);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 0.165_148_677_414_626_83);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cbrt() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.cbrt(64);
|
||||
var b = Math.cbrt(-1);
|
||||
var c = Math.cbrt(1);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 4_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), -1_f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 1_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ceil() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.ceil(1.95);
|
||||
var b = Math.ceil(4);
|
||||
var c = Math.ceil(-7.004);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 2_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 4_f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), -7_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn clz32() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.clz32();
|
||||
var b = Math.clz32({});
|
||||
var c = Math.clz32(-173);
|
||||
var d = Math.clz32("1");
|
||||
var e = Math.clz32(2147483647);
|
||||
var f = Math.clz32(Infinity);
|
||||
var g = Math.clz32(true);
|
||||
var h = Math.clz32(0);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
let d = forward_val(&mut context, "d").unwrap();
|
||||
let e = forward_val(&mut context, "e").unwrap();
|
||||
let f = forward_val(&mut context, "f").unwrap();
|
||||
let g = forward_val(&mut context, "g").unwrap();
|
||||
let h = forward_val(&mut context, "h").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 32_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 32_f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(d.to_number(&mut context).unwrap(), 31_f64);
|
||||
assert_eq!(e.to_number(&mut context).unwrap(), 1_f64);
|
||||
assert_eq!(f.to_number(&mut context).unwrap(), 32_f64);
|
||||
assert_eq!(g.to_number(&mut context).unwrap(), 31_f64);
|
||||
assert_eq!(h.to_number(&mut context).unwrap(), 32_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cos() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.cos(0);
|
||||
var b = Math.cos(1);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 1_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 0.540_302_305_868_139_8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cosh() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.cosh(0);
|
||||
var b = Math.cosh(1);
|
||||
var c = Math.cosh(-1);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 1_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 1.543_080_634_815_243_7);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 1.543_080_634_815_243_7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exp() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.exp(0);
|
||||
var b = Math.exp(-1);
|
||||
var c = Math.exp(2);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 1_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 0.367_879_441_171_442_33);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 7.389_056_098_930_65);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn expm1() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.expm1();
|
||||
var b = Math.expm1({});
|
||||
var c = Math.expm1(1);
|
||||
var d = Math.expm1(-1);
|
||||
var e = Math.expm1(0);
|
||||
var f = Math.expm1(2);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward(&mut context, "a");
|
||||
let b = forward(&mut context, "b");
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
let d = forward_val(&mut context, "d").unwrap();
|
||||
let e = forward_val(&mut context, "e").unwrap();
|
||||
let f = forward_val(&mut context, "f").unwrap();
|
||||
|
||||
assert_eq!(a, String::from("NaN"));
|
||||
assert_eq!(b, String::from("NaN"));
|
||||
assert!(float_cmp::approx_eq!(
|
||||
f64,
|
||||
c.to_number(&mut context).unwrap(),
|
||||
1.718_281_828_459_045
|
||||
));
|
||||
assert!(float_cmp::approx_eq!(
|
||||
f64,
|
||||
d.to_number(&mut context).unwrap(),
|
||||
-0.632_120_558_828_557_7
|
||||
));
|
||||
assert!(float_cmp::approx_eq!(
|
||||
f64,
|
||||
e.to_number(&mut context).unwrap(),
|
||||
0_f64
|
||||
));
|
||||
assert!(float_cmp::approx_eq!(
|
||||
f64,
|
||||
f.to_number(&mut context).unwrap(),
|
||||
6.389_056_098_930_65
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floor() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.floor(1.95);
|
||||
var b = Math.floor(-3.01);
|
||||
var c = Math.floor(3.01);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 1_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), -4_f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 3_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn fround() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.fround(NaN);
|
||||
var b = Math.fround(Infinity);
|
||||
var c = Math.fround(5);
|
||||
var d = Math.fround(5.5);
|
||||
var e = Math.fround(5.05);
|
||||
var f = Math.fround(-5.05);
|
||||
var g = Math.fround();
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward(&mut context, "a");
|
||||
let b = forward(&mut context, "b");
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
let d = forward_val(&mut context, "d").unwrap();
|
||||
let e = forward_val(&mut context, "e").unwrap();
|
||||
let f = forward_val(&mut context, "f").unwrap();
|
||||
let g = forward(&mut context, "g");
|
||||
|
||||
assert_eq!(a, String::from("NaN"));
|
||||
assert_eq!(b, String::from("Infinity"));
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 5f64);
|
||||
assert_eq!(d.to_number(&mut context).unwrap(), 5.5f64);
|
||||
assert_eq!(e.to_number(&mut context).unwrap(), 5.050_000_190_734_863);
|
||||
assert_eq!(f.to_number(&mut context).unwrap(), -5.050_000_190_734_863);
|
||||
assert_eq!(g, String::from("NaN"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn hypot() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.hypot();
|
||||
var b = Math.hypot(3, 4);
|
||||
var c = Math.hypot(5, 12);
|
||||
var d = Math.hypot(3, 4, -5);
|
||||
var e = Math.hypot(4, [5], 6);
|
||||
var f = Math.hypot(3, -Infinity);
|
||||
var g = Math.hypot(12);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
let d = forward_val(&mut context, "d").unwrap();
|
||||
let e = forward_val(&mut context, "e").unwrap();
|
||||
let f = forward_val(&mut context, "f").unwrap();
|
||||
let g = forward_val(&mut context, "g").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 0f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 5f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 13f64);
|
||||
assert_eq!(d.to_number(&mut context).unwrap(), 7.071_067_811_865_475_5);
|
||||
assert_eq!(e.to_number(&mut context).unwrap(), 8.774964387392123);
|
||||
assert!(f.to_number(&mut context).unwrap().is_infinite());
|
||||
assert_eq!(g.to_number(&mut context).unwrap(), 12f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn imul() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.imul(3, 4);
|
||||
var b = Math.imul(-5, 12);
|
||||
var c = Math.imul(0xffffffff, 5);
|
||||
var d = Math.imul(0xfffffffe, 5);
|
||||
var e = Math.imul(12);
|
||||
var f = Math.imul();
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
let d = forward_val(&mut context, "d").unwrap();
|
||||
let e = forward_val(&mut context, "e").unwrap();
|
||||
let f = forward_val(&mut context, "f").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 12f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), -60f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), -5f64);
|
||||
assert_eq!(d.to_number(&mut context).unwrap(), -10f64);
|
||||
assert_eq!(e.to_number(&mut context).unwrap(), 0f64);
|
||||
assert_eq!(f.to_number(&mut context).unwrap(), 0f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn log() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.log(1);
|
||||
var b = Math.log(10);
|
||||
var c = Math.log(-1);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward(&mut context, "c");
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), f64::consts::LN_10);
|
||||
assert_eq!(c, String::from("NaN"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn log1p() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.log1p(1);
|
||||
var b = Math.log1p(0);
|
||||
var c = Math.log1p(-0.9999999999999999);
|
||||
var d = Math.log1p(-1);
|
||||
var e = Math.log1p(-1.000000000000001);
|
||||
var f = Math.log1p(-2);
|
||||
var g = Math.log1p();
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
let d = forward(&mut context, "d");
|
||||
let e = forward(&mut context, "e");
|
||||
let f = forward(&mut context, "f");
|
||||
let g = forward(&mut context, "g");
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), f64::consts::LN_2);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 0f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), -36.736_800_569_677_1);
|
||||
assert_eq!(d, "-Infinity");
|
||||
assert_eq!(e, String::from("NaN"));
|
||||
assert_eq!(f, String::from("NaN"));
|
||||
assert_eq!(g, String::from("NaN"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn log10() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.log10(2);
|
||||
var b = Math.log10(1);
|
||||
var c = Math.log10(-2);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward(&mut context, "c");
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), f64::consts::LOG10_2);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(c, String::from("NaN"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn log2() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.log2(3);
|
||||
var b = Math.log2(1);
|
||||
var c = Math.log2(-2);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward(&mut context, "c");
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 1.584_962_500_721_156);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(c, String::from("NaN"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.max(10, 20);
|
||||
var b = Math.max(-10, -20);
|
||||
var c = Math.max(-10, 20);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 20_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), -10_f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 20_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn min() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.min(10, 20);
|
||||
var b = Math.min(-10, -20);
|
||||
var c = Math.min(-10, 20);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 10_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), -20_f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), -10_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.pow(2, 10);
|
||||
var b = Math.pow(-7, 2);
|
||||
var c = Math.pow(4, 0.5);
|
||||
var d = Math.pow(7, -2);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
let d = forward_val(&mut context, "d").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 1_024_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 49_f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 2.0);
|
||||
assert_eq!(d.to_number(&mut context).unwrap(), 0.020_408_163_265_306_12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.round(20.5);
|
||||
var b = Math.round(-20.3);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 21.0);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), -20.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.sign(3);
|
||||
var b = Math.sign(-3);
|
||||
var c = Math.sign(0);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 1_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), -1_f64);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 0_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sin() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.sin(0);
|
||||
var b = Math.sin(1);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 0.841_470_984_807_896_5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sinh() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.sinh(0);
|
||||
var b = Math.sinh(1);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 1.175_201_193_643_801_4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqrt() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.sqrt(0);
|
||||
var b = Math.sqrt(2);
|
||||
var c = Math.sqrt(9);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
let c = forward_val(&mut context, "c").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), f64::consts::SQRT_2);
|
||||
assert_eq!(c.to_number(&mut context).unwrap(), 3_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tan() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.tan(1.1);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
|
||||
assert!(float_cmp::approx_eq!(
|
||||
f64,
|
||||
a.to_number(&mut context).unwrap(),
|
||||
1.964_759_657_248_652_5
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tanh() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.tanh(1);
|
||||
var b = Math.tanh(0);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 0.761_594_155_955_764_9);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 0_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trunc() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var a = Math.trunc(13.37);
|
||||
var b = Math.trunc(0.123);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let a = forward_val(&mut context, "a").unwrap();
|
||||
let b = forward_val(&mut context, "b").unwrap();
|
||||
|
||||
assert_eq!(a.to_number(&mut context).unwrap(), 13_f64);
|
||||
assert_eq!(b.to_number(&mut context).unwrap(), 0_f64);
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
//! Builtins live here, such as Object, String, Math, etc.
|
||||
|
||||
pub mod array;
|
||||
pub mod array_buffer;
|
||||
pub mod async_function;
|
||||
pub mod bigint;
|
||||
pub mod boolean;
|
||||
pub mod dataview;
|
||||
pub mod date;
|
||||
pub mod error;
|
||||
pub mod eval;
|
||||
pub mod function;
|
||||
pub mod generator;
|
||||
pub mod generator_function;
|
||||
pub mod global_this;
|
||||
pub mod infinity;
|
||||
pub mod iterable;
|
||||
pub mod json;
|
||||
pub mod map;
|
||||
pub mod math;
|
||||
pub mod nan;
|
||||
pub mod number;
|
||||
pub mod object;
|
||||
pub mod promise;
|
||||
pub mod proxy;
|
||||
pub mod reflect;
|
||||
pub mod regexp;
|
||||
pub mod set;
|
||||
pub mod string;
|
||||
pub mod symbol;
|
||||
pub mod typed_array;
|
||||
pub mod undefined;
|
||||
|
||||
#[cfg(feature = "console")]
|
||||
pub mod console;
|
||||
|
||||
#[cfg(feature = "intl")]
|
||||
pub mod intl;
|
||||
|
||||
pub(crate) use self::{
|
||||
array::{array_iterator::ArrayIterator, Array},
|
||||
async_function::AsyncFunction,
|
||||
bigint::BigInt,
|
||||
boolean::Boolean,
|
||||
dataview::DataView,
|
||||
date::Date,
|
||||
error::{
|
||||
AggregateError, Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError,
|
||||
UriError,
|
||||
},
|
||||
eval::Eval,
|
||||
function::BuiltInFunctionObject,
|
||||
global_this::GlobalThis,
|
||||
infinity::Infinity,
|
||||
json::Json,
|
||||
map::map_iterator::MapIterator,
|
||||
map::Map,
|
||||
math::Math,
|
||||
nan::NaN,
|
||||
number::Number,
|
||||
object::for_in_iterator::ForInIterator,
|
||||
object::Object as BuiltInObjectObject,
|
||||
promise::Promise,
|
||||
proxy::Proxy,
|
||||
reflect::Reflect,
|
||||
regexp::RegExp,
|
||||
set::set_iterator::SetIterator,
|
||||
set::Set,
|
||||
string::String,
|
||||
symbol::Symbol,
|
||||
typed_array::{
|
||||
BigInt64Array, BigUint64Array, Float32Array, Float64Array, Int16Array, Int32Array,
|
||||
Int8Array, Uint16Array, Uint32Array, Uint8Array, Uint8ClampedArray,
|
||||
},
|
||||
undefined::Undefined,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
builtins::{
|
||||
array_buffer::ArrayBuffer, generator::Generator, generator_function::GeneratorFunction,
|
||||
typed_array::TypedArray,
|
||||
},
|
||||
property::{Attribute, PropertyDescriptor},
|
||||
Context, JsValue,
|
||||
};
|
||||
|
||||
/// Trait representing a global built-in object such as `Math`, `Object` or
|
||||
/// `String`.
|
||||
///
|
||||
/// This trait must be implemented for any global built-in accessible from
|
||||
/// Javascript.
|
||||
pub(crate) trait BuiltIn {
|
||||
/// Binding name of the built-in inside the global object.
|
||||
///
|
||||
/// E.g. If you want access the properties of a `Complex` built-in
|
||||
/// with the name `Cplx` you must assign `"Cplx"` to this constant,
|
||||
/// making any property inside it accessible from Javascript as `Cplx.prop`
|
||||
const NAME: &'static str;
|
||||
|
||||
/// Property attribute flags of the built-in.
|
||||
/// Check [Attribute] for more information.
|
||||
const ATTRIBUTE: Attribute = Attribute::WRITABLE
|
||||
.union(Attribute::NON_ENUMERABLE)
|
||||
.union(Attribute::CONFIGURABLE);
|
||||
|
||||
/// Initialization code for the built-in.
|
||||
/// This is where the methods, properties, static methods and the constructor
|
||||
/// of a built-in must be initialized to be accessible from Javascript.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// A return value of `None` indicates that the value must not be added as
|
||||
/// a global attribute for the global object.
|
||||
fn init(context: &mut Context) -> Option<JsValue>;
|
||||
}
|
||||
|
||||
/// Utility function that checks if a type implements `BuiltIn` before
|
||||
/// initializing it as a global built-in.
|
||||
#[inline]
|
||||
fn init_builtin<B: BuiltIn>(context: &mut Context) {
|
||||
if let Some(value) = B::init(context) {
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(value)
|
||||
.writable(B::ATTRIBUTE.writable())
|
||||
.enumerable(B::ATTRIBUTE.enumerable())
|
||||
.configurable(B::ATTRIBUTE.configurable())
|
||||
.build();
|
||||
context
|
||||
.global_bindings_mut()
|
||||
.insert(B::NAME.into(), property);
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes built-in objects and functions
|
||||
#[inline]
|
||||
pub fn init(context: &mut Context) {
|
||||
macro_rules! globals {
|
||||
($( $builtin:ty ),*) => {
|
||||
$(init_builtin::<$builtin>(context)
|
||||
);*
|
||||
}
|
||||
}
|
||||
|
||||
globals! {
|
||||
Undefined,
|
||||
Infinity,
|
||||
NaN,
|
||||
GlobalThis,
|
||||
BuiltInFunctionObject,
|
||||
BuiltInObjectObject,
|
||||
Math,
|
||||
Json,
|
||||
Array,
|
||||
Proxy,
|
||||
ArrayBuffer,
|
||||
BigInt,
|
||||
Boolean,
|
||||
Date,
|
||||
DataView,
|
||||
Map,
|
||||
Number,
|
||||
Eval,
|
||||
Set,
|
||||
String,
|
||||
RegExp,
|
||||
TypedArray,
|
||||
Int8Array,
|
||||
Uint8Array,
|
||||
Uint8ClampedArray,
|
||||
Int16Array,
|
||||
Uint16Array,
|
||||
Int32Array,
|
||||
Uint32Array,
|
||||
BigInt64Array,
|
||||
BigUint64Array,
|
||||
Float32Array,
|
||||
Float64Array,
|
||||
Symbol,
|
||||
Error,
|
||||
RangeError,
|
||||
ReferenceError,
|
||||
TypeError,
|
||||
SyntaxError,
|
||||
EvalError,
|
||||
UriError,
|
||||
AggregateError,
|
||||
Reflect,
|
||||
Generator,
|
||||
GeneratorFunction,
|
||||
Promise,
|
||||
AsyncFunction
|
||||
};
|
||||
|
||||
#[cfg(feature = "intl")]
|
||||
init_builtin::<intl::Intl>(context);
|
||||
|
||||
#[cfg(feature = "console")]
|
||||
init_builtin::<console::Console>(context);
|
||||
}
|
||||
|
||||
pub trait JsArgs {
|
||||
/// Utility function to `get` a parameter from
|
||||
/// a `[JsValue]` or default to `JsValue::Undefined`
|
||||
/// if `get` returns `None`.
|
||||
///
|
||||
/// Call this if you are thinking of calling something similar to
|
||||
/// `args.get(n).cloned().unwrap_or_default()` or
|
||||
/// `args.get(n).unwrap_or(&undefined)`.
|
||||
///
|
||||
/// This returns a reference for efficiency, in case
|
||||
/// you only need to call methods of `JsValue`, so
|
||||
/// try to minimize calling `clone`.
|
||||
fn get_or_undefined(&self, index: usize) -> &JsValue;
|
||||
}
|
||||
|
||||
impl JsArgs for [JsValue] {
|
||||
fn get_or_undefined(&self, index: usize) -> &JsValue {
|
||||
const UNDEFINED: &JsValue = &JsValue::Undefined;
|
||||
self.get(index).unwrap_or(UNDEFINED)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//! This module implements the global `NaN` property.
|
||||
//!
|
||||
//! The global `NaN` is a property of the global object. In other words,
|
||||
//! it is a variable in global scope.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-value-properties-of-the-global-object-nan
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{builtins::BuiltIn, property::Attribute, Context, JsValue};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// JavaScript global `NaN` property.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) struct NaN;
|
||||
|
||||
impl BuiltIn for NaN {
|
||||
const NAME: &'static str = "NaN";
|
||||
|
||||
const ATTRIBUTE: Attribute = Attribute::READONLY
|
||||
.union(Attribute::NON_ENUMERABLE)
|
||||
.union(Attribute::PERMANENT);
|
||||
|
||||
fn init(_: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
Some(f64::NAN.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
use crate::exec;
|
||||
|
||||
#[test]
|
||||
fn nan_exists_on_global_object_and_evaluates_to_nan_value() {
|
||||
let scenario = r#"
|
||||
NaN;
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "NaN");
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/// Converts a 64-bit floating point number to an `i32` according to the [`ToInt32`][ToInt32] algorithm.
|
||||
///
|
||||
/// [ToInt32]: https://tc39.es/ecma262/#sec-toint32
|
||||
#[inline]
|
||||
#[allow(clippy::float_cmp)]
|
||||
pub(crate) fn f64_to_int32(number: f64) -> i32 {
|
||||
const SIGN_MASK: u64 = 0x8000000000000000;
|
||||
const EXPONENT_MASK: u64 = 0x7FF0000000000000;
|
||||
const SIGNIFICAND_MASK: u64 = 0x000FFFFFFFFFFFFF;
|
||||
const HIDDEN_BIT: u64 = 0x0010000000000000;
|
||||
const PHYSICAL_SIGNIFICAND_SIZE: i32 = 52; // Excludes the hidden bit.
|
||||
const SIGNIFICAND_SIZE: i32 = 53;
|
||||
|
||||
const EXPONENT_BIAS: i32 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE;
|
||||
const DENORMAL_EXPONENT: i32 = -EXPONENT_BIAS + 1;
|
||||
|
||||
#[inline]
|
||||
fn is_denormal(number: f64) -> bool {
|
||||
(number.to_bits() & EXPONENT_MASK) == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn exponent(number: f64) -> i32 {
|
||||
if is_denormal(number) {
|
||||
return DENORMAL_EXPONENT;
|
||||
}
|
||||
|
||||
let d64 = number.to_bits();
|
||||
let biased_e = ((d64 & EXPONENT_MASK) >> PHYSICAL_SIGNIFICAND_SIZE) as i32;
|
||||
|
||||
biased_e - EXPONENT_BIAS
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn significand(number: f64) -> u64 {
|
||||
let d64 = number.to_bits();
|
||||
let significand = d64 & SIGNIFICAND_MASK;
|
||||
|
||||
if is_denormal(number) {
|
||||
significand
|
||||
} else {
|
||||
significand + HIDDEN_BIT
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sign(number: f64) -> i64 {
|
||||
if (number.to_bits() & SIGN_MASK) == 0 {
|
||||
1
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
if number.is_finite() && number <= f64::from(i32::MAX) && number >= f64::from(i32::MIN) {
|
||||
let i = number as i32;
|
||||
if f64::from(i) == number {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
let exponent = exponent(number);
|
||||
let bits = if exponent < 0 {
|
||||
if exponent <= -SIGNIFICAND_SIZE {
|
||||
return 0;
|
||||
}
|
||||
|
||||
significand(number) >> -exponent
|
||||
} else {
|
||||
if exponent > 31 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
(significand(number) << exponent) & 0xFFFFFFFF
|
||||
};
|
||||
|
||||
(sign(number) * (bits as i64)) as i32
|
||||
}
|
||||
|
||||
/// Converts a 64-bit floating point number to an `u32` according to the [`ToUint32`][ToUint32] algorithm.
|
||||
///
|
||||
/// [ToUint32]: https://tc39.es/ecma262/#sec-touint32
|
||||
#[inline]
|
||||
pub(crate) fn f64_to_uint32(number: f64) -> u32 {
|
||||
f64_to_int32(number) as u32
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,886 @@
|
||||
#![allow(clippy::float_cmp)]
|
||||
|
||||
use crate::{builtins::Number, forward, forward_val, value::AbstractRelation, Context};
|
||||
|
||||
#[test]
|
||||
fn integer_number_primitive_to_number_object() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let scenario = r#"
|
||||
(100).toString() === "100"
|
||||
"#;
|
||||
|
||||
assert_eq!(forward(&mut context, scenario), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_number() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var default_zero = Number();
|
||||
var int_one = Number(1);
|
||||
var float_two = Number(2.1);
|
||||
var str_three = Number('3.2');
|
||||
var bool_one = Number(true);
|
||||
var bool_zero = Number(false);
|
||||
var invalid_nan = Number("I am not a number");
|
||||
var from_exp = Number("2.34e+2");
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let default_zero = forward_val(&mut context, "default_zero").unwrap();
|
||||
let int_one = forward_val(&mut context, "int_one").unwrap();
|
||||
let float_two = forward_val(&mut context, "float_two").unwrap();
|
||||
let str_three = forward_val(&mut context, "str_three").unwrap();
|
||||
let bool_one = forward_val(&mut context, "bool_one").unwrap();
|
||||
let bool_zero = forward_val(&mut context, "bool_zero").unwrap();
|
||||
let invalid_nan = forward_val(&mut context, "invalid_nan").unwrap();
|
||||
let from_exp = forward_val(&mut context, "from_exp").unwrap();
|
||||
|
||||
assert_eq!(default_zero.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(int_one.to_number(&mut context).unwrap(), 1_f64);
|
||||
assert_eq!(float_two.to_number(&mut context).unwrap(), 2.1);
|
||||
assert_eq!(str_three.to_number(&mut context).unwrap(), 3.2);
|
||||
assert_eq!(bool_one.to_number(&mut context).unwrap(), 1_f64);
|
||||
assert!(invalid_nan.to_number(&mut context).unwrap().is_nan());
|
||||
assert_eq!(bool_zero.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(from_exp.to_number(&mut context).unwrap(), 234_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_exponential() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var default_exp = Number().toExponential();
|
||||
var int_exp = Number(5).toExponential();
|
||||
var float_exp = Number(1.234).toExponential();
|
||||
var big_exp = Number(1234).toExponential();
|
||||
var nan_exp = Number("I am also not a number").toExponential();
|
||||
var noop_exp = Number("1.23e+2").toExponential();
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let default_exp = forward(&mut context, "default_exp");
|
||||
let int_exp = forward(&mut context, "int_exp");
|
||||
let float_exp = forward(&mut context, "float_exp");
|
||||
let big_exp = forward(&mut context, "big_exp");
|
||||
let nan_exp = forward(&mut context, "nan_exp");
|
||||
let noop_exp = forward(&mut context, "noop_exp");
|
||||
|
||||
assert_eq!(default_exp, "\"0e+0\"");
|
||||
assert_eq!(int_exp, "\"5e+0\"");
|
||||
assert_eq!(float_exp, "\"1.234e+0\"");
|
||||
assert_eq!(big_exp, "\"1.234e+3\"");
|
||||
assert_eq!(nan_exp, "\"NaN\"");
|
||||
assert_eq!(noop_exp, "\"1.23e+2\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_fixed() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var default_fixed = Number().toFixed();
|
||||
var pos_fixed = Number("3.456e+4").toFixed();
|
||||
var neg_fixed = Number("3.456e-4").toFixed();
|
||||
var noop_fixed = Number(5).toFixed();
|
||||
var nan_fixed = Number("I am not a number").toFixed();
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let default_fixed = forward(&mut context, "default_fixed");
|
||||
let pos_fixed = forward(&mut context, "pos_fixed");
|
||||
let neg_fixed = forward(&mut context, "neg_fixed");
|
||||
let noop_fixed = forward(&mut context, "noop_fixed");
|
||||
let nan_fixed = forward(&mut context, "nan_fixed");
|
||||
|
||||
assert_eq!(default_fixed, "\"0\"");
|
||||
assert_eq!(pos_fixed, "\"34560\"");
|
||||
assert_eq!(neg_fixed, "\"0\"");
|
||||
assert_eq!(noop_fixed, "\"5\"");
|
||||
assert_eq!(nan_fixed, "\"NaN\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_locale_string() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var default_locale = Number().toLocaleString();
|
||||
var small_locale = Number(5).toLocaleString();
|
||||
var big_locale = Number("345600").toLocaleString();
|
||||
var neg_locale = Number(-25).toLocaleString();
|
||||
"#;
|
||||
|
||||
// TODO: We don't actually do any locale checking here
|
||||
// To honor the spec we should print numbers according to user locale.
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let default_locale = forward(&mut context, "default_locale");
|
||||
let small_locale = forward(&mut context, "small_locale");
|
||||
let big_locale = forward(&mut context, "big_locale");
|
||||
let neg_locale = forward(&mut context, "neg_locale");
|
||||
|
||||
assert_eq!(default_locale, "\"0\"");
|
||||
assert_eq!(small_locale, "\"5\"");
|
||||
assert_eq!(big_locale, "\"345600\"");
|
||||
assert_eq!(neg_locale, "\"-25\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_precision() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var infinity = (1/0).toPrecision(3);
|
||||
var default_precision = Number().toPrecision();
|
||||
var explicit_ud_precision = Number().toPrecision(undefined);
|
||||
var low_precision = (123456789).toPrecision(1);
|
||||
var more_precision = (123456789).toPrecision(4);
|
||||
var exact_precision = (123456789).toPrecision(9);
|
||||
var over_precision = (123456789).toPrecision(50);
|
||||
var neg_precision = (-123456789).toPrecision(4);
|
||||
var neg_exponent = (0.1).toPrecision(4);
|
||||
var ieee754_limits = (1/3).toPrecision(60);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let infinity = forward(&mut context, "infinity");
|
||||
let default_precision = forward(&mut context, "default_precision");
|
||||
let explicit_ud_precision = forward(&mut context, "explicit_ud_precision");
|
||||
let low_precision = forward(&mut context, "low_precision");
|
||||
let more_precision = forward(&mut context, "more_precision");
|
||||
let exact_precision = forward(&mut context, "exact_precision");
|
||||
let over_precision = forward(&mut context, "over_precision");
|
||||
let neg_precision = forward(&mut context, "neg_precision");
|
||||
let neg_exponent = forward(&mut context, "neg_exponent");
|
||||
let ieee754_limits = forward(&mut context, "ieee754_limits");
|
||||
|
||||
assert_eq!(infinity, String::from("\"Infinity\""));
|
||||
assert_eq!(default_precision, String::from("\"0\""));
|
||||
assert_eq!(explicit_ud_precision, String::from("\"0\""));
|
||||
assert_eq!(low_precision, String::from("\"1e+8\""));
|
||||
assert_eq!(more_precision, String::from("\"1.235e+8\""));
|
||||
assert_eq!(exact_precision, String::from("\"123456789\""));
|
||||
assert_eq!(neg_precision, String::from("\"-1.235e+8\""));
|
||||
assert_eq!(
|
||||
over_precision,
|
||||
String::from("\"123456789.00000000000000000000000000000000000000000\"")
|
||||
);
|
||||
assert_eq!(neg_exponent, String::from("\"0.1000\""));
|
||||
assert_eq!(
|
||||
ieee754_limits,
|
||||
String::from("\"0.333333333333333314829616256247390992939472198486328125000000\"")
|
||||
);
|
||||
|
||||
let expected = "Uncaught \"RangeError\": \"precision must be an integer at least 1 and no greater than 100\"";
|
||||
|
||||
let range_error_1 = r#"(1).toPrecision(101);"#;
|
||||
let range_error_2 = r#"(1).toPrecision(0);"#;
|
||||
let range_error_3 = r#"(1).toPrecision(-2000);"#;
|
||||
let range_error_4 = r#"(1).toPrecision('%');"#;
|
||||
|
||||
assert_eq!(forward(&mut context, range_error_1), expected);
|
||||
assert_eq!(forward(&mut context, range_error_2), expected);
|
||||
assert_eq!(forward(&mut context, range_error_3), expected);
|
||||
assert_eq!(forward(&mut context, range_error_4), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!("\"NaN\"", &forward(&mut context, "Number(NaN).toString()"));
|
||||
assert_eq!(
|
||||
"\"Infinity\"",
|
||||
&forward(&mut context, "Number(1/0).toString()")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-Infinity\"",
|
||||
&forward(&mut context, "Number(-1/0).toString()")
|
||||
);
|
||||
assert_eq!("\"0\"", &forward(&mut context, "Number(0).toString()"));
|
||||
assert_eq!("\"9\"", &forward(&mut context, "Number(9).toString()"));
|
||||
assert_eq!("\"90\"", &forward(&mut context, "Number(90).toString()"));
|
||||
assert_eq!(
|
||||
"\"90.12\"",
|
||||
&forward(&mut context, "Number(90.12).toString()")
|
||||
);
|
||||
assert_eq!("\"0.1\"", &forward(&mut context, "Number(0.1).toString()"));
|
||||
assert_eq!(
|
||||
"\"0.01\"",
|
||||
&forward(&mut context, "Number(0.01).toString()")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.0123\"",
|
||||
&forward(&mut context, "Number(0.0123).toString()")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.00001\"",
|
||||
&forward(&mut context, "Number(0.00001).toString()")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.000001\"",
|
||||
&forward(&mut context, "Number(0.000001).toString()")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"NaN\"",
|
||||
&forward(&mut context, "Number(NaN).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"Infinity\"",
|
||||
&forward(&mut context, "Number(1/0).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-Infinity\"",
|
||||
&forward(&mut context, "Number(-1/0).toString(16)")
|
||||
);
|
||||
assert_eq!("\"0\"", &forward(&mut context, "Number(0).toString(16)"));
|
||||
assert_eq!("\"9\"", &forward(&mut context, "Number(9).toString(16)"));
|
||||
assert_eq!("\"5a\"", &forward(&mut context, "Number(90).toString(16)"));
|
||||
assert_eq!(
|
||||
"\"5a.1eb851eb852\"",
|
||||
&forward(&mut context, "Number(90.12).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.1999999999999a\"",
|
||||
&forward(&mut context, "Number(0.1).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.028f5c28f5c28f6\"",
|
||||
&forward(&mut context, "Number(0.01).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.032617c1bda511a\"",
|
||||
&forward(&mut context, "Number(0.0123).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"605f9f6dd18bc8000\"",
|
||||
&forward(&mut context, "Number(111111111111111111111).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"3c3bc3a4a2f75c0000\"",
|
||||
&forward(&mut context, "Number(1111111111111111111111).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"25a55a46e5da9a00000\"",
|
||||
&forward(&mut context, "Number(11111111111111111111111).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.0000a7c5ac471b4788\"",
|
||||
&forward(&mut context, "Number(0.00001).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.000010c6f7a0b5ed8d\"",
|
||||
&forward(&mut context, "Number(0.000001).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.000001ad7f29abcaf48\"",
|
||||
&forward(&mut context, "Number(0.0000001).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.000002036565348d256\"",
|
||||
&forward(&mut context, "Number(0.00000012).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.0000021047ee22aa466\"",
|
||||
&forward(&mut context, "Number(0.000000123).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.0000002af31dc4611874\"",
|
||||
&forward(&mut context, "Number(0.00000001).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.000000338a23b87483be\"",
|
||||
&forward(&mut context, "Number(0.000000012).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"0.00000034d3fe36aaa0a2\"",
|
||||
&forward(&mut context, "Number(0.0000000123).toString(16)")
|
||||
);
|
||||
|
||||
assert_eq!("\"0\"", &forward(&mut context, "Number(-0).toString(16)"));
|
||||
assert_eq!("\"-9\"", &forward(&mut context, "Number(-9).toString(16)"));
|
||||
assert_eq!(
|
||||
"\"-5a\"",
|
||||
&forward(&mut context, "Number(-90).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-5a.1eb851eb852\"",
|
||||
&forward(&mut context, "Number(-90.12).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-0.1999999999999a\"",
|
||||
&forward(&mut context, "Number(-0.1).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-0.028f5c28f5c28f6\"",
|
||||
&forward(&mut context, "Number(-0.01).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-0.032617c1bda511a\"",
|
||||
&forward(&mut context, "Number(-0.0123).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-605f9f6dd18bc8000\"",
|
||||
&forward(&mut context, "Number(-111111111111111111111).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-3c3bc3a4a2f75c0000\"",
|
||||
&forward(&mut context, "Number(-1111111111111111111111).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-25a55a46e5da9a00000\"",
|
||||
&forward(
|
||||
&mut context,
|
||||
"Number(-11111111111111111111111).toString(16)"
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-0.0000a7c5ac471b4788\"",
|
||||
&forward(&mut context, "Number(-0.00001).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-0.000010c6f7a0b5ed8d\"",
|
||||
&forward(&mut context, "Number(-0.000001).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-0.000001ad7f29abcaf48\"",
|
||||
&forward(&mut context, "Number(-0.0000001).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-0.000002036565348d256\"",
|
||||
&forward(&mut context, "Number(-0.00000012).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-0.0000021047ee22aa466\"",
|
||||
&forward(&mut context, "Number(-0.000000123).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-0.0000002af31dc4611874\"",
|
||||
&forward(&mut context, "Number(-0.00000001).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-0.000000338a23b87483be\"",
|
||||
&forward(&mut context, "Number(-0.000000012).toString(16)")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"-0.00000034d3fe36aaa0a2\"",
|
||||
&forward(&mut context, "Number(-0.0000000123).toString(16)")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_to_string_exponential() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!("\"0\"", forward(&mut context, "(0).toString()"));
|
||||
assert_eq!("\"0\"", forward(&mut context, "(-0).toString()"));
|
||||
assert_eq!(
|
||||
"\"111111111111111110000\"",
|
||||
forward(&mut context, "(111111111111111111111).toString()")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"1.1111111111111111e+21\"",
|
||||
forward(&mut context, "(1111111111111111111111).toString()")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"1.1111111111111111e+22\"",
|
||||
forward(&mut context, "(11111111111111111111111).toString()")
|
||||
);
|
||||
assert_eq!("\"1e-7\"", forward(&mut context, "(0.0000001).toString()"));
|
||||
assert_eq!(
|
||||
"\"1.2e-7\"",
|
||||
forward(&mut context, "(0.00000012).toString()")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"1.23e-7\"",
|
||||
forward(&mut context, "(0.000000123).toString()")
|
||||
);
|
||||
assert_eq!("\"1e-8\"", forward(&mut context, "(0.00000001).toString()"));
|
||||
assert_eq!(
|
||||
"\"1.2e-8\"",
|
||||
forward(&mut context, "(0.000000012).toString()")
|
||||
);
|
||||
assert_eq!(
|
||||
"\"1.23e-8\"",
|
||||
forward(&mut context, "(0.0000000123).toString()")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_of() {
|
||||
let mut context = Context::default();
|
||||
// TODO: In addition to parsing numbers from strings, parse them bare As of October 2019
|
||||
// the parser does not understand scientific e.g., Xe+Y or -Xe-Y notation.
|
||||
let init = r#"
|
||||
var default_val = Number().valueOf();
|
||||
var int_val = Number("123").valueOf();
|
||||
var float_val = Number(1.234).valueOf();
|
||||
var exp_val = Number("1.2e+4").valueOf()
|
||||
var neg_val = Number("-1.2e+4").valueOf()
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let default_val = forward_val(&mut context, "default_val").unwrap();
|
||||
let int_val = forward_val(&mut context, "int_val").unwrap();
|
||||
let float_val = forward_val(&mut context, "float_val").unwrap();
|
||||
let exp_val = forward_val(&mut context, "exp_val").unwrap();
|
||||
let neg_val = forward_val(&mut context, "neg_val").unwrap();
|
||||
|
||||
assert_eq!(default_val.to_number(&mut context).unwrap(), 0_f64);
|
||||
assert_eq!(int_val.to_number(&mut context).unwrap(), 123_f64);
|
||||
assert_eq!(float_val.to_number(&mut context).unwrap(), 1.234);
|
||||
assert_eq!(exp_val.to_number(&mut context).unwrap(), 12_000_f64);
|
||||
assert_eq!(neg_val.to_number(&mut context).unwrap(), -12_000_f64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equal() {
|
||||
assert!(Number::equal(0.0, 0.0));
|
||||
assert!(Number::equal(-0.0, 0.0));
|
||||
assert!(Number::equal(0.0, -0.0));
|
||||
assert!(!Number::equal(f64::NAN, -0.0));
|
||||
assert!(!Number::equal(0.0, f64::NAN));
|
||||
|
||||
assert!(Number::equal(1.0, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_value() {
|
||||
assert!(Number::same_value(0.0, 0.0));
|
||||
assert!(!Number::same_value(-0.0, 0.0));
|
||||
assert!(!Number::same_value(0.0, -0.0));
|
||||
assert!(!Number::same_value(f64::NAN, -0.0));
|
||||
assert!(!Number::same_value(0.0, f64::NAN));
|
||||
assert!(Number::equal(1.0, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn less_than() {
|
||||
assert_eq!(
|
||||
Number::less_than(f64::NAN, 0.0),
|
||||
AbstractRelation::Undefined
|
||||
);
|
||||
assert_eq!(
|
||||
Number::less_than(0.0, f64::NAN),
|
||||
AbstractRelation::Undefined
|
||||
);
|
||||
assert_eq!(
|
||||
Number::less_than(f64::NEG_INFINITY, 0.0),
|
||||
AbstractRelation::True
|
||||
);
|
||||
assert_eq!(
|
||||
Number::less_than(0.0, f64::NEG_INFINITY),
|
||||
AbstractRelation::False
|
||||
);
|
||||
assert_eq!(
|
||||
Number::less_than(f64::INFINITY, 0.0),
|
||||
AbstractRelation::False
|
||||
);
|
||||
assert_eq!(
|
||||
Number::less_than(0.0, f64::INFINITY),
|
||||
AbstractRelation::True
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_value_zero() {
|
||||
assert!(Number::same_value_zero(0.0, 0.0));
|
||||
assert!(Number::same_value_zero(-0.0, 0.0));
|
||||
assert!(Number::same_value_zero(0.0, -0.0));
|
||||
assert!(!Number::same_value_zero(f64::NAN, -0.0));
|
||||
assert!(!Number::same_value_zero(0.0, f64::NAN));
|
||||
assert!(Number::equal(1.0, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_bigint() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "Number(0n)"), "0",);
|
||||
assert_eq!(&forward(&mut context, "Number(100000n)"), "100000",);
|
||||
assert_eq!(&forward(&mut context, "Number(100000n)"), "100000",);
|
||||
assert_eq!(&forward(&mut context, "Number(1n << 1240n)"), "Infinity",);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_constants() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert!(!forward_val(&mut context, "Number.EPSILON")
|
||||
.unwrap()
|
||||
.is_null_or_undefined());
|
||||
assert!(!forward_val(&mut context, "Number.MAX_SAFE_INTEGER")
|
||||
.unwrap()
|
||||
.is_null_or_undefined());
|
||||
assert!(!forward_val(&mut context, "Number.MIN_SAFE_INTEGER")
|
||||
.unwrap()
|
||||
.is_null_or_undefined());
|
||||
assert!(!forward_val(&mut context, "Number.MAX_VALUE")
|
||||
.unwrap()
|
||||
.is_null_or_undefined());
|
||||
assert!(!forward_val(&mut context, "Number.MIN_VALUE")
|
||||
.unwrap()
|
||||
.is_null_or_undefined());
|
||||
assert!(!forward_val(&mut context, "Number.NEGATIVE_INFINITY")
|
||||
.unwrap()
|
||||
.is_null_or_undefined());
|
||||
assert!(!forward_val(&mut context, "Number.POSITIVE_INFINITY")
|
||||
.unwrap()
|
||||
.is_null_or_undefined());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_int_simple() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseInt(\"6\")"), "6");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_int_negative() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseInt(\"-9\")"), "-9");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_int_already_int() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseInt(100)"), "100");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_int_float() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseInt(100.5)"), "100");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_int_float_str() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseInt(\"100.5\")"), "100");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_int_inferred_hex() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseInt(\"0xA\")"), "10");
|
||||
}
|
||||
|
||||
/// This test demonstrates that this version of parseInt treats strings starting with 0 to be parsed with
|
||||
/// a radix 10 if no radix is specified. Some alternative implementations default to a radix of 8.
|
||||
#[test]
|
||||
fn parse_int_zero_start() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseInt(\"018\")"), "18");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_int_varying_radix() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let base_str = "1000";
|
||||
|
||||
for radix in 2..36 {
|
||||
let expected = i32::from_str_radix(base_str, radix).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, &format!("parseInt(\"{base_str}\", {radix} )")),
|
||||
expected.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_int_negative_varying_radix() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let base_str = "-1000";
|
||||
|
||||
for radix in 2..36 {
|
||||
let expected = i32::from_str_radix(base_str, radix).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, &format!("parseInt(\"{base_str}\", {radix} )")),
|
||||
expected.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_int_malformed_str() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseInt(\"hello\")"), "NaN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_int_undefined() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseInt(undefined)"), "NaN");
|
||||
}
|
||||
|
||||
/// Shows that no arguments to parseInt is treated the same as if undefined was
|
||||
/// passed as the first argument.
|
||||
#[test]
|
||||
fn parse_int_no_args() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseInt()"), "NaN");
|
||||
}
|
||||
|
||||
/// Shows that extra arguments to parseInt are ignored.
|
||||
#[test]
|
||||
fn parse_int_too_many_args() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseInt(\"100\", 10, 10)"), "100");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_float_simple() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseFloat(\"6.5\")"), "6.5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_float_int() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseFloat(10)"), "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_float_int_str() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseFloat(\"8\")"), "8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_float_already_float() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseFloat(17.5)"), "17.5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_float_negative() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseFloat(\"-99.7\")"), "-99.7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_float_malformed_str() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseFloat(\"hello\")"), "NaN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_float_undefined() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseFloat(undefined)"), "NaN");
|
||||
}
|
||||
|
||||
/// No arguments to parseFloat is treated the same as passing undefined as the first argument.
|
||||
#[test]
|
||||
fn parse_float_no_args() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseFloat()"), "NaN");
|
||||
}
|
||||
|
||||
/// Shows that the parseFloat function ignores extra arguments.
|
||||
#[test]
|
||||
fn parse_float_too_many_args() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(&forward(&mut context, "parseFloat(\"100.5\", 10)"), "100.5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_is_finite() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!("false", &forward(&mut context, "isFinite(Infinity)"));
|
||||
assert_eq!("false", &forward(&mut context, "isFinite(NaN)"));
|
||||
assert_eq!("false", &forward(&mut context, "isFinite(-Infinity)"));
|
||||
assert_eq!("true", &forward(&mut context, "isFinite(0)"));
|
||||
assert_eq!("true", &forward(&mut context, "isFinite(2e64)"));
|
||||
assert_eq!("true", &forward(&mut context, "isFinite(910)"));
|
||||
assert_eq!("true", &forward(&mut context, "isFinite(null)"));
|
||||
assert_eq!("true", &forward(&mut context, "isFinite('0')"));
|
||||
assert_eq!("false", &forward(&mut context, "isFinite()"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_is_nan() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!("true", &forward(&mut context, "isNaN(NaN)"));
|
||||
assert_eq!("true", &forward(&mut context, "isNaN('NaN')"));
|
||||
assert_eq!("true", &forward(&mut context, "isNaN(undefined)"));
|
||||
assert_eq!("true", &forward(&mut context, "isNaN({})"));
|
||||
assert_eq!("false", &forward(&mut context, "isNaN(true)"));
|
||||
assert_eq!("false", &forward(&mut context, "isNaN(null)"));
|
||||
assert_eq!("false", &forward(&mut context, "isNaN(37)"));
|
||||
assert_eq!("false", &forward(&mut context, "isNaN('37')"));
|
||||
assert_eq!("false", &forward(&mut context, "isNaN('37.37')"));
|
||||
assert_eq!("true", &forward(&mut context, "isNaN('37,5')"));
|
||||
assert_eq!("true", &forward(&mut context, "isNaN('123ABC')"));
|
||||
// Incorrect due to ToNumber implementation inconsistencies.
|
||||
//assert_eq!("false", &forward(&mut context, "isNaN('')"));
|
||||
//assert_eq!("false", &forward(&mut context, "isNaN(' ')"));
|
||||
assert_eq!("true", &forward(&mut context, "isNaN('blabla')"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_is_finite() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!("false", &forward(&mut context, "Number.isFinite(Infinity)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isFinite(NaN)"));
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isFinite(-Infinity)")
|
||||
);
|
||||
assert_eq!("true", &forward(&mut context, "Number.isFinite(0)"));
|
||||
assert_eq!("true", &forward(&mut context, "Number.isFinite(2e64)"));
|
||||
assert_eq!("true", &forward(&mut context, "Number.isFinite(910)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isFinite(null)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isFinite('0')"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isFinite()"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isFinite({})"));
|
||||
assert_eq!("true", &forward(&mut context, "Number.isFinite(Number(5))"));
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isFinite(new Number(5))")
|
||||
);
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isFinite(new Number(NaN))")
|
||||
);
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isFinite(BigInt(5))")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_is_integer() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!("true", &forward(&mut context, "Number.isInteger(0)"));
|
||||
assert_eq!("true", &forward(&mut context, "Number.isInteger(1)"));
|
||||
assert_eq!("true", &forward(&mut context, "Number.isInteger(-100000)"));
|
||||
assert_eq!(
|
||||
"true",
|
||||
&forward(&mut context, "Number.isInteger(99999999999999999999999)")
|
||||
);
|
||||
assert_eq!("false", &forward(&mut context, "Number.isInteger(0.1)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isInteger(Math.PI)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isInteger(NaN)"));
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isInteger(Infinity)")
|
||||
);
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isInteger(-Infinity)")
|
||||
);
|
||||
assert_eq!("false", &forward(&mut context, "Number.isInteger('10')"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isInteger(true)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isInteger(false)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isInteger([1])"));
|
||||
assert_eq!("true", &forward(&mut context, "Number.isInteger(5.0)"));
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isInteger(5.000000000000001)")
|
||||
);
|
||||
assert_eq!(
|
||||
"true",
|
||||
&forward(&mut context, "Number.isInteger(5.0000000000000001)")
|
||||
);
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isInteger(Number(5.000000000000001))")
|
||||
);
|
||||
assert_eq!(
|
||||
"true",
|
||||
&forward(&mut context, "Number.isInteger(Number(5.0000000000000001))")
|
||||
);
|
||||
assert_eq!("false", &forward(&mut context, "Number.isInteger()"));
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isInteger(new Number(5))")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_is_nan() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!("true", &forward(&mut context, "Number.isNaN(NaN)"));
|
||||
assert_eq!("true", &forward(&mut context, "Number.isNaN(Number.NaN)"));
|
||||
assert_eq!("true", &forward(&mut context, "Number.isNaN(0 / 0)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN(undefined)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN({})"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN(true)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN(null)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN(37)"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN('37')"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN('37.37')"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN('37,5')"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN('123ABC')"));
|
||||
// Incorrect due to ToNumber implementation inconsistencies.
|
||||
//assert_eq!("false", &forward(&mut context, "Number.isNaN('')"));
|
||||
//assert_eq!("false", &forward(&mut context, "Number.isNaN(' ')"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN('blabla')"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN(Number(5))"));
|
||||
assert_eq!("true", &forward(&mut context, "Number.isNaN(Number(NaN))"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isNaN(BigInt(5))"));
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isNaN(new Number(5))")
|
||||
);
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isNaN(new Number(NaN))")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_is_safe_integer() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!("true", &forward(&mut context, "Number.isSafeInteger(3)"));
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isSafeInteger(Math.pow(2, 53))")
|
||||
);
|
||||
assert_eq!(
|
||||
"true",
|
||||
&forward(&mut context, "Number.isSafeInteger(Math.pow(2, 53) - 1)")
|
||||
);
|
||||
assert_eq!("false", &forward(&mut context, "Number.isSafeInteger(NaN)"));
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isSafeInteger(Infinity)")
|
||||
);
|
||||
assert_eq!("false", &forward(&mut context, "Number.isSafeInteger('3')"));
|
||||
assert_eq!("false", &forward(&mut context, "Number.isSafeInteger(3.1)"));
|
||||
assert_eq!("true", &forward(&mut context, "Number.isSafeInteger(3.0)"));
|
||||
assert_eq!(
|
||||
"false",
|
||||
&forward(&mut context, "Number.isSafeInteger(new Number(5))")
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
use crate::{
|
||||
builtins::{function::make_builtin_fn, iterable::create_iter_result_object},
|
||||
object::{JsObject, ObjectData},
|
||||
property::PropertyDescriptor,
|
||||
property::PropertyKey,
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// The `ForInIterator` object represents an iteration over some specific object.
|
||||
/// It implements the iterator protocol.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-for-in-iterator-objects
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct ForInIterator {
|
||||
object: JsValue,
|
||||
visited_keys: FxHashSet<JsString>,
|
||||
remaining_keys: VecDeque<JsString>,
|
||||
object_was_visited: bool,
|
||||
}
|
||||
|
||||
impl ForInIterator {
|
||||
pub(crate) const NAME: &'static str = "ForInIterator";
|
||||
|
||||
fn new(object: JsValue) -> Self {
|
||||
Self {
|
||||
object,
|
||||
visited_keys: FxHashSet::default(),
|
||||
remaining_keys: VecDeque::default(),
|
||||
object_was_visited: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// `CreateForInIterator( object )`
|
||||
///
|
||||
/// Creates a new iterator over the given object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createforiniterator
|
||||
pub(crate) fn create_for_in_iterator(object: JsValue, context: &Context) -> JsValue {
|
||||
let for_in_iterator = JsObject::from_proto_and_data(
|
||||
context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.for_in_iterator(),
|
||||
ObjectData::for_in_iterator(Self::new(object)),
|
||||
);
|
||||
for_in_iterator.into()
|
||||
}
|
||||
|
||||
/// %ForInIteratorPrototype%.next( )
|
||||
///
|
||||
/// Gets the next result in the object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next
|
||||
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let mut iterator = this.as_object().map(JsObject::borrow_mut);
|
||||
let iterator = iterator
|
||||
.as_mut()
|
||||
.and_then(|obj| obj.as_for_in_iterator_mut())
|
||||
.ok_or_else(|| context.construct_type_error("`this` is not a ForInIterator"))?;
|
||||
let mut object = iterator.object.to_object(context)?;
|
||||
loop {
|
||||
if !iterator.object_was_visited {
|
||||
let keys = object.__own_property_keys__(context)?;
|
||||
for k in keys {
|
||||
match k {
|
||||
PropertyKey::String(ref k) => {
|
||||
iterator.remaining_keys.push_back(k.clone());
|
||||
}
|
||||
PropertyKey::Index(i) => {
|
||||
iterator.remaining_keys.push_back(i.to_string().into());
|
||||
}
|
||||
PropertyKey::Symbol(_) => {}
|
||||
}
|
||||
}
|
||||
iterator.object_was_visited = true;
|
||||
}
|
||||
while let Some(r) = iterator.remaining_keys.pop_front() {
|
||||
if !iterator.visited_keys.contains(&r) {
|
||||
if let Some(desc) =
|
||||
object.__get_own_property__(&PropertyKey::from(r.clone()), context)?
|
||||
{
|
||||
iterator.visited_keys.insert(r.clone());
|
||||
if desc.expect_enumerable() {
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::new(r.to_string()),
|
||||
false,
|
||||
context,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let proto = object.prototype().clone();
|
||||
match proto {
|
||||
Some(o) => {
|
||||
object = o;
|
||||
}
|
||||
_ => {
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
))
|
||||
}
|
||||
}
|
||||
iterator.object = JsValue::new(object.clone());
|
||||
iterator.object_was_visited = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the `%ArrayIteratorPrototype%` object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%-object
|
||||
pub(crate) fn create_prototype(
|
||||
iterator_prototype: JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsObject {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
// Create prototype
|
||||
let for_in_iterator =
|
||||
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
|
||||
make_builtin_fn(Self::next, "next", &for_in_iterator, 0, context);
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
let to_string_tag_property = PropertyDescriptor::builder()
|
||||
.value("For In Iterator")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
for_in_iterator.insert(to_string_tag, to_string_tag_property);
|
||||
for_in_iterator
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,422 @@
|
||||
use crate::{check_output, forward, Context, JsValue, TestAction};
|
||||
|
||||
#[test]
|
||||
fn object_create_with_regular_object() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
const foo = { a: 5 };
|
||||
const bar = Object.create(foo);
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "bar.a"), "5");
|
||||
assert_eq!(forward(&mut context, "Object.create.length"), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_create_with_undefined() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
try {
|
||||
const bar = Object.create();
|
||||
} catch (err) {
|
||||
err.toString()
|
||||
}
|
||||
"#;
|
||||
|
||||
let result = forward(&mut context, init);
|
||||
assert_eq!(
|
||||
result,
|
||||
"\"TypeError: Object prototype may only be an Object or null: undefined\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_create_with_number() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
try {
|
||||
const bar = Object.create(5);
|
||||
} catch (err) {
|
||||
err.toString()
|
||||
}
|
||||
"#;
|
||||
|
||||
let result = forward(&mut context, init);
|
||||
assert_eq!(
|
||||
result,
|
||||
"\"TypeError: Object prototype may only be an Object or null: 5\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
// TODO: to test on __proto__ somehow. __proto__ getter is not working as expected currently
|
||||
fn object_create_with_function() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
const x = function (){};
|
||||
const bar = Object.create(5);
|
||||
bar.__proto__
|
||||
"#;
|
||||
|
||||
let result = forward(&mut context, init);
|
||||
assert_eq!(result, "...something on __proto__...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_is() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
var foo = { a: 1};
|
||||
var bar = { a: 1};
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "Object.is('foo', 'foo')"), "true");
|
||||
assert_eq!(forward(&mut context, "Object.is('foo', 'bar')"), "false");
|
||||
assert_eq!(forward(&mut context, "Object.is([], [])"), "false");
|
||||
assert_eq!(forward(&mut context, "Object.is(foo, foo)"), "true");
|
||||
assert_eq!(forward(&mut context, "Object.is(foo, bar)"), "false");
|
||||
assert_eq!(forward(&mut context, "Object.is(null, null)"), "true");
|
||||
assert_eq!(forward(&mut context, "Object.is(0, -0)"), "false");
|
||||
assert_eq!(forward(&mut context, "Object.is(-0, -0)"), "true");
|
||||
assert_eq!(forward(&mut context, "Object.is(NaN, 0/0)"), "true");
|
||||
assert_eq!(forward(&mut context, "Object.is()"), "true");
|
||||
assert_eq!(forward(&mut context, "Object.is(undefined)"), "true");
|
||||
assert!(context.global_object().is_global());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_has_own_property() {
|
||||
let scenario = r#"
|
||||
let symA = Symbol('a');
|
||||
let symB = Symbol('b');
|
||||
|
||||
let x = {
|
||||
undefinedProp: undefined,
|
||||
nullProp: null,
|
||||
someProp: 1,
|
||||
[symA]: 2,
|
||||
100: 3,
|
||||
};
|
||||
"#;
|
||||
|
||||
check_output(&[
|
||||
TestAction::Execute(scenario),
|
||||
TestAction::TestEq("x.hasOwnProperty('hasOwnProperty')", "false"),
|
||||
TestAction::TestEq("x.hasOwnProperty('undefinedProp')", "true"),
|
||||
TestAction::TestEq("x.hasOwnProperty('nullProp')", "true"),
|
||||
TestAction::TestEq("x.hasOwnProperty('someProp')", "true"),
|
||||
TestAction::TestEq("x.hasOwnProperty(symB)", "false"),
|
||||
TestAction::TestEq("x.hasOwnProperty(symA)", "true"),
|
||||
TestAction::TestEq("x.hasOwnProperty(1000)", "false"),
|
||||
TestAction::TestEq("x.hasOwnProperty(100)", "true"),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_has_own() {
|
||||
let scenario = r#"
|
||||
let symA = Symbol('a');
|
||||
let symB = Symbol('b');
|
||||
|
||||
let x = {
|
||||
undefinedProp: undefined,
|
||||
nullProp: null,
|
||||
someProp: 1,
|
||||
[symA]: 2,
|
||||
100: 3,
|
||||
};
|
||||
"#;
|
||||
|
||||
check_output(&[
|
||||
TestAction::Execute(scenario),
|
||||
TestAction::TestEq("Object.hasOwn(x, 'hasOwnProperty')", "false"),
|
||||
TestAction::TestEq("Object.hasOwn(x, 'undefinedProp')", "true"),
|
||||
TestAction::TestEq("Object.hasOwn(x, 'nullProp')", "true"),
|
||||
TestAction::TestEq("Object.hasOwn(x, 'someProp')", "true"),
|
||||
TestAction::TestEq("Object.hasOwn(x, symB)", "false"),
|
||||
TestAction::TestEq("Object.hasOwn(x, symA)", "true"),
|
||||
TestAction::TestEq("Object.hasOwn(x, 1000)", "false"),
|
||||
TestAction::TestEq("Object.hasOwn(x, 100)", "true"),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_property_is_enumerable() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let x = { enumerableProp: 'yes' };
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
assert_eq!(
|
||||
forward(&mut context, r#"x.propertyIsEnumerable('enumerableProp')"#),
|
||||
"true"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, r#"x.propertyIsEnumerable('hasOwnProperty')"#),
|
||||
"false"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, r#"x.propertyIsEnumerable('not_here')"#),
|
||||
"false",
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, r#"x.propertyIsEnumerable()"#),
|
||||
"false",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_to_string() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let u = undefined;
|
||||
let n = null;
|
||||
let a = [];
|
||||
Array.prototype.toString = Object.prototype.toString;
|
||||
let f = () => {};
|
||||
Function.prototype.toString = Object.prototype.toString;
|
||||
let e = new Error('test');
|
||||
Error.prototype.toString = Object.prototype.toString;
|
||||
let b = Boolean();
|
||||
Boolean.prototype.toString = Object.prototype.toString;
|
||||
let i = Number(42);
|
||||
Number.prototype.toString = Object.prototype.toString;
|
||||
let s = String('boa');
|
||||
String.prototype.toString = Object.prototype.toString;
|
||||
let d = new Date(Date.now());
|
||||
Date.prototype.toString = Object.prototype.toString;
|
||||
let re = /boa/;
|
||||
RegExp.prototype.toString = Object.prototype.toString;
|
||||
let o = Object();
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
assert_eq!(
|
||||
forward(&mut context, "Object.prototype.toString.call(u)"),
|
||||
"\"[object Undefined]\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "Object.prototype.toString.call(n)"),
|
||||
"\"[object Null]\""
|
||||
);
|
||||
assert_eq!(forward(&mut context, "a.toString()"), "\"[object Array]\"");
|
||||
assert_eq!(
|
||||
forward(&mut context, "f.toString()"),
|
||||
"\"[object Function]\""
|
||||
);
|
||||
assert_eq!(forward(&mut context, "e.toString()"), "\"[object Error]\"");
|
||||
assert_eq!(
|
||||
forward(&mut context, "b.toString()"),
|
||||
"\"[object Boolean]\""
|
||||
);
|
||||
assert_eq!(forward(&mut context, "i.toString()"), "\"[object Number]\"");
|
||||
assert_eq!(forward(&mut context, "s.toString()"), "\"[object String]\"");
|
||||
assert_eq!(forward(&mut context, "d.toString()"), "\"[object Date]\"");
|
||||
assert_eq!(
|
||||
forward(&mut context, "re.toString()"),
|
||||
"\"[object RegExp]\""
|
||||
);
|
||||
assert_eq!(forward(&mut context, "o.toString()"), "\"[object Object]\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn define_symbol_property() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
let obj = {};
|
||||
let sym = Symbol("key");
|
||||
Object.defineProperty(obj, sym, { value: "val" });
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
assert_eq!(forward(&mut context, "obj[sym]"), "\"val\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_own_property_descriptor_1_arg_returns_undefined() {
|
||||
let mut context = Context::default();
|
||||
let code = r#"
|
||||
let obj = {a: 2};
|
||||
Object.getOwnPropertyDescriptor(obj)
|
||||
"#;
|
||||
assert_eq!(context.eval(code).unwrap(), JsValue::undefined());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_own_property_descriptor() {
|
||||
let mut context = Context::default();
|
||||
forward(
|
||||
&mut context,
|
||||
r#"
|
||||
let obj = {a: 2};
|
||||
let result = Object.getOwnPropertyDescriptor(obj, "a");
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_eq!(forward(&mut context, "result.enumerable"), "true");
|
||||
assert_eq!(forward(&mut context, "result.writable"), "true");
|
||||
assert_eq!(forward(&mut context, "result.configurable"), "true");
|
||||
assert_eq!(forward(&mut context, "result.value"), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_own_property_descriptors() {
|
||||
let mut context = Context::default();
|
||||
forward(
|
||||
&mut context,
|
||||
r#"
|
||||
let obj = {a: 1, b: 2};
|
||||
let result = Object.getOwnPropertyDescriptors(obj);
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_eq!(forward(&mut context, "result.a.enumerable"), "true");
|
||||
assert_eq!(forward(&mut context, "result.a.writable"), "true");
|
||||
assert_eq!(forward(&mut context, "result.a.configurable"), "true");
|
||||
assert_eq!(forward(&mut context, "result.a.value"), "1");
|
||||
|
||||
assert_eq!(forward(&mut context, "result.b.enumerable"), "true");
|
||||
assert_eq!(forward(&mut context, "result.b.writable"), "true");
|
||||
assert_eq!(forward(&mut context, "result.b.configurable"), "true");
|
||||
assert_eq!(forward(&mut context, "result.b.value"), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_define_properties() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
const obj = {};
|
||||
|
||||
Object.defineProperties(obj, {
|
||||
p: {
|
||||
value: 42,
|
||||
writable: true
|
||||
}
|
||||
});
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
assert_eq!(forward(&mut context, "obj.p"), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_is_prototype_of() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
Object.prototype.isPrototypeOf(String.prototype)
|
||||
"#;
|
||||
|
||||
assert_eq!(context.eval(init).unwrap(), JsValue::new(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_get_own_property_names_invalid_args() {
|
||||
let error_message = r#"Uncaught "TypeError": "cannot convert 'null' or 'undefined' to object""#;
|
||||
|
||||
check_output(&[
|
||||
TestAction::TestEq("Object.getOwnPropertyNames()", error_message),
|
||||
TestAction::TestEq("Object.getOwnPropertyNames(null)", error_message),
|
||||
TestAction::TestEq("Object.getOwnPropertyNames(undefined)", error_message),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_get_own_property_names() {
|
||||
check_output(&[
|
||||
TestAction::TestEq("Object.getOwnPropertyNames(0)", "[]"),
|
||||
TestAction::TestEq("Object.getOwnPropertyNames(false)", "[]"),
|
||||
TestAction::TestEq(r#"Object.getOwnPropertyNames(Symbol("a"))"#, "[]"),
|
||||
TestAction::TestEq("Object.getOwnPropertyNames({})", "[]"),
|
||||
TestAction::TestEq("Object.getOwnPropertyNames(NaN)", "[]"),
|
||||
TestAction::TestEq(
|
||||
"Object.getOwnPropertyNames([1, 2, 3])",
|
||||
r#"[ "0", "1", "2", "length" ]"#,
|
||||
),
|
||||
TestAction::TestEq(
|
||||
r#"Object.getOwnPropertyNames({
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
[ Symbol("c") ]: 3,
|
||||
[ Symbol("d") ]: 4,
|
||||
})"#,
|
||||
r#"[ "a", "b" ]"#,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_get_own_property_symbols_invalid_args() {
|
||||
let error_message = r#"Uncaught "TypeError": "cannot convert 'null' or 'undefined' to object""#;
|
||||
|
||||
check_output(&[
|
||||
TestAction::TestEq("Object.getOwnPropertySymbols()", error_message),
|
||||
TestAction::TestEq("Object.getOwnPropertySymbols(null)", error_message),
|
||||
TestAction::TestEq("Object.getOwnPropertySymbols(undefined)", error_message),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_get_own_property_symbols() {
|
||||
check_output(&[
|
||||
TestAction::TestEq("Object.getOwnPropertySymbols(0)", "[]"),
|
||||
TestAction::TestEq("Object.getOwnPropertySymbols(false)", "[]"),
|
||||
TestAction::TestEq(r#"Object.getOwnPropertySymbols(Symbol("a"))"#, "[]"),
|
||||
TestAction::TestEq("Object.getOwnPropertySymbols({})", "[]"),
|
||||
TestAction::TestEq("Object.getOwnPropertySymbols(NaN)", "[]"),
|
||||
TestAction::TestEq("Object.getOwnPropertySymbols([1, 2, 3])", "[]"),
|
||||
TestAction::TestEq(
|
||||
r#"
|
||||
Object.getOwnPropertySymbols({
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
[ Symbol("c") ]: 3,
|
||||
[ Symbol("d") ]: 4,
|
||||
})"#,
|
||||
"[ Symbol(c), Symbol(d) ]",
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_from_entries_invalid_args() {
|
||||
let error_message = r#"Uncaught "TypeError": "cannot convert null or undefined to Object""#;
|
||||
|
||||
check_output(&[
|
||||
TestAction::TestEq("Object.fromEntries()", error_message),
|
||||
TestAction::TestEq("Object.fromEntries(null)", error_message),
|
||||
TestAction::TestEq("Object.fromEntries(undefined)", error_message),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_from_entries() {
|
||||
let scenario = r#"
|
||||
let sym = Symbol("sym");
|
||||
let map = Object.fromEntries([
|
||||
["long key", 1],
|
||||
["short", 2],
|
||||
[sym, 3],
|
||||
[5, 4],
|
||||
]);
|
||||
"#;
|
||||
|
||||
check_output(&[
|
||||
TestAction::Execute(scenario),
|
||||
TestAction::TestEq("map['long key']", "1"),
|
||||
TestAction::TestEq("map.short", "2"),
|
||||
TestAction::TestEq("map[sym]", "3"),
|
||||
TestAction::TestEq("map[5]", "4"),
|
||||
]);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,188 @@
|
||||
use super::{Promise, PromiseCapability};
|
||||
use crate::{
|
||||
builtins::promise::{ReactionRecord, ReactionType},
|
||||
job::JobCallback,
|
||||
object::{FunctionBuilder, JsObject},
|
||||
Context, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct PromiseJob;
|
||||
|
||||
impl PromiseJob {
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-newpromisereactionjob
|
||||
pub(crate) fn new_promise_reaction_job(
|
||||
reaction: ReactionRecord,
|
||||
argument: JsValue,
|
||||
context: &mut Context,
|
||||
) -> JobCallback {
|
||||
#[derive(Debug, Trace, Finalize)]
|
||||
struct ReactionJobCaptures {
|
||||
reaction: ReactionRecord,
|
||||
argument: JsValue,
|
||||
}
|
||||
|
||||
// 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called:
|
||||
let job = FunctionBuilder::closure_with_captures(
|
||||
context,
|
||||
|_this, _args, captures, context| {
|
||||
let ReactionJobCaptures { reaction, argument } = captures;
|
||||
|
||||
let ReactionRecord {
|
||||
// a. Let promiseCapability be reaction.[[Capability]].
|
||||
promise_capability,
|
||||
// b. Let type be reaction.[[Type]].
|
||||
reaction_type,
|
||||
// c. Let handler be reaction.[[Handler]].
|
||||
handler,
|
||||
} = reaction;
|
||||
|
||||
let handler_result = match handler {
|
||||
// d. If handler is empty, then
|
||||
None => match reaction_type {
|
||||
// i. If type is Fulfill, let handlerResult be NormalCompletion(argument).
|
||||
ReactionType::Fulfill => Ok(argument.clone()),
|
||||
// ii. Else,
|
||||
// 1. Assert: type is Reject.
|
||||
ReactionType::Reject => {
|
||||
// 2. Let handlerResult be ThrowCompletion(argument).
|
||||
Err(argument.clone())
|
||||
}
|
||||
},
|
||||
// e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)).
|
||||
Some(handler) => {
|
||||
handler.call_job_callback(&JsValue::Undefined, &[argument.clone()], context)
|
||||
}
|
||||
};
|
||||
|
||||
match promise_capability {
|
||||
None => {
|
||||
// f. If promiseCapability is undefined, then
|
||||
// i. Assert: handlerResult is not an abrupt completion.
|
||||
assert!(
|
||||
handler_result.is_ok(),
|
||||
"Assertion: <handlerResult is not an abrupt completion> failed"
|
||||
);
|
||||
|
||||
// ii. Return empty.
|
||||
Ok(JsValue::Undefined)
|
||||
}
|
||||
Some(promise_capability_record) => {
|
||||
// g. Assert: promiseCapability is a PromiseCapability Record.
|
||||
let PromiseCapability {
|
||||
promise: _,
|
||||
resolve,
|
||||
reject,
|
||||
} = promise_capability_record;
|
||||
|
||||
match handler_result {
|
||||
// h. If handlerResult is an abrupt completion, then
|
||||
Err(value) => {
|
||||
// i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »).
|
||||
context.call(&reject.clone().into(), &JsValue::Undefined, &[value])
|
||||
}
|
||||
|
||||
// i. Else,
|
||||
Ok(value) => {
|
||||
// i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »).
|
||||
context.call(&resolve.clone().into(), &JsValue::Undefined, &[value])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ReactionJobCaptures { reaction, argument },
|
||||
)
|
||||
.build()
|
||||
.into();
|
||||
|
||||
// 2. Let handlerRealm be null.
|
||||
// 3. If reaction.[[Handler]] is not empty, then
|
||||
// a. Let getHandlerRealmResult be Completion(GetFunctionRealm(reaction.[[Handler]].[[Callback]])).
|
||||
// b. If getHandlerRealmResult is a normal completion, set handlerRealm to getHandlerRealmResult.[[Value]].
|
||||
// c. Else, set handlerRealm to the current Realm Record.
|
||||
// d. NOTE: handlerRealm is never null unless the handler is undefined. When the handler is a revoked Proxy and no ECMAScript code runs, handlerRealm is used to create error objects.
|
||||
// 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }.
|
||||
JobCallback::make_job_callback(job)
|
||||
}
|
||||
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
|
||||
pub(crate) fn new_promise_resolve_thenable_job(
|
||||
promise_to_resolve: JsObject,
|
||||
thenable: JsValue,
|
||||
then: JobCallback,
|
||||
context: &mut Context,
|
||||
) -> JobCallback {
|
||||
// 1. Let job be a new Job Abstract Closure with no parameters that captures promiseToResolve, thenable, and then and performs the following steps when called:
|
||||
let job = FunctionBuilder::closure_with_captures(
|
||||
context,
|
||||
|_this: &JsValue, _args: &[JsValue], captures, context: &mut Context| {
|
||||
let JobCapture {
|
||||
promise_to_resolve,
|
||||
thenable,
|
||||
then,
|
||||
} = captures;
|
||||
|
||||
// a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve).
|
||||
let resolving_functions =
|
||||
Promise::create_resolving_functions(promise_to_resolve, context);
|
||||
|
||||
// b. Let thenCallResult be Completion(HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)).
|
||||
let then_call_result = then.call_job_callback(
|
||||
thenable,
|
||||
&[
|
||||
resolving_functions.resolve,
|
||||
resolving_functions.reject.clone(),
|
||||
],
|
||||
context,
|
||||
);
|
||||
|
||||
// c. If thenCallResult is an abrupt completion, then
|
||||
if let Err(value) = then_call_result {
|
||||
// i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »).
|
||||
return context.call(
|
||||
&resolving_functions.reject,
|
||||
&JsValue::Undefined,
|
||||
&[value],
|
||||
);
|
||||
}
|
||||
|
||||
// d. Return ? thenCallResult.
|
||||
then_call_result
|
||||
},
|
||||
JobCapture::new(promise_to_resolve, thenable, then),
|
||||
)
|
||||
.build();
|
||||
|
||||
// 2. Let getThenRealmResult be Completion(GetFunctionRealm(then.[[Callback]])).
|
||||
// 3. If getThenRealmResult is a normal completion, let thenRealm be getThenRealmResult.[[Value]].
|
||||
// 4. Else, let thenRealm be the current Realm Record.
|
||||
// 5. NOTE: thenRealm is never null. When then.[[Callback]] is a revoked Proxy and no code runs, thenRealm is used to create error objects.
|
||||
// 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }.
|
||||
JobCallback::make_job_callback(job.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Trace, Finalize)]
|
||||
struct JobCapture {
|
||||
promise_to_resolve: JsObject,
|
||||
thenable: JsValue,
|
||||
then: JobCallback,
|
||||
}
|
||||
|
||||
impl JobCapture {
|
||||
fn new(promise_to_resolve: JsObject, thenable: JsValue, then: JobCallback) -> Self {
|
||||
Self {
|
||||
promise_to_resolve,
|
||||
thenable,
|
||||
then,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use crate::{forward, Context};
|
||||
|
||||
#[test]
|
||||
fn promise() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let count = 0;
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
count += 1;
|
||||
resolve(undefined);
|
||||
}).then((_) => (count += 1));
|
||||
count += 1;
|
||||
count;
|
||||
"#;
|
||||
let result = context.eval(init).unwrap();
|
||||
assert_eq!(result.as_number(), Some(2_f64));
|
||||
let after_completion = forward(&mut context, "count");
|
||||
assert_eq!(after_completion, String::from("3"));
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
//! This module implements the global `Proxy` object.
|
||||
//!
|
||||
//! The `Proxy` object enables you to create a proxy for another object,
|
||||
//! which can intercept and redefine fundamental operations for that object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-proxy-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
object::{ConstructorBuilder, FunctionBuilder, JsFunction, JsObject, ObjectData},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
/// Javascript `Proxy` object.
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct Proxy {
|
||||
// (target, handler)
|
||||
data: Option<(JsObject, JsObject)>,
|
||||
}
|
||||
|
||||
impl BuiltIn for Proxy {
|
||||
const NAME: &'static str = "Proxy";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().proxy().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.has_prototype_property(false)
|
||||
.static_method(Self::revocable, "revocable", 2)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Proxy {
|
||||
const LENGTH: usize = 2;
|
||||
|
||||
pub(crate) fn new(target: JsObject, handler: JsObject) -> Self {
|
||||
Self {
|
||||
data: Some((target, handler)),
|
||||
}
|
||||
}
|
||||
|
||||
/// This is an internal method only built for usage in the proxy internal methods.
|
||||
///
|
||||
/// It returns the (target, handler) of the proxy.
|
||||
pub(crate) fn try_data(&self, context: &mut Context) -> JsResult<(JsObject, JsObject)> {
|
||||
self.data.clone().ok_or_else(|| {
|
||||
context.construct_type_error("Proxy object has empty handler and target")
|
||||
})
|
||||
}
|
||||
|
||||
/// `28.2.1.1 Proxy ( target, handler )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy-target-handler
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if new_target.is_undefined() {
|
||||
return context.throw_type_error("Proxy constructor called on undefined new target");
|
||||
}
|
||||
|
||||
// 2. Return ? ProxyCreate(target, handler).
|
||||
Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context).map(JsValue::from)
|
||||
}
|
||||
|
||||
// `10.5.14 ProxyCreate ( target, handler )`
|
||||
//
|
||||
// More information:
|
||||
// - [ECMAScript reference][spec]
|
||||
//
|
||||
// [spec]: https://tc39.es/ecma262/#sec-proxycreate
|
||||
pub(crate) fn create(
|
||||
target: &JsValue,
|
||||
handler: &JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject> {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
let target = target.as_object().ok_or_else(|| {
|
||||
context.construct_type_error("Proxy constructor called with non-object target")
|
||||
})?;
|
||||
|
||||
// 2. If Type(handler) is not Object, throw a TypeError exception.
|
||||
let handler = handler.as_object().ok_or_else(|| {
|
||||
context.construct_type_error("Proxy constructor called with non-object handler")
|
||||
})?;
|
||||
|
||||
// 3. Let P be ! MakeBasicObject(« [[ProxyHandler]], [[ProxyTarget]] »).
|
||||
// 4. Set P's essential internal methods, except for [[Call]] and [[Construct]], to the definitions specified in 10.5.
|
||||
// 5. If IsCallable(target) is true, then
|
||||
// a. Set P.[[Call]] as specified in 10.5.12.
|
||||
// b. If IsConstructor(target) is true, then
|
||||
// i. Set P.[[Construct]] as specified in 10.5.13.
|
||||
// 6. Set P.[[ProxyTarget]] to target.
|
||||
// 7. Set P.[[ProxyHandler]] to handler.
|
||||
let p = JsObject::from_proto_and_data(
|
||||
context.intrinsics().constructors().object().prototype(),
|
||||
ObjectData::proxy(
|
||||
Self::new(target.clone(), handler.clone()),
|
||||
target.is_callable(),
|
||||
target.is_constructor(),
|
||||
),
|
||||
);
|
||||
|
||||
// 8. Return P.
|
||||
Ok(p)
|
||||
}
|
||||
|
||||
pub(crate) fn revoker(proxy: JsObject, context: &mut Context) -> JsFunction {
|
||||
// 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »).
|
||||
// 4. Set revoker.[[RevocableProxy]] to p.
|
||||
FunctionBuilder::closure_with_captures(
|
||||
context,
|
||||
|_, _, revocable_proxy, _| {
|
||||
// a. Let F be the active function object.
|
||||
// b. Let p be F.[[RevocableProxy]].
|
||||
// d. Set F.[[RevocableProxy]] to null.
|
||||
if let Some(p) = revocable_proxy.take() {
|
||||
// e. Assert: p is a Proxy object.
|
||||
// f. Set p.[[ProxyTarget]] to null.
|
||||
// g. Set p.[[ProxyHandler]] to null.
|
||||
p.borrow_mut()
|
||||
.as_proxy_mut()
|
||||
.expect("[[RevocableProxy]] must be a proxy object")
|
||||
.data = None;
|
||||
}
|
||||
|
||||
// c. If p is null, return undefined.
|
||||
// h. Return undefined.
|
||||
Ok(JsValue::undefined())
|
||||
},
|
||||
Some(proxy),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
/// `28.2.2.1 Proxy.revocable ( target, handler )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-proxy.revocable
|
||||
fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let p be ? ProxyCreate(target, handler).
|
||||
let p = Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context)?;
|
||||
|
||||
// Revoker creation steps on `Proxy::revoker`
|
||||
let revoker = Self::revoker(p.clone(), context);
|
||||
|
||||
// 5. Let result be ! OrdinaryObjectCreate(%Object.prototype%).
|
||||
let result = context.construct_object();
|
||||
|
||||
// 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p).
|
||||
result
|
||||
.create_data_property_or_throw("proxy", p, context)
|
||||
.expect("CreateDataPropertyOrThrow cannot fail here");
|
||||
|
||||
// 7. Perform ! CreateDataPropertyOrThrow(result, "revoke", revoker).
|
||||
result
|
||||
.create_data_property_or_throw("revoke", revoker, context)
|
||||
.expect("CreateDataPropertyOrThrow cannot fail here");
|
||||
|
||||
// 8. Return result.
|
||||
Ok(result.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
//! This module implements the global `Reflect` object.
|
||||
//!
|
||||
//! The `Reflect` global object is a built-in object that provides methods for interceptable
|
||||
//! JavaScript operations.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-reflect-object
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
|
||||
|
||||
use super::{Array, JsArgs};
|
||||
use crate::{
|
||||
builtins::{self, BuiltIn},
|
||||
object::ObjectInitializer,
|
||||
property::Attribute,
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Javascript `Reflect` object.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) struct Reflect;
|
||||
|
||||
impl BuiltIn for Reflect {
|
||||
const NAME: &'static str = "Reflect";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
|
||||
ObjectInitializer::new(context)
|
||||
.function(Self::apply, "apply", 3)
|
||||
.function(Self::construct, "construct", 2)
|
||||
.function(Self::define_property, "defineProperty", 3)
|
||||
.function(Self::delete_property, "deleteProperty", 2)
|
||||
.function(Self::get, "get", 2)
|
||||
.function(
|
||||
Self::get_own_property_descriptor,
|
||||
"getOwnPropertyDescriptor",
|
||||
2,
|
||||
)
|
||||
.function(Self::get_prototype_of, "getPrototypeOf", 1)
|
||||
.function(Self::has, "has", 2)
|
||||
.function(Self::is_extensible, "isExtensible", 1)
|
||||
.function(Self::own_keys, "ownKeys", 1)
|
||||
.function(Self::prevent_extensions, "preventExtensions", 1)
|
||||
.function(Self::set, "set", 3)
|
||||
.function(Self::set_prototype_of, "setPrototypeOf", 2)
|
||||
.property(
|
||||
to_string_tag,
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Reflect {
|
||||
/// Calls a target function with arguments.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.apply
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/apply
|
||||
pub(crate) fn apply(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let target = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| context.construct_type_error("target must be a function"))?;
|
||||
let this_arg = args.get_or_undefined(1);
|
||||
let args_list = args.get_or_undefined(2);
|
||||
|
||||
if !target.is_callable() {
|
||||
return context.throw_type_error("target must be a function");
|
||||
}
|
||||
let args = args_list.create_list_from_array_like(&[], context)?;
|
||||
target.call(this_arg, &args, context)
|
||||
}
|
||||
|
||||
/// Calls a target function as a constructor with arguments.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.construct
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct
|
||||
pub(crate) fn construct(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If IsConstructor(target) is false, throw a TypeError exception.
|
||||
let target = args
|
||||
.get_or_undefined(0)
|
||||
.as_constructor()
|
||||
.ok_or_else(|| context.construct_type_error("target must be a constructor"))?;
|
||||
|
||||
let new_target = if let Some(new_target) = args.get(2) {
|
||||
// 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception.
|
||||
if let Some(new_target) = new_target.as_constructor() {
|
||||
new_target
|
||||
} else {
|
||||
return context.throw_type_error("newTarget must be a constructor");
|
||||
}
|
||||
} else {
|
||||
// 2. If newTarget is not present, set newTarget to target.
|
||||
target
|
||||
};
|
||||
|
||||
// 4. Let args be ? CreateListFromArrayLike(argumentsList).
|
||||
let args = args
|
||||
.get_or_undefined(1)
|
||||
.create_list_from_array_like(&[], context)?;
|
||||
|
||||
// 5. Return ? Construct(target, args, newTarget).
|
||||
target
|
||||
.construct(&args, Some(new_target), context)
|
||||
.map(JsValue::from)
|
||||
}
|
||||
|
||||
/// Defines a property on an object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.defineProperty
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/defineProperty
|
||||
pub(crate) fn define_property(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let target = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
|
||||
let key = args.get_or_undefined(1).to_property_key(context)?;
|
||||
let prop_desc: JsValue = args
|
||||
.get(2)
|
||||
.and_then(|v| v.as_object().cloned())
|
||||
.ok_or_else(|| context.construct_type_error("property descriptor must be an object"))?
|
||||
.into();
|
||||
|
||||
target
|
||||
.__define_own_property__(key, prop_desc.to_property_descriptor(context)?, context)
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
/// Defines a property on an object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.deleteproperty
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/deleteProperty
|
||||
pub(crate) fn delete_property(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let target = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
|
||||
let key = args.get_or_undefined(1).to_property_key(context)?;
|
||||
|
||||
Ok(target.__delete__(&key, context)?.into())
|
||||
}
|
||||
|
||||
/// Gets a property of an object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.get
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get
|
||||
pub(crate) fn get(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
let target = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
|
||||
// 2. Let key be ? ToPropertyKey(propertyKey).
|
||||
let key = args.get_or_undefined(1).to_property_key(context)?;
|
||||
// 3. If receiver is not present, then
|
||||
let receiver = if let Some(receiver) = args.get(2).cloned() {
|
||||
receiver
|
||||
} else {
|
||||
// 3.a. Set receiver to target.
|
||||
target.clone().into()
|
||||
};
|
||||
// 4. Return ? target.[[Get]](key, receiver).
|
||||
target.__get__(&key, receiver, context)
|
||||
}
|
||||
|
||||
/// Gets a property of an object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.getownpropertydescriptor
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getOwnPropertyDescriptor
|
||||
pub(crate) fn get_own_property_descriptor(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
if args.get_or_undefined(0).is_object() {
|
||||
// This function is the same as Object.prototype.getOwnPropertyDescriptor, that why
|
||||
// it is invoked here.
|
||||
builtins::object::Object::get_own_property_descriptor(
|
||||
&JsValue::undefined(),
|
||||
args,
|
||||
context,
|
||||
)
|
||||
} else {
|
||||
context.throw_type_error("target must be an object")
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the prototype of an object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.getprototypeof
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getPrototypeOf
|
||||
pub(crate) fn get_prototype_of(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let target = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
|
||||
Ok(target
|
||||
.__get_prototype_of__(context)?
|
||||
.map_or(JsValue::Null, JsValue::new))
|
||||
}
|
||||
|
||||
/// Returns `true` if the object has the property, `false` otherwise.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.has
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/has
|
||||
pub(crate) fn has(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let target = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
|
||||
let key = args
|
||||
.get(1)
|
||||
.unwrap_or(&JsValue::undefined())
|
||||
.to_property_key(context)?;
|
||||
Ok(target.__has_property__(&key, context)?.into())
|
||||
}
|
||||
|
||||
/// Returns `true` if the object is extensible, `false` otherwise.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.isextensible
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/isExtensible
|
||||
pub(crate) fn is_extensible(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let target = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
|
||||
Ok(target.__is_extensible__(context)?.into())
|
||||
}
|
||||
|
||||
/// Returns an array of object own property keys.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.ownkeys
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys
|
||||
pub(crate) fn own_keys(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let target = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
|
||||
|
||||
let keys: Vec<JsValue> = target
|
||||
.__own_property_keys__(context)?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
|
||||
Ok(Array::create_array_from_list(keys, context).into())
|
||||
}
|
||||
|
||||
/// Prevents new properties from ever being added to an object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.preventextensions
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/preventExtensions
|
||||
pub(crate) fn prevent_extensions(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let target = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
|
||||
|
||||
Ok(target.__prevent_extensions__(context)?.into())
|
||||
}
|
||||
|
||||
/// Sets a property of an object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.set
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set
|
||||
pub(crate) fn set(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let target = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
|
||||
let key = args.get_or_undefined(1).to_property_key(context)?;
|
||||
let value = args.get_or_undefined(2);
|
||||
let receiver = if let Some(receiver) = args.get(3).cloned() {
|
||||
receiver
|
||||
} else {
|
||||
target.clone().into()
|
||||
};
|
||||
Ok(target
|
||||
.__set__(key, value.clone(), receiver, context)?
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Sets the prototype of an object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-reflect.setprototypeof
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/setPrototypeOf
|
||||
pub(crate) fn set_prototype_of(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let target = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
|
||||
let proto = match args.get_or_undefined(1) {
|
||||
JsValue::Object(obj) => Some(obj.clone()),
|
||||
JsValue::Null => None,
|
||||
_ => return context.throw_type_error("proto must be an object or null"),
|
||||
};
|
||||
Ok(target.__set_prototype_of__(proto, context)?.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
use crate::{forward, Context};
|
||||
|
||||
#[test]
|
||||
fn apply() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
var called = {};
|
||||
function f(n) { called.result = n };
|
||||
Reflect.apply(f, undefined, [42]);
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "called.result"), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
var called = {};
|
||||
function f(n) { called.result = n };
|
||||
Reflect.construct(f, [42]);
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "called.result"), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn define_property() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
let obj = {};
|
||||
Reflect.defineProperty(obj, 'p', { value: 42 });
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "obj.p"), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_property() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
let obj = { p: 42 };
|
||||
let deleted = Reflect.deleteProperty(obj, 'p');
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "obj.p"), "undefined");
|
||||
assert_eq!(forward(&mut context, "deleted"), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
let obj = { p: 42 }
|
||||
let p = Reflect.get(obj, 'p');
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "p"), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_own_property_descriptor() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
let obj = { p: 42 };
|
||||
let desc = Reflect.getOwnPropertyDescriptor(obj, 'p');
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "desc.value"), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_prototype_of() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
function F() { this.p = 42 };
|
||||
let f = new F();
|
||||
let proto = Reflect.getPrototypeOf(f);
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "proto.constructor.name"), "\"F\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
let obj = { p: 42 };
|
||||
let hasP = Reflect.has(obj, 'p');
|
||||
let hasP2 = Reflect.has(obj, 'p2');
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "hasP"), "true");
|
||||
assert_eq!(forward(&mut context, "hasP2"), "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_extensible() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
let obj = { p: 42 };
|
||||
let isExtensible = Reflect.isExtensible(obj);
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "isExtensible"), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn own_keys() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
let obj = { p: 42 };
|
||||
let ownKeys = Reflect.ownKeys(obj);
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "ownKeys"), r#"[ "p" ]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prevent_extensions() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
let obj = { p: 42 };
|
||||
let r = Reflect.preventExtensions(obj);
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "r"), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
let obj = {};
|
||||
Reflect.set(obj, 'p', 42);
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "obj.p"), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_prototype_of() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
function F() { this.p = 42 };
|
||||
let obj = {}
|
||||
Reflect.setPrototypeOf(obj, F);
|
||||
let p = Reflect.getPrototypeOf(obj);
|
||||
"#;
|
||||
|
||||
forward(&mut context, init);
|
||||
|
||||
assert_eq!(forward(&mut context, "p.name"), "\"F\"");
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,170 @@
|
||||
//! This module implements the global `RegExp String Iterator` object.
|
||||
//!
|
||||
//! A `RegExp` String Iterator is an object, that represents a specific iteration over some
|
||||
//! specific String instance object, matching against some specific `RegExp` instance object.
|
||||
//! There is not a named constructor for `RegExp` String Iterator objects. Instead, `RegExp`
|
||||
//! String Iterator objects are created by calling certain methods of `RegExp` instance objects.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-regexp-string-iterator-objects
|
||||
|
||||
use crate::{
|
||||
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, regexp},
|
||||
object::{JsObject, ObjectData},
|
||||
property::PropertyDescriptor,
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
use regexp::{advance_string_index, RegExp};
|
||||
|
||||
// TODO: See todos in create_regexp_string_iterator and next.
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct RegExpStringIterator {
|
||||
matcher: JsObject,
|
||||
string: JsString,
|
||||
global: bool,
|
||||
unicode: bool,
|
||||
completed: bool,
|
||||
}
|
||||
|
||||
// TODO: See todos in create_regexp_string_iterator and next.
|
||||
impl RegExpStringIterator {
|
||||
fn new(matcher: JsObject, string: JsString, global: bool, unicode: bool) -> Self {
|
||||
Self {
|
||||
matcher,
|
||||
string,
|
||||
global,
|
||||
unicode,
|
||||
completed: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// `22.2.7.1 CreateRegExpStringIterator ( R, S, global, fullUnicode )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createregexpstringiterator
|
||||
pub(crate) fn create_regexp_string_iterator(
|
||||
matcher: JsObject,
|
||||
string: JsString,
|
||||
global: bool,
|
||||
unicode: bool,
|
||||
context: &mut Context,
|
||||
) -> JsValue {
|
||||
// TODO: Implement this with closures and generators.
|
||||
// For now all values of the closure are stored in RegExpStringIterator and the actual closure execution is in `.next()`.
|
||||
|
||||
// 1. Assert: Type(S) is String.
|
||||
// 2. Assert: Type(global) is Boolean.
|
||||
// 3. Assert: Type(fullUnicode) is Boolean.
|
||||
|
||||
// 4. Let closure be a new Abstract Closure with no parameters that captures R, S, global,
|
||||
// and fullUnicode and performs the following steps when called:
|
||||
|
||||
// 5. Return ! CreateIteratorFromClosure(closure, "%RegExpStringIteratorPrototype%", %RegExpStringIteratorPrototype%).
|
||||
|
||||
let regexp_string_iterator = JsObject::from_proto_and_data(
|
||||
context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.regexp_string_iterator(),
|
||||
ObjectData::reg_exp_string_iterator(Self::new(matcher, string, global, unicode)),
|
||||
);
|
||||
|
||||
regexp_string_iterator.into()
|
||||
}
|
||||
|
||||
pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let mut iterator = this.as_object().map(JsObject::borrow_mut);
|
||||
let iterator = iterator
|
||||
.as_mut()
|
||||
.and_then(|obj| obj.as_regexp_string_iterator_mut())
|
||||
.ok_or_else(|| context.construct_type_error("`this` is not a RegExpStringIterator"))?;
|
||||
if iterator.completed {
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: This is the code that should be created as a closure in create_regexp_string_iterator.
|
||||
|
||||
// i. Let match be ? RegExpExec(R, S).
|
||||
let m = RegExp::abstract_exec(&iterator.matcher, iterator.string.clone(), context)?;
|
||||
|
||||
if let Some(m) = m {
|
||||
// iii. If global is false, then
|
||||
if !iterator.global {
|
||||
// 1. Perform ? Yield(match).
|
||||
// 2. Return undefined.
|
||||
iterator.completed = true;
|
||||
return Ok(create_iter_result_object(m.into(), false, context));
|
||||
}
|
||||
|
||||
// iv. Let matchStr be ? ToString(? Get(match, "0")).
|
||||
let m_str = m.get("0", context)?.to_string(context)?;
|
||||
|
||||
// v. If matchStr is the empty String, then
|
||||
if m_str.is_empty() {
|
||||
// 1. Let thisIndex be ℝ(? ToLength(? Get(R, "lastIndex"))).
|
||||
let this_index = iterator
|
||||
.matcher
|
||||
.get("lastIndex", context)?
|
||||
.to_length(context)?;
|
||||
|
||||
// 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode).
|
||||
let next_index =
|
||||
advance_string_index(&iterator.string, this_index, iterator.unicode);
|
||||
|
||||
// 3. Perform ? Set(R, "lastIndex", 𝔽(nextIndex), true).
|
||||
iterator
|
||||
.matcher
|
||||
.set("lastIndex", next_index, true, context)?;
|
||||
}
|
||||
|
||||
// vi. Perform ? Yield(match).
|
||||
Ok(create_iter_result_object(m.into(), false, context))
|
||||
} else {
|
||||
// ii. If match is null, return undefined.
|
||||
iterator.completed = true;
|
||||
Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the `%ArrayIteratorPrototype%` object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
|
||||
pub(crate) fn create_prototype(
|
||||
iterator_prototype: JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsObject {
|
||||
let _timer = Profiler::global().start_event("RegExp String Iterator", "init");
|
||||
|
||||
// Create prototype
|
||||
let result = JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
|
||||
make_builtin_fn(Self::next, "next", &result, 0, context);
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
let to_string_tag_property = PropertyDescriptor::builder()
|
||||
.value("RegExp String Iterator")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
result.insert(to_string_tag, to_string_tag_property);
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
use crate::{forward, Context};
|
||||
|
||||
#[test]
|
||||
fn constructors() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var constructed = new RegExp("[0-9]+(\\.[0-9]+)?");
|
||||
var literal = /[0-9]+(\.[0-9]+)?/;
|
||||
var ctor_literal = new RegExp(/[0-9]+(\.[0-9]+)?/);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
assert_eq!(forward(&mut context, "constructed.test('1.0')"), "true");
|
||||
assert_eq!(forward(&mut context, "literal.test('1.0')"), "true");
|
||||
assert_eq!(forward(&mut context, "ctor_literal.test('1.0')"), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn species() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
var descriptor = Object.getOwnPropertyDescriptor(RegExp, Symbol.species);
|
||||
var accessor = Object.getOwnPropertyDescriptor(RegExp, Symbol.species).get;
|
||||
var name = Object.getOwnPropertyDescriptor(descriptor.get, "name");
|
||||
var length = Object.getOwnPropertyDescriptor(descriptor.get, "length");
|
||||
var thisVal = {};
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
// length
|
||||
assert_eq!(forward(&mut context, "descriptor.get.length"), "0");
|
||||
assert_eq!(forward(&mut context, "length.enumerable"), "false");
|
||||
assert_eq!(forward(&mut context, "length.writable"), "false");
|
||||
assert_eq!(forward(&mut context, "length.configurable"), "true");
|
||||
|
||||
// return-value
|
||||
assert_eq!(
|
||||
forward(&mut context, "Object.is(accessor.call(thisVal), thisVal)"),
|
||||
"true"
|
||||
);
|
||||
|
||||
// symbol-species-name
|
||||
assert_eq!(
|
||||
forward(&mut context, "descriptor.get.name"),
|
||||
"\"get [Symbol.species]\""
|
||||
);
|
||||
assert_eq!(forward(&mut context, "name.enumerable"), "false");
|
||||
assert_eq!(forward(&mut context, "name.writable"), "false");
|
||||
assert_eq!(forward(&mut context, "name.configurable"), "true");
|
||||
|
||||
// symbol-species
|
||||
assert_eq!(forward(&mut context, "descriptor.set"), "undefined");
|
||||
assert_eq!(
|
||||
forward(&mut context, "typeof descriptor.get"),
|
||||
"\"function\""
|
||||
);
|
||||
assert_eq!(forward(&mut context, "descriptor.enumerable"), "false");
|
||||
assert_eq!(forward(&mut context, "descriptor.configurable"), "true");
|
||||
}
|
||||
|
||||
// TODO: uncomment this test when property getters are supported
|
||||
|
||||
// #[test]
|
||||
// fn flags() {
|
||||
// let mut context = Context::default();
|
||||
// let init = r#"
|
||||
// var re_gi = /test/gi;
|
||||
// var re_sm = /test/sm;
|
||||
// "#;
|
||||
//
|
||||
// eprintln!("{}", forward(&mut context, init));
|
||||
// assert_eq!(forward(&mut context, "re_gi.global"), "true");
|
||||
// assert_eq!(forward(&mut context, "re_gi.ignoreCase"), "true");
|
||||
// assert_eq!(forward(&mut context, "re_gi.multiline"), "false");
|
||||
// assert_eq!(forward(&mut context, "re_gi.dotAll"), "false");
|
||||
// assert_eq!(forward(&mut context, "re_gi.unicode"), "false");
|
||||
// assert_eq!(forward(&mut context, "re_gi.sticky"), "false");
|
||||
// assert_eq!(forward(&mut context, "re_gi.flags"), "gi");
|
||||
//
|
||||
// assert_eq!(forward(&mut context, "re_sm.global"), "false");
|
||||
// assert_eq!(forward(&mut context, "re_sm.ignoreCase"), "false");
|
||||
// assert_eq!(forward(&mut context, "re_sm.multiline"), "true");
|
||||
// assert_eq!(forward(&mut context, "re_sm.dotAll"), "true");
|
||||
// assert_eq!(forward(&mut context, "re_sm.unicode"), "false");
|
||||
// assert_eq!(forward(&mut context, "re_sm.sticky"), "false");
|
||||
// assert_eq!(forward(&mut context, "re_sm.flags"), "ms");
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn last_index() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var regex = /[0-9]+(\.[0-9]+)?/g;
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
assert_eq!(forward(&mut context, "regex.lastIndex"), "0");
|
||||
assert_eq!(forward(&mut context, "regex.test('1.0foo')"), "true");
|
||||
assert_eq!(forward(&mut context, "regex.lastIndex"), "3");
|
||||
assert_eq!(forward(&mut context, "regex.test('1.0foo')"), "false");
|
||||
assert_eq!(forward(&mut context, "regex.lastIndex"), "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var re = /quick\s(brown).+?(jumps)/ig;
|
||||
var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
assert_eq!(
|
||||
forward(&mut context, "result[0]"),
|
||||
"\"Quick Brown Fox Jumps\""
|
||||
);
|
||||
assert_eq!(forward(&mut context, "result[1]"), "\"Brown\"");
|
||||
assert_eq!(forward(&mut context, "result[2]"), "\"Jumps\"");
|
||||
assert_eq!(forward(&mut context, "result.index"), "4");
|
||||
assert_eq!(
|
||||
forward(&mut context, "result.input"),
|
||||
"\"The Quick Brown Fox Jumps Over The Lazy Dog\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, "(new RegExp('a+b+c')).toString()"),
|
||||
"\"/a+b+c/\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "(new RegExp('bar', 'g')).toString()"),
|
||||
"\"/bar/g\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "(new RegExp('\\\\n', 'g')).toString()"),
|
||||
"\"/\\n/g\""
|
||||
);
|
||||
assert_eq!(forward(&mut context, "/\\n/g.toString()"), "\"/\\n/g\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_panic_on_invalid_character_escape() {
|
||||
let mut context = Context::default();
|
||||
|
||||
// This used to panic, we now return an error
|
||||
// The line below should not cause Boa to panic
|
||||
forward(&mut context, r"const a = /,\;/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search() {
|
||||
let mut context = Context::default();
|
||||
|
||||
// coerce-string
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
r#"
|
||||
var obj = {
|
||||
toString: function() {
|
||||
return 'toString value';
|
||||
}
|
||||
};
|
||||
/ring/[Symbol.search](obj)
|
||||
"#
|
||||
),
|
||||
"4"
|
||||
);
|
||||
|
||||
// failure-return-val
|
||||
assert_eq!(forward(&mut context, "/z/[Symbol.search]('a')"), "-1");
|
||||
|
||||
// length
|
||||
assert_eq!(
|
||||
forward(&mut context, "RegExp.prototype[Symbol.search].length"),
|
||||
"1"
|
||||
);
|
||||
|
||||
let init =
|
||||
"var obj = Object.getOwnPropertyDescriptor(RegExp.prototype[Symbol.search], \"length\")";
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
assert_eq!(forward(&mut context, "obj.enumerable"), "false");
|
||||
assert_eq!(forward(&mut context, "obj.writable"), "false");
|
||||
assert_eq!(forward(&mut context, "obj.configurable"), "true");
|
||||
|
||||
// name
|
||||
assert_eq!(
|
||||
forward(&mut context, "RegExp.prototype[Symbol.search].name"),
|
||||
"\"[Symbol.search]\""
|
||||
);
|
||||
|
||||
let init =
|
||||
"var obj = Object.getOwnPropertyDescriptor(RegExp.prototype[Symbol.search], \"name\")";
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
assert_eq!(forward(&mut context, "obj.enumerable"), "false");
|
||||
assert_eq!(forward(&mut context, "obj.writable"), "false");
|
||||
assert_eq!(forward(&mut context, "obj.configurable"), "true");
|
||||
|
||||
// prop-desc
|
||||
let init = "var obj = Object.getOwnPropertyDescriptor(RegExp.prototype, Symbol.search)";
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
assert_eq!(forward(&mut context, "obj.enumerable"), "false");
|
||||
assert_eq!(forward(&mut context, "obj.writable"), "true");
|
||||
assert_eq!(forward(&mut context, "obj.configurable"), "true");
|
||||
|
||||
// success-return-val
|
||||
assert_eq!(forward(&mut context, "/a/[Symbol.search]('abc')"), "0");
|
||||
assert_eq!(forward(&mut context, "/b/[Symbol.search]('abc')"), "1");
|
||||
assert_eq!(forward(&mut context, "/c/[Symbol.search]('abc')"), "2");
|
||||
|
||||
// this-val-non-obj
|
||||
let error = "Uncaught \"TypeError\": \"RegExp.prototype[Symbol.search] method called on incompatible value\"";
|
||||
let init = "var search = RegExp.prototype[Symbol.search]";
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
assert_eq!(forward(&mut context, "search.call()"), error);
|
||||
assert_eq!(forward(&mut context, "search.call(undefined)"), error);
|
||||
assert_eq!(forward(&mut context, "search.call(null)"), error);
|
||||
assert_eq!(forward(&mut context, "search.call(true)"), error);
|
||||
assert_eq!(forward(&mut context, "search.call('string')"), error);
|
||||
assert_eq!(forward(&mut context, "search.call(Symbol.search)"), error);
|
||||
assert_eq!(forward(&mut context, "search.call(86)"), error);
|
||||
|
||||
// u-lastindex-advance
|
||||
assert_eq!(
|
||||
forward(&mut context, "/\\udf06/u[Symbol.search]('\\ud834\\udf06')"),
|
||||
"-1"
|
||||
);
|
||||
|
||||
assert_eq!(forward(&mut context, "/a/[Symbol.search](\"a\")"), "0");
|
||||
assert_eq!(forward(&mut context, "/a/[Symbol.search](\"ba\")"), "1");
|
||||
assert_eq!(forward(&mut context, "/a/[Symbol.search](\"bb\")"), "-1");
|
||||
assert_eq!(forward(&mut context, "/u/[Symbol.search](null)"), "1");
|
||||
assert_eq!(forward(&mut context, "/d/[Symbol.search](undefined)"), "2");
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
//! This module implements the global `Set` object.
|
||||
//!
|
||||
//! The JavaScript `Set` class is a global object that is used in the construction of sets; which
|
||||
//! are high-level, collections of values.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-set-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
|
||||
|
||||
use self::{ordered_set::OrderedSet, set_iterator::SetIterator};
|
||||
use super::JsArgs;
|
||||
use crate::{
|
||||
builtins::BuiltIn,
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
|
||||
JsObject, ObjectData,
|
||||
},
|
||||
property::{Attribute, PropertyNameKind},
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
pub mod ordered_set;
|
||||
pub mod set_iterator;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Set(OrderedSet<JsValue>);
|
||||
|
||||
impl BuiltIn for Set {
|
||||
const NAME: &'static str = "Set";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let get_species = FunctionBuilder::native(context, Self::get_species)
|
||||
.name("get [Symbol.species]")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let size_getter = FunctionBuilder::native(context, Self::size_getter)
|
||||
.constructor(false)
|
||||
.name("get size")
|
||||
.build();
|
||||
|
||||
let iterator_symbol = WellKnownSymbols::iterator();
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
|
||||
let values_function = FunctionBuilder::native(context, Self::values)
|
||||
.name("values")
|
||||
.length(0)
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().set().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.static_accessor(
|
||||
WellKnownSymbols::species(),
|
||||
Some(get_species),
|
||||
None,
|
||||
Attribute::CONFIGURABLE,
|
||||
)
|
||||
.method(Self::add, "add", 1)
|
||||
.method(Self::clear, "clear", 0)
|
||||
.method(Self::delete, "delete", 1)
|
||||
.method(Self::entries, "entries", 0)
|
||||
.method(Self::for_each, "forEach", 1)
|
||||
.method(Self::has, "has", 1)
|
||||
.property(
|
||||
"keys",
|
||||
values_function.clone(),
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.accessor("size", Some(size_getter), None, Attribute::CONFIGURABLE)
|
||||
.property(
|
||||
"values",
|
||||
values_function.clone(),
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
iterator_symbol,
|
||||
values_function,
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
to_string_tag,
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Set {
|
||||
pub(crate) const LENGTH: usize = 0;
|
||||
|
||||
/// Create a new set
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if new_target.is_undefined() {
|
||||
return context
|
||||
.throw_type_error("calling a builtin Set constructor without new is forbidden");
|
||||
}
|
||||
|
||||
// 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%Set.prototype%", « [[SetData]] »).
|
||||
// 3. Set set.[[SetData]] to a new empty List.
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::set, context)?;
|
||||
let set = JsObject::from_proto_and_data(prototype, ObjectData::set(OrderedSet::default()));
|
||||
|
||||
// 4. If iterable is either undefined or null, return set.
|
||||
let iterable = args.get_or_undefined(0);
|
||||
if iterable.is_null_or_undefined() {
|
||||
return Ok(set.into());
|
||||
}
|
||||
|
||||
// 5. Let adder be ? Get(set, "add").
|
||||
let adder = set.get("add", context)?;
|
||||
|
||||
// 6. If IsCallable(adder) is false, throw a TypeError exception.
|
||||
let adder = adder.as_callable().ok_or_else(|| {
|
||||
context.construct_type_error("'add' of 'newTarget' is not a function")
|
||||
})?;
|
||||
|
||||
// 7. Let iteratorRecord be ? GetIterator(iterable).
|
||||
let iterator_record = iterable.clone().get_iterator(context, None, None)?;
|
||||
|
||||
// 8. Repeat,
|
||||
// a. Let next be ? IteratorStep(iteratorRecord).
|
||||
// b. If next is false, return set.
|
||||
// c. Let nextValue be ? IteratorValue(next).
|
||||
// d. Let status be Completion(Call(adder, set, « nextValue »)).
|
||||
// e. IfAbruptCloseIterator(status, iteratorRecord).
|
||||
while let Some(next) = iterator_record.step(context)? {
|
||||
// c
|
||||
let next_value = next.value(context)?;
|
||||
|
||||
// d, e
|
||||
if let Err(status) = adder.call(&set.clone().into(), &[next_value], context) {
|
||||
return iterator_record.close(Err(status), context);
|
||||
}
|
||||
}
|
||||
|
||||
// 8.b
|
||||
Ok(set.into())
|
||||
}
|
||||
|
||||
/// Utility for constructing `Set` objects.
|
||||
pub(crate) fn set_create(prototype: Option<JsObject>, context: &mut Context) -> JsObject {
|
||||
let prototype =
|
||||
prototype.unwrap_or_else(|| context.intrinsics().constructors().set().prototype());
|
||||
|
||||
JsObject::from_proto_and_data(prototype, ObjectData::set(OrderedSet::new()))
|
||||
}
|
||||
|
||||
/// Utility for constructing `Set` objects from an iterator of `JsValue`'s.
|
||||
pub(crate) fn create_set_from_list<I>(elements: I, context: &mut Context) -> JsObject
|
||||
where
|
||||
I: IntoIterator<Item = JsValue>,
|
||||
{
|
||||
// Create empty Set
|
||||
let set = Self::set_create(None, context);
|
||||
// For each element e of elements, do
|
||||
for elem in elements {
|
||||
Self::add(&set.clone().into(), &[elem], context)
|
||||
.expect("adding new element shouldn't error out");
|
||||
}
|
||||
|
||||
set
|
||||
}
|
||||
|
||||
/// `get Set [ @@species ]`
|
||||
///
|
||||
/// The Set[Symbol.species] accessor property returns the Set constructor.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-set-@@species
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/@@species
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Return the this value.
|
||||
Ok(this.clone())
|
||||
}
|
||||
|
||||
/// `Set.prototype.add( value )`
|
||||
///
|
||||
/// This method adds an entry with value into the set. Returns the set object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.add
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add
|
||||
pub(crate) fn add(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let value = args.get_or_undefined(0);
|
||||
|
||||
if let Some(object) = this.as_object() {
|
||||
if let Some(set) = object.borrow_mut().as_set_mut() {
|
||||
set.add(if value.as_number().map_or(false, |n| n == -0f64) {
|
||||
JsValue::Integer(0)
|
||||
} else {
|
||||
value.clone()
|
||||
});
|
||||
} else {
|
||||
return context.throw_type_error("'this' is not a Set");
|
||||
}
|
||||
} else {
|
||||
return context.throw_type_error("'this' is not a Set");
|
||||
};
|
||||
|
||||
Ok(this.clone())
|
||||
}
|
||||
|
||||
/// `Set.prototype.clear( )`
|
||||
///
|
||||
/// This method removes all entries from the set.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.clear
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear
|
||||
pub(crate) fn clear(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
if let Some(object) = this.as_object() {
|
||||
if object.borrow().is_set() {
|
||||
this.set_data(ObjectData::set(OrderedSet::new()));
|
||||
Ok(JsValue::undefined())
|
||||
} else {
|
||||
context.throw_type_error("'this' is not a Set")
|
||||
}
|
||||
} else {
|
||||
context.throw_type_error("'this' is not a Set")
|
||||
}
|
||||
}
|
||||
|
||||
/// `Set.prototype.delete( value )`
|
||||
///
|
||||
/// This method removes the entry for the given value if it exists.
|
||||
/// Returns true if there was an element, false otherwise.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.delete
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete
|
||||
pub(crate) fn delete(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let value = args.get_or_undefined(0);
|
||||
|
||||
let res = if let Some(object) = this.as_object() {
|
||||
if let Some(set) = object.borrow_mut().as_set_mut() {
|
||||
set.delete(value)
|
||||
} else {
|
||||
return context.throw_type_error("'this' is not a Set");
|
||||
}
|
||||
} else {
|
||||
return context.throw_type_error("'this' is not a Set");
|
||||
};
|
||||
|
||||
Ok(res.into())
|
||||
}
|
||||
|
||||
/// `Set.prototype.entries( )`
|
||||
///
|
||||
/// This method returns an iterator over the entries of the set
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.entries
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries
|
||||
pub(crate) fn entries(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
if let Some(object) = this.as_object() {
|
||||
let object = object.borrow();
|
||||
if !object.is_set() {
|
||||
return context.throw_type_error(
|
||||
"Method Set.prototype.entries called on incompatible receiver",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return context
|
||||
.throw_type_error("Method Set.prototype.entries called on incompatible receiver");
|
||||
}
|
||||
|
||||
Ok(SetIterator::create_set_iterator(
|
||||
this.clone(),
|
||||
PropertyNameKind::KeyAndValue,
|
||||
context,
|
||||
))
|
||||
}
|
||||
|
||||
/// `Set.prototype.forEach( callbackFn [ , thisArg ] )`
|
||||
///
|
||||
/// This method executes the provided callback function for each value in the set
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.foreach
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/foreach
|
||||
pub(crate) fn for_each(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
if args.is_empty() {
|
||||
return Err(JsValue::new("Missing argument for Set.prototype.forEach"));
|
||||
}
|
||||
|
||||
let callback_arg = &args[0];
|
||||
let this_arg = args.get_or_undefined(1);
|
||||
|
||||
// TODO: if condition should also check that we are not in strict mode
|
||||
let this_arg = if this_arg.is_undefined() {
|
||||
context.global_object().clone().into()
|
||||
} else {
|
||||
this_arg.clone()
|
||||
};
|
||||
|
||||
let mut index = 0;
|
||||
|
||||
while index < Self::get_size(this, context)? {
|
||||
let arguments = this
|
||||
.as_object()
|
||||
.and_then(|obj| {
|
||||
obj.borrow().as_set_ref().map(|set| {
|
||||
set.get_index(index)
|
||||
.map(|value| [value.clone(), value.clone(), this.clone()])
|
||||
})
|
||||
})
|
||||
.ok_or_else(|| context.construct_type_error("'this' is not a Set"))?;
|
||||
|
||||
if let Some(arguments) = arguments {
|
||||
context.call(callback_arg, &this_arg, &arguments)?;
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
|
||||
Ok(JsValue::Undefined)
|
||||
}
|
||||
|
||||
/// `Map.prototype.has( key )`
|
||||
///
|
||||
/// This method checks if the map contains an entry with the given key.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.has
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has
|
||||
pub(crate) fn has(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let value = args.get_or_undefined(0);
|
||||
|
||||
this.as_object()
|
||||
.and_then(|obj| {
|
||||
obj.borrow()
|
||||
.as_set_ref()
|
||||
.map(|set| set.contains(value).into())
|
||||
})
|
||||
.ok_or_else(|| context.construct_type_error("'this' is not a Set"))
|
||||
}
|
||||
|
||||
/// `Set.prototype.values( )`
|
||||
///
|
||||
/// This method returns an iterator over the values of the set
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.values
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values
|
||||
pub(crate) fn values(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
if let Some(object) = this.as_object() {
|
||||
let object = object.borrow();
|
||||
if !object.is_set() {
|
||||
return context.throw_type_error(
|
||||
"Method Set.prototype.values called on incompatible receiver",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return context
|
||||
.throw_type_error("Method Set.prototype.values called on incompatible receiver");
|
||||
}
|
||||
|
||||
Ok(SetIterator::create_set_iterator(
|
||||
this.clone(),
|
||||
PropertyNameKind::Value,
|
||||
context,
|
||||
))
|
||||
}
|
||||
|
||||
fn size_getter(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
Self::get_size(this, context).map(JsValue::from)
|
||||
}
|
||||
|
||||
/// Helper function to get the size of the `Set` object.
|
||||
pub(crate) fn get_size(set: &JsValue, context: &mut Context) -> JsResult<usize> {
|
||||
set.as_object()
|
||||
.and_then(|obj| obj.borrow().as_set_ref().map(OrderedSet::size))
|
||||
.ok_or_else(|| context.construct_type_error("'this' is not a Set"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
use boa_gc::{custom_trace, Finalize, Trace};
|
||||
use indexmap::{
|
||||
set::{IntoIter, Iter},
|
||||
IndexSet,
|
||||
};
|
||||
use std::{
|
||||
collections::hash_map::RandomState,
|
||||
fmt::Debug,
|
||||
hash::{BuildHasher, Hash},
|
||||
};
|
||||
|
||||
/// A type wrapping `indexmap::IndexSet`
|
||||
#[derive(Clone)]
|
||||
pub struct OrderedSet<V, S = RandomState>
|
||||
where
|
||||
V: Hash + Eq,
|
||||
{
|
||||
inner: IndexSet<V, S>,
|
||||
}
|
||||
|
||||
impl<V: Eq + Hash + Trace, S: BuildHasher> Finalize for OrderedSet<V, S> {}
|
||||
unsafe impl<V: Eq + Hash + Trace, S: BuildHasher> Trace for OrderedSet<V, S> {
|
||||
custom_trace!(this, {
|
||||
for v in this.inner.iter() {
|
||||
mark(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl<V: Hash + Eq + Debug> Debug for OrderedSet<V> {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
self.inner.fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Hash + Eq> Default for OrderedSet<V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> OrderedSet<V>
|
||||
where
|
||||
V: Hash + Eq,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: IndexSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
inner: IndexSet::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of key-value pairs in the map.
|
||||
///
|
||||
/// Computes in **O(1)** time.
|
||||
pub fn size(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
/// Returns true if the map contains no elements.
|
||||
///
|
||||
/// Computes in **O(1)** time.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.len() == 0
|
||||
}
|
||||
|
||||
/// Insert a value pair in the set.
|
||||
///
|
||||
/// If an equivalent value already exists in the set: ???
|
||||
///
|
||||
/// If no equivalent value existed in the set: the new value is
|
||||
/// inserted, last in order, and false
|
||||
///
|
||||
/// Computes in **O(1)** time (amortized average).
|
||||
pub fn add(&mut self, value: V) -> bool {
|
||||
self.inner.insert(value)
|
||||
}
|
||||
|
||||
/// Delete the `value` from the set and return true if successful
|
||||
///
|
||||
/// Return `false` if `value` is not in map.
|
||||
///
|
||||
/// Computes in **O(n)** time (average).
|
||||
pub fn delete(&mut self, value: &V) -> bool {
|
||||
self.inner.shift_remove(value)
|
||||
}
|
||||
|
||||
/// Checks if a given value is present in the set
|
||||
///
|
||||
/// Return `true` if `value` is present in set, false otherwise.
|
||||
///
|
||||
/// Computes in **O(n)** time (average).
|
||||
pub fn contains(&self, value: &V) -> bool {
|
||||
self.inner.contains(value)
|
||||
}
|
||||
|
||||
/// Get a key-value pair by index
|
||||
/// Valid indices are 0 <= index < self.len()
|
||||
/// Computes in O(1) time.
|
||||
pub fn get_index(&self, index: usize) -> Option<&V> {
|
||||
self.inner.get_index(index)
|
||||
}
|
||||
|
||||
/// Return an iterator over the values of the set, in their order
|
||||
pub fn iter(&self) -> Iter<'_, V> {
|
||||
self.inner.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, V, S> IntoIterator for &'a OrderedSet<V, S>
|
||||
where
|
||||
V: Hash + Eq,
|
||||
S: BuildHasher,
|
||||
{
|
||||
type Item = &'a V;
|
||||
type IntoIter = Iter<'a, V>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.inner.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, S> IntoIterator for OrderedSet<V, S>
|
||||
where
|
||||
V: Hash + Eq,
|
||||
S: BuildHasher,
|
||||
{
|
||||
type Item = V;
|
||||
type IntoIter = IntoIter<V>;
|
||||
fn into_iter(self) -> IntoIter<V> {
|
||||
self.inner.into_iter()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
use crate::{
|
||||
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue},
|
||||
object::{JsObject, ObjectData},
|
||||
property::{PropertyDescriptor, PropertyNameKind},
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// The Set Iterator object represents an iteration over a set. It implements the iterator protocol.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set-iterator-objects
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct SetIterator {
|
||||
iterated_set: JsValue,
|
||||
next_index: usize,
|
||||
iteration_kind: PropertyNameKind,
|
||||
}
|
||||
|
||||
impl SetIterator {
|
||||
pub(crate) const NAME: &'static str = "SetIterator";
|
||||
|
||||
/// Constructs a new `SetIterator`, that will iterate over `set`, starting at index 0
|
||||
fn new(set: JsValue, kind: PropertyNameKind) -> Self {
|
||||
Self {
|
||||
iterated_set: set,
|
||||
next_index: 0,
|
||||
iteration_kind: kind,
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract operation `CreateSetIterator( set, kind )`
|
||||
///
|
||||
/// Creates a new iterator over the given set.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createsetiterator
|
||||
pub(crate) fn create_set_iterator(
|
||||
set: JsValue,
|
||||
kind: PropertyNameKind,
|
||||
context: &Context,
|
||||
) -> JsValue {
|
||||
let set_iterator = JsObject::from_proto_and_data(
|
||||
context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.set_iterator(),
|
||||
ObjectData::set_iterator(Self::new(set, kind)),
|
||||
);
|
||||
set_iterator.into()
|
||||
}
|
||||
|
||||
/// %SetIteratorPrototype%.next( )
|
||||
///
|
||||
/// Advances the iterator and gets the next result in the set.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next
|
||||
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let mut set_iterator = this.as_object().map(JsObject::borrow_mut);
|
||||
|
||||
let set_iterator = set_iterator
|
||||
.as_mut()
|
||||
.and_then(|obj| obj.as_set_iterator_mut())
|
||||
.ok_or_else(|| context.construct_type_error("`this` is not an SetIterator"))?;
|
||||
{
|
||||
let m = &set_iterator.iterated_set;
|
||||
let mut index = set_iterator.next_index;
|
||||
let item_kind = &set_iterator.iteration_kind;
|
||||
|
||||
if set_iterator.iterated_set.is_undefined() {
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
));
|
||||
}
|
||||
|
||||
let entries = m.as_object().map(JsObject::borrow);
|
||||
let entries = entries
|
||||
.as_ref()
|
||||
.and_then(|obj| obj.as_set_ref())
|
||||
.ok_or_else(|| context.construct_type_error("'this' is not a Set"))?;
|
||||
|
||||
let num_entries = entries.size();
|
||||
while index < num_entries {
|
||||
let e = entries.get_index(index);
|
||||
index += 1;
|
||||
set_iterator.next_index = index;
|
||||
if let Some(value) = e {
|
||||
match item_kind {
|
||||
PropertyNameKind::Value => {
|
||||
return Ok(create_iter_result_object(value.clone(), false, context));
|
||||
}
|
||||
PropertyNameKind::KeyAndValue => {
|
||||
let result = Array::create_array_from_list(
|
||||
[value.clone(), value.clone()],
|
||||
context,
|
||||
);
|
||||
return Ok(create_iter_result_object(result.into(), false, context));
|
||||
}
|
||||
PropertyNameKind::Key => {
|
||||
panic!("tried to collect only keys of Set")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_iterator.iterated_set = JsValue::undefined();
|
||||
Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
))
|
||||
}
|
||||
|
||||
/// Create the `%SetIteratorPrototype%` object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%-object
|
||||
pub(crate) fn create_prototype(
|
||||
iterator_prototype: JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsObject {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
// Create prototype
|
||||
let set_iterator =
|
||||
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
|
||||
make_builtin_fn(Self::next, "next", &set_iterator, 0, context);
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
let to_string_tag_property = PropertyDescriptor::builder()
|
||||
.value("Set Iterator")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
set_iterator.insert(to_string_tag, to_string_tag_property);
|
||||
set_iterator
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
use crate::{forward, Context};
|
||||
|
||||
#[test]
|
||||
fn construct_empty() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var empty = new Set();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "empty.size");
|
||||
assert_eq!(result, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_from_array() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let set = new Set(["one", "two"]);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "set.size");
|
||||
assert_eq!(result, "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let original = new Set(["one", "two"]);
|
||||
let clone = new Set(original);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "clone.size");
|
||||
assert_eq!(result, "2");
|
||||
let result = forward(
|
||||
&mut context,
|
||||
r#"
|
||||
original.add("three");
|
||||
original.size"#,
|
||||
);
|
||||
assert_eq!(result, "3");
|
||||
let result = forward(&mut context, "clone.size");
|
||||
assert_eq!(result, "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_iterator() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
const set1 = new Set();
|
||||
set1.add('foo');
|
||||
set1.add('bar');
|
||||
const iterator = set1[Symbol.iterator]();
|
||||
let item1 = iterator.next();
|
||||
let item2 = iterator.next();
|
||||
let item3 = iterator.next();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "item1.value");
|
||||
assert_eq!(result, "\"foo\"");
|
||||
let result = forward(&mut context, "item1.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item2.value");
|
||||
assert_eq!(result, "\"bar\"");
|
||||
let result = forward(&mut context, "item2.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item3.value");
|
||||
assert_eq!(result, "undefined");
|
||||
let result = forward(&mut context, "item3.done");
|
||||
assert_eq!(result, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entries() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
const set1 = new Set();
|
||||
set1.add('foo');
|
||||
set1.add('bar');
|
||||
const entriesIterator = set1.entries();
|
||||
let item1 = entriesIterator.next();
|
||||
let item2 = entriesIterator.next();
|
||||
let item3 = entriesIterator.next();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "item1.value.length");
|
||||
assert_eq!(result, "2");
|
||||
let result = forward(&mut context, "item1.value[0]");
|
||||
assert_eq!(result, "\"foo\"");
|
||||
let result = forward(&mut context, "item1.value[1]");
|
||||
assert_eq!(result, "\"foo\"");
|
||||
let result = forward(&mut context, "item1.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item2.value.length");
|
||||
assert_eq!(result, "2");
|
||||
let result = forward(&mut context, "item2.value[0]");
|
||||
assert_eq!(result, "\"bar\"");
|
||||
let result = forward(&mut context, "item2.value[1]");
|
||||
assert_eq!(result, "\"bar\"");
|
||||
let result = forward(&mut context, "item2.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item3.value");
|
||||
assert_eq!(result, "undefined");
|
||||
let result = forward(&mut context, "item3.done");
|
||||
assert_eq!(result, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let first = new Set(["one", "two"]);
|
||||
let second = new Set(["three", "four"]);
|
||||
let third = new Set(["four", "five"]);
|
||||
let merged1 = new Set([...first, ...second]);
|
||||
let merged2 = new Set([...second, ...third]);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "merged1.size");
|
||||
assert_eq!(result, "4");
|
||||
let result = forward(&mut context, "merged2.size");
|
||||
assert_eq!(result, "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let set = new Set(["one", "two"]);
|
||||
set.clear();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "set.size");
|
||||
assert_eq!(result, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let set = new Set(["one", "two"]);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "set.delete('one')");
|
||||
assert_eq!(result, "true");
|
||||
let result = forward(&mut context, "set.size");
|
||||
assert_eq!(result, "1");
|
||||
let result = forward(&mut context, "set.delete('one')");
|
||||
assert_eq!(result, "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let set = new Set(["one", "two"]);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "set.has('one')");
|
||||
assert_eq!(result, "true");
|
||||
let result = forward(&mut context, "set.has('two')");
|
||||
assert_eq!(result, "true");
|
||||
let result = forward(&mut context, "set.has('three')");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "set.has()");
|
||||
assert_eq!(result, "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn values_and_keys() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
const set1 = new Set();
|
||||
set1.add('foo');
|
||||
set1.add('bar');
|
||||
const valuesIterator = set1.values();
|
||||
let item1 = valuesIterator.next();
|
||||
let item2 = valuesIterator.next();
|
||||
let item3 = valuesIterator.next();
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "item1.value");
|
||||
assert_eq!(result, "\"foo\"");
|
||||
let result = forward(&mut context, "item1.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item2.value");
|
||||
assert_eq!(result, "\"bar\"");
|
||||
let result = forward(&mut context, "item2.done");
|
||||
assert_eq!(result, "false");
|
||||
let result = forward(&mut context, "item3.value");
|
||||
assert_eq!(result, "undefined");
|
||||
let result = forward(&mut context, "item3.done");
|
||||
assert_eq!(result, "true");
|
||||
let result = forward(&mut context, "set1.values == set1.keys");
|
||||
assert_eq!(result, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_each() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let set = new Set([5, 10, 15]);
|
||||
let value1Sum = 0;
|
||||
let value2Sum = 0;
|
||||
let sizeSum = 0;
|
||||
function callingCallback(value1, value2, set) {
|
||||
value1Sum += value1;
|
||||
value2Sum += value2;
|
||||
sizeSum += set.size;
|
||||
}
|
||||
set.forEach(callingCallback);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
assert_eq!(forward(&mut context, "value1Sum"), "30");
|
||||
assert_eq!(forward(&mut context, "value2Sum"), "30");
|
||||
assert_eq!(forward(&mut context, "sizeSum"), "9");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_display() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let set = new Set();
|
||||
let array = new Array([set]);
|
||||
set.add(set);
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
let result = forward(&mut context, "set");
|
||||
assert_eq!(result, "Set { Set(1) }");
|
||||
let result = forward(&mut context, "set.add(array)");
|
||||
assert_eq!(result, "Set { Set(2), Array(1) }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_a_function() {
|
||||
let mut context = Context::default();
|
||||
let init = r"
|
||||
try {
|
||||
let set = Set()
|
||||
} catch(e) {
|
||||
e.toString()
|
||||
}
|
||||
";
|
||||
assert_eq!(
|
||||
forward(&mut context, init),
|
||||
"\"TypeError: calling a builtin Set constructor without new is forbidden\""
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,100 @@
|
||||
use crate::{
|
||||
builtins::{
|
||||
function::make_builtin_fn, iterable::create_iter_result_object, string::code_point_at,
|
||||
},
|
||||
object::{JsObject, ObjectData},
|
||||
property::PropertyDescriptor,
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct StringIterator {
|
||||
string: JsValue,
|
||||
next_index: i32,
|
||||
}
|
||||
|
||||
impl StringIterator {
|
||||
fn new(string: JsValue) -> Self {
|
||||
Self {
|
||||
string,
|
||||
next_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_string_iterator(string: JsValue, context: &mut Context) -> JsResult<JsValue> {
|
||||
let string_iterator = JsObject::from_proto_and_data(
|
||||
context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.string_iterator(),
|
||||
ObjectData::string_iterator(Self::new(string)),
|
||||
);
|
||||
Ok(string_iterator.into())
|
||||
}
|
||||
|
||||
pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let mut string_iterator = this.as_object().map(JsObject::borrow_mut);
|
||||
let string_iterator = string_iterator
|
||||
.as_mut()
|
||||
.and_then(|obj| obj.as_string_iterator_mut())
|
||||
.ok_or_else(|| context.construct_type_error("`this` is not an ArrayIterator"))?;
|
||||
|
||||
if string_iterator.string.is_undefined() {
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
));
|
||||
}
|
||||
let native_string = string_iterator.string.to_string(context)?;
|
||||
let len = native_string.encode_utf16().count() as i32;
|
||||
let position = string_iterator.next_index;
|
||||
if position >= len {
|
||||
string_iterator.string = JsValue::undefined();
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
));
|
||||
}
|
||||
let (_, code_unit_count, _) = code_point_at(&native_string, position as usize);
|
||||
string_iterator.next_index += i32::from(code_unit_count);
|
||||
let result_string = crate::builtins::string::String::substring(
|
||||
&string_iterator.string,
|
||||
&[position.into(), string_iterator.next_index.into()],
|
||||
context,
|
||||
)?;
|
||||
Ok(create_iter_result_object(result_string, false, context))
|
||||
}
|
||||
|
||||
/// Create the `%ArrayIteratorPrototype%` object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
|
||||
pub(crate) fn create_prototype(
|
||||
iterator_prototype: JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsObject {
|
||||
let _timer = Profiler::global().start_event("String Iterator", "init");
|
||||
|
||||
// Create prototype
|
||||
let array_iterator =
|
||||
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
|
||||
make_builtin_fn(Self::next, "next", &array_iterator, 0, context);
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
let to_string_tag_property = PropertyDescriptor::builder()
|
||||
.value("String Iterator")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
array_iterator.insert(to_string_tag, to_string_tag_property);
|
||||
array_iterator
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,346 @@
|
||||
//! This module implements the global `Symbol` object.
|
||||
//!
|
||||
//! The data type symbol is a primitive data type.
|
||||
//! The `Symbol()` function returns a value of type symbol, has static properties that expose
|
||||
//! several members of built-in objects, has static methods that expose the global symbol registry,
|
||||
//! and resembles a built-in object class, but is incomplete as a constructor because it does not
|
||||
//! support the syntax "`new Symbol()`".
|
||||
//!
|
||||
//! Every symbol value returned from `Symbol()` is unique.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-symbol-value
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use super::JsArgs;
|
||||
use crate::{
|
||||
builtins::BuiltIn,
|
||||
object::{ConstructorBuilder, FunctionBuilder},
|
||||
property::Attribute,
|
||||
symbol::{JsSymbol, WellKnownSymbols},
|
||||
value::JsValue,
|
||||
Context, JsResult, JsString,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::cell::RefCell;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
thread_local! {
|
||||
static GLOBAL_SYMBOL_REGISTRY: RefCell<GlobalSymbolRegistry> = RefCell::new(GlobalSymbolRegistry::new());
|
||||
}
|
||||
|
||||
struct GlobalSymbolRegistry {
|
||||
keys: FxHashMap<JsString, JsSymbol>,
|
||||
symbols: FxHashMap<JsSymbol, JsString>,
|
||||
}
|
||||
|
||||
impl GlobalSymbolRegistry {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
keys: FxHashMap::default(),
|
||||
symbols: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_insert_key(&mut self, key: JsString) -> JsSymbol {
|
||||
if let Some(symbol) = self.keys.get(&key) {
|
||||
return symbol.clone();
|
||||
}
|
||||
|
||||
let symbol = JsSymbol::new(Some(key.clone()));
|
||||
self.keys.insert(key.clone(), symbol.clone());
|
||||
self.symbols.insert(symbol.clone(), key);
|
||||
symbol
|
||||
}
|
||||
|
||||
fn get_symbol(&self, sym: &JsSymbol) -> Option<JsString> {
|
||||
if let Some(key) = self.symbols.get(sym) {
|
||||
return Some(key.clone());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Symbol;
|
||||
|
||||
impl BuiltIn for Symbol {
|
||||
const NAME: &'static str = "Symbol";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let symbol_async_iterator = WellKnownSymbols::async_iterator();
|
||||
let symbol_has_instance = WellKnownSymbols::has_instance();
|
||||
let symbol_is_concat_spreadable = WellKnownSymbols::is_concat_spreadable();
|
||||
let symbol_iterator = WellKnownSymbols::iterator();
|
||||
let symbol_match = WellKnownSymbols::r#match();
|
||||
let symbol_match_all = WellKnownSymbols::match_all();
|
||||
let symbol_replace = WellKnownSymbols::replace();
|
||||
let symbol_search = WellKnownSymbols::search();
|
||||
let symbol_species = WellKnownSymbols::species();
|
||||
let symbol_split = WellKnownSymbols::split();
|
||||
let symbol_to_primitive = WellKnownSymbols::to_primitive();
|
||||
let symbol_to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
let symbol_unscopables = WellKnownSymbols::unscopables();
|
||||
|
||||
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
|
||||
|
||||
let to_primitive = FunctionBuilder::native(context, Self::to_primitive)
|
||||
.name("[Symbol.toPrimitive]")
|
||||
.length(1)
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let get_description = FunctionBuilder::native(context, Self::get_description)
|
||||
.name("get description")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().symbol().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.callable(true)
|
||||
.constructor(true)
|
||||
.static_method(Self::for_, "for", 1)
|
||||
.static_method(Self::key_for, "keyFor", 1)
|
||||
.static_property("asyncIterator", symbol_async_iterator, attribute)
|
||||
.static_property("hasInstance", symbol_has_instance, attribute)
|
||||
.static_property("isConcatSpreadable", symbol_is_concat_spreadable, attribute)
|
||||
.static_property("iterator", symbol_iterator, attribute)
|
||||
.static_property("match", symbol_match, attribute)
|
||||
.static_property("matchAll", symbol_match_all, attribute)
|
||||
.static_property("replace", symbol_replace, attribute)
|
||||
.static_property("search", symbol_search, attribute)
|
||||
.static_property("species", symbol_species, attribute)
|
||||
.static_property("split", symbol_split, attribute)
|
||||
.static_property("toPrimitive", symbol_to_primitive.clone(), attribute)
|
||||
.static_property("toStringTag", symbol_to_string_tag.clone(), attribute)
|
||||
.static_property("unscopables", symbol_unscopables, attribute)
|
||||
.method(Self::to_string, "toString", 0)
|
||||
.method(Self::value_of, "valueOf", 0)
|
||||
.accessor(
|
||||
"description",
|
||||
Some(get_description),
|
||||
None,
|
||||
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
|
||||
)
|
||||
.property(
|
||||
symbol_to_string_tag,
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
symbol_to_primitive,
|
||||
to_primitive,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Symbol {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 0;
|
||||
|
||||
/// The `Symbol()` constructor returns a value of type symbol.
|
||||
///
|
||||
/// It is incomplete as a constructor because it does not support
|
||||
/// the syntax `new Symbol()` and it is not intended to be subclassed.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-symbol-description
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is not undefined, throw a TypeError exception.
|
||||
if !new_target.is_undefined() {
|
||||
return context.throw_type_error("Symbol is not a constructor");
|
||||
}
|
||||
|
||||
// 2. If description is undefined, let descString be undefined.
|
||||
// 3. Else, let descString be ? ToString(description).
|
||||
let description = match args.get(0) {
|
||||
Some(value) if !value.is_undefined() => Some(value.to_string(context)?),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// 4. Return a new unique Symbol value whose [[Description]] value is descString.
|
||||
Ok(JsSymbol::new(description).into())
|
||||
}
|
||||
|
||||
fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult<JsSymbol> {
|
||||
value
|
||||
.as_symbol()
|
||||
.or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol()))
|
||||
.ok_or_else(|| context.construct_type_error("'this' is not a Symbol"))
|
||||
}
|
||||
|
||||
/// `Symbol.prototype.toString()`
|
||||
///
|
||||
/// This method returns a string representing the specified `Symbol` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.tostring
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toString
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn to_string(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let sym be ? thisSymbolValue(this value).
|
||||
let symbol = Self::this_symbol_value(this, context)?;
|
||||
|
||||
// 2. Return SymbolDescriptiveString(sym).
|
||||
Ok(symbol.descriptive_string().into())
|
||||
}
|
||||
|
||||
/// `Symbol.prototype.valueOf()`
|
||||
///
|
||||
/// This method returns a `Symbol` that is the primitive value of the specified `Symbol` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/valueOf
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.valueof
|
||||
pub(crate) fn value_of(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Return ? thisSymbolValue(this value).
|
||||
let symbol = Self::this_symbol_value(this, context)?;
|
||||
Ok(JsValue::Symbol(symbol))
|
||||
}
|
||||
|
||||
/// `get Symbol.prototype.description`
|
||||
///
|
||||
/// This accessor returns the description of the `Symbol` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.description
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/description
|
||||
pub(crate) fn get_description(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let symbol = Self::this_symbol_value(this, context)?;
|
||||
if let Some(ref description) = symbol.description() {
|
||||
Ok(description.clone().into())
|
||||
} else {
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
}
|
||||
|
||||
/// `Symbol.for( key )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.for
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/for
|
||||
pub(crate) fn for_(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let stringKey be ? ToString(key).
|
||||
let string_key = args
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.to_string(context)?;
|
||||
// 2. For each element e of the GlobalSymbolRegistry List, do
|
||||
// a. If SameValue(e.[[Key]], stringKey) is true, return e.[[Symbol]].
|
||||
// 3. Assert: GlobalSymbolRegistry does not currently contain an entry for stringKey.
|
||||
// 4. Let newSymbol be a new unique Symbol value whose [[Description]] value is stringKey.
|
||||
// 5. Append the Record { [[Key]]: stringKey, [[Symbol]]: newSymbol } to the GlobalSymbolRegistry List.
|
||||
// 6. Return newSymbol.
|
||||
Ok(GLOBAL_SYMBOL_REGISTRY
|
||||
.with(move |registry| {
|
||||
let mut registry = registry.borrow_mut();
|
||||
registry.get_or_insert_key(string_key)
|
||||
})
|
||||
.into())
|
||||
}
|
||||
|
||||
/// `Symbol.keyFor( sym )`
|
||||
///
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.keyfor
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/keyFor
|
||||
pub(crate) fn key_for(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let sym = args.get_or_undefined(0);
|
||||
// 1. If Type(sym) is not Symbol, throw a TypeError exception.
|
||||
if let Some(sym) = sym.as_symbol() {
|
||||
// 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do
|
||||
// a. If SameValue(e.[[Symbol]], sym) is true, return e.[[Key]].
|
||||
// 3. Assert: GlobalSymbolRegistry does not currently contain an entry for sym.
|
||||
// 4. Return undefined.
|
||||
let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| {
|
||||
let registry = registry.borrow();
|
||||
registry.get_symbol(&sym)
|
||||
});
|
||||
|
||||
Ok(symbol.map(JsValue::from).unwrap_or_default())
|
||||
} else {
|
||||
context.throw_type_error("Symbol.keyFor: sym is not a symbol")
|
||||
}
|
||||
}
|
||||
|
||||
/// `Symbol.prototype [ @@toPrimitive ]`
|
||||
///
|
||||
/// This function is called by ECMAScript language operators to convert a Symbol object to a primitive value.
|
||||
/// NOTE: The argument is ignored
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/multipage/#sec-symbol.prototype-@@toprimitive
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/@@toPrimitive
|
||||
pub(crate) fn to_primitive(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let sym = Self::this_symbol_value(this, context)?;
|
||||
// 1. Return ? thisSymbolValue(this value).
|
||||
Ok(sym.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use crate::{check_output, forward, forward_val, Context, TestAction};
|
||||
|
||||
#[test]
|
||||
fn call_symbol_and_check_return_type() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var sym = Symbol();
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let sym = forward_val(&mut context, "sym").unwrap();
|
||||
assert!(sym.is_symbol());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_symbol_expect_description() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var sym = Symbol("Hello");
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let sym = forward_val(&mut context, "sym.toString()").unwrap();
|
||||
assert_eq!(sym.display().to_string(), "\"Symbol(Hello)\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_access() {
|
||||
let init = r#"
|
||||
var x = {};
|
||||
var sym1 = Symbol("Hello");
|
||||
var sym2 = Symbol("Hello");
|
||||
x[sym1] = 10;
|
||||
x[sym2] = 20;
|
||||
"#;
|
||||
check_output(&[
|
||||
TestAction::Execute(init),
|
||||
TestAction::TestEq("x[sym1]", "10"),
|
||||
TestAction::TestEq("x[sym2]", "20"),
|
||||
TestAction::TestEq("x['Symbol(Hello)']", "undefined"),
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
//! This module implements the `Integer-Indexed` exotic object.
|
||||
//!
|
||||
//! An `Integer-Indexed` exotic object is an exotic object that performs
|
||||
//! special handling of integer index property keys.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects
|
||||
|
||||
use crate::{
|
||||
builtins::typed_array::TypedArrayKind,
|
||||
object::{JsObject, ObjectData},
|
||||
Context,
|
||||
};
|
||||
use boa_gc::{unsafe_empty_trace, Finalize, Trace};
|
||||
|
||||
/// Type of the array content.
|
||||
#[derive(Debug, Clone, Copy, Finalize, PartialEq)]
|
||||
pub(crate) enum ContentType {
|
||||
Number,
|
||||
BigInt,
|
||||
}
|
||||
|
||||
unsafe impl Trace for ContentType {
|
||||
// safe because `ContentType` is `Copy`
|
||||
unsafe_empty_trace!();
|
||||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#integer-indexed-exotic-object>
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct IntegerIndexed {
|
||||
viewed_array_buffer: Option<JsObject>,
|
||||
typed_array_name: TypedArrayKind,
|
||||
byte_offset: usize,
|
||||
byte_length: usize,
|
||||
array_length: usize,
|
||||
}
|
||||
|
||||
impl IntegerIndexed {
|
||||
pub(crate) fn new(
|
||||
viewed_array_buffer: Option<JsObject>,
|
||||
typed_array_name: TypedArrayKind,
|
||||
byte_offset: usize,
|
||||
byte_length: usize,
|
||||
array_length: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
viewed_array_buffer,
|
||||
typed_array_name,
|
||||
byte_offset,
|
||||
byte_length,
|
||||
array_length,
|
||||
}
|
||||
}
|
||||
|
||||
/// `IntegerIndexedObjectCreate ( prototype )`
|
||||
///
|
||||
/// Create a new `JsObject` from a prototype and a `IntegerIndexedObject`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integerindexedobjectcreate
|
||||
pub(super) fn create(prototype: JsObject, data: Self, context: &Context) -> JsObject {
|
||||
// 1. Let internalSlotsList be « [[Prototype]], [[Extensible]], [[ViewedArrayBuffer]],
|
||||
// [[TypedArrayName]], [[ContentType]], [[ByteLength]], [[ByteOffset]],
|
||||
// [[ArrayLength]] ».
|
||||
// 2. Let A be ! MakeBasicObject(internalSlotsList).
|
||||
let a = context.construct_object();
|
||||
|
||||
// 3. Set A.[[GetOwnProperty]] as specified in 10.4.5.1.
|
||||
// 4. Set A.[[HasProperty]] as specified in 10.4.5.2.
|
||||
// 5. Set A.[[DefineOwnProperty]] as specified in 10.4.5.3.
|
||||
// 6. Set A.[[Get]] as specified in 10.4.5.4.
|
||||
// 7. Set A.[[Set]] as specified in 10.4.5.5.
|
||||
// 8. Set A.[[Delete]] as specified in 10.4.5.6.
|
||||
// 9. Set A.[[OwnPropertyKeys]] as specified in 10.4.5.7.
|
||||
a.borrow_mut().data = ObjectData::integer_indexed(data);
|
||||
|
||||
// 10. Set A.[[Prototype]] to prototype.
|
||||
a.set_prototype(prototype.into());
|
||||
|
||||
// 11. Return A.
|
||||
a
|
||||
}
|
||||
|
||||
/// Abstract operation `IsDetachedBuffer ( arrayBuffer )`.
|
||||
///
|
||||
/// Check if `[[ArrayBufferData]]` is null.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer
|
||||
pub(crate) fn is_detached(&self) -> bool {
|
||||
if let Some(obj) = &self.viewed_array_buffer {
|
||||
obj.borrow()
|
||||
.as_array_buffer()
|
||||
.expect("Typed array must have internal array buffer object")
|
||||
.is_detached_buffer()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the integer indexed object's byte offset.
|
||||
pub(crate) fn byte_offset(&self) -> usize {
|
||||
self.byte_offset
|
||||
}
|
||||
|
||||
/// Set the integer indexed object's byte offset.
|
||||
pub(crate) fn set_byte_offset(&mut self, byte_offset: usize) {
|
||||
self.byte_offset = byte_offset;
|
||||
}
|
||||
|
||||
/// Get the integer indexed object's typed array name.
|
||||
pub(crate) fn typed_array_name(&self) -> TypedArrayKind {
|
||||
self.typed_array_name
|
||||
}
|
||||
|
||||
/// Get a reference to the integer indexed object's viewed array buffer.
|
||||
pub fn viewed_array_buffer(&self) -> Option<&JsObject> {
|
||||
self.viewed_array_buffer.as_ref()
|
||||
}
|
||||
|
||||
///(crate) Set the integer indexed object's viewed array buffer.
|
||||
pub fn set_viewed_array_buffer(&mut self, viewed_array_buffer: Option<JsObject>) {
|
||||
self.viewed_array_buffer = viewed_array_buffer;
|
||||
}
|
||||
|
||||
/// Get the integer indexed object's byte length.
|
||||
pub fn byte_length(&self) -> usize {
|
||||
self.byte_length
|
||||
}
|
||||
|
||||
/// Set the integer indexed object's byte length.
|
||||
pub(crate) fn set_byte_length(&mut self, byte_length: usize) {
|
||||
self.byte_length = byte_length;
|
||||
}
|
||||
|
||||
/// Get the integer indexed object's array length.
|
||||
pub fn array_length(&self) -> usize {
|
||||
self.array_length
|
||||
}
|
||||
|
||||
/// Set the integer indexed object's array length.
|
||||
pub(crate) fn set_array_length(&mut self, array_length: usize) {
|
||||
self.array_length = array_length;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user