feat: add dependency
This commit is contained in:
95
javascript-engine/external/boa/boa_engine/Cargo.toml
vendored
Normal file
95
javascript-engine/external/boa/boa_engine/Cargo.toml
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
[package]
|
||||
name = "boa_engine"
|
||||
keywords = ["javascript", "js", "compiler", "lexer", "parser"]
|
||||
categories = ["parser-implementations", "compilers"]
|
||||
readme = "../README.md"
|
||||
description.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[features]
|
||||
profiler = ["boa_profiler/profiler"]
|
||||
deser = ["boa_interner/serde", "boa_ast/serde"]
|
||||
intl = [
|
||||
"dep:boa_icu_provider",
|
||||
"dep:icu_locid_transform",
|
||||
"dep:icu_locid",
|
||||
"dep:icu_datetime",
|
||||
"dep:icu_plurals",
|
||||
"dep:icu_provider",
|
||||
"dep:icu_calendar",
|
||||
"dep:icu_collator",
|
||||
"dep:icu_list",
|
||||
"dep:writeable",
|
||||
"dep:sys-locale",
|
||||
]
|
||||
|
||||
fuzz = ["boa_ast/fuzz", "boa_interner/fuzz"]
|
||||
|
||||
# Enable Boa's VM instruction flowgraph generator.
|
||||
flowgraph = []
|
||||
|
||||
# Enable Boa's WHATWG console object implementation.
|
||||
console = []
|
||||
|
||||
[dependencies]
|
||||
boa_interner.workspace = true
|
||||
boa_gc.workspace = true
|
||||
boa_profiler.workspace = true
|
||||
boa_macros.workspace = true
|
||||
boa_ast.workspace = true
|
||||
boa_parser.workspace = true
|
||||
serde = { version = "1.0.152", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.91"
|
||||
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.2"
|
||||
ryu-js = "0.2.2"
|
||||
chrono = "0.4.23"
|
||||
fast-float = "0.2.0"
|
||||
unicode-normalization = "0.1.22"
|
||||
once_cell = "1.17.0"
|
||||
tap = "1.0.1"
|
||||
sptr = "0.3.2"
|
||||
static_assertions = "1.1.0"
|
||||
thiserror = "1.0.38"
|
||||
dashmap = "5.4.0"
|
||||
num_enum = "0.5.7"
|
||||
|
||||
# intl deps
|
||||
boa_icu_provider = { workspace = true, optional = true }
|
||||
icu_locid_transform = { version = "1.0.0", features = ["serde"], optional = true }
|
||||
icu_locid = { version = "1.0.0", features = ["serde"], optional = true }
|
||||
icu_datetime = { version = "1.0.0", features = ["serde", "experimental"], optional = true }
|
||||
icu_calendar = { version = "1.0.0", optional = true }
|
||||
icu_collator = { version = "1.0.1", features = ["serde"], optional = true }
|
||||
icu_plurals = { version = "1.0.0", features = ["serde"], optional = true }
|
||||
icu_provider = { version = "1.0.1", optional = true }
|
||||
icu_list = { version = "1.0.0", features = ["serde"], optional = true }
|
||||
writeable = { version = "0.5.0", optional = true }
|
||||
sys-locale = { version = "0.2.3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.4.0"
|
||||
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
|
||||
10
javascript-engine/external/boa/boa_engine/benches/README.md
vendored
Normal file
10
javascript-engine/external/boa/boa_engine/benches/README.md
vendored
Normal file
@@ -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.
|
||||
1
javascript-engine/external/boa/boa_engine/benches/bench_scripts/arithmetic_operations.js
vendored
Normal file
1
javascript-engine/external/boa/boa_engine/benches/bench_scripts/arithmetic_operations.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
((2 + 2) ** 3 / 100 - 5 ** 3 * -1000) ** 2 + 100 - 8;
|
||||
7
javascript-engine/external/boa/boa_engine/benches/bench_scripts/array_access.js
vendored
Normal file
7
javascript-engine/external/boa/boa_engine/benches/bench_scripts/array_access.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
(function () {
|
||||
let testArr = [1, 2, 3, 4, 5];
|
||||
|
||||
let res = testArr[2];
|
||||
|
||||
return res;
|
||||
})();
|
||||
8
javascript-engine/external/boa/boa_engine/benches/bench_scripts/array_create.js
vendored
Normal file
8
javascript-engine/external/boa/boa_engine/benches/bench_scripts/array_create.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let testArr = [];
|
||||
for (let a = 0; a <= 500; a++) {
|
||||
testArr[a] = "p" + a;
|
||||
}
|
||||
|
||||
return testArr;
|
||||
})();
|
||||
22
javascript-engine/external/boa/boa_engine/benches/bench_scripts/array_pop.js
vendored
Normal file
22
javascript-engine/external/boa/boa_engine/benches/bench_scripts/array_pop.js
vendored
Normal file
@@ -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;
|
||||
})();
|
||||
7
javascript-engine/external/boa/boa_engine/benches/bench_scripts/boolean_object_access.js
vendored
Normal file
7
javascript-engine/external/boa/boa_engine/benches/bench_scripts/boolean_object_access.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
new Boolean(
|
||||
!new Boolean(
|
||||
new Boolean(
|
||||
!new Boolean(false).valueOf() && new Boolean(true).valueOf()
|
||||
).valueOf()
|
||||
).valueOf()
|
||||
).valueOf();
|
||||
15
javascript-engine/external/boa/boa_engine/benches/bench_scripts/clean_js.js
vendored
Normal file
15
javascript-engine/external/boa/boa_engine/benches/bench_scripts/clean_js.js
vendored
Normal file
@@ -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
|
||||
});
|
||||
}();
|
||||
10
javascript-engine/external/boa/boa_engine/benches/bench_scripts/fibonacci.js
vendored
Normal file
10
javascript-engine/external/boa/boa_engine/benches/bench_scripts/fibonacci.js
vendored
Normal file
@@ -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);
|
||||
})();
|
||||
10
javascript-engine/external/boa/boa_engine/benches/bench_scripts/for_loop.js
vendored
Normal file
10
javascript-engine/external/boa/boa_engine/benches/bench_scripts/for_loop.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
(function () {
|
||||
let b = "hello";
|
||||
for (let a = 10; a < 100; a += 5) {
|
||||
if (a < 50) {
|
||||
b += "world";
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
})();
|
||||
1
javascript-engine/external/boa/boa_engine/benches/bench_scripts/mini_js.js
vendored
Normal file
1
javascript-engine/external/boa/boa_engine/benches/bench_scripts/mini_js.js
vendored
Normal file
@@ -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})}();
|
||||
5
javascript-engine/external/boa/boa_engine/benches/bench_scripts/number_object_access.js
vendored
Normal file
5
javascript-engine/external/boa/boa_engine/benches/bench_scripts/number_object_access.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
new Number(
|
||||
new Number(
|
||||
new Number(new Number(100).valueOf() - 10.5).valueOf() + 100
|
||||
).valueOf() * 1.6
|
||||
);
|
||||
8
javascript-engine/external/boa/boa_engine/benches/bench_scripts/object_creation.js
vendored
Normal file
8
javascript-engine/external/boa/boa_engine/benches/bench_scripts/object_creation.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let test = {
|
||||
my_prop: "hello",
|
||||
another: 65,
|
||||
};
|
||||
|
||||
return test;
|
||||
})();
|
||||
8
javascript-engine/external/boa/boa_engine/benches/bench_scripts/object_prop_access_const.js
vendored
Normal file
8
javascript-engine/external/boa/boa_engine/benches/bench_scripts/object_prop_access_const.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let test = {
|
||||
my_prop: "hello",
|
||||
another: 65,
|
||||
};
|
||||
|
||||
return test.my_prop;
|
||||
})();
|
||||
8
javascript-engine/external/boa/boa_engine/benches/bench_scripts/object_prop_access_dyn.js
vendored
Normal file
8
javascript-engine/external/boa/boa_engine/benches/bench_scripts/object_prop_access_dyn.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let test = {
|
||||
my_prop: "hello",
|
||||
another: 65,
|
||||
};
|
||||
|
||||
return test["my" + "_prop"];
|
||||
})();
|
||||
5
javascript-engine/external/boa/boa_engine/benches/bench_scripts/regexp.js
vendored
Normal file
5
javascript-engine/external/boa/boa_engine/benches/bench_scripts/regexp.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = new RegExp("hello", "i");
|
||||
|
||||
return regExp.test("Hello World");
|
||||
})();
|
||||
5
javascript-engine/external/boa/boa_engine/benches/bench_scripts/regexp_creation.js
vendored
Normal file
5
javascript-engine/external/boa/boa_engine/benches/bench_scripts/regexp_creation.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = new RegExp("hello", "i");
|
||||
|
||||
return regExp;
|
||||
})();
|
||||
5
javascript-engine/external/boa/boa_engine/benches/bench_scripts/regexp_literal.js
vendored
Normal file
5
javascript-engine/external/boa/boa_engine/benches/bench_scripts/regexp_literal.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = /hello/i;
|
||||
|
||||
return regExp.test("Hello World");
|
||||
})();
|
||||
5
javascript-engine/external/boa/boa_engine/benches/bench_scripts/regexp_literal_creation.js
vendored
Normal file
5
javascript-engine/external/boa/boa_engine/benches/bench_scripts/regexp_literal_creation.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = /hello/i;
|
||||
|
||||
return regExp;
|
||||
})();
|
||||
9
javascript-engine/external/boa/boa_engine/benches/bench_scripts/string_compare.js
vendored
Normal file
9
javascript-engine/external/boa/boa_engine/benches/bench_scripts/string_compare.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
(function () {
|
||||
var a = "hello";
|
||||
var b = "world";
|
||||
|
||||
var c = a == b;
|
||||
|
||||
var d = b;
|
||||
var e = d == b;
|
||||
})();
|
||||
6
javascript-engine/external/boa/boa_engine/benches/bench_scripts/string_concat.js
vendored
Normal file
6
javascript-engine/external/boa/boa_engine/benches/bench_scripts/string_concat.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
(function () {
|
||||
var a = "hello";
|
||||
var b = "world";
|
||||
|
||||
var c = a + b;
|
||||
})();
|
||||
4
javascript-engine/external/boa/boa_engine/benches/bench_scripts/string_copy.js
vendored
Normal file
4
javascript-engine/external/boa/boa_engine/benches/bench_scripts/string_copy.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
(function () {
|
||||
var a = "hello";
|
||||
var b = a;
|
||||
})();
|
||||
7
javascript-engine/external/boa/boa_engine/benches/bench_scripts/string_object_access.js
vendored
Normal file
7
javascript-engine/external/boa/boa_engine/benches/bench_scripts/string_object_access.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
new String(
|
||||
new String(
|
||||
new String(
|
||||
new String("Hello").valueOf() + new String(", world").valueOf()
|
||||
).valueOf() + "!"
|
||||
).valueOf()
|
||||
).valueOf();
|
||||
3
javascript-engine/external/boa/boa_engine/benches/bench_scripts/symbol_creation.js
vendored
Normal file
3
javascript-engine/external/boa/boa_engine/benches/bench_scripts/symbol_creation.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
(function () {
|
||||
return Symbol();
|
||||
})();
|
||||
95
javascript-engine/external/boa/boa_engine/benches/full.rs
vendored
Normal file
95
javascript-engine/external/boa/boa_engine/benches/full.rs
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
//! Benchmarks of the whole execution engine in Boa.
|
||||
|
||||
use boa_engine::{realm::Realm, Context};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use std::hint::black_box;
|
||||
|
||||
#[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(None)));
|
||||
}
|
||||
|
||||
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);
|
||||
465
javascript-engine/external/boa/boa_engine/src/bigint.rs
vendored
Normal file
465
javascript-engine/external/boa/boa_engine/src/bigint.rs
vendored
Normal file
@@ -0,0 +1,465 @@
|
||||
//! Boa's implementation of ECMAScript's bigint primitive type.
|
||||
|
||||
use crate::{builtins::Number, error::JsNativeError, JsResult};
|
||||
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, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct JsBigInt {
|
||||
inner: Rc<RawBigInt>,
|
||||
}
|
||||
|
||||
impl JsBigInt {
|
||||
/// Create a new [`JsBigInt`].
|
||||
#[must_use]
|
||||
pub fn new<T: Into<Self>>(value: T) -> Self {
|
||||
value.into()
|
||||
}
|
||||
|
||||
/// Create a [`JsBigInt`] with value `0`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::zero()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if is zero.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.inner.is_zero()
|
||||
}
|
||||
|
||||
/// Create a [`JsBigInt`] with value `1`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn one() -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::one()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if is one.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_one(&self) -> bool {
|
||||
self.inner.is_one()
|
||||
}
|
||||
|
||||
/// Convert bigint to string with radix.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
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]
|
||||
#[must_use]
|
||||
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]
|
||||
#[must_use]
|
||||
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]
|
||||
#[must_use]
|
||||
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]
|
||||
#[must_use]
|
||||
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]
|
||||
#[must_use]
|
||||
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]
|
||||
#[must_use]
|
||||
pub fn equal(x: &Self, y: &Self) -> bool {
|
||||
x == y
|
||||
}
|
||||
|
||||
/// Returns `x` to the power `y`.
|
||||
#[inline]
|
||||
pub fn pow(x: &Self, y: &Self) -> JsResult<Self> {
|
||||
let y = y
|
||||
.inner
|
||||
.to_biguint()
|
||||
.ok_or_else(|| JsNativeError::range().with_message("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 Err(JsNativeError::range()
|
||||
.with_message("Maximum BigInt size exceeded")
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Self::new(x.inner.as_ref().clone().pow(y)))
|
||||
}
|
||||
|
||||
/// Performs the `>>` operation.
|
||||
#[inline]
|
||||
pub fn shift_right(x: &Self, y: &Self) -> JsResult<Self> {
|
||||
match y.inner.to_i32() {
|
||||
Some(n) if n > 0 => Ok(Self::new(x.inner.as_ref().clone().shr(n as usize))),
|
||||
Some(n) => Ok(Self::new(x.inner.as_ref().clone().shl(n.unsigned_abs()))),
|
||||
None => Err(JsNativeError::range()
|
||||
.with_message("Maximum BigInt size exceeded")
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs the `<<` operation.
|
||||
#[inline]
|
||||
pub fn shift_left(x: &Self, y: &Self) -> JsResult<Self> {
|
||||
match y.inner.to_i32() {
|
||||
Some(n) if n > 0 => Ok(Self::new(x.inner.as_ref().clone().shl(n as usize))),
|
||||
Some(n) => Ok(Self::new(x.inner.as_ref().clone().shr(n.unsigned_abs()))),
|
||||
None => Err(JsNativeError::range()
|
||||
.with_message("Maximum BigInt size exceeded")
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Floored integer modulo.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use num_integer::Integer;
|
||||
/// assert_eq!((8).mod_floor(&3), 2);
|
||||
/// assert_eq!((8).mod_floor(&-3), -1);
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn mod_floor(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.mod_floor(&y.inner))
|
||||
}
|
||||
|
||||
/// Performs the `+` operation.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn add(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().add(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
/// Performs the `-` operation.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn sub(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().sub(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
/// Performs the `*` operation.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn mul(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().mul(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
/// Performs the `/` operation.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn div(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().div(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
/// Performs the `%` operation.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn rem(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().rem(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
/// Performs the `&` operation.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn bitand(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().bitand(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
/// Performs the `|` operation.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn bitor(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().bitor(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
/// Performs the `^` operation.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn bitxor(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().bitxor(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
/// Performs the unary `-` operation.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn neg(x: &Self) -> Self {
|
||||
Self::new(x.as_inner().neg())
|
||||
}
|
||||
|
||||
/// Performs the unary `!` operation.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn not(x: &Self) -> Self {
|
||||
Self::new(!x.as_inner())
|
||||
}
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The error indicates that the conversion from [`f64`] to [`JsBigInt`] failed.
|
||||
#[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);
|
||||
}
|
||||
RawBigInt::from_f64(n).map_or(Err(TryFromF64Error), |bigint| Ok(Self::new(bigint)))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
other.fract().is_zero()
|
||||
&& RawBigInt::from_f64(*other).map_or(false, |bigint| self.inner.as_ref() == &bigint)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<JsBigInt> for f64 {
|
||||
#[inline]
|
||||
fn eq(&self, other: &JsBigInt) -> bool {
|
||||
self.fract().is_zero()
|
||||
&& RawBigInt::from_f64(*self).map_or(false, |bigint| other.inner.as_ref() == &bigint)
|
||||
}
|
||||
}
|
||||
160
javascript-engine/external/boa/boa_engine/src/builtins/array/array_iterator.rs
vendored
Normal file
160
javascript-engine/external/boa/boa_engine/src/builtins/array/array_iterator.rs
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
//! This module implements the `ArrayIterator` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects
|
||||
|
||||
use crate::{
|
||||
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue},
|
||||
error::JsNativeError,
|
||||
object::{JsObject, ObjectData},
|
||||
property::{PropertyDescriptor, PropertyNameKind},
|
||||
symbol::JsSymbol,
|
||||
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: u64,
|
||||
#[unsafe_ignore_trace]
|
||||
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(|| JsNativeError::typ().with_message("`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 Err(JsNativeError::typ()
|
||||
.with_message(
|
||||
"Cannot get value from typed array that has a detached array buffer",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
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 = JsSymbol::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
|
||||
}
|
||||
}
|
||||
3029
javascript-engine/external/boa/boa_engine/src/builtins/array/mod.rs
vendored
Normal file
3029
javascript-engine/external/boa/boa_engine/src/builtins/array/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1582
javascript-engine/external/boa/boa_engine/src/builtins/array/tests.rs
vendored
Normal file
1582
javascript-engine/external/boa/boa_engine/src/builtins/array/tests.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
842
javascript-engine/external/boa/boa_engine/src/builtins/array_buffer/mod.rs
vendored
Normal file
842
javascript-engine/external/boa/boa_engine/src/builtins/array_buffer/mod.rs
vendored
Normal file
@@ -0,0 +1,842 @@
|
||||
//! Boa's implementation of ECMAScript's global `ArrayBuffer` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-arraybuffer-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{
|
||||
builtins::{typed_array::TypedArrayKind, BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
error::JsNativeError,
|
||||
native_function::NativeFunction,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder,
|
||||
FunctionObjectBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
symbol::JsSymbol,
|
||||
value::{IntegerOrInfinity, Numeric},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
use num_traits::{Signed, ToPrimitive};
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
/// The internal representation of an `ArrayBuffer` object.
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct ArrayBuffer {
|
||||
/// The `[[ArrayBufferData]]` internal slot.
|
||||
pub array_buffer_data: Option<Vec<u8>>,
|
||||
|
||||
/// The `[[ArrayBufferByteLength]]` internal slot.
|
||||
pub array_buffer_byte_length: u64,
|
||||
|
||||
/// The `[[ArrayBufferDetachKey]]` internal slot.
|
||||
pub array_buffer_detach_key: JsValue,
|
||||
}
|
||||
|
||||
impl ArrayBuffer {
|
||||
pub(crate) const fn array_buffer_byte_length(&self) -> u64 {
|
||||
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 =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species))
|
||||
.name("get [Symbol.species]")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let get_byte_length =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(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(
|
||||
JsSymbol::species(),
|
||||
Some(get_species),
|
||||
None,
|
||||
Attribute::CONFIGURABLE,
|
||||
)
|
||||
.static_method(Self::is_view, "isView", 1)
|
||||
.method(Self::slice, "slice", 2)
|
||||
.property(
|
||||
JsSymbol::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 Err(JsNativeError::typ()
|
||||
.with_message("ArrayBuffer.constructor called with undefined new target")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
let obj = this.as_object().ok_or_else(|| {
|
||||
JsNativeError::typ().with_message("ArrayBuffer.byteLength called with non-object value")
|
||||
})?;
|
||||
let obj = obj.borrow();
|
||||
let buf = obj.as_array_buffer().ok_or_else(|| {
|
||||
JsNativeError::typ().with_message("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(buf) {
|
||||
return Ok(0.into());
|
||||
}
|
||||
|
||||
// 5. Let length be O.[[ArrayBufferByteLength]].
|
||||
// 6. Return 𝔽(length).
|
||||
Ok(buf.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 = this.as_object().ok_or_else(|| {
|
||||
JsNativeError::typ().with_message("ArrayBuffer.slice called with non-object value")
|
||||
})?;
|
||||
let obj_borrow = obj.borrow();
|
||||
let buf = obj_borrow.as_array_buffer().ok_or_else(|| {
|
||||
JsNativeError::typ().with_message("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(buf) {
|
||||
return Err(JsNativeError::typ()
|
||||
.with_message("ArrayBuffer.slice called with detached buffer")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 5. Let len be O.[[ArrayBufferByteLength]].
|
||||
let len = buf.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 u64;
|
||||
|
||||
// 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(|| {
|
||||
JsNativeError::typ().with_message("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 Err(JsNativeError::typ()
|
||||
.with_message("ArrayBuffer constructor returned detached ArrayBuffer")
|
||||
.into());
|
||||
}
|
||||
}
|
||||
// 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 Err(JsNativeError::typ()
|
||||
.with_message("New ArrayBuffer is the same as this ArrayBuffer")
|
||||
.into());
|
||||
}
|
||||
|
||||
{
|
||||
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 Err(JsNativeError::typ()
|
||||
.with_message("New ArrayBuffer length too small")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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(buf) {
|
||||
return Err(JsNativeError::typ()
|
||||
.with_message("ArrayBuffer detached while ArrayBuffer.slice was running")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 24. Let fromBuf be O.[[ArrayBufferData]].
|
||||
let from_buf = buf
|
||||
.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 as usize);
|
||||
}
|
||||
|
||||
// 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: u64,
|
||||
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,
|
||||
)?;
|
||||
|
||||
// 2. Let block be ? CreateByteDataBlock(byteLength).
|
||||
let block = create_byte_data_block(byte_length)?;
|
||||
|
||||
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||
let obj = JsObject::from_proto_and_data(
|
||||
prototype,
|
||||
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) const 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: u64,
|
||||
src_length: u64,
|
||||
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 = self.array_buffer_data.as_deref().ok_or_else(|| {
|
||||
JsNativeError::syntax().with_message("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 as usize,
|
||||
src_length as usize,
|
||||
);
|
||||
}
|
||||
|
||||
// 6. Return targetBuffer.
|
||||
Ok(target_buffer)
|
||||
}
|
||||
|
||||
/// `25.1.2.6 IsUnclampedIntegerElementType ( type )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isunclampedintegerelementtype
|
||||
const 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
|
||||
const 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)]
|
||||
const 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: u64,
|
||||
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() as usize;
|
||||
|
||||
// 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 byte_index = byte_index as usize;
|
||||
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: u64,
|
||||
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 as usize + 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: u64) -> 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 size = size.try_into().map_err(|e| {
|
||||
JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}"))
|
||||
})?;
|
||||
|
||||
let mut data_block = Vec::new();
|
||||
data_block.try_reserve(size).map_err(|e| {
|
||||
JsNativeError::range().with_message(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,
|
||||
}
|
||||
11
javascript-engine/external/boa/boa_engine/src/builtins/array_buffer/tests.rs
vendored
Normal file
11
javascript-engine/external/boa/boa_engine/src/builtins/array_buffer/tests.rs
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ut_sunny_day_create_byte_data_block() {
|
||||
assert!(create_byte_data_block(100).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ut_rainy_day_create_byte_data_block() {
|
||||
assert!(create_byte_data_block(u64::MAX).is_err());
|
||||
}
|
||||
109
javascript-engine/external/boa/boa_engine/src/builtins/async_function/mod.rs
vendored
Normal file
109
javascript-engine/external/boa/boa_engine/src/builtins/async_function/mod.rs
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
//! Boa's implementation of ECMAScript's 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,
|
||||
},
|
||||
native_function::NativeFunction,
|
||||
object::ObjectData,
|
||||
property::PropertyDescriptor,
|
||||
symbol::JsSymbol,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// The internal representation of an `AsyncFunction` object.
|
||||
#[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: NativeFunction::from_fn_ptr(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(JsSymbol::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)
|
||||
}
|
||||
}
|
||||
777
javascript-engine/external/boa/boa_engine/src/builtins/async_generator/mod.rs
vendored
Normal file
777
javascript-engine/external/boa/boa_engine/src/builtins/async_generator/mod.rs
vendored
Normal file
@@ -0,0 +1,777 @@
|
||||
//! Boa's implementation of ECMAScript's global `AsyncGenerator` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-objects
|
||||
|
||||
use crate::{
|
||||
builtins::{
|
||||
generator::GeneratorContext, iterable::create_iter_result_object,
|
||||
promise::if_abrupt_reject_promise, promise::PromiseCapability, BuiltIn, JsArgs, Promise,
|
||||
},
|
||||
error::JsNativeError,
|
||||
native_function::NativeFunction,
|
||||
object::{ConstructorBuilder, FunctionObjectBuilder, JsObject, ObjectData},
|
||||
property::{Attribute, PropertyDescriptor},
|
||||
symbol::JsSymbol,
|
||||
value::JsValue,
|
||||
vm::GeneratorResumeKind,
|
||||
Context, JsError, JsResult,
|
||||
};
|
||||
use boa_gc::{Finalize, Gc, GcCell, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Indicates the state of an async generator.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) enum AsyncGeneratorState {
|
||||
Undefined,
|
||||
SuspendedStart,
|
||||
SuspendedYield,
|
||||
Executing,
|
||||
AwaitingReturn,
|
||||
Completed,
|
||||
}
|
||||
|
||||
/// `AsyncGeneratorRequest Records`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorrequest-records
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub(crate) struct AsyncGeneratorRequest {
|
||||
/// The `[[Completion]]` slot.
|
||||
pub(crate) completion: (JsResult<JsValue>, bool),
|
||||
|
||||
/// The `[[Capability]]` slot.
|
||||
capability: PromiseCapability,
|
||||
}
|
||||
|
||||
/// The internal representation of an `AsyncGenerator` object.
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct AsyncGenerator {
|
||||
/// The `[[AsyncGeneratorState]]` internal slot.
|
||||
#[unsafe_ignore_trace]
|
||||
pub(crate) state: AsyncGeneratorState,
|
||||
|
||||
/// The `[[AsyncGeneratorContext]]` internal slot.
|
||||
pub(crate) context: Option<Gc<GcCell<GeneratorContext>>>,
|
||||
|
||||
/// The `[[AsyncGeneratorQueue]]` internal slot.
|
||||
pub(crate) queue: VecDeque<AsyncGeneratorRequest>,
|
||||
}
|
||||
|
||||
impl BuiltIn for AsyncGenerator {
|
||||
const NAME: &'static str = "AsyncGenerator";
|
||||
|
||||
fn init(context: &mut Context<'_>) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let iterator_prototype = context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.async_iterator_prototype();
|
||||
|
||||
let generator_function_prototype = context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_generator_function()
|
||||
.prototype();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_generator()
|
||||
.clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(0)
|
||||
.property(
|
||||
JsSymbol::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()
|
||||
.async_generator()
|
||||
.prototype
|
||||
.insert_property(
|
||||
"constructor",
|
||||
PropertyDescriptor::builder()
|
||||
.value(generator_function_prototype)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncGenerator {
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn constructor(
|
||||
_: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
let prototype = context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_generator()
|
||||
.prototype();
|
||||
|
||||
let this = JsObject::from_proto_and_data(
|
||||
prototype,
|
||||
ObjectData::async_generator(Self {
|
||||
state: AsyncGeneratorState::Undefined,
|
||||
context: None,
|
||||
queue: VecDeque::new(),
|
||||
}),
|
||||
);
|
||||
|
||||
Ok(this.into())
|
||||
}
|
||||
|
||||
/// `AsyncGenerator.prototype.next ( value )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-next
|
||||
pub(crate) fn next(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let generator be the this value.
|
||||
let generator = this;
|
||||
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
let promise_capability = PromiseCapability::new(
|
||||
&context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.promise()
|
||||
.constructor()
|
||||
.into(),
|
||||
context,
|
||||
)
|
||||
.expect("cannot fail with promise constructor");
|
||||
|
||||
// 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
let generator_object: JsResult<_> = generator.as_object().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("generator resumed on non generator object")
|
||||
.into()
|
||||
});
|
||||
if_abrupt_reject_promise!(generator_object, promise_capability, context);
|
||||
let mut generator_obj_mut = generator_object.borrow_mut();
|
||||
let generator: JsResult<_> = generator_obj_mut.as_async_generator_mut().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("generator resumed on non generator object")
|
||||
.into()
|
||||
});
|
||||
if_abrupt_reject_promise!(generator, promise_capability, context);
|
||||
|
||||
// 5. Let state be generator.[[AsyncGeneratorState]].
|
||||
let state = generator.state;
|
||||
|
||||
// 6. If state is completed, then
|
||||
if state == AsyncGeneratorState::Completed {
|
||||
drop(generator_obj_mut);
|
||||
|
||||
// a. Let iteratorResult be CreateIterResultObject(undefined, true).
|
||||
let iterator_result = create_iter_result_object(JsValue::undefined(), true, context);
|
||||
|
||||
// b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
|
||||
promise_capability
|
||||
.resolve()
|
||||
.call(&JsValue::undefined(), &[iterator_result], context)
|
||||
.expect("cannot fail per spec");
|
||||
|
||||
// c. Return promiseCapability.[[Promise]].
|
||||
return Ok(promise_capability.promise().clone().into());
|
||||
}
|
||||
|
||||
// 7. Let completion be NormalCompletion(value).
|
||||
let completion = (Ok(args.get_or_undefined(0).clone()), false);
|
||||
|
||||
// 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
generator.enqueue(completion.clone(), promise_capability.clone());
|
||||
|
||||
// 9. If state is either suspendedStart or suspendedYield, then
|
||||
if state == AsyncGeneratorState::SuspendedStart
|
||||
|| state == AsyncGeneratorState::SuspendedYield
|
||||
{
|
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
let generator_context = generator
|
||||
.context
|
||||
.clone()
|
||||
.expect("generator context cannot be empty here");
|
||||
|
||||
drop(generator_obj_mut);
|
||||
|
||||
Self::resume(
|
||||
generator_object,
|
||||
state,
|
||||
&generator_context,
|
||||
completion,
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
// 11. Return promiseCapability.[[Promise]].
|
||||
Ok(promise_capability.promise().clone().into())
|
||||
}
|
||||
|
||||
/// `AsyncGenerator.prototype.return ( value )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-return
|
||||
pub(crate) fn r#return(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let generator be the this value.
|
||||
let generator = this;
|
||||
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
let promise_capability = PromiseCapability::new(
|
||||
&context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.promise()
|
||||
.constructor()
|
||||
.into(),
|
||||
context,
|
||||
)
|
||||
.expect("cannot fail with promise constructor");
|
||||
|
||||
// 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
let generator_object: JsResult<_> = generator.as_object().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("generator resumed on non generator object")
|
||||
.into()
|
||||
});
|
||||
if_abrupt_reject_promise!(generator_object, promise_capability, context);
|
||||
let mut generator_obj_mut = generator_object.borrow_mut();
|
||||
let generator: JsResult<_> = generator_obj_mut.as_async_generator_mut().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("generator resumed on non generator object")
|
||||
.into()
|
||||
});
|
||||
if_abrupt_reject_promise!(generator, promise_capability, context);
|
||||
|
||||
// 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
|
||||
let completion = (Ok(args.get_or_undefined(0).clone()), true);
|
||||
|
||||
// 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
generator.enqueue(completion.clone(), promise_capability.clone());
|
||||
|
||||
// 7. Let state be generator.[[AsyncGeneratorState]].
|
||||
let state = generator.state;
|
||||
|
||||
// 8. If state is either suspendedStart or completed, then
|
||||
if state == AsyncGeneratorState::SuspendedStart || state == AsyncGeneratorState::Completed {
|
||||
// a. Set generator.[[AsyncGeneratorState]] to awaiting-return.
|
||||
generator.state = AsyncGeneratorState::AwaitingReturn;
|
||||
|
||||
// b. Perform ! AsyncGeneratorAwaitReturn(generator).
|
||||
let next = generator
|
||||
.queue
|
||||
.front()
|
||||
.cloned()
|
||||
.expect("queue cannot be empty here");
|
||||
drop(generator_obj_mut);
|
||||
let (completion, _) = &next.completion;
|
||||
Self::await_return(generator_object.clone(), completion.clone(), context);
|
||||
}
|
||||
// 9. Else if state is suspendedYield, then
|
||||
else if state == AsyncGeneratorState::SuspendedYield {
|
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
let generator_context = generator
|
||||
.context
|
||||
.clone()
|
||||
.expect("generator context cannot be empty here");
|
||||
|
||||
drop(generator_obj_mut);
|
||||
Self::resume(
|
||||
generator_object,
|
||||
state,
|
||||
&generator_context,
|
||||
completion,
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
// 11. Return promiseCapability.[[Promise]].
|
||||
Ok(promise_capability.promise().clone().into())
|
||||
}
|
||||
|
||||
/// `AsyncGenerator.prototype.throw ( exception )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-throw
|
||||
pub(crate) fn throw(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let generator be the this value.
|
||||
let generator = this;
|
||||
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
let promise_capability = PromiseCapability::new(
|
||||
&context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.promise()
|
||||
.constructor()
|
||||
.into(),
|
||||
context,
|
||||
)
|
||||
.expect("cannot fail with promise constructor");
|
||||
|
||||
// 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
let generator_object: JsResult<_> = generator.as_object().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("generator resumed on non generator object")
|
||||
.into()
|
||||
});
|
||||
if_abrupt_reject_promise!(generator_object, promise_capability, context);
|
||||
let mut generator_obj_mut = generator_object.borrow_mut();
|
||||
let generator: JsResult<_> = generator_obj_mut.as_async_generator_mut().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("generator resumed on non generator object")
|
||||
.into()
|
||||
});
|
||||
if_abrupt_reject_promise!(generator, promise_capability, context);
|
||||
|
||||
// 5. Let state be generator.[[AsyncGeneratorState]].
|
||||
let mut state = generator.state;
|
||||
|
||||
// 6. If state is suspendedStart, then
|
||||
if state == AsyncGeneratorState::SuspendedStart {
|
||||
// a. Set generator.[[AsyncGeneratorState]] to completed.
|
||||
generator.state = AsyncGeneratorState::Completed;
|
||||
|
||||
// b. Set state to completed.
|
||||
state = AsyncGeneratorState::Completed;
|
||||
}
|
||||
|
||||
// 7. If state is completed, then
|
||||
if state == AsyncGeneratorState::Completed {
|
||||
drop(generator_obj_mut);
|
||||
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »).
|
||||
promise_capability
|
||||
.reject()
|
||||
.call(
|
||||
&JsValue::undefined(),
|
||||
&[args.get_or_undefined(0).clone()],
|
||||
context,
|
||||
)
|
||||
.expect("cannot fail per spec");
|
||||
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return Ok(promise_capability.promise().clone().into());
|
||||
}
|
||||
|
||||
// 8. Let completion be ThrowCompletion(exception).
|
||||
let completion = (
|
||||
Err(JsError::from_opaque(args.get_or_undefined(0).clone())),
|
||||
false,
|
||||
);
|
||||
|
||||
// 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
generator.enqueue(completion.clone(), promise_capability.clone());
|
||||
|
||||
// 10. If state is suspendedYield, then
|
||||
if state == AsyncGeneratorState::SuspendedYield {
|
||||
let generator_context = generator
|
||||
.context
|
||||
.clone()
|
||||
.expect("generator context cannot be empty here");
|
||||
drop(generator_obj_mut);
|
||||
|
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
Self::resume(
|
||||
generator_object,
|
||||
state,
|
||||
&generator_context,
|
||||
completion,
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
// 12. Return promiseCapability.[[Promise]].
|
||||
Ok(promise_capability.promise().clone().into())
|
||||
}
|
||||
|
||||
/// `AsyncGeneratorEnqueue ( generator, completion, promiseCapability )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorenqueue
|
||||
pub(crate) fn enqueue(
|
||||
&mut self,
|
||||
completion: (JsResult<JsValue>, bool),
|
||||
promise_capability: PromiseCapability,
|
||||
) {
|
||||
// 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }.
|
||||
let request = AsyncGeneratorRequest {
|
||||
completion,
|
||||
capability: promise_capability,
|
||||
};
|
||||
|
||||
// 2. Append request to the end of generator.[[AsyncGeneratorQueue]].
|
||||
self.queue.push_back(request);
|
||||
}
|
||||
|
||||
/// `AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep
|
||||
pub(crate) fn complete_step(
|
||||
next: &AsyncGeneratorRequest,
|
||||
completion: JsResult<JsValue>,
|
||||
done: bool,
|
||||
context: &mut Context<'_>,
|
||||
) {
|
||||
// 1. Let queue be generator.[[AsyncGeneratorQueue]].
|
||||
// 2. Assert: queue is not empty.
|
||||
// 3. Let next be the first element of queue.
|
||||
// 4. Remove the first element from queue.
|
||||
// 5. Let promiseCapability be next.[[Capability]].
|
||||
let promise_capability = &next.capability;
|
||||
|
||||
// 6. Let value be completion.[[Value]].
|
||||
match completion {
|
||||
// 7. If completion.[[Type]] is throw, then
|
||||
Err(e) => {
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »).
|
||||
promise_capability
|
||||
.reject()
|
||||
.call(&JsValue::undefined(), &[e.to_opaque(context)], context)
|
||||
.expect("cannot fail per spec");
|
||||
}
|
||||
// 8. Else,
|
||||
Ok(value) => {
|
||||
// a. Assert: completion.[[Type]] is normal.
|
||||
|
||||
// TODO: Realm handling not implemented yet.
|
||||
// b. If realm is present, then
|
||||
// i. Let oldRealm be the running execution context's Realm.
|
||||
// ii. Set the running execution context's Realm to realm.
|
||||
// iii. Let iteratorResult be CreateIterResultObject(value, done).
|
||||
// iv. Set the running execution context's Realm to oldRealm.
|
||||
// c. Else,
|
||||
// i. Let iteratorResult be CreateIterResultObject(value, done).
|
||||
let iterator_result = create_iter_result_object(value, done, context);
|
||||
|
||||
// d. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
|
||||
promise_capability
|
||||
.resolve()
|
||||
.call(&JsValue::undefined(), &[iterator_result], context)
|
||||
.expect("cannot fail per spec");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `AsyncGeneratorResume ( generator, completion )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorresume
|
||||
pub(crate) fn resume(
|
||||
generator: &JsObject,
|
||||
state: AsyncGeneratorState,
|
||||
generator_context: &Gc<GcCell<GeneratorContext>>,
|
||||
completion: (JsResult<JsValue>, bool),
|
||||
context: &mut Context<'_>,
|
||||
) {
|
||||
// 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield.
|
||||
assert!(
|
||||
state == AsyncGeneratorState::SuspendedStart
|
||||
|| state == AsyncGeneratorState::SuspendedYield
|
||||
);
|
||||
|
||||
// 2. Let genContext be generator.[[AsyncGeneratorContext]].
|
||||
let mut generator_context_mut = generator_context.borrow_mut();
|
||||
|
||||
// 3. Let callerContext be the running execution context.
|
||||
// 4. Suspend callerContext.
|
||||
|
||||
// 5. Set generator.[[AsyncGeneratorState]] to executing.
|
||||
generator
|
||||
.borrow_mut()
|
||||
.as_async_generator_mut()
|
||||
.expect("already checked before")
|
||||
.state = AsyncGeneratorState::Executing;
|
||||
|
||||
// 6. Push genContext onto the execution context stack; genContext is now the running execution context.
|
||||
std::mem::swap(
|
||||
&mut context.realm.environments,
|
||||
&mut generator_context_mut.environments,
|
||||
);
|
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context_mut.stack);
|
||||
context
|
||||
.vm
|
||||
.push_frame(generator_context_mut.call_frame.clone());
|
||||
|
||||
// 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation.
|
||||
match completion {
|
||||
(Ok(value), r#return) => {
|
||||
context.vm.push(value);
|
||||
if r#return {
|
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return;
|
||||
} else {
|
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal;
|
||||
}
|
||||
}
|
||||
(Err(value), _) => {
|
||||
let value = value.to_opaque(context);
|
||||
context.vm.push(value);
|
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw;
|
||||
}
|
||||
}
|
||||
drop(generator_context_mut);
|
||||
let result = context.run();
|
||||
|
||||
let mut generator_context_mut = generator_context.borrow_mut();
|
||||
std::mem::swap(
|
||||
&mut context.realm.environments,
|
||||
&mut generator_context_mut.environments,
|
||||
);
|
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context_mut.stack);
|
||||
generator_context_mut.call_frame =
|
||||
context.vm.pop_frame().expect("generator frame must exist");
|
||||
drop(generator_context_mut);
|
||||
|
||||
// 8. Assert: result is never an abrupt completion.
|
||||
assert!(result.is_ok());
|
||||
|
||||
// 9. Assert: When we return here, genContext has already been removed from the execution context stack and callerContext is the currently running execution context.
|
||||
// 10. Return unused.
|
||||
}
|
||||
|
||||
/// `AsyncGeneratorAwaitReturn ( generator )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn
|
||||
pub(crate) fn await_return(
|
||||
generator: JsObject,
|
||||
completion: JsResult<JsValue>,
|
||||
context: &mut Context<'_>,
|
||||
) {
|
||||
// 1. Let queue be generator.[[AsyncGeneratorQueue]].
|
||||
// 2. Assert: queue is not empty.
|
||||
// 3. Let next be the first element of queue.
|
||||
// 4. Let completion be Completion(next.[[Completion]]).
|
||||
|
||||
// 5. Assert: completion.[[Type]] is return.
|
||||
let value = completion.expect("completion must be a return completion");
|
||||
|
||||
// Note: The spec is currently broken here.
|
||||
// See: https://github.com/tc39/ecma262/pull/2683
|
||||
|
||||
// 6. Let promise be ? PromiseResolve(%Promise%, completion.[[Value]]).
|
||||
let promise_completion = Promise::promise_resolve(
|
||||
context.intrinsics().constructors().promise().constructor(),
|
||||
value,
|
||||
context,
|
||||
);
|
||||
|
||||
let promise = match promise_completion {
|
||||
Ok(value) => value,
|
||||
Err(value) => {
|
||||
let mut generator_borrow_mut = generator.borrow_mut();
|
||||
let gen = generator_borrow_mut
|
||||
.as_async_generator_mut()
|
||||
.expect("already checked before");
|
||||
gen.state = AsyncGeneratorState::Completed;
|
||||
let next = gen.queue.pop_front().expect("queue must not be empty");
|
||||
drop(generator_borrow_mut);
|
||||
Self::complete_step(&next, Err(value), true, context);
|
||||
Self::drain_queue(&generator, context);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// 7. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called:
|
||||
// 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
|
||||
let on_fulfilled = FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_copy_closure_with_captures(
|
||||
|_this, args, generator, context| {
|
||||
let next = {
|
||||
let mut generator_borrow_mut = generator.borrow_mut();
|
||||
let gen = generator_borrow_mut
|
||||
.as_async_generator_mut()
|
||||
.expect("already checked before");
|
||||
|
||||
// a. Set generator.[[AsyncGeneratorState]] to completed.
|
||||
gen.state = AsyncGeneratorState::Completed;
|
||||
|
||||
gen.queue.pop_front().expect("must have one entry")
|
||||
};
|
||||
|
||||
// b. Let result be NormalCompletion(value).
|
||||
let result = Ok(args.get_or_undefined(0).clone());
|
||||
|
||||
// c. Perform AsyncGeneratorCompleteStep(generator, result, true).
|
||||
Self::complete_step(&next, result, true, context);
|
||||
|
||||
// d. Perform AsyncGeneratorDrainQueue(generator).
|
||||
Self::drain_queue(generator, context);
|
||||
|
||||
// e. Return undefined.
|
||||
Ok(JsValue::undefined())
|
||||
},
|
||||
generator.clone(),
|
||||
),
|
||||
)
|
||||
.name("")
|
||||
.length(1)
|
||||
.build();
|
||||
|
||||
// 9. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called:
|
||||
// 10. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
|
||||
let on_rejected = FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_copy_closure_with_captures(
|
||||
|_this, args, generator, context| {
|
||||
let mut generator_borrow_mut = generator.borrow_mut();
|
||||
let gen = generator_borrow_mut
|
||||
.as_async_generator_mut()
|
||||
.expect("already checked before");
|
||||
|
||||
// a. Set generator.[[AsyncGeneratorState]] to completed.
|
||||
gen.state = AsyncGeneratorState::Completed;
|
||||
|
||||
// b. Let result be ThrowCompletion(reason).
|
||||
let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone()));
|
||||
|
||||
// c. Perform AsyncGeneratorCompleteStep(generator, result, true).
|
||||
let next = gen.queue.pop_front().expect("must have one entry");
|
||||
drop(generator_borrow_mut);
|
||||
Self::complete_step(&next, result, true, context);
|
||||
|
||||
// d. Perform AsyncGeneratorDrainQueue(generator).
|
||||
Self::drain_queue(generator, context);
|
||||
|
||||
// e. Return undefined.
|
||||
Ok(JsValue::undefined())
|
||||
},
|
||||
generator,
|
||||
),
|
||||
)
|
||||
.name("")
|
||||
.length(1)
|
||||
.build();
|
||||
|
||||
// 11. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
|
||||
Promise::perform_promise_then(
|
||||
&promise,
|
||||
&on_fulfilled.into(),
|
||||
&on_rejected.into(),
|
||||
None,
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
/// `AsyncGeneratorDrainQueue ( generator )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue
|
||||
pub(crate) fn drain_queue(generator: &JsObject, context: &mut Context<'_>) {
|
||||
let mut generator_borrow_mut = generator.borrow_mut();
|
||||
let gen = generator_borrow_mut
|
||||
.as_async_generator_mut()
|
||||
.expect("already checked before");
|
||||
|
||||
// 1. Assert: generator.[[AsyncGeneratorState]] is completed.
|
||||
assert_eq!(gen.state, AsyncGeneratorState::Completed);
|
||||
|
||||
// 2. Let queue be generator.[[AsyncGeneratorQueue]].
|
||||
let queue = &mut gen.queue;
|
||||
|
||||
// 3. If queue is empty, return unused.
|
||||
if queue.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Let done be false.
|
||||
// 5. Repeat, while done is false,
|
||||
loop {
|
||||
// a. Let next be the first element of queue.
|
||||
let next = queue.front().expect("must have entry");
|
||||
|
||||
// b. Let completion be Completion(next.[[Completion]]).
|
||||
match &next.completion {
|
||||
// c. If completion.[[Type]] is return, then
|
||||
(completion, true) => {
|
||||
// i. Set generator.[[AsyncGeneratorState]] to awaiting-return.
|
||||
gen.state = AsyncGeneratorState::AwaitingReturn;
|
||||
|
||||
// ii. Perform ! AsyncGeneratorAwaitReturn(generator).
|
||||
let completion = completion.clone();
|
||||
drop(generator_borrow_mut);
|
||||
Self::await_return(generator.clone(), completion, context);
|
||||
|
||||
// iii. Set done to true.
|
||||
break;
|
||||
}
|
||||
// d. Else,
|
||||
(completion, false) => {
|
||||
// i. If completion.[[Type]] is normal, then
|
||||
let completion = if completion.is_ok() {
|
||||
// 1. Set completion to NormalCompletion(undefined).
|
||||
Ok(JsValue::undefined())
|
||||
} else {
|
||||
completion.clone()
|
||||
};
|
||||
|
||||
// ii. Perform AsyncGeneratorCompleteStep(generator, completion, true).
|
||||
let next = queue.pop_front().expect("must have entry");
|
||||
Self::complete_step(&next, completion, true, context);
|
||||
|
||||
// iii. If queue is empty, set done to true.
|
||||
if queue.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
130
javascript-engine/external/boa/boa_engine/src/builtins/async_generator_function/mod.rs
vendored
Normal file
130
javascript-engine/external/boa/boa_engine/src/builtins/async_generator_function/mod.rs
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
//! Boa's implementation of ECMAScript's `AsyncGeneratorFunction` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorfunction-objects
|
||||
|
||||
use crate::{
|
||||
builtins::{
|
||||
function::{BuiltInFunctionObject, ConstructorKind, Function},
|
||||
BuiltIn,
|
||||
},
|
||||
native_function::NativeFunction,
|
||||
object::ObjectData,
|
||||
property::PropertyDescriptor,
|
||||
symbol::JsSymbol,
|
||||
value::JsValue,
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// The internal representation of an `AsyncGeneratorFunction` object.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AsyncGeneratorFunction;
|
||||
|
||||
impl BuiltIn for AsyncGeneratorFunction {
|
||||
const NAME: &'static str = "AsyncGeneratorFunction";
|
||||
|
||||
fn init(context: &mut Context<'_>) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let prototype = &context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_generator_function()
|
||||
.prototype;
|
||||
let constructor = &context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_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(Self::NAME)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
constructor.borrow_mut().insert("name", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_generator_function()
|
||||
.prototype(),
|
||||
)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(false);
|
||||
constructor.borrow_mut().insert("prototype", property);
|
||||
constructor.borrow_mut().data = ObjectData::function(Function::Native {
|
||||
function: NativeFunction::from_fn_ptr(Self::constructor),
|
||||
constructor: Some(ConstructorKind::Base),
|
||||
});
|
||||
|
||||
prototype.set_prototype(Some(
|
||||
context.intrinsics().constructors().function().prototype(),
|
||||
));
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_generator_function()
|
||||
.constructor(),
|
||||
)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype.borrow_mut().insert("constructor", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_generator()
|
||||
.prototype(),
|
||||
)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype.borrow_mut().insert("prototype", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value("AsyncGeneratorFunction")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype
|
||||
.borrow_mut()
|
||||
.insert(JsSymbol::to_string_tag(), property);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncGeneratorFunction {
|
||||
/// `AsyncGeneratorFunction ( p1, p2, … , pn, body )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorfunction
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
BuiltInFunctionObject::create_dynamic_function(new_target, args, true, true, context)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
295
javascript-engine/external/boa/boa_engine/src/builtins/bigint/mod.rs
vendored
Normal file
295
javascript-engine/external/boa/boa_engine/src/builtins/bigint/mod.rs
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
//! Boa's implementation of ECMAScript's 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},
|
||||
error::JsNativeError,
|
||||
object::ConstructorBuilder,
|
||||
property::Attribute,
|
||||
symbol::JsSymbol,
|
||||
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 = JsSymbol::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 Err(JsNativeError::typ()
|
||||
.with_message("BigInt is not a constructor")
|
||||
.into());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
fn number_to_bigint(number: f64) -> JsResult<JsValue> {
|
||||
// 1. If IsIntegralNumber(number) is false, throw a RangeError exception.
|
||||
if number.is_nan() || number.is_infinite() || number.fract() != 0.0 {
|
||||
return Err(JsNativeError::range()
|
||||
.with_message(format!("Cannot convert {number} to BigInt"))
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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
|
||||
fn this_bigint_value(value: &JsValue) -> 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(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("'this' is not a BigInt")
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
/// `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)?;
|
||||
|
||||
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 Err(JsNativeError::range()
|
||||
.with_message("radix must be an integer at least 2 and no greater than 36")
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
// 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],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
Ok(JsValue::new(Self::this_bigint_value(this)?))
|
||||
}
|
||||
|
||||
/// `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))?
|
||||
{
|
||||
Ok(JsValue::new(JsBigInt::sub(
|
||||
&modulo,
|
||||
&JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)))?,
|
||||
)))
|
||||
} 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)))?,
|
||||
),
|
||||
bits,
|
||||
))
|
||||
}
|
||||
}
|
||||
413
javascript-engine/external/boa/boa_engine/src/builtins/bigint/tests.rs
vendored
Normal file
413
javascript-engine/external/boa/boa_engine/src/builtins/bigint/tests.rs
vendored
Normal file
@@ -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");
|
||||
}
|
||||
127
javascript-engine/external/boa/boa_engine/src/builtins/boolean/mod.rs
vendored
Normal file
127
javascript-engine/external/boa/boa_engine/src/builtins/boolean/mod.rs
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
//! Boa's implementation of ECMAScript's 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,
|
||||
error::JsNativeError,
|
||||
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) -> JsResult<bool> {
|
||||
value
|
||||
.as_boolean()
|
||||
.or_else(|| value.as_object().and_then(|obj| obj.borrow().as_boolean()))
|
||||
.ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("'this' is not a boolean")
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
/// 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],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
let boolean = Self::this_boolean_value(this)?;
|
||||
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
|
||||
pub(crate) fn value_of(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
Ok(JsValue::new(Self::this_boolean_value(this)?))
|
||||
}
|
||||
}
|
||||
65
javascript-engine/external/boa/boa_engine/src/builtins/boolean/tests.rs
vendored
Normal file
65
javascript-engine/external/boa/boa_engine/src/builtins/boolean/tests.rs
vendored
Normal file
@@ -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()
|
||||
);
|
||||
}
|
||||
634
javascript-engine/external/boa/boa_engine/src/builtins/console/mod.rs
vendored
Normal file
634
javascript-engine/external/boa/boa_engine/src/builtins/console/mod.rs
vendored
Normal file
@@ -0,0 +1,634 @@
|
||||
//! Boa's implementation of JavaScript's `console` Web API 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)]
|
||||
enum LogMessage {
|
||||
Log(String),
|
||||
Info(String),
|
||||
Warn(String),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
/// Helper function for logging messages.
|
||||
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> {
|
||||
match data {
|
||||
[] => Ok(String::new()),
|
||||
[val] => Ok(val.to_string(context)?.to_std_string_escaped()),
|
||||
data => {
|
||||
let mut formatted = String::new();
|
||||
let mut arg_index = 1;
|
||||
let target = data
|
||||
.get_or_undefined(0)
|
||||
.to_string(context)?
|
||||
.to_std_string_escaped();
|
||||
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_or_undefined(arg_index)
|
||||
.to_string(context)?
|
||||
.to_std_string_escaped();
|
||||
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)?.to_std_string_escaped()
|
||||
));
|
||||
}
|
||||
|
||||
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_block.name)
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
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.to_std_string_escaped());
|
||||
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.to_std_string_escaped())),
|
||||
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 '{}' already exist",
|
||||
label.to_std_string_escaped()
|
||||
)),
|
||||
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!("{}: {} ms", label.to_std_string_escaped(), 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 '{}' doesn't exist",
|
||||
label.to_std_string_escaped()
|
||||
)),
|
||||
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) {
|
||||
let time = Self::system_time_in_ms();
|
||||
logger(
|
||||
LogMessage::Info(format!(
|
||||
"{}: {} ms - timer removed",
|
||||
label.to_std_string_escaped(),
|
||||
time - t
|
||||
)),
|
||||
context.console(),
|
||||
);
|
||||
} else {
|
||||
logger(
|
||||
LogMessage::Warn(format!(
|
||||
"Timer '{}' doesn't exist",
|
||||
label.to_std_string_escaped()
|
||||
)),
|
||||
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())
|
||||
}
|
||||
}
|
||||
68
javascript-engine/external/boa/boa_engine/src/builtins/console/tests.rs
vendored
Normal file
68
javascript-engine/external/boa/boa_engine/src/builtins/console/tests.rs
vendored
Normal file
@@ -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");
|
||||
}
|
||||
1067
javascript-engine/external/boa/boa_engine/src/builtins/dataview/mod.rs
vendored
Normal file
1067
javascript-engine/external/boa/boa_engine/src/builtins/dataview/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1484
javascript-engine/external/boa/boa_engine/src/builtins/date/mod.rs
vendored
Normal file
1484
javascript-engine/external/boa/boa_engine/src/builtins/date/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1410
javascript-engine/external/boa/boa_engine/src/builtins/date/tests.rs
vendored
Normal file
1410
javascript-engine/external/boa/boa_engine/src/builtins/date/tests.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
199
javascript-engine/external/boa/boa_engine/src/builtins/date/utils.rs
vendored
Normal file
199
javascript-engine/external/boa/boa_engine/src/builtins/date/utils.rs
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
use chrono::{Datelike, Local, NaiveDateTime, TimeZone, Timelike};
|
||||
|
||||
use crate::value::IntegerOrNan;
|
||||
|
||||
/// The absolute maximum value of a timestamp
|
||||
pub(super) const MAX_TIMESTAMP: i64 = 864 * 10i64.pow(13);
|
||||
/// The number of milliseconds in a second.
|
||||
pub(super) const MILLIS_PER_SECOND: i64 = 1000;
|
||||
/// The number of milliseconds in a minute.
|
||||
pub(super) const MILLIS_PER_MINUTE: i64 = MILLIS_PER_SECOND * 60;
|
||||
/// The number of milliseconds in an hour.
|
||||
pub(super) const MILLIS_PER_HOUR: i64 = MILLIS_PER_MINUTE * 60;
|
||||
/// The number of milliseconds in a day.
|
||||
pub(super) const MILLIS_PER_DAY: i64 = MILLIS_PER_HOUR * 24;
|
||||
|
||||
// https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-time-values-and-time-range
|
||||
//
|
||||
// The smaller range supported by a time value as specified in this section is approximately -273,790 to 273,790
|
||||
// years relative to 1970.
|
||||
pub(super) const MIN_YEAR: i64 = -300_000;
|
||||
pub(super) const MAX_YEAR: i64 = -MIN_YEAR;
|
||||
pub(super) const MIN_MONTH: i64 = MIN_YEAR * 12;
|
||||
pub(super) const MAX_MONTH: i64 = MAX_YEAR * 12;
|
||||
|
||||
/// Calculates the absolute day number from the year number.
|
||||
pub(super) const fn day_from_year(year: i64) -> i64 {
|
||||
// Taken from https://chromium.googlesource.com/v8/v8/+/refs/heads/main/src/date/date.cc#496
|
||||
// Useful to avoid negative divisions and overflows on 32-bit platforms (if we plan to support them).
|
||||
const YEAR_DELTA: i64 = 399_999;
|
||||
const fn day(year: i64) -> i64 {
|
||||
let year = year + YEAR_DELTA;
|
||||
365 * year + year / 4 - year / 100 + year / 400
|
||||
}
|
||||
|
||||
assert!(MIN_YEAR <= year && year <= MAX_YEAR);
|
||||
day(year) - day(1970)
|
||||
}
|
||||
|
||||
/// Abstract operation [`MakeTime`][spec].
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-maketime
|
||||
pub(super) fn make_time(hour: i64, min: i64, sec: i64, ms: i64) -> Option<i64> {
|
||||
// 1. If hour is not finite or min is not finite or sec is not finite or ms is not finite, return NaN.
|
||||
// 2. Let h be 𝔽(! ToIntegerOrInfinity(hour)).
|
||||
// 3. Let m be 𝔽(! ToIntegerOrInfinity(min)).
|
||||
// 4. Let s be 𝔽(! ToIntegerOrInfinity(sec)).
|
||||
// 5. Let milli be 𝔽(! ToIntegerOrInfinity(ms)).
|
||||
|
||||
// 6. Let t be ((h * msPerHour + m * msPerMinute) + s * msPerSecond) + milli, performing the arithmetic according to IEEE 754-2019 rules (that is, as if using the ECMAScript operators * and +).
|
||||
// 7. Return t.
|
||||
|
||||
let h_ms = hour.checked_mul(MILLIS_PER_HOUR)?;
|
||||
let m_ms = min.checked_mul(MILLIS_PER_MINUTE)?;
|
||||
let s_ms = sec.checked_mul(MILLIS_PER_SECOND)?;
|
||||
|
||||
h_ms.checked_add(m_ms)?.checked_add(s_ms)?.checked_add(ms)
|
||||
}
|
||||
|
||||
/// Abstract operation [`MakeDay`][spec].
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-makeday
|
||||
pub(super) fn make_day(mut year: i64, mut month: i64, date: i64) -> Option<i64> {
|
||||
// 1. If year is not finite or month is not finite or date is not finite, return NaN.
|
||||
// 2. Let y be 𝔽(! ToIntegerOrInfinity(year)).
|
||||
// 3. Let m be 𝔽(! ToIntegerOrInfinity(month)).
|
||||
// 4. Let dt be 𝔽(! ToIntegerOrInfinity(date)).
|
||||
if !(MIN_YEAR..=MAX_YEAR).contains(&year) || !(MIN_MONTH..=MAX_MONTH).contains(&month) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// At this point, we've already asserted that year and month are much less than its theoretical
|
||||
// maximum and minimum values (i64::MAX/MIN), so we don't need to do checked operations.
|
||||
|
||||
// 5. Let ym be y + 𝔽(floor(ℝ(m) / 12)).
|
||||
// 6. If ym is not finite, return NaN.
|
||||
year += month / 12;
|
||||
// 7. Let mn be 𝔽(ℝ(m) modulo 12).
|
||||
month %= 12;
|
||||
if month < 0 {
|
||||
month += 12;
|
||||
year -= 1;
|
||||
}
|
||||
|
||||
// 8. Find a finite time value t such that YearFromTime(t) is ym and MonthFromTime(t) is mn and DateFromTime(t) is
|
||||
// 1𝔽; but if this is not possible (because some argument is out of range), return NaN.
|
||||
let month = usize::try_from(month).expect("month must be between 0 and 11 at this point");
|
||||
|
||||
let mut day = day_from_year(year);
|
||||
|
||||
// Consider leap years when calculating the cumulative days added to the year from the input month
|
||||
if (year % 4 != 0) || (year % 100 == 0 && year % 400 != 0) {
|
||||
day += [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334][month];
|
||||
} else {
|
||||
day += [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335][month];
|
||||
}
|
||||
|
||||
// 9. Return Day(t) + dt - 1𝔽.
|
||||
(day - 1).checked_add(date)
|
||||
}
|
||||
|
||||
/// Abstract operation [`MakeDate`][spec].
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-makedate
|
||||
pub(super) fn make_date(day: i64, time: i64) -> Option<i64> {
|
||||
// 1. If day is not finite or time is not finite, return NaN.
|
||||
// 2. Let tv be day × msPerDay + time.
|
||||
// 3. If tv is not finite, return NaN.
|
||||
// 4. Return tv.
|
||||
day.checked_mul(MILLIS_PER_DAY)?.checked_add(time)
|
||||
}
|
||||
|
||||
/// Abstract operation [`TimeClip`][spec]
|
||||
/// Returns the timestamp (number of milliseconds) if it is in the expected range.
|
||||
/// Otherwise, returns `None`.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-timeclip
|
||||
pub(super) fn time_clip(time: i64) -> Option<i64> {
|
||||
// 1. If time is not finite, return NaN.
|
||||
// 2. If abs(ℝ(time)) > 8.64 × 10^15, return NaN.
|
||||
// 3. Return 𝔽(! ToIntegerOrInfinity(time)).
|
||||
(time.checked_abs()? <= MAX_TIMESTAMP).then_some(time)
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub(super) struct DateParameters {
|
||||
pub(super) year: Option<IntegerOrNan>,
|
||||
pub(super) month: Option<IntegerOrNan>,
|
||||
pub(super) date: Option<IntegerOrNan>,
|
||||
pub(super) hour: Option<IntegerOrNan>,
|
||||
pub(super) minute: Option<IntegerOrNan>,
|
||||
pub(super) second: Option<IntegerOrNan>,
|
||||
pub(super) millisecond: Option<IntegerOrNan>,
|
||||
}
|
||||
|
||||
/// Replaces some (or all) parameters of `date` with the specified parameters
|
||||
pub(super) fn replace_params(
|
||||
datetime: NaiveDateTime,
|
||||
params: DateParameters,
|
||||
local: bool,
|
||||
) -> Option<NaiveDateTime> {
|
||||
let DateParameters {
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
millisecond,
|
||||
} = params;
|
||||
|
||||
let datetime = if local {
|
||||
Local.from_utc_datetime(&datetime).naive_local()
|
||||
} else {
|
||||
datetime
|
||||
};
|
||||
|
||||
let year = match year {
|
||||
Some(i) => i.as_integer()?,
|
||||
None => i64::from(datetime.year()),
|
||||
};
|
||||
let month = match month {
|
||||
Some(i) => i.as_integer()?,
|
||||
None => i64::from(datetime.month() - 1),
|
||||
};
|
||||
let date = match date {
|
||||
Some(i) => i.as_integer()?,
|
||||
None => i64::from(datetime.day()),
|
||||
};
|
||||
let hour = match hour {
|
||||
Some(i) => i.as_integer()?,
|
||||
None => i64::from(datetime.hour()),
|
||||
};
|
||||
let minute = match minute {
|
||||
Some(i) => i.as_integer()?,
|
||||
None => i64::from(datetime.minute()),
|
||||
};
|
||||
let second = match second {
|
||||
Some(i) => i.as_integer()?,
|
||||
None => i64::from(datetime.second()),
|
||||
};
|
||||
let millisecond = match millisecond {
|
||||
Some(i) => i.as_integer()?,
|
||||
None => i64::from(datetime.timestamp_subsec_millis()),
|
||||
};
|
||||
|
||||
let new_day = make_day(year, month, date)?;
|
||||
let new_time = make_time(hour, minute, second, millisecond)?;
|
||||
let mut ts = make_date(new_day, new_time)?;
|
||||
|
||||
if local {
|
||||
ts = Local
|
||||
.from_local_datetime(&NaiveDateTime::from_timestamp_millis(ts)?)
|
||||
.earliest()?
|
||||
.naive_utc()
|
||||
.timestamp_millis();
|
||||
}
|
||||
|
||||
NaiveDateTime::from_timestamp_millis(time_clip(ts)?)
|
||||
}
|
||||
116
javascript-engine/external/boa/boa_engine/src/builtins/error/aggregate.rs
vendored
Normal file
116
javascript-engine/external/boa/boa_engine/src/builtins/error/aggregate.rs
vendored
Normal file
@@ -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, ErrorKind};
|
||||
|
||||
#[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(ErrorKind::Aggregate));
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
91
javascript-engine/external/boa/boa_engine/src/builtins/error/eval.rs
vendored
Normal file
91
javascript-engine/external/boa/boa_engine/src/builtins/error/eval.rs
vendored
Normal file
@@ -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, ErrorKind};
|
||||
|
||||
/// 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(ErrorKind::Eval));
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
267
javascript-engine/external/boa/boa_engine/src/builtins/error/mod.rs
vendored
Normal file
267
javascript-engine/external/boa/boa_engine/src/builtins/error/mod.rs
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
//! Boa's implementation of ECMAScript's 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,
|
||||
error::JsNativeError,
|
||||
js_string,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
string::utf16,
|
||||
Context, JsResult, 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;
|
||||
|
||||
/// The kind of a `NativeError` object, per the [ECMAScript spec][spec].
|
||||
///
|
||||
/// This is used internally to convert between [`JsObject`] and
|
||||
/// [`JsNativeError`] correctly, but it can also be used to manually create `Error`
|
||||
/// objects. However, the recommended way to create them is to construct a
|
||||
/// `JsNativeError` first, then call [`JsNativeError::to_opaque`],
|
||||
/// which will assign its prototype, properties and kind automatically.
|
||||
///
|
||||
/// For a description of every error kind and its usage, see
|
||||
/// [`JsNativeErrorKind`][crate::error::JsNativeErrorKind].
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-error-objects
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum ErrorKind {
|
||||
/// The `AggregateError` object type.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-aggregate-error-objects
|
||||
Aggregate,
|
||||
|
||||
/// The `Error` object type.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-error-objects
|
||||
Error,
|
||||
|
||||
/// The `EvalError` type.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror
|
||||
Eval,
|
||||
|
||||
/// The `TypeError` type.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-typeerror
|
||||
Type,
|
||||
|
||||
/// The `RangeError` type.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror
|
||||
Range,
|
||||
|
||||
/// The `ReferenceError` type.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror
|
||||
Reference,
|
||||
|
||||
/// The `SyntaxError` type.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror
|
||||
Syntax,
|
||||
|
||||
/// The `URIError` type.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror
|
||||
Uri,
|
||||
}
|
||||
|
||||
/// 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(ErrorKind::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.
|
||||
// 2. If Type(O) is not Object, throw a TypeError exception.
|
||||
let o = this
|
||||
.as_object()
|
||||
.ok_or_else(|| JsNativeError::typ().with_message("'this' is not an Object"))?;
|
||||
|
||||
// 3. Let name be ? Get(O, "name").
|
||||
let name = o.get(js_string!("name"), context)?;
|
||||
|
||||
// 4. If name is undefined, set name to "Error"; otherwise set name to ? ToString(name).
|
||||
let name = if name.is_undefined() {
|
||||
js_string!("Error")
|
||||
} else {
|
||||
name.to_string(context)?
|
||||
};
|
||||
|
||||
// 5. Let msg be ? Get(O, "message").
|
||||
let msg = o.get(js_string!("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() {
|
||||
js_string!()
|
||||
} 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(js_string!(&name, utf16!(": "), &msg).into())
|
||||
}
|
||||
}
|
||||
89
javascript-engine/external/boa/boa_engine/src/builtins/error/range.rs
vendored
Normal file
89
javascript-engine/external/boa/boa_engine/src/builtins/error/range.rs
vendored
Normal file
@@ -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, ErrorKind};
|
||||
|
||||
/// 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(ErrorKind::Range));
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
95
javascript-engine/external/boa/boa_engine/src/builtins/error/reference.rs
vendored
Normal file
95
javascript-engine/external/boa/boa_engine/src/builtins/error/reference.rs
vendored
Normal file
@@ -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, ErrorKind};
|
||||
|
||||
#[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(ErrorKind::Reference));
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
94
javascript-engine/external/boa/boa_engine/src/builtins/error/syntax.rs
vendored
Normal file
94
javascript-engine/external/boa/boa_engine/src/builtins/error/syntax.rs
vendored
Normal file
@@ -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, ErrorKind};
|
||||
|
||||
/// 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(ErrorKind::Syntax));
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
86
javascript-engine/external/boa/boa_engine/src/builtins/error/tests.rs
vendored
Normal file
86
javascript-engine/external/boa/boa_engine/src/builtins/error/tests.rs
vendored
Normal file
@@ -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\""
|
||||
);
|
||||
}
|
||||
120
javascript-engine/external/boa/boa_engine/src/builtins/error/type.rs
vendored
Normal file
120
javascript-engine/external/boa/boa_engine/src/builtins/error/type.rs
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
//! 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,
|
||||
error::JsNativeError,
|
||||
native_function::NativeFunction,
|
||||
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, ErrorKind};
|
||||
|
||||
/// 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(ErrorKind::Type));
|
||||
|
||||
// 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], _: &mut Context<'_>) -> JsResult<JsValue> {
|
||||
Err(JsNativeError::typ().with_message("'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them").into())
|
||||
}
|
||||
|
||||
let function = JsObject::from_proto_and_data(
|
||||
context.intrinsics().constructors().function().prototype(),
|
||||
ObjectData::function(Function::Native {
|
||||
function: NativeFunction::from_fn_ptr(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
|
||||
}
|
||||
90
javascript-engine/external/boa/boa_engine/src/builtins/error/uri.rs
vendored
Normal file
90
javascript-engine/external/boa/boa_engine/src/builtins/error/uri.rs
vendored
Normal file
@@ -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, ErrorKind};
|
||||
|
||||
/// 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(ErrorKind::Uri));
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
273
javascript-engine/external/boa/boa_engine/src/builtins/eval/mod.rs
vendored
Normal file
273
javascript-engine/external/boa/boa_engine/src/builtins/eval/mod.rs
vendored
Normal file
@@ -0,0 +1,273 @@
|
||||
//! Boa's implementation of ECMAScript's global `eval` function.
|
||||
//!
|
||||
//! The `eval()` function evaluates ECMAScript 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},
|
||||
environments::DeclarativeEnvironment,
|
||||
error::JsNativeError,
|
||||
native_function::NativeFunction,
|
||||
object::FunctionObjectBuilder,
|
||||
property::Attribute,
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
use boa_ast::operations::{
|
||||
contains, contains_arguments, top_level_var_declared_names, ContainsSymbol,
|
||||
};
|
||||
use boa_gc::Gc;
|
||||
use boa_parser::Parser;
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
#[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 = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(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<'_>) -> JsResult<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,
|
||||
mut strict: bool,
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
bitflags::bitflags! {
|
||||
/// Flags used to throw early errors on invalid `eval` calls.
|
||||
#[derive(Default)]
|
||||
struct Flags: u8 {
|
||||
const IN_FUNCTION = 0b0001;
|
||||
const IN_METHOD = 0b0010;
|
||||
const IN_DERIVED_CONSTRUCTOR = 0b0100;
|
||||
const IN_CLASS_FIELD_INITIALIZER = 0b1000;
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible actions that can be executed after exiting this function to restore the environment to its
|
||||
/// original state.
|
||||
#[derive(Debug)]
|
||||
enum EnvStackAction {
|
||||
Truncate(usize),
|
||||
Restore(Vec<Gc<DeclarativeEnvironment>>),
|
||||
}
|
||||
|
||||
/// Restores the environment after calling `eval` or after throwing an error.
|
||||
fn restore_environment(context: &mut Context<'_>, action: EnvStackAction) {
|
||||
match action {
|
||||
EnvStackAction::Truncate(size) => {
|
||||
context.realm.environments.truncate(size);
|
||||
}
|
||||
EnvStackAction::Restore(envs) => {
|
||||
// Pop all environments created during the eval execution and restore the original stack.
|
||||
context.realm.environments.truncate(1);
|
||||
context.realm.environments.extend(envs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Assert: If direct is false, then strictCaller is also false.
|
||||
debug_assert!(direct || !strict);
|
||||
|
||||
// 2. If Type(x) is not String, return x.
|
||||
// TODO: rework parser to take an iterator of `u32` unicode codepoints
|
||||
let Some(x) = x.as_string().map(JsString::to_std_string_escaped) else {
|
||||
return Ok(x.clone());
|
||||
};
|
||||
|
||||
// Because of implementation details the following code differs from the spec.
|
||||
|
||||
// 5. Perform ? HostEnsureCanCompileStrings(evalRealm).
|
||||
context.host_hooks().ensure_can_compile_strings(context)?;
|
||||
|
||||
// 11. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection:
|
||||
// a. Let script be ParseText(StringToCodePoints(x), Script).
|
||||
// b. If script is a List of errors, throw a SyntaxError exception.
|
||||
// c. If script Contains ScriptBody is false, return undefined.
|
||||
// d. Let body be the ScriptBody of script.
|
||||
let mut parser = Parser::new(x.as_bytes());
|
||||
if strict {
|
||||
parser.set_strict();
|
||||
}
|
||||
let body = parser.parse_eval(direct, context.interner_mut())?;
|
||||
|
||||
// 6. Let inFunction be false.
|
||||
// 7. Let inMethod be false.
|
||||
// 8. Let inDerivedConstructor be false.
|
||||
// 9. Let inClassFieldInitializer be false.
|
||||
// a. Let thisEnvRec be GetThisEnvironment().
|
||||
let flags = match context
|
||||
.realm
|
||||
.environments
|
||||
.get_this_environment()
|
||||
.as_function_slots()
|
||||
{
|
||||
// 10. If direct is true, then
|
||||
// b. If thisEnvRec is a Function Environment Record, then
|
||||
Some(function_env) if direct => {
|
||||
let function_env = function_env.borrow();
|
||||
// i. Let F be thisEnvRec.[[FunctionObject]].
|
||||
let function_object = function_env.function_object().borrow();
|
||||
|
||||
// ii. Set inFunction to true.
|
||||
let mut flags = Flags::IN_FUNCTION;
|
||||
|
||||
// iii. Set inMethod to thisEnvRec.HasSuperBinding().
|
||||
if function_env.has_super_binding() {
|
||||
flags |= Flags::IN_METHOD;
|
||||
}
|
||||
|
||||
let function_object = function_object
|
||||
.as_function()
|
||||
.expect("must be function object");
|
||||
|
||||
// iv. If F.[[ConstructorKind]] is derived, set inDerivedConstructor to true.
|
||||
if function_object.is_derived_constructor() {
|
||||
flags |= Flags::IN_DERIVED_CONSTRUCTOR;
|
||||
}
|
||||
|
||||
// v. Let classFieldInitializerName be F.[[ClassFieldInitializerName]].
|
||||
// vi. If classFieldInitializerName is not empty, set inClassFieldInitializer to true.
|
||||
if function_object.class_field_initializer_name().is_some() {
|
||||
flags |= Flags::IN_CLASS_FIELD_INITIALIZER;
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
_ => Flags::default(),
|
||||
};
|
||||
|
||||
if !flags.contains(Flags::IN_FUNCTION) && contains(&body, ContainsSymbol::NewTarget) {
|
||||
return Err(JsNativeError::syntax()
|
||||
.with_message("invalid `new.target` expression inside eval")
|
||||
.into());
|
||||
}
|
||||
if !flags.contains(Flags::IN_METHOD) && contains(&body, ContainsSymbol::SuperProperty) {
|
||||
return Err(JsNativeError::syntax()
|
||||
.with_message("invalid `super` reference inside eval")
|
||||
.into());
|
||||
}
|
||||
if !flags.contains(Flags::IN_DERIVED_CONSTRUCTOR)
|
||||
&& contains(&body, ContainsSymbol::SuperCall)
|
||||
{
|
||||
return Err(JsNativeError::syntax()
|
||||
.with_message("invalid `super` call inside eval")
|
||||
.into());
|
||||
}
|
||||
if flags.contains(Flags::IN_CLASS_FIELD_INITIALIZER) && contains_arguments(&body) {
|
||||
return Err(JsNativeError::syntax()
|
||||
.with_message("invalid `arguments` reference inside eval")
|
||||
.into());
|
||||
}
|
||||
|
||||
strict |= body.strict();
|
||||
|
||||
// Because our environment model does not map directly to the spec, this section looks very different.
|
||||
// 12 - 13 are implicit in the call of `Context::compile_with_new_declarative`.
|
||||
// 14 - 33 are in the following section, together with EvalDeclarationInstantiation.
|
||||
let action = 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.
|
||||
if !strict {
|
||||
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();
|
||||
|
||||
// Pop any added runtime environments that were not removed during the eval execution.
|
||||
EnvStackAction::Truncate(environments_len)
|
||||
} 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.
|
||||
if !strict {
|
||||
context.realm.environments.poison_all();
|
||||
}
|
||||
|
||||
// Pop all environments before the eval execution.
|
||||
let environments = context.realm.environments.pop_to_global();
|
||||
context.realm.compile_env = context.realm.environments.current_compile_environment();
|
||||
|
||||
// Restore all environments to the state from before the eval execution.
|
||||
EnvStackAction::Restore(environments)
|
||||
};
|
||||
|
||||
// Only need to check on non-strict mode since strict mode automatically creates a function
|
||||
// environment for all eval calls.
|
||||
if !strict {
|
||||
// Error if any var declaration in the eval code already exists as a let/const declaration in the current running environment.
|
||||
if let Some(name) = context
|
||||
.realm
|
||||
.environments
|
||||
.has_lex_binding_until_function_environment(&top_level_var_declared_names(&body))
|
||||
{
|
||||
restore_environment(context, action);
|
||||
let name = context.interner().resolve_expect(name.sym());
|
||||
let msg = format!("variable declaration {name} in eval function already exists as a lexical variable");
|
||||
return Err(JsNativeError::syntax().with_message(msg).into());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check if private identifiers inside `eval` are valid.
|
||||
|
||||
// Compile and execute the eval statement list.
|
||||
let code_block = context.compile_with_new_declarative(&body, strict)?;
|
||||
// Indirect calls don't need extensions, because a non-strict indirect call modifies only
|
||||
// the global object.
|
||||
// Strict direct calls also don't need extensions, since all strict eval calls push a new
|
||||
// function environment before evaluating.
|
||||
if direct && !strict {
|
||||
context
|
||||
.realm
|
||||
.environments
|
||||
.extend_outer_function_environment();
|
||||
}
|
||||
let result = context.execute(code_block);
|
||||
|
||||
restore_environment(context, action);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
293
javascript-engine/external/boa/boa_engine/src/builtins/function/arguments.rs
vendored
Normal file
293
javascript-engine/external/boa/boa_engine/src/builtins/function/arguments.rs
vendored
Normal file
@@ -0,0 +1,293 @@
|
||||
use crate::{
|
||||
environments::DeclarativeEnvironment,
|
||||
object::{JsObject, ObjectData},
|
||||
property::PropertyDescriptor,
|
||||
symbol::{self, JsSymbol},
|
||||
Context, JsValue,
|
||||
};
|
||||
use boa_ast::{function::FormalParameterList, operations::bound_names};
|
||||
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]] »).
|
||||
// 3. Set obj.[[ParameterMap]] to undefined.
|
||||
// skipped because the `Arguments` enum ensures ordinary argument objects don't have a `[[ParameterMap]]`
|
||||
let obj = JsObject::from_proto_and_data(
|
||||
context.intrinsics().constructors().object().prototype(),
|
||||
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 }).
|
||||
let values_function = context.intrinsics().objects().array_prototype_values();
|
||||
obj.define_property_or_throw(
|
||||
symbol::JsSymbol::iterator(),
|
||||
PropertyDescriptor::builder()
|
||||
.value(values_function)
|
||||
.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;
|
||||
for name in bound_names(formals) {
|
||||
if property_index >= len {
|
||||
break;
|
||||
}
|
||||
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 }).
|
||||
let values_function = context.intrinsics().objects().array_prototype_values();
|
||||
obj.define_property_or_throw(
|
||||
JsSymbol::iterator(),
|
||||
PropertyDescriptor::builder()
|
||||
.value(values_function)
|
||||
.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
|
||||
}
|
||||
}
|
||||
1048
javascript-engine/external/boa/boa_engine/src/builtins/function/mod.rs
vendored
Normal file
1048
javascript-engine/external/boa/boa_engine/src/builtins/function/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
275
javascript-engine/external/boa/boa_engine/src/builtins/function/tests.rs
vendored
Normal file
275
javascript-engine/external/boa/boa_engine/src/builtins/function/tests.rs
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
use crate::{
|
||||
error::JsNativeError,
|
||||
forward, forward_val, js_string,
|
||||
native_function::NativeFunction,
|
||||
object::{FunctionObjectBuilder, JsObject},
|
||||
property::{Attribute, PropertyDescriptor},
|
||||
string::utf16,
|
||||
Context, JsNativeErrorKind,
|
||||
};
|
||||
|
||||
#[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(), utf16!("[object Error]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_call_throw() {
|
||||
let mut context = Context::default();
|
||||
let throw = r#"
|
||||
let call = Function.prototype.call;
|
||||
call(call)
|
||||
"#;
|
||||
let err = forward_val(&mut context, throw).unwrap_err();
|
||||
let err = err.as_native().unwrap();
|
||||
assert!(matches!(
|
||||
err,
|
||||
JsNativeError {
|
||||
kind: JsNativeErrorKind::Type,
|
||||
..
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[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 = js_string!("Hello");
|
||||
let object = JsObject::with_object_proto(&mut context);
|
||||
|
||||
object
|
||||
.define_property_or_throw(
|
||||
"key",
|
||||
PropertyDescriptor::builder()
|
||||
.value(" world!")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(false),
|
||||
&mut context,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let func = FunctionObjectBuilder::new(
|
||||
&mut context,
|
||||
NativeFunction::from_copy_closure_with_captures(
|
||||
|_, _, captures, context| {
|
||||
let (string, object) = &captures;
|
||||
|
||||
let hw = js_string!(
|
||||
string,
|
||||
&object
|
||||
.__get_own_property__(&"key".into(), context)?
|
||||
.and_then(|prop| prop.value().cloned())
|
||||
.and_then(|val| val.as_string().cloned())
|
||||
.ok_or_else(
|
||||
|| JsNativeError::typ().with_message("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!\"");
|
||||
}
|
||||
431
javascript-engine/external/boa/boa_engine/src/builtins/generator/mod.rs
vendored
Normal file
431
javascript-engine/external/boa/boa_engine/src/builtins/generator/mod.rs
vendored
Normal file
@@ -0,0 +1,431 @@
|
||||
//! Boa's implementation of ECMAScript's 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,
|
||||
error::JsNativeError,
|
||||
object::{ConstructorBuilder, JsObject, ObjectData},
|
||||
property::{Attribute, PropertyDescriptor},
|
||||
symbol::JsSymbol,
|
||||
value::JsValue,
|
||||
vm::{CallFrame, GeneratorResumeKind, ReturnType},
|
||||
Context, JsError, JsResult,
|
||||
};
|
||||
use boa_gc::{Finalize, Gc, GcCell, 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 of 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<GcCell<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(
|
||||
JsSymbol::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).
|
||||
this.as_object().map_or_else(
|
||||
|| {
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("Generator.prototype.next called on non generator")
|
||||
.into())
|
||||
},
|
||||
|obj| Self::generator_resume(obj, args.get_or_undefined(0), context),
|
||||
)
|
||||
}
|
||||
|
||||
/// `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(JsError::from_opaque(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(|| {
|
||||
JsNativeError::typ().with_message("generator resumed on non generator object")
|
||||
})?;
|
||||
let state = generator.state;
|
||||
|
||||
if state == GeneratorState::Executing {
|
||||
return Err(JsNativeError::typ()
|
||||
.with_message("Generator should not be executing")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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.clone());
|
||||
}
|
||||
|
||||
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(|| {
|
||||
JsNativeError::typ().with_message("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(|| {
|
||||
JsNativeError::typ().with_message("generator resumed on non generator object")
|
||||
})?;
|
||||
let mut state = generator.state;
|
||||
|
||||
if state == GeneratorState::Executing {
|
||||
return Err(JsNativeError::typ()
|
||||
.with_message("Generator should not be executing")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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) => {
|
||||
let value = value.to_opaque(context);
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
129
javascript-engine/external/boa/boa_engine/src/builtins/generator_function/mod.rs
vendored
Normal file
129
javascript-engine/external/boa/boa_engine/src/builtins/generator_function/mod.rs
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
//! Boa's implementation of ECMAScript's global `GeneratorFunction` object.
|
||||
//!
|
||||
//! The `GeneratorFunction` constructor creates a new generator function object.
|
||||
//! In ECMAScript, 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,
|
||||
},
|
||||
native_function::NativeFunction,
|
||||
object::ObjectData,
|
||||
property::PropertyDescriptor,
|
||||
symbol::JsSymbol,
|
||||
value::JsValue,
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// The internal representation of 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: NativeFunction::from_fn_ptr(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(JsSymbol::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)
|
||||
}
|
||||
}
|
||||
30
javascript-engine/external/boa/boa_engine/src/builtins/global_this/mod.rs
vendored
Normal file
30
javascript-engine/external/boa/boa_engine/src/builtins/global_this/mod.rs
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
//! Boa's implementation of ECMAScript's 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())
|
||||
}
|
||||
}
|
||||
10
javascript-engine/external/boa/boa_engine/src/builtins/global_this/tests.rs
vendored
Normal file
10
javascript-engine/external/boa/boa_engine/src/builtins/global_this/tests.rs
vendored
Normal file
@@ -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\"");
|
||||
}
|
||||
34
javascript-engine/external/boa/boa_engine/src/builtins/infinity/mod.rs
vendored
Normal file
34
javascript-engine/external/boa/boa_engine/src/builtins/infinity/mod.rs
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
//! Boa's implementation of ECMAScript's 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())
|
||||
}
|
||||
}
|
||||
19
javascript-engine/external/boa/boa_engine/src/builtins/infinity/tests.rs
vendored
Normal file
19
javascript-engine/external/boa/boa_engine/src/builtins/infinity/tests.rs
vendored
Normal file
@@ -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");
|
||||
}
|
||||
558
javascript-engine/external/boa/boa_engine/src/builtins/intl/collator/mod.rs
vendored
Normal file
558
javascript-engine/external/boa/boa_engine/src/builtins/intl/collator/mod.rs
vendored
Normal file
@@ -0,0 +1,558 @@
|
||||
use boa_gc::{custom_trace, Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
use icu_collator::{
|
||||
provider::CollationMetadataV1Marker, AlternateHandling, CaseFirst, MaxVariable, Numeric,
|
||||
};
|
||||
|
||||
use icu_locid::{
|
||||
extensions::unicode::Value, extensions_unicode_key as key, extensions_unicode_value as value,
|
||||
Locale,
|
||||
};
|
||||
use icu_provider::DataLocale;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::{intrinsics::StandardConstructors, BoaProvider},
|
||||
native_function::NativeFunction,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder,
|
||||
FunctionObjectBuilder, JsFunction, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
symbol::JsSymbol,
|
||||
Context, JsNativeError, JsResult, JsValue,
|
||||
};
|
||||
|
||||
use super::{
|
||||
locale::{canonicalize_locale_list, resolve_locale, supported_locales, validate_extension},
|
||||
options::{coerce_options_to_object, get_option, IntlOptions, LocaleMatcher},
|
||||
Service,
|
||||
};
|
||||
|
||||
mod options;
|
||||
pub(crate) use options::*;
|
||||
|
||||
pub struct Collator {
|
||||
locale: Locale,
|
||||
collation: Value,
|
||||
numeric: bool,
|
||||
case_first: Option<CaseFirst>,
|
||||
usage: Usage,
|
||||
sensitivity: Sensitivity,
|
||||
ignore_punctuation: bool,
|
||||
collator: icu_collator::Collator,
|
||||
bound_compare: Option<JsFunction>,
|
||||
}
|
||||
|
||||
impl Finalize for Collator {}
|
||||
|
||||
// SAFETY: only `bound_compare` is a traceable object.
|
||||
unsafe impl Trace for Collator {
|
||||
custom_trace!(this, mark(&this.bound_compare));
|
||||
}
|
||||
|
||||
impl Collator {
|
||||
/// Gets the inner [`icu_collator::Collator`] comparator.
|
||||
pub(crate) const fn collator(&self) -> &icu_collator::Collator {
|
||||
&self.collator
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Collator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Collator")
|
||||
.field("locale", &self.locale)
|
||||
.field("collation", &self.collation)
|
||||
.field("numeric", &self.numeric)
|
||||
.field("case_first", &self.case_first)
|
||||
.field("usage", &self.usage)
|
||||
.field("sensitivity", &self.sensitivity)
|
||||
.field("ignore_punctuation", &self.ignore_punctuation)
|
||||
.field("collator", &"ICUCollator")
|
||||
.field("bound_compare", &self.bound_compare)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::builtins::intl) struct CollatorLocaleOptions {
|
||||
collation: Option<Value>,
|
||||
numeric: Option<bool>,
|
||||
case_first: Option<CaseFirst>,
|
||||
}
|
||||
|
||||
impl Service for Collator {
|
||||
type LangMarker = CollationMetadataV1Marker;
|
||||
|
||||
type LocaleOptions = CollatorLocaleOptions;
|
||||
|
||||
fn resolve(locale: &mut Locale, options: &mut Self::LocaleOptions, provider: BoaProvider<'_>) {
|
||||
let collation = options
|
||||
.collation
|
||||
.take()
|
||||
.filter(|co| {
|
||||
validate_extension::<Self::LangMarker>(locale.id.clone(), key!("co"), co, &provider)
|
||||
})
|
||||
.or_else(|| {
|
||||
locale
|
||||
.extensions
|
||||
.unicode
|
||||
.keywords
|
||||
.get(&key!("co"))
|
||||
.cloned()
|
||||
.filter(|co| {
|
||||
validate_extension::<Self::LangMarker>(
|
||||
locale.id.clone(),
|
||||
key!("co"),
|
||||
co,
|
||||
&provider,
|
||||
)
|
||||
})
|
||||
})
|
||||
.filter(|co| co != &value!("search"));
|
||||
|
||||
let numeric =
|
||||
options.numeric.or_else(
|
||||
|| match locale.extensions.unicode.keywords.get(&key!("kn")) {
|
||||
Some(a) if a == &value!("true") => Some(true),
|
||||
Some(_) => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
|
||||
let case_first = options.case_first.or_else(|| {
|
||||
match locale.extensions.unicode.keywords.get(&key!("kf")) {
|
||||
Some(a) if a == &value!("upper") => Some(CaseFirst::UpperFirst),
|
||||
Some(a) if a == &value!("lower") => Some(CaseFirst::LowerFirst),
|
||||
Some(_) => Some(CaseFirst::Off),
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
|
||||
locale.extensions.unicode.clear();
|
||||
|
||||
if let Some(co) = collation.clone() {
|
||||
locale.extensions.unicode.keywords.set(key!("co"), co);
|
||||
}
|
||||
|
||||
if let Some(kn) = numeric.map(|kn| if kn { value!("true") } else { value!("false") }) {
|
||||
locale.extensions.unicode.keywords.set(key!("kn"), kn);
|
||||
}
|
||||
|
||||
if let Some(kf) = case_first.map(|kf| match kf {
|
||||
CaseFirst::Off => value!("false"),
|
||||
CaseFirst::LowerFirst => value!("lower"),
|
||||
CaseFirst::UpperFirst => value!("upper"),
|
||||
_ => unreachable!(),
|
||||
}) {
|
||||
locale.extensions.unicode.keywords.set(key!("kf"), kf);
|
||||
}
|
||||
|
||||
options.collation = collation;
|
||||
options.numeric = numeric;
|
||||
options.case_first = case_first;
|
||||
}
|
||||
}
|
||||
|
||||
impl BuiltIn for Collator {
|
||||
const NAME: &'static str = "Collator";
|
||||
|
||||
fn init(context: &mut Context<'_>) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let compare =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::compare))
|
||||
.name("get compare")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().collator().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.static_method(Self::supported_locales_of, "supportedLocalesOf", 1)
|
||||
.property(
|
||||
JsSymbol::to_string_tag(),
|
||||
"Intl.Collator",
|
||||
Attribute::CONFIGURABLE,
|
||||
)
|
||||
.accessor("compare", Some(compare), None, Attribute::CONFIGURABLE)
|
||||
.method(Self::resolved_options, "resolvedOptions", 0)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Collator {
|
||||
pub(crate) const LENGTH: usize = 0;
|
||||
|
||||
/// Constructor [`Intl.Collator ( [ locales [ , options ] ] )`][spec].
|
||||
///
|
||||
/// Constructor for `Collator` objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-intl.collator
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator
|
||||
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 internalSlotsList be « [[InitializedCollator]], [[Locale]], [[Usage]], [[Sensitivity]], [[IgnorePunctuation]], [[Collation]], [[BoundCompare]] ».
|
||||
// 3. If %Collator%.[[RelevantExtensionKeys]] contains "kn", then
|
||||
// a. Append [[Numeric]] as the last element of internalSlotsList.
|
||||
// 4. If %Collator%.[[RelevantExtensionKeys]] contains "kf", then
|
||||
// a. Append [[CaseFirst]] as the last element of internalSlotsList.
|
||||
// 5. Let collator be ? OrdinaryCreateFromConstructor(newTarget, "%Collator.prototype%", internalSlotsList).
|
||||
// 6. Return ? InitializeCollator(collator, locales, options).
|
||||
|
||||
let locales = args.get_or_undefined(0);
|
||||
let options = args.get_or_undefined(1);
|
||||
|
||||
// Abstract operation `InitializeCollator ( collator, locales, options )`
|
||||
// https://tc39.es/ecma402/#sec-initializecollator
|
||||
|
||||
// 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
|
||||
let requested_locales = canonicalize_locale_list(locales, context)?;
|
||||
|
||||
// 2. Set options to ? CoerceOptionsToObject(options).
|
||||
let options = coerce_options_to_object(options, context)?;
|
||||
|
||||
// 3. Let usage be ? GetOption(options, "usage", string, « "sort", "search" », "sort").
|
||||
// 4. Set collator.[[Usage]] to usage.
|
||||
// 5. If usage is "sort", then
|
||||
// a. Let localeData be %Collator%.[[SortLocaleData]].
|
||||
// 6. Else,
|
||||
// a. Let localeData be %Collator%.[[SearchLocaleData]].
|
||||
let usage = get_option::<Usage>(&options, "usage", false, context)?.unwrap_or_default();
|
||||
|
||||
// 7. Let opt be a new Record.
|
||||
// 8. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
|
||||
// 9. Set opt.[[localeMatcher]] to matcher.
|
||||
let matcher = get_option::<LocaleMatcher>(&options, "localeMatcher", false, context)?
|
||||
.unwrap_or_default();
|
||||
|
||||
// 10. Let collation be ? GetOption(options, "collation", string, empty, undefined).
|
||||
// 11. If collation is not undefined, then
|
||||
// a. If collation does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
|
||||
// 12. Set opt.[[co]] to collation.
|
||||
let collation = get_option::<Value>(&options, "collation", false, context)?;
|
||||
|
||||
// 13. Let numeric be ? GetOption(options, "numeric", boolean, empty, undefined).
|
||||
// 14. If numeric is not undefined, then
|
||||
// a. Let numeric be ! ToString(numeric).
|
||||
// 15. Set opt.[[kn]] to numeric.
|
||||
let numeric = get_option::<bool>(&options, "numeric", false, context)?;
|
||||
|
||||
// 16. Let caseFirst be ? GetOption(options, "caseFirst", string, « "upper", "lower", "false" », undefined).
|
||||
// 17. Set opt.[[kf]] to caseFirst.
|
||||
let case_first = get_option::<CaseFirst>(&options, "caseFirst", false, context)?;
|
||||
|
||||
let mut intl_options = IntlOptions {
|
||||
matcher,
|
||||
service_options: CollatorLocaleOptions {
|
||||
collation,
|
||||
numeric,
|
||||
case_first,
|
||||
},
|
||||
};
|
||||
|
||||
// 18. Let relevantExtensionKeys be %Collator%.[[RelevantExtensionKeys]].
|
||||
// 19. Let r be ResolveLocale(%Collator%.[[AvailableLocales]], requestedLocales, opt, relevantExtensionKeys, localeData).
|
||||
let mut locale =
|
||||
resolve_locale::<Self>(&requested_locales, &mut intl_options, context.icu());
|
||||
|
||||
let collator_locale = {
|
||||
// `collator_locale` needs to be different from the resolved locale because ECMA402 doesn't
|
||||
// define `search` as a resolvable extension of a locale, so we need to add that extension
|
||||
// only to the locale passed to the collator.
|
||||
let mut col_loc = DataLocale::from(&locale);
|
||||
if usage == Usage::Search {
|
||||
intl_options.service_options.collation = None;
|
||||
locale.extensions.unicode.keywords.remove(key!("co"));
|
||||
col_loc.set_unicode_ext(key!("co"), value!("search"));
|
||||
}
|
||||
col_loc
|
||||
};
|
||||
|
||||
// 20. Set collator.[[Locale]] to r.[[locale]].
|
||||
|
||||
// 21. Let collation be r.[[co]].
|
||||
// 22. If collation is null, let collation be "default".
|
||||
// 23. Set collator.[[Collation]] to collation.
|
||||
let collation = intl_options
|
||||
.service_options
|
||||
.collation
|
||||
.unwrap_or(value!("default"));
|
||||
|
||||
// 24. If relevantExtensionKeys contains "kn", then
|
||||
// a. Set collator.[[Numeric]] to SameValue(r.[[kn]], "true").
|
||||
let numeric = intl_options.service_options.numeric.unwrap_or_default();
|
||||
|
||||
// 25. If relevantExtensionKeys contains "kf", then
|
||||
// a. Set collator.[[CaseFirst]] to r.[[kf]].
|
||||
let case_first = intl_options.service_options.case_first;
|
||||
|
||||
// 26. Let sensitivity be ? GetOption(options, "sensitivity", string, « "base", "accent", "case", "variant" », undefined).
|
||||
// 28. Set collator.[[Sensitivity]] to sensitivity.
|
||||
let sensitivity = get_option::<Sensitivity>(&options, "sensitivity", false, context)?
|
||||
// 27. If sensitivity is undefined, then
|
||||
// a. If usage is "sort", then
|
||||
// i. Let sensitivity be "variant".
|
||||
// b. Else,
|
||||
// i. Let dataLocale be r.[[dataLocale]].
|
||||
// ii. Let dataLocaleData be localeData.[[<dataLocale>]].
|
||||
// iii. Let sensitivity be dataLocaleData.[[sensitivity]].
|
||||
.or_else(|| (usage == Usage::Sort).then_some(Sensitivity::Variant));
|
||||
|
||||
// 29. Let ignorePunctuation be ? GetOption(options, "ignorePunctuation", boolean, empty, false).
|
||||
// 30. Set collator.[[IgnorePunctuation]] to ignorePunctuation.
|
||||
let ignore_punctuation =
|
||||
get_option::<bool>(&options, "ignorePunctuation", false, context)?.unwrap_or_default();
|
||||
|
||||
let (strength, case_level) = sensitivity.map(Sensitivity::to_collator_options).unzip();
|
||||
|
||||
let (alternate_handling, max_variable) = ignore_punctuation
|
||||
.then_some((AlternateHandling::Shifted, MaxVariable::Punctuation))
|
||||
.unzip();
|
||||
|
||||
let collator = context
|
||||
.icu()
|
||||
.provider()
|
||||
.try_new_collator(&collator_locale, {
|
||||
let mut options = icu_collator::CollatorOptions::new();
|
||||
options.strength = strength;
|
||||
options.case_level = case_level;
|
||||
options.case_first = case_first;
|
||||
options.numeric = Some(if numeric { Numeric::On } else { Numeric::Off });
|
||||
options.alternate_handling = alternate_handling;
|
||||
options.max_variable = max_variable;
|
||||
options
|
||||
})
|
||||
.map_err(|e| JsNativeError::typ().with_message(e.to_string()))?;
|
||||
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::collator, context)?;
|
||||
let collator = JsObject::from_proto_and_data(
|
||||
prototype,
|
||||
ObjectData::collator(Collator {
|
||||
locale,
|
||||
collation,
|
||||
numeric,
|
||||
case_first,
|
||||
usage,
|
||||
sensitivity: sensitivity.unwrap_or(Sensitivity::Variant),
|
||||
ignore_punctuation,
|
||||
collator,
|
||||
bound_compare: None,
|
||||
}),
|
||||
);
|
||||
|
||||
// 31. Return collator.
|
||||
Ok(collator.into())
|
||||
}
|
||||
|
||||
/// [`Intl.Collator.supportedLocalesOf ( locales [ , options ] )`][spec].
|
||||
///
|
||||
/// Returns an array containing those of the provided locales that are supported in collation
|
||||
/// without having to fall back to the runtime's default locale.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-intl.collator.supportedlocalesof
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/supportedLocalesOf
|
||||
fn supported_locales_of(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
let locales = args.get_or_undefined(0);
|
||||
let options = args.get_or_undefined(1);
|
||||
|
||||
// 1. Let availableLocales be %Collator%.[[AvailableLocales]].
|
||||
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
|
||||
let requested_locales = canonicalize_locale_list(locales, context)?;
|
||||
|
||||
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
|
||||
supported_locales::<<Self as Service>::LangMarker>(&requested_locales, options, context)
|
||||
.map(JsValue::from)
|
||||
}
|
||||
|
||||
/// [`get Intl.Collator.prototype.compare`][spec].
|
||||
///
|
||||
/// Compares two strings according to the sort order of this Intl.Collator object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-intl.collator.prototype.compare
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/compare
|
||||
fn compare(this: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
|
||||
// 1. Let collator be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(collator, [[InitializedCollator]]).
|
||||
let this = this.as_object().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`resolvedOptions` can only be called on a `Collator` object")
|
||||
})?;
|
||||
let collator_obj = this.clone();
|
||||
let mut collator = this.borrow_mut();
|
||||
let collator = collator.as_collator_mut().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`resolvedOptions` can only be called on a `Collator` object")
|
||||
})?;
|
||||
|
||||
// 3. If collator.[[BoundCompare]] is undefined, then
|
||||
// a. Let F be a new built-in function object as defined in 10.3.3.1.
|
||||
// b. Set F.[[Collator]] to collator.
|
||||
// c. Set collator.[[BoundCompare]] to F.
|
||||
let bound_compare = if let Some(f) = collator.bound_compare.clone() {
|
||||
f
|
||||
} else {
|
||||
let bound_compare = FunctionObjectBuilder::new(
|
||||
context,
|
||||
// 10.3.3.1. Collator Compare Functions
|
||||
// https://tc39.es/ecma402/#sec-collator-compare-functions
|
||||
NativeFunction::from_copy_closure_with_captures(
|
||||
|_, args, collator, context| {
|
||||
// 1. Let collator be F.[[Collator]].
|
||||
// 2. Assert: Type(collator) is Object and collator has an [[InitializedCollator]] internal slot.
|
||||
let collator = collator.borrow();
|
||||
let collator = collator
|
||||
.as_collator()
|
||||
.expect("checked above that the object was a collator object");
|
||||
|
||||
// 3. If x is not provided, let x be undefined.
|
||||
// 5. Let X be ? ToString(x).
|
||||
let x = args.get_or_undefined(0).to_string(context)?;
|
||||
|
||||
// 4. If y is not provided, let y be undefined.
|
||||
// 6. Let Y be ? ToString(y).
|
||||
let y = args.get_or_undefined(1).to_string(context)?;
|
||||
|
||||
// 7. Return CompareStrings(collator, X, Y).
|
||||
let result = collator.collator.compare_utf16(&x, &y) as i32;
|
||||
|
||||
Ok(result.into())
|
||||
},
|
||||
collator_obj,
|
||||
),
|
||||
)
|
||||
.length(2)
|
||||
.build();
|
||||
|
||||
collator.bound_compare = Some(bound_compare.clone());
|
||||
bound_compare
|
||||
};
|
||||
|
||||
// 4. Return collator.[[BoundCompare]].
|
||||
Ok(bound_compare.into())
|
||||
}
|
||||
|
||||
/// [`Intl.Collator.prototype.resolvedOptions ( )`][spec].
|
||||
///
|
||||
/// Returns a new object with properties reflecting the locale and collation options computed
|
||||
/// during initialization of this `Intl.Collator` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-intl.collator.prototype.resolvedoptions
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/resolvedOptions
|
||||
fn resolved_options(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let collator be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(collator, [[InitializedCollator]]).
|
||||
let collator = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`resolvedOptions` can only be called on a `Collator` object")
|
||||
})?;
|
||||
let collator = collator.as_collator().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`resolvedOptions` can only be called on a `Collator` object")
|
||||
})?;
|
||||
|
||||
// 3. Let options be OrdinaryObjectCreate(%Object.prototype%).
|
||||
let options = JsObject::from_proto_and_data(
|
||||
context.intrinsics().constructors().object().prototype(),
|
||||
ObjectData::ordinary(),
|
||||
);
|
||||
|
||||
// 4. For each row of Table 4, except the header row, in table order, do
|
||||
// a. Let p be the Property value of the current row.
|
||||
// b. Let v be the value of collator's internal slot whose name is the Internal Slot value of the current row.
|
||||
// c. If the current row has an Extension Key value, then
|
||||
// i. Let extensionKey be the Extension Key value of the current row.
|
||||
// ii. If %Collator%.[[RelevantExtensionKeys]] does not contain extensionKey, then
|
||||
// 1. Let v be undefined.
|
||||
// d. If v is not undefined, then
|
||||
// i. Perform ! CreateDataPropertyOrThrow(options, p, v).
|
||||
// 5. Return options.
|
||||
options
|
||||
.create_data_property_or_throw("locale", collator.locale.to_string(), context)
|
||||
.expect("operation must not fail per the spec");
|
||||
options
|
||||
.create_data_property_or_throw(
|
||||
"usage",
|
||||
match collator.usage {
|
||||
Usage::Search => "search",
|
||||
Usage::Sort => "sort",
|
||||
},
|
||||
context,
|
||||
)
|
||||
.expect("operation must not fail per the spec");
|
||||
options
|
||||
.create_data_property_or_throw(
|
||||
"sensitivity",
|
||||
match collator.sensitivity {
|
||||
Sensitivity::Base => "base",
|
||||
Sensitivity::Accent => "accent",
|
||||
Sensitivity::Case => "case",
|
||||
Sensitivity::Variant => "variant",
|
||||
},
|
||||
context,
|
||||
)
|
||||
.expect("operation must not fail per the spec");
|
||||
options
|
||||
.create_data_property_or_throw(
|
||||
"ignorePunctuation",
|
||||
collator.ignore_punctuation,
|
||||
context,
|
||||
)
|
||||
.expect("operation must not fail per the spec");
|
||||
options
|
||||
.create_data_property_or_throw("collation", collator.collation.to_string(), context)
|
||||
.expect("operation must not fail per the spec");
|
||||
options
|
||||
.create_data_property_or_throw("numeric", collator.numeric, context)
|
||||
.expect("operation must not fail per the spec");
|
||||
if let Some(kf) = collator.case_first {
|
||||
options
|
||||
.create_data_property_or_throw(
|
||||
"caseFirst",
|
||||
match kf {
|
||||
CaseFirst::Off => "false",
|
||||
CaseFirst::LowerFirst => "lower",
|
||||
CaseFirst::UpperFirst => "upper",
|
||||
_ => unreachable!(),
|
||||
},
|
||||
context,
|
||||
)
|
||||
.expect("operation must not fail per the spec");
|
||||
}
|
||||
|
||||
// 5. Return options.
|
||||
Ok(options.into())
|
||||
}
|
||||
}
|
||||
80
javascript-engine/external/boa/boa_engine/src/builtins/intl/collator/options.rs
vendored
Normal file
80
javascript-engine/external/boa/boa_engine/src/builtins/intl/collator/options.rs
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use icu_collator::{CaseLevel, Strength};
|
||||
|
||||
use crate::builtins::intl::options::OptionTypeParsable;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum Sensitivity {
|
||||
Base,
|
||||
Accent,
|
||||
Case,
|
||||
Variant,
|
||||
}
|
||||
|
||||
impl Sensitivity {
|
||||
/// Converts the sensitivity option to the equivalent ICU4X collator options.
|
||||
pub(crate) const fn to_collator_options(self) -> (Strength, CaseLevel) {
|
||||
match self {
|
||||
Sensitivity::Base => (Strength::Primary, CaseLevel::Off),
|
||||
Sensitivity::Accent => (Strength::Secondary, CaseLevel::Off),
|
||||
Sensitivity::Case => (Strength::Primary, CaseLevel::On),
|
||||
Sensitivity::Variant => (Strength::Tertiary, CaseLevel::On),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParseSensitivityError;
|
||||
|
||||
impl std::fmt::Display for ParseSensitivityError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("provided string was not `base`, `accent`, `case` or `variant`")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Sensitivity {
|
||||
type Err = ParseSensitivityError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"base" => Ok(Self::Base),
|
||||
"accent" => Ok(Self::Accent),
|
||||
"case" => Ok(Self::Case),
|
||||
"variant" => Ok(Self::Variant),
|
||||
_ => Err(ParseSensitivityError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionTypeParsable for Sensitivity {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub(crate) enum Usage {
|
||||
#[default]
|
||||
Sort,
|
||||
Search,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParseUsageError;
|
||||
|
||||
impl std::fmt::Display for ParseUsageError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("provided string was not `sort` or `search`")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Usage {
|
||||
type Err = ParseUsageError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"sort" => Ok(Self::Sort),
|
||||
"search" => Ok(Self::Search),
|
||||
_ => Err(ParseUsageError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionTypeParsable for Usage {}
|
||||
259
javascript-engine/external/boa/boa_engine/src/builtins/intl/date_time_format.rs
vendored
Normal file
259
javascript-engine/external/boa/boa_engine/src/builtins/intl/date_time_format.rs
vendored
Normal file
@@ -0,0 +1,259 @@
|
||||
//! 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,
|
||||
error::JsNativeError,
|
||||
js_string,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsFunction, JsObject,
|
||||
ObjectData,
|
||||
},
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
use icu_datetime::options::preferences::HourCycle;
|
||||
|
||||
use super::options::OptionType;
|
||||
|
||||
impl OptionType for HourCycle {
|
||||
fn from_value(value: JsValue, context: &mut Context<'_>) -> JsResult<Self> {
|
||||
match value.to_string(context)?.to_std_string_escaped().as_str() {
|
||||
"h11" => Ok(HourCycle::H11),
|
||||
"h12" => Ok(HourCycle::H12),
|
||||
"h23" => Ok(HourCycle::H23),
|
||||
"h24" => Ok(HourCycle::H24),
|
||||
_ => Err(JsNativeError::range()
|
||||
.with_message("provided string was not `h11`, `h12`, `h23` or `h24`")
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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: js_string!("en-US"),
|
||||
calendar: js_string!("gregory"),
|
||||
numbering_system: js_string!("arab"),
|
||||
time_zone: js_string!("UTC"),
|
||||
weekday: js_string!("narrow"),
|
||||
era: js_string!("narrow"),
|
||||
year: js_string!("numeric"),
|
||||
month: js_string!("narrow"),
|
||||
day: js_string!("numeric"),
|
||||
day_period: js_string!("narrow"),
|
||||
hour: js_string!("numeric"),
|
||||
minute: js_string!("numeric"),
|
||||
second: js_string!("numeric"),
|
||||
fractional_second_digits: js_string!(""),
|
||||
time_zone_name: js_string!(""),
|
||||
hour_cycle: js_string!("h24"),
|
||||
pattern: js_string!("{hour}:{minute}"),
|
||||
bound_format: js_string!("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 Err(JsNativeError::typ()
|
||||
.with_message("'date' is required, but timeStyle was defined")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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 Err(JsNativeError::typ()
|
||||
.with_message("'time' is required, but dateStyle was defined")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
498
javascript-engine/external/boa/boa_engine/src/builtins/intl/list_format/mod.rs
vendored
Normal file
498
javascript-engine/external/boa/boa_engine/src/builtins/intl/list_format/mod.rs
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use boa_profiler::Profiler;
|
||||
use icu_list::{provider::AndListV1Marker, ListFormatter, ListLength};
|
||||
use icu_locid::Locale;
|
||||
use icu_provider::DataLocale;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use crate::{
|
||||
builtins::{Array, BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
symbol::JsSymbol,
|
||||
Context, JsNativeError, JsResult, JsString, JsValue,
|
||||
};
|
||||
|
||||
use super::{
|
||||
locale::{canonicalize_locale_list, resolve_locale, supported_locales},
|
||||
options::{get_option, get_options_object, IntlOptions, LocaleMatcher},
|
||||
Service,
|
||||
};
|
||||
|
||||
mod options;
|
||||
pub(crate) use options::*;
|
||||
pub struct ListFormat {
|
||||
locale: Locale,
|
||||
typ: ListFormatType,
|
||||
style: ListLength,
|
||||
formatter: ListFormatter,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ListFormat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ListFormat")
|
||||
.field("locale", &self.locale)
|
||||
.field("typ", &self.typ)
|
||||
.field("style", &self.style)
|
||||
.field("formatter", &"ListFormatter")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for ListFormat {
|
||||
type LangMarker = AndListV1Marker;
|
||||
|
||||
type LocaleOptions = ();
|
||||
}
|
||||
|
||||
impl BuiltIn for ListFormat {
|
||||
const NAME: &'static str = "ListFormat";
|
||||
|
||||
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().list_format().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.static_method(Self::supported_locales_of, "supportedLocalesOf", 1)
|
||||
.property(
|
||||
JsSymbol::to_string_tag(),
|
||||
"Intl.ListFormat",
|
||||
Attribute::CONFIGURABLE,
|
||||
)
|
||||
.method(Self::format, "format", 1)
|
||||
.method(Self::format_to_parts, "formatToParts", 1)
|
||||
.method(Self::resolved_options, "resolvedOptions", 0)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl ListFormat {
|
||||
pub(crate) const LENGTH: usize = 0;
|
||||
|
||||
/// Constructor [`Intl.ListFormat ( [ locales [ , options ] ] )`][spec].
|
||||
///
|
||||
/// Constructor for `ListFormat` objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-Intl.ListFormat
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat
|
||||
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 Err(JsNativeError::typ()
|
||||
.with_message("cannot call `Intl.ListFormat` constructor without `new`")
|
||||
.into());
|
||||
}
|
||||
|
||||
let locales = args.get_or_undefined(0);
|
||||
let options = args.get_or_undefined(1);
|
||||
|
||||
// 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
|
||||
let requested_locales = canonicalize_locale_list(locales, context)?;
|
||||
|
||||
// 4. Set options to ? GetOptionsObject(options).
|
||||
let options = get_options_object(options)?;
|
||||
|
||||
// 5. Let opt be a new Record.
|
||||
// 6. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
|
||||
let matcher = get_option::<LocaleMatcher>(&options, "localeMatcher", false, context)?
|
||||
.unwrap_or_default();
|
||||
|
||||
// 7. Set opt.[[localeMatcher]] to matcher.
|
||||
// 8. Let localeData be %ListFormat%.[[LocaleData]].
|
||||
// 9. Let r be ResolveLocale(%ListFormat%.[[AvailableLocales]], requestedLocales, opt, %ListFormat%.[[RelevantExtensionKeys]], localeData).
|
||||
// 10. Set listFormat.[[Locale]] to r.[[locale]].
|
||||
let locale = resolve_locale::<Self>(
|
||||
&requested_locales,
|
||||
&mut IntlOptions {
|
||||
matcher,
|
||||
..Default::default()
|
||||
},
|
||||
context.icu(),
|
||||
);
|
||||
|
||||
// 11. Let type be ? GetOption(options, "type", string, « "conjunction", "disjunction", "unit" », "conjunction").
|
||||
// 12. Set listFormat.[[Type]] to type.
|
||||
let typ =
|
||||
get_option::<ListFormatType>(&options, "type", false, context)?.unwrap_or_default();
|
||||
|
||||
// 13. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow" », "long").
|
||||
// 14. Set listFormat.[[Style]] to style.
|
||||
let style = get_option::<ListLength>(&options, "style", false, context)?
|
||||
.unwrap_or(ListLength::Wide);
|
||||
|
||||
// 15. Let dataLocale be r.[[dataLocale]].
|
||||
// 16. Let dataLocaleData be localeData.[[<dataLocale>]].
|
||||
// 17. Let dataLocaleTypes be dataLocaleData.[[<type>]].
|
||||
// 18. Set listFormat.[[Templates]] to dataLocaleTypes.[[<style>]].
|
||||
|
||||
// 2. Let listFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%ListFormat.prototype%", « [[InitializedListFormat]], [[Locale]], [[Type]], [[Style]], [[Templates]] »).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::list_format, context)?;
|
||||
let list_format = JsObject::from_proto_and_data(
|
||||
prototype,
|
||||
ObjectData::list_format(ListFormat {
|
||||
formatter: context
|
||||
.icu()
|
||||
.provider()
|
||||
.try_new_list_formatter(&DataLocale::from(&locale), typ, style)
|
||||
.map_err(|e| JsNativeError::typ().with_message(e.to_string()))?,
|
||||
locale,
|
||||
typ,
|
||||
style,
|
||||
}),
|
||||
);
|
||||
|
||||
// 19. Return listFormat.
|
||||
Ok(list_format.into())
|
||||
}
|
||||
|
||||
/// [`Intl.ListFormat.supportedLocalesOf ( locales [ , options ] )`][spec].
|
||||
///
|
||||
/// Returns an array containing those of the provided locales that are supported in list
|
||||
/// formatting without having to fall back to the runtime's default locale.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-Intl.ListFormat.supportedLocalesOf
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/supportedLocalesOf
|
||||
fn supported_locales_of(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
let locales = args.get_or_undefined(0);
|
||||
let options = args.get_or_undefined(1);
|
||||
|
||||
// 1. Let availableLocales be %ListFormat%.[[AvailableLocales]].
|
||||
// 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
|
||||
let requested_locales = canonicalize_locale_list(locales, context)?;
|
||||
|
||||
// 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
|
||||
supported_locales::<<Self as Service>::LangMarker>(&requested_locales, options, context)
|
||||
.map(JsValue::from)
|
||||
}
|
||||
|
||||
/// [`Intl.ListFormat.prototype.format ( list )`][spec].
|
||||
///
|
||||
/// Returns a language-specific formatted string representing the elements of the list.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-Intl.ListFormat.prototype.format
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/format
|
||||
fn format(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
|
||||
// 1. Let lf be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(lf, [[InitializedListFormat]]).
|
||||
let lf = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`format` can only be called on a `ListFormat` object")
|
||||
})?;
|
||||
let lf = lf.as_list_format().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`format` can only be called on a `ListFormat` object")
|
||||
})?;
|
||||
|
||||
// 3. Let stringList be ? StringListFromIterable(list).
|
||||
// TODO: support for UTF-16 unpaired surrogates formatting
|
||||
let strings = string_list_from_iterable(args.get_or_undefined(0), context)?;
|
||||
|
||||
// 4. Return ! FormatList(lf, stringList).
|
||||
Ok(lf
|
||||
.formatter
|
||||
.format_to_string(strings.into_iter().map(|s| s.to_std_string_escaped()))
|
||||
.into())
|
||||
}
|
||||
|
||||
/// [`Intl.ListFormat.prototype.formatToParts ( list )`][spec].
|
||||
///
|
||||
/// Returns a language-specific formatted string representing the elements of the list.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-Intl.ListFormat.prototype.formatToParts
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/formatToParts
|
||||
fn format_to_parts(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// TODO: maybe try to move this into icu4x?
|
||||
use writeable::{PartsWrite, Writeable};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Part {
|
||||
Literal(String),
|
||||
Element(String),
|
||||
}
|
||||
|
||||
impl Part {
|
||||
const fn typ(&self) -> &'static str {
|
||||
match self {
|
||||
Part::Literal(_) => "literal",
|
||||
Part::Element(_) => "element",
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_const_for_fn)]
|
||||
fn value(self) -> String {
|
||||
match self {
|
||||
Part::Literal(s) | Part::Element(s) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct WriteString(String);
|
||||
|
||||
impl Write for WriteString {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
self.0.write_str(s)
|
||||
}
|
||||
|
||||
fn write_char(&mut self, c: char) -> std::fmt::Result {
|
||||
self.0.write_char(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartsWrite for WriteString {
|
||||
type SubPartsWrite = WriteString;
|
||||
|
||||
fn with_part(
|
||||
&mut self,
|
||||
_part: writeable::Part,
|
||||
mut f: impl FnMut(&mut Self::SubPartsWrite) -> std::fmt::Result,
|
||||
) -> std::fmt::Result {
|
||||
f(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PartsCollector(Vec<Part>);
|
||||
|
||||
impl Write for PartsCollector {
|
||||
fn write_str(&mut self, _: &str) -> std::fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartsWrite for PartsCollector {
|
||||
type SubPartsWrite = WriteString;
|
||||
|
||||
fn with_part(
|
||||
&mut self,
|
||||
part: writeable::Part,
|
||||
mut f: impl FnMut(&mut Self::SubPartsWrite) -> core::fmt::Result,
|
||||
) -> core::fmt::Result {
|
||||
assert!(part.category == "list");
|
||||
let mut string = WriteString(String::new());
|
||||
f(&mut string)?;
|
||||
if !string.0.is_empty() {
|
||||
match part.value {
|
||||
"element" => self.0.push(Part::Element(string.0)),
|
||||
"literal" => self.0.push(Part::Literal(string.0)),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Let lf be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(lf, [[InitializedListFormat]]).
|
||||
let lf = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`formatToParts` can only be called on a `ListFormat` object")
|
||||
})?;
|
||||
let lf = lf.as_list_format().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`formatToParts` can only be called on a `ListFormat` object")
|
||||
})?;
|
||||
|
||||
// 3. Let stringList be ? StringListFromIterable(list).
|
||||
// TODO: support for UTF-16 unpaired surrogates formatting
|
||||
let strings = string_list_from_iterable(args.get_or_undefined(0), context)?
|
||||
.into_iter()
|
||||
.map(|s| s.to_std_string_escaped());
|
||||
|
||||
// 4. Return ! FormatListToParts(lf, stringList).
|
||||
|
||||
// Abstract operation `FormatListToParts ( listFormat, list )`
|
||||
// https://tc39.es/ecma402/#sec-formatlisttoparts
|
||||
|
||||
// 1. Let parts be ! CreatePartsFromList(listFormat, list).
|
||||
let mut parts = PartsCollector(Vec::new());
|
||||
lf.formatter
|
||||
.format(strings)
|
||||
.write_to_parts(&mut parts)
|
||||
.map_err(|e| JsNativeError::typ().with_message(e.to_string()))?;
|
||||
|
||||
// 2. Let result be ! ArrayCreate(0).
|
||||
let result = Array::array_create(0, None, context)
|
||||
.expect("creating an empty array with default proto must not fail");
|
||||
|
||||
// 3. Let n be 0.
|
||||
// 4. For each Record { [[Type]], [[Value]] } part in parts, do
|
||||
for (n, part) in parts.0.into_iter().enumerate() {
|
||||
// a. Let O be OrdinaryObjectCreate(%Object.prototype%).
|
||||
let o = JsObject::from_proto_and_data(
|
||||
context.intrinsics().constructors().object().prototype(),
|
||||
ObjectData::ordinary(),
|
||||
);
|
||||
|
||||
// b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
|
||||
o.create_data_property_or_throw("type", part.typ(), context)
|
||||
.expect("operation must not fail per the spec");
|
||||
|
||||
// c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
|
||||
o.create_data_property_or_throw("value", part.value(), context)
|
||||
.expect("operation must not fail per the spec");
|
||||
|
||||
// d. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O).
|
||||
result
|
||||
.create_data_property_or_throw(n, o, context)
|
||||
.expect("operation must not fail per the spec");
|
||||
|
||||
// e. Increment n by 1.
|
||||
}
|
||||
|
||||
// 5. Return result.
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
/// [`Intl.ListFormat.prototype.resolvedOptions ( )`][spec].
|
||||
///
|
||||
/// Returns a new object with properties reflecting the locale and style formatting options
|
||||
/// computed during the construction of the current `Intl.ListFormat` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-Intl.ListFormat.prototype.resolvedoptions
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat/resolvedOptions
|
||||
fn resolved_options(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let lf be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(lf, [[InitializedListFormat]]).
|
||||
let lf = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`resolvedOptions` can only be called on a `ListFormat` object")
|
||||
})?;
|
||||
let lf = lf.as_list_format().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`resolvedOptions` can only be called on a `ListFormat` object")
|
||||
})?;
|
||||
|
||||
// 3. Let options be OrdinaryObjectCreate(%Object.prototype%).
|
||||
let options = JsObject::from_proto_and_data(
|
||||
context.intrinsics().constructors().object().prototype(),
|
||||
ObjectData::ordinary(),
|
||||
);
|
||||
|
||||
// 4. For each row of Table 11, except the header row, in table order, do
|
||||
// a. Let p be the Property value of the current row.
|
||||
// b. Let v be the value of lf's internal slot whose name is the Internal Slot value of the current row.
|
||||
// c. Assert: v is not undefined.
|
||||
// d. Perform ! CreateDataPropertyOrThrow(options, p, v).
|
||||
options
|
||||
.create_data_property_or_throw("locale", lf.locale.to_string(), context)
|
||||
.expect("operation must not fail per the spec");
|
||||
options
|
||||
.create_data_property_or_throw(
|
||||
"type",
|
||||
match lf.typ {
|
||||
ListFormatType::Conjunction => "conjunction",
|
||||
ListFormatType::Disjunction => "disjunction",
|
||||
ListFormatType::Unit => "unit",
|
||||
},
|
||||
context,
|
||||
)
|
||||
.expect("operation must not fail per the spec");
|
||||
options
|
||||
.create_data_property_or_throw(
|
||||
"style",
|
||||
match lf.style {
|
||||
ListLength::Wide => "long",
|
||||
ListLength::Short => "short",
|
||||
ListLength::Narrow => "narrow",
|
||||
_ => unreachable!(),
|
||||
},
|
||||
context,
|
||||
)
|
||||
.expect("operation must not fail per the spec");
|
||||
|
||||
// 5. Return options.
|
||||
Ok(options.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract operation [`StringListFromIterable ( iterable )`][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-createstringlistfromiterable
|
||||
fn string_list_from_iterable(
|
||||
iterable: &JsValue,
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<Vec<JsString>> {
|
||||
// 1. If iterable is undefined, then
|
||||
if iterable.is_undefined() {
|
||||
// a. Return a new empty List.
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
// 2. Let iteratorRecord be ? GetIterator(iterable).
|
||||
let iterator = iterable.get_iterator(context, None, None)?;
|
||||
|
||||
// 3. Let list be a new empty List.
|
||||
let mut list = 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
|
||||
while let Some(item) = iterator.step(context)? {
|
||||
// i. Let nextValue be ? IteratorValue(next).
|
||||
let item = item.value(context)?;
|
||||
// ii. If Type(nextValue) is not String, then
|
||||
let Some(s) = item.as_string().cloned() else {
|
||||
// 1. Let error be ThrowCompletion(a newly created TypeError object).
|
||||
// 2. Return ? IteratorClose(iteratorRecord, error).
|
||||
return Err(iterator
|
||||
.close(
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("StringListFromIterable: can only format strings into a list")
|
||||
.into()),
|
||||
context,
|
||||
)
|
||||
.expect_err("Should return the provided error"));
|
||||
};
|
||||
|
||||
// iii. Append nextValue to the end of the List list.
|
||||
list.push(s);
|
||||
}
|
||||
|
||||
// 6. Return list.
|
||||
Ok(list)
|
||||
}
|
||||
53
javascript-engine/external/boa/boa_engine/src/builtins/intl/list_format/options.rs
vendored
Normal file
53
javascript-engine/external/boa/boa_engine/src/builtins/intl/list_format/options.rs
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use icu_list::ListLength;
|
||||
|
||||
use crate::{
|
||||
builtins::intl::options::{OptionType, OptionTypeParsable},
|
||||
Context, JsNativeError, JsResult, JsValue,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub(crate) enum ListFormatType {
|
||||
#[default]
|
||||
Conjunction,
|
||||
Disjunction,
|
||||
Unit,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParseListFormatTypeError;
|
||||
|
||||
impl std::fmt::Display for ParseListFormatTypeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("provided string was not `conjunction`, `disjunction` or `unit`")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ListFormatType {
|
||||
type Err = ParseListFormatTypeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"conjunction" => Ok(Self::Conjunction),
|
||||
"disjunction" => Ok(Self::Disjunction),
|
||||
"unit" => Ok(Self::Unit),
|
||||
_ => Err(ParseListFormatTypeError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionTypeParsable for ListFormatType {}
|
||||
|
||||
impl OptionType for ListLength {
|
||||
fn from_value(value: JsValue, context: &mut Context<'_>) -> JsResult<Self> {
|
||||
match value.to_string(context)?.to_std_string_escaped().as_str() {
|
||||
"long" => Ok(Self::Wide),
|
||||
"short" => Ok(Self::Short),
|
||||
"narrow" => Ok(Self::Narrow),
|
||||
_ => Err(JsNativeError::range()
|
||||
.with_message("provided string was not `long`, `short` or `narrow`")
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
769
javascript-engine/external/boa/boa_engine/src/builtins/intl/locale/mod.rs
vendored
Normal file
769
javascript-engine/external/boa/boa_engine/src/builtins/intl/locale/mod.rs
vendored
Normal file
@@ -0,0 +1,769 @@
|
||||
use boa_profiler::Profiler;
|
||||
use icu_collator::CaseFirst;
|
||||
use icu_datetime::options::preferences::HourCycle;
|
||||
use icu_locid::{
|
||||
extensions::unicode::Value,
|
||||
extensions_unicode_key as key, extensions_unicode_value as value,
|
||||
subtags::{Language, Region, Script},
|
||||
};
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
mod utils;
|
||||
pub(crate) use utils::*;
|
||||
|
||||
mod options;
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
js_string,
|
||||
native_function::NativeFunction,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder,
|
||||
FunctionObjectBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
symbol::JsSymbol,
|
||||
Context, JsNativeError, JsResult, JsString, JsValue,
|
||||
};
|
||||
|
||||
use super::options::{coerce_options_to_object, get_option};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Locale;
|
||||
|
||||
impl BuiltIn for Locale {
|
||||
const NAME: &'static str = "Locale";
|
||||
|
||||
fn init(context: &mut Context<'_>) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let base_name =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::base_name))
|
||||
.name("get baseName")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let calendar =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::calendar))
|
||||
.name("get calendar")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let case_first =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::case_first))
|
||||
.name("get caseFirst")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let collation =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::collation))
|
||||
.name("get collation")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let hour_cycle =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::hour_cycle))
|
||||
.name("get hourCycle")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let numeric =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::numeric))
|
||||
.name("get numeric")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let numbering_system = FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_fn_ptr(Self::numbering_system),
|
||||
)
|
||||
.name("get numberingSystem")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let language =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::language))
|
||||
.name("get language")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let script = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::script))
|
||||
.name("get script")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let region = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::region))
|
||||
.name("get region")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().locale().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.property(
|
||||
JsSymbol::to_string_tag(),
|
||||
"Intl.Locale",
|
||||
Attribute::CONFIGURABLE,
|
||||
)
|
||||
.method(Self::maximize, "maximize", 0)
|
||||
.method(Self::minimize, "minimize", 0)
|
||||
.method(Self::to_string, "toString", 0)
|
||||
.accessor("baseName", Some(base_name), None, Attribute::CONFIGURABLE)
|
||||
.accessor("calendar", Some(calendar), None, Attribute::CONFIGURABLE)
|
||||
.accessor("caseFirst", Some(case_first), None, Attribute::CONFIGURABLE)
|
||||
.accessor("collation", Some(collation), None, Attribute::CONFIGURABLE)
|
||||
.accessor("hourCycle", Some(hour_cycle), None, Attribute::CONFIGURABLE)
|
||||
.accessor("numeric", Some(numeric), None, Attribute::CONFIGURABLE)
|
||||
.accessor(
|
||||
"numberingSystem",
|
||||
Some(numbering_system),
|
||||
None,
|
||||
Attribute::CONFIGURABLE,
|
||||
)
|
||||
.accessor("language", Some(language), None, Attribute::CONFIGURABLE)
|
||||
.accessor("script", Some(script), None, Attribute::CONFIGURABLE)
|
||||
.accessor("region", Some(region), None, Attribute::CONFIGURABLE)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Locale {
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Constructor [`Intl.Locale ( tag [ , options ] )`][spec].
|
||||
///
|
||||
/// Constructor for `Locale` objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-Intl.Locale
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale
|
||||
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 Err(JsNativeError::typ()
|
||||
.with_message("cannot call `Intl.Locale` constructor without `new`")
|
||||
.into());
|
||||
}
|
||||
|
||||
let tag = args.get_or_undefined(0);
|
||||
let options = args.get_or_undefined(1);
|
||||
|
||||
// 2. Let relevantExtensionKeys be %Locale%.[[RelevantExtensionKeys]].
|
||||
// 3. Let internalSlotsList be « [[InitializedLocale]], [[Locale]], [[Calendar]], [[Collation]], [[HourCycle]], [[NumberingSystem]] ».
|
||||
// 4. If relevantExtensionKeys contains "kf", then
|
||||
// a. Append [[CaseFirst]] as the last element of internalSlotsList.
|
||||
// 5. If relevantExtensionKeys contains "kn", then
|
||||
// a. Append [[Numeric]] as the last element of internalSlotsList.
|
||||
|
||||
// 7. If Type(tag) is not String or Object, throw a TypeError exception.
|
||||
if !(tag.is_object() || tag.is_string()) {
|
||||
return Err(JsNativeError::typ()
|
||||
.with_message("Intl.Locale: `tag` should be a String or Object")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 8. If Type(tag) is Object and tag has an [[InitializedLocale]] internal slot, then
|
||||
|
||||
let mut tag = if let Some(tag) = tag
|
||||
.as_object()
|
||||
.and_then(|obj| obj.borrow().as_locale().cloned())
|
||||
{
|
||||
// a. Let tag be tag.[[Locale]].
|
||||
tag
|
||||
}
|
||||
// 9. Else,
|
||||
else {
|
||||
// a. Let tag be ? ToString(tag).
|
||||
tag.to_string(context)?
|
||||
.to_std_string_escaped()
|
||||
.parse()
|
||||
.map_err(|_| {
|
||||
JsNativeError::range()
|
||||
.with_message("Intl.Locale: `tag` is not a structurally valid language tag")
|
||||
})?
|
||||
};
|
||||
|
||||
// 10. Set options to ? CoerceOptionsToObject(options).
|
||||
let options = &coerce_options_to_object(options, context)?;
|
||||
|
||||
// 11. Set tag to ? ApplyOptionsToTag(tag, options).
|
||||
|
||||
// Abstract operation [`ApplyOptionsToTag ( tag, options )`][https://tc39.es/ecma402/#sec-apply-options-to-tag]
|
||||
{
|
||||
// 1. Assert: Type(tag) is String.
|
||||
// 2. Assert: Type(options) is Object.
|
||||
// 3. If ! IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
|
||||
// 4. Let language be ? GetOption(options, "language", string, empty, undefined).
|
||||
// 5. If language is not undefined, then
|
||||
let language = get_option::<JsString>(options, "language", false, context)?
|
||||
// a. If language does not match the unicode_language_subtag production, throw a RangeError exception.
|
||||
.map(|s| s.to_std_string_escaped().parse::<Language>())
|
||||
.transpose()
|
||||
.map_err(|e| JsNativeError::range().with_message(e.to_string()))?;
|
||||
|
||||
// 6. Let script be ? GetOption(options, "script", string, empty, undefined).
|
||||
// 7. If script is not undefined, then
|
||||
let script = get_option::<JsString>(options, "script", false, context)?
|
||||
.map(|s| s.to_std_string_escaped().parse::<Script>())
|
||||
.transpose()
|
||||
// a. If script does not match the unicode_script_subtag production, throw a RangeError exception.
|
||||
.map_err(|e| JsNativeError::range().with_message(e.to_string()))?;
|
||||
|
||||
// 8. Let region be ? GetOption(options, "region", string, empty, undefined).
|
||||
// 9. If region is not undefined, then
|
||||
let region = get_option::<JsString>(options, "region", false, context)?
|
||||
.map(|s| s.to_std_string_escaped().parse::<Region>())
|
||||
.transpose()
|
||||
// a. If region does not match the unicode_region_subtag production, throw a RangeError exception.
|
||||
.map_err(|e| JsNativeError::range().with_message(e.to_string()))?;
|
||||
|
||||
// 10. Set tag to ! CanonicalizeUnicodeLocaleId(tag).
|
||||
context.icu().locale_canonicalizer().canonicalize(&mut tag);
|
||||
|
||||
// Skipping some boilerplate since this is easier to do using the `Locale` type, but putting the
|
||||
// spec for completion.
|
||||
// 11. Assert: tag matches the unicode_locale_id production.
|
||||
// 12. Let languageId be the substring of tag corresponding to the unicode_language_id production.
|
||||
// 13. If language is not undefined, then
|
||||
// a. Set languageId to languageId with the substring corresponding to the unicode_language_subtag production replaced by the string language.
|
||||
// 14. If script is not undefined, then
|
||||
// a. If languageId does not contain a unicode_script_subtag production, then
|
||||
// i. Set languageId to the string-concatenation of the unicode_language_subtag production of languageId, "-", script, and the rest of languageId.
|
||||
// b. Else,
|
||||
// i. Set languageId to languageId with the substring corresponding to the unicode_script_subtag production replaced by the string script.
|
||||
// 15. If region is not undefined, then
|
||||
// a. If languageId does not contain a unicode_region_subtag production, then
|
||||
// i. Set languageId to the string-concatenation of the unicode_language_subtag production of languageId, the substring corresponding to "-"` and the `unicode_script_subtag` production if present, `"-", region, and the rest of languageId.
|
||||
// b. Else,
|
||||
// i. Set languageId to languageId with the substring corresponding to the unicode_region_subtag production replaced by the string region.
|
||||
// 16. Set tag to tag with the substring corresponding to the unicode_language_id production replaced by the string languageId.
|
||||
|
||||
if let Some(language) = language {
|
||||
tag.id.language = language;
|
||||
}
|
||||
if let Some(script) = script {
|
||||
tag.id.script = Some(script);
|
||||
}
|
||||
if let Some(region) = region {
|
||||
tag.id.region = Some(region);
|
||||
}
|
||||
|
||||
// 17. Return ! CanonicalizeUnicodeLocaleId(tag).
|
||||
context.icu().locale_canonicalizer().canonicalize(&mut tag);
|
||||
}
|
||||
|
||||
// 12. Let opt be a new Record.
|
||||
// 13. Let calendar be ? GetOption(options, "calendar", string, empty, undefined).
|
||||
// 14. If calendar is not undefined, then
|
||||
// 15. Set opt.[[ca]] to calendar.
|
||||
// a. If calendar does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
|
||||
let ca = get_option::<Value>(options, "calendar", false, context)?;
|
||||
|
||||
// 16. Let collation be ? GetOption(options, "collation", string, empty, undefined).
|
||||
// 17. If collation is not undefined, then
|
||||
// 18. Set opt.[[co]] to collation.
|
||||
// a. If collation does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
|
||||
let co = get_option::<Value>(options, "collation", false, context)?;
|
||||
|
||||
// 19. Let hc be ? GetOption(options, "hourCycle", string, « "h11", "h12", "h23", "h24" », undefined).
|
||||
// 20. Set opt.[[hc]] to hc.
|
||||
let hc =
|
||||
get_option::<HourCycle>(options, "hourCycle", false, context)?.map(|hc| match hc {
|
||||
HourCycle::H24 => value!("h24"),
|
||||
HourCycle::H23 => value!("h23"),
|
||||
HourCycle::H12 => value!("h12"),
|
||||
HourCycle::H11 => value!("h11"),
|
||||
});
|
||||
|
||||
// 21. Let kf be ? GetOption(options, "caseFirst", string, « "upper", "lower", "false" », undefined).
|
||||
// 22. Set opt.[[kf]] to kf.
|
||||
let kf =
|
||||
get_option::<CaseFirst>(options, "caseFirst", false, context)?.map(|kf| match kf {
|
||||
CaseFirst::UpperFirst => value!("upper"),
|
||||
CaseFirst::LowerFirst => value!("lower"),
|
||||
CaseFirst::Off => value!("false"),
|
||||
_ => unreachable!(),
|
||||
});
|
||||
|
||||
// 23. Let kn be ? GetOption(options, "numeric", boolean, empty, undefined).
|
||||
// 24. If kn is not undefined, set kn to ! ToString(kn).
|
||||
// 25. Set opt.[[kn]] to kn.
|
||||
let kn = get_option::<bool>(options, "numeric", false, context)?.map(|b| {
|
||||
if b {
|
||||
value!("true")
|
||||
} else {
|
||||
value!("false")
|
||||
}
|
||||
});
|
||||
|
||||
// 26. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined).
|
||||
// 27. If numberingSystem is not undefined, then
|
||||
// 28. Set opt.[[nu]] to numberingSystem.
|
||||
// a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
|
||||
let nu = get_option::<Value>(options, "numberingSystem", false, context)?;
|
||||
|
||||
// 29. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys).
|
||||
// 30. Set locale.[[Locale]] to r.[[locale]].
|
||||
if let Some(ca) = ca {
|
||||
// 31. Set locale.[[Calendar]] to r.[[ca]].
|
||||
tag.extensions.unicode.keywords.set(key!("ca"), ca);
|
||||
}
|
||||
if let Some(co) = co {
|
||||
// 32. Set locale.[[Collation]] to r.[[co]].
|
||||
tag.extensions.unicode.keywords.set(key!("co"), co);
|
||||
}
|
||||
if let Some(hc) = hc {
|
||||
// 33. Set locale.[[HourCycle]] to r.[[hc]].
|
||||
tag.extensions.unicode.keywords.set(key!("hc"), hc);
|
||||
}
|
||||
if let Some(kf) = kf {
|
||||
// 34. If relevantExtensionKeys contains "kf", then
|
||||
// a. Set locale.[[CaseFirst]] to r.[[kf]].
|
||||
tag.extensions.unicode.keywords.set(key!("kf"), kf);
|
||||
}
|
||||
if let Some(kn) = kn {
|
||||
// 35. If relevantExtensionKeys contains "kn", then
|
||||
// a. If SameValue(r.[[kn]], "true") is true or r.[[kn]] is the empty String, then
|
||||
// i. Set locale.[[Numeric]] to true.
|
||||
// b. Else,
|
||||
// i. Set locale.[[Numeric]] to false.
|
||||
tag.extensions.unicode.keywords.set(key!("kn"), kn);
|
||||
}
|
||||
if let Some(nu) = nu {
|
||||
// 36. Set locale.[[NumberingSystem]] to r.[[nu]].
|
||||
tag.extensions.unicode.keywords.set(key!("nu"), nu);
|
||||
}
|
||||
|
||||
context.icu().locale_canonicalizer().canonicalize(&mut tag);
|
||||
|
||||
// 6. Let locale be ? OrdinaryCreateFromConstructor(NewTarget, "%Locale.prototype%", internalSlotsList).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::locale, context)?;
|
||||
let locale = JsObject::from_proto_and_data(prototype, ObjectData::locale(tag));
|
||||
|
||||
// 37. Return locale.
|
||||
Ok(locale.into())
|
||||
}
|
||||
|
||||
/// [`Intl.Locale.prototype.maximize ( )`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.maximize
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/maximize
|
||||
pub(crate) fn maximize(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ().with_message("`maximize` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let mut loc = loc
|
||||
.as_locale()
|
||||
.ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`maximize` can only be called on a `Locale` object")
|
||||
})?
|
||||
.clone();
|
||||
|
||||
// 3. Let maximal be the result of the Add Likely Subtags algorithm applied to loc.[[Locale]]. If an error is signaled, set maximal to loc.[[Locale]].
|
||||
context.icu().locale_expander().maximize(&mut loc);
|
||||
|
||||
// 4. Return ! Construct(%Locale%, maximal).
|
||||
let prototype = context.intrinsics().constructors().locale().prototype();
|
||||
Ok(JsObject::from_proto_and_data(prototype, ObjectData::locale(loc)).into())
|
||||
}
|
||||
|
||||
/// [`Intl.Locale.prototype.minimize ( )`][spec]
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.minimize
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/minimize
|
||||
pub(crate) fn minimize(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ().with_message("`minimize` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let mut loc = loc
|
||||
.as_locale()
|
||||
.ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`minimize` can only be called on a `Locale` object")
|
||||
})?
|
||||
.clone();
|
||||
|
||||
// 3. Let minimal be the result of the Remove Likely Subtags algorithm applied to loc.[[Locale]]. If an error is signaled, set minimal to loc.[[Locale]].
|
||||
context.icu().locale_expander().minimize(&mut loc);
|
||||
|
||||
// 4. Return ! Construct(%Locale%, minimal).
|
||||
let prototype = context.intrinsics().constructors().locale().prototype();
|
||||
Ok(JsObject::from_proto_and_data(prototype, ObjectData::locale(loc)).into())
|
||||
}
|
||||
|
||||
/// [`Intl.Locale.prototype.toString ( )`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.toString
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/toString
|
||||
pub(crate) fn to_string(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ().with_message("`toString` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let loc = loc.as_locale().ok_or_else(|| {
|
||||
JsNativeError::typ().with_message("`toString` can only be called on a `Locale` object")
|
||||
})?;
|
||||
|
||||
// 3. Return loc.[[Locale]].
|
||||
Ok(js_string!(loc.to_string()).into())
|
||||
}
|
||||
|
||||
/// [`get Intl.Locale.prototype.baseName`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/baseName
|
||||
/// [mdn]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.baseName
|
||||
pub(crate) fn base_name(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get baseName` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let loc = loc.as_locale().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get baseName` can only be called on a `Locale` object")
|
||||
})?;
|
||||
|
||||
// 3. Let locale be loc.[[Locale]].
|
||||
// 4. Return the substring of locale corresponding to the unicode_language_id production.
|
||||
Ok(js_string!(loc.id.to_string()).into())
|
||||
}
|
||||
|
||||
/// [`get Intl.Locale.prototype.calendar`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar
|
||||
/// [mdn]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.calendar
|
||||
pub(crate) fn calendar(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get calendar` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let loc = loc.as_locale().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get calendar` can only be called on a `Locale` object")
|
||||
})?;
|
||||
|
||||
// 3. Return loc.[[Calendar]].
|
||||
Ok(loc
|
||||
.extensions
|
||||
.unicode
|
||||
.keywords
|
||||
.get(&key!("ca"))
|
||||
.map(|v| js_string!(v.to_string()).into())
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// [`get Intl.Locale.prototype.caseFirst`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar
|
||||
/// [mdn]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.calendar
|
||||
pub(crate) fn case_first(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get caseFirst` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let loc = loc.as_locale().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get caseFirst` can only be called on a `Locale` object")
|
||||
})?;
|
||||
|
||||
// 3. Return loc.[[CaseFirst]].
|
||||
Ok(loc
|
||||
.extensions
|
||||
.unicode
|
||||
.keywords
|
||||
.get(&key!("kf"))
|
||||
.map(|v| js_string!(v.to_string()).into())
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// [`get Intl.Locale.prototype.collation`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/collation
|
||||
/// [mdn]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.collation
|
||||
pub(crate) fn collation(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get collation` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let loc = loc.as_locale().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get collation` can only be called on a `Locale` object")
|
||||
})?;
|
||||
|
||||
// 3. Return loc.[[Collation]].
|
||||
Ok(loc
|
||||
.extensions
|
||||
.unicode
|
||||
.keywords
|
||||
.get(&key!("co"))
|
||||
.map(|v| js_string!(v.to_string()).into())
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// [`get Intl.Locale.prototype.hourCycle`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/hourCycle
|
||||
/// [mdn]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.hourCycle
|
||||
pub(crate) fn hour_cycle(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get hourCycle` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let loc = loc.as_locale().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get hourCycle` can only be called on a `Locale` object")
|
||||
})?;
|
||||
|
||||
// 3. Return loc.[[HourCycle]].
|
||||
Ok(loc
|
||||
.extensions
|
||||
.unicode
|
||||
.keywords
|
||||
.get(&key!("hc"))
|
||||
.map(|v| js_string!(v.to_string()).into())
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// [`get Intl.Locale.prototype.numeric`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/numeric
|
||||
/// [mdn]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.numeric
|
||||
pub(crate) fn numeric(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get numeric` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let loc = loc.as_locale().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get numeric` can only be called on a `Locale` object")
|
||||
})?;
|
||||
|
||||
// 3. Return loc.[[Numeric]].
|
||||
let kn = loc
|
||||
.extensions
|
||||
.unicode
|
||||
.keywords
|
||||
.get(&key!("kn"))
|
||||
.map(Value::as_tinystr_slice);
|
||||
Ok(JsValue::Boolean(match kn {
|
||||
Some([]) => true,
|
||||
Some([kn]) if kn == "true" => true,
|
||||
_ => false,
|
||||
}))
|
||||
}
|
||||
|
||||
/// [`get Intl.Locale.prototype.numberingSystem`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/numeric
|
||||
/// [mdn]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.numeric
|
||||
pub(crate) fn numbering_system(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get numberingSystem` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let loc = loc.as_locale().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get numberingSystem` can only be called on a `Locale` object")
|
||||
})?;
|
||||
|
||||
// 3. Return loc.[[NumberingSystem]].
|
||||
Ok(loc
|
||||
.extensions
|
||||
.unicode
|
||||
.keywords
|
||||
.get(&key!("nu"))
|
||||
.map(|v| js_string!(v.to_string()).into())
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// [`get Intl.Locale.prototype.language`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/language
|
||||
/// [mdn]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.language
|
||||
pub(crate) fn language(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get language` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let loc = loc.as_locale().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get language` can only be called on a `Locale` object")
|
||||
})?;
|
||||
|
||||
// 3. Let locale be loc.[[Locale]].
|
||||
// 4. Assert: locale matches the unicode_locale_id production.
|
||||
// 5. Return the substring of locale corresponding to the unicode_language_subtag production of the unicode_language_id.
|
||||
Ok(js_string!(loc.id.language.to_string()).into())
|
||||
}
|
||||
|
||||
/// [`get Intl.Locale.prototype.script`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/script
|
||||
/// [mdn]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.script
|
||||
pub(crate) fn script(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get script` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let loc = loc.as_locale().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get script` can only be called on a `Locale` object")
|
||||
})?;
|
||||
|
||||
// 3. Let locale be loc.[[Locale]].
|
||||
// 4. Assert: locale matches the unicode_locale_id production.
|
||||
// 5. If the unicode_language_id production of locale does not contain the ["-" unicode_script_subtag] sequence, return undefined.
|
||||
// 6. Return the substring of locale corresponding to the unicode_script_subtag production of the unicode_language_id.
|
||||
Ok(loc
|
||||
.id
|
||||
.script
|
||||
.map(|sc| js_string!(sc.to_string()).into())
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// [`get Intl.Locale.prototype.region`][spec].
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/region
|
||||
/// [mdn]: https://tc39.es/ecma402/#sec-Intl.Locale.prototype.region
|
||||
pub(crate) fn region(this: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
|
||||
// 1. Let loc be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
|
||||
let loc = this.as_object().map(JsObject::borrow).ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get region` can only be called on a `Locale` object")
|
||||
})?;
|
||||
let loc = loc.as_locale().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("`get region` can only be called on a `Locale` object")
|
||||
})?;
|
||||
|
||||
// 3. Let locale be loc.[[Locale]].
|
||||
// 4. Assert: locale matches the unicode_locale_id production.
|
||||
// 5. If the unicode_language_id production of locale does not contain the ["-" unicode_region_subtag] sequence, return undefined.
|
||||
// 6. Return the substring of locale corresponding to the unicode_region_subtag production of the unicode_language_id.
|
||||
Ok(loc
|
||||
.id
|
||||
.region
|
||||
.map(|sc| js_string!(sc.to_string()).into())
|
||||
.unwrap_or_default())
|
||||
}
|
||||
}
|
||||
21
javascript-engine/external/boa/boa_engine/src/builtins/intl/locale/options.rs
vendored
Normal file
21
javascript-engine/external/boa/boa_engine/src/builtins/intl/locale/options.rs
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
use icu_locid::extensions::unicode::Value;
|
||||
|
||||
use crate::{builtins::intl::options::OptionType, Context, JsNativeError};
|
||||
|
||||
impl OptionType for Value {
|
||||
fn from_value(value: crate::JsValue, context: &mut Context<'_>) -> crate::JsResult<Self> {
|
||||
let val = value
|
||||
.to_string(context)?
|
||||
.to_std_string_escaped()
|
||||
.parse::<Value>()
|
||||
.map_err(|e| JsNativeError::range().with_message(e.to_string()))?;
|
||||
|
||||
if val.as_tinystr_slice().is_empty() {
|
||||
return Err(JsNativeError::range()
|
||||
.with_message("Unicode Locale Identifier `type` cannot be empty")
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
121
javascript-engine/external/boa/boa_engine/src/builtins/intl/locale/tests.rs
vendored
Normal file
121
javascript-engine/external/boa/boa_engine/src/builtins/intl/locale/tests.rs
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
use icu_datetime::{
|
||||
options::preferences::HourCycle, pattern::CoarseHourCycle,
|
||||
provider::calendar::TimeLengthsV1Marker,
|
||||
};
|
||||
use icu_locid::{
|
||||
extensions::unicode::Value, extensions_unicode_key as key, extensions_unicode_value as value,
|
||||
locale, Locale,
|
||||
};
|
||||
use icu_plurals::provider::CardinalV1Marker;
|
||||
use icu_provider::{DataLocale, DataProvider, DataRequest, DataRequestMetadata};
|
||||
|
||||
use crate::{
|
||||
builtins::intl::{
|
||||
locale::{best_locale_for_provider, default_locale, resolve_locale},
|
||||
options::{IntlOptions, LocaleMatcher},
|
||||
Service,
|
||||
},
|
||||
context::icu::{BoaProvider, Icu},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestOptions {
|
||||
hc: Option<HourCycle>,
|
||||
}
|
||||
|
||||
struct TestService;
|
||||
|
||||
impl Service for TestService {
|
||||
type LangMarker = CardinalV1Marker;
|
||||
|
||||
type LocaleOptions = TestOptions;
|
||||
|
||||
fn resolve(locale: &mut Locale, options: &mut Self::LocaleOptions, provider: BoaProvider<'_>) {
|
||||
let loc_hc = locale
|
||||
.extensions
|
||||
.unicode
|
||||
.keywords
|
||||
.get(&key!("hc"))
|
||||
.and_then(Value::as_single_subtag)
|
||||
.and_then(|s| match &**s {
|
||||
"h11" => Some(HourCycle::H11),
|
||||
"h12" => Some(HourCycle::H12),
|
||||
"h23" => Some(HourCycle::H23),
|
||||
"h24" => Some(HourCycle::H24),
|
||||
_ => None,
|
||||
});
|
||||
let hc = options.hc.or(loc_hc).unwrap_or_else(|| {
|
||||
let req = DataRequest {
|
||||
locale: &DataLocale::from(&*locale),
|
||||
metadata: DataRequestMetadata::default(),
|
||||
};
|
||||
let preferred = DataProvider::<TimeLengthsV1Marker>::load(&provider, req)
|
||||
.unwrap()
|
||||
.take_payload()
|
||||
.unwrap()
|
||||
.get()
|
||||
.preferred_hour_cycle;
|
||||
match preferred {
|
||||
CoarseHourCycle::H11H12 => HourCycle::H11,
|
||||
CoarseHourCycle::H23H24 => HourCycle::H23,
|
||||
}
|
||||
});
|
||||
let hc_value = match hc {
|
||||
HourCycle::H11 => value!("h11"),
|
||||
HourCycle::H12 => value!("h12"),
|
||||
HourCycle::H23 => value!("h23"),
|
||||
HourCycle::H24 => value!("h24"),
|
||||
};
|
||||
locale.extensions.unicode.keywords.set(key!("hc"), hc_value);
|
||||
options.hc = Some(hc);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locale_resolution() {
|
||||
let provider = boa_icu_provider::buffer();
|
||||
let icu = Icu::new(BoaProvider::Buffer(provider)).unwrap();
|
||||
let mut default = default_locale(icu.locale_canonicalizer());
|
||||
default
|
||||
.extensions
|
||||
.unicode
|
||||
.keywords
|
||||
.set(key!("hc"), value!("h11"));
|
||||
|
||||
// test lookup
|
||||
let mut options = IntlOptions {
|
||||
matcher: LocaleMatcher::Lookup,
|
||||
service_options: TestOptions {
|
||||
hc: Some(HourCycle::H11),
|
||||
},
|
||||
};
|
||||
let locale = resolve_locale::<TestService>(&[], &mut options, &icu);
|
||||
assert_eq!(locale, default);
|
||||
|
||||
// test best fit
|
||||
let mut options = IntlOptions {
|
||||
matcher: LocaleMatcher::BestFit,
|
||||
service_options: TestOptions {
|
||||
hc: Some(HourCycle::H11),
|
||||
},
|
||||
};
|
||||
|
||||
let locale = resolve_locale::<TestService>(&[], &mut options, &icu);
|
||||
let best = best_locale_for_provider::<<TestService as Service>::LangMarker>(
|
||||
default.id.clone(),
|
||||
&icu.provider(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut best = Locale::from(best);
|
||||
best.extensions = locale.extensions.clone();
|
||||
assert_eq!(locale, best);
|
||||
|
||||
// requested: [es-ES]
|
||||
let mut options = IntlOptions {
|
||||
matcher: LocaleMatcher::Lookup,
|
||||
service_options: TestOptions { hc: None },
|
||||
};
|
||||
|
||||
let locale = resolve_locale::<TestService>(&[locale!("es-AR")], &mut options, &icu);
|
||||
assert_eq!(locale, "es-u-hc-h23".parse().unwrap());
|
||||
}
|
||||
608
javascript-engine/external/boa/boa_engine/src/builtins/intl/locale/utils.rs
vendored
Normal file
608
javascript-engine/external/boa/boa_engine/src/builtins/intl/locale/utils.rs
vendored
Normal file
@@ -0,0 +1,608 @@
|
||||
use crate::{
|
||||
builtins::{
|
||||
intl::{
|
||||
options::{coerce_options_to_object, get_option, IntlOptions, LocaleMatcher},
|
||||
Service,
|
||||
},
|
||||
Array,
|
||||
},
|
||||
context::{icu::Icu, BoaProvider},
|
||||
object::JsObject,
|
||||
Context, JsNativeError, JsResult, JsValue,
|
||||
};
|
||||
|
||||
use icu_collator::provider::CollationMetadataV1Marker;
|
||||
use icu_locid::{
|
||||
extensions::unicode::{Key, Value},
|
||||
subtags::Variants,
|
||||
LanguageIdentifier, Locale,
|
||||
};
|
||||
use icu_locid_transform::LocaleCanonicalizer;
|
||||
use icu_provider::{DataLocale, DataProvider, DataRequest, DataRequestMetadata, KeyedDataMarker};
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use tap::TapOptional;
|
||||
|
||||
/// 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
|
||||
pub(crate) fn default_locale(canonicalizer: &LocaleCanonicalizer) -> Locale {
|
||||
sys_locale::get_locale()
|
||||
.and_then(|loc| loc.parse::<Locale>().ok())
|
||||
.tap_some_mut(|loc| {
|
||||
canonicalizer.canonicalize(loc);
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// 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 [`Locale`]s instead 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
|
||||
pub(crate) fn canonicalize_locale_list(
|
||||
locales: &JsValue,
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<Vec<Locale>> {
|
||||
// 1. If locales is undefined, then
|
||||
if locales.is_undefined() {
|
||||
// a. Return a new empty List.
|
||||
return Ok(Vec::default());
|
||||
}
|
||||
|
||||
// 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
|
||||
let o = if locales.is_string()
|
||||
|| locales
|
||||
.as_object()
|
||||
.map_or(false, |o| o.borrow().is_locale())
|
||||
{
|
||||
// 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 Err(JsNativeError::typ()
|
||||
.with_message("locale should be a String or Object")
|
||||
.into());
|
||||
}
|
||||
// iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
|
||||
let mut tag = if let Some(tag) = k_value
|
||||
.as_object()
|
||||
.and_then(|obj| obj.borrow().as_locale().cloned())
|
||||
{
|
||||
// 1. Let tag be kValue.[[Locale]].
|
||||
tag
|
||||
}
|
||||
// iv. Else,
|
||||
else {
|
||||
// 1. Let tag be ? ToString(kValue).
|
||||
k_value
|
||||
.to_string(context)?
|
||||
.to_std_string_escaped()
|
||||
.parse()
|
||||
// v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
|
||||
.map_err(|_| {
|
||||
JsNativeError::range()
|
||||
.with_message("locale is not a structurally valid language tag")
|
||||
})?
|
||||
};
|
||||
|
||||
// vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
|
||||
context.icu().locale_canonicalizer().canonicalize(&mut tag);
|
||||
|
||||
// vii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
|
||||
seen.insert(tag);
|
||||
}
|
||||
// d. Increase k by 1.
|
||||
}
|
||||
|
||||
// 8. Return seen.
|
||||
Ok(seen.into_iter().collect())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// We only work with language identifiers, which have the same semantics
|
||||
/// but are a bit easier to manipulate.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-bestavailablelocale
|
||||
pub(crate) fn best_available_locale<M: KeyedDataMarker>(
|
||||
candidate: LanguageIdentifier,
|
||||
provider: &(impl DataProvider<M> + ?Sized),
|
||||
) -> Option<LanguageIdentifier> {
|
||||
// 1. Let candidate be locale.
|
||||
let mut candidate = candidate.into();
|
||||
// 2. Repeat
|
||||
loop {
|
||||
// a. If availableLocales contains an element equal to candidate, return candidate.
|
||||
// ICU4X requires doing data requests in order to check if a locale
|
||||
// is part of the set of supported locales.
|
||||
let response = DataProvider::<M>::load(
|
||||
provider,
|
||||
DataRequest {
|
||||
locale: &candidate,
|
||||
metadata: DataRequestMetadata::default(),
|
||||
},
|
||||
);
|
||||
|
||||
if let Ok(req) = response {
|
||||
// `metadata.locale` returns None when the provider doesn't have a fallback mechanism,
|
||||
// but supports the required locale. However, if the provider has a fallback mechanism,
|
||||
// this will return `Some(locale)`, where the locale is the used locale after applying
|
||||
// the fallback algorithm, even if the used locale is exactly the same as the required
|
||||
// locale.
|
||||
match req.metadata.locale {
|
||||
Some(loc)
|
||||
if loc == candidate
|
||||
// TODO: ugly hack to accept locales that fallback to "und" in the collator service
|
||||
|| (loc.is_empty() && M::KEY.path() == CollationMetadataV1Marker::KEY.path()) =>
|
||||
{
|
||||
return Some(candidate.into_locale().id)
|
||||
}
|
||||
None => return Some(candidate.into_locale().id),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// b. Let pos be the character index of the last occurrence of "-" (U+002D) within candidate. If that character does not occur, return undefined.
|
||||
// c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, decrease pos by 2.
|
||||
// d. Let candidate be the substring of candidate from position 0, inclusive, to position pos, exclusive.
|
||||
//
|
||||
// Since the definition of `LanguageIdentifier` allows us to manipulate it
|
||||
// without using strings, we can replace these steps by a simpler
|
||||
// algorithm.
|
||||
|
||||
if candidate.has_variants() {
|
||||
let mut variants = candidate
|
||||
.clear_variants()
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
variants.pop();
|
||||
candidate.set_variants(Variants::from_vec_unchecked(variants));
|
||||
} else if candidate.region().is_some() {
|
||||
candidate.set_region(None);
|
||||
} else if candidate.script().is_some() {
|
||||
candidate.set_script(None);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the locale resolved by the `provider` after using the ICU4X fallback
|
||||
/// algorithm with `candidate` (if the provider supports this), or None if the locale is not
|
||||
/// supported.
|
||||
pub(crate) fn best_locale_for_provider<M: KeyedDataMarker>(
|
||||
candidate: LanguageIdentifier,
|
||||
provider: &(impl DataProvider<M> + ?Sized),
|
||||
) -> Option<LanguageIdentifier> {
|
||||
let response = DataProvider::<M>::load(
|
||||
provider,
|
||||
DataRequest {
|
||||
locale: &DataLocale::from(&candidate),
|
||||
metadata: DataRequestMetadata::default(),
|
||||
},
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
if candidate == LanguageIdentifier::UND {
|
||||
return Some(LanguageIdentifier::UND);
|
||||
}
|
||||
|
||||
response
|
||||
.metadata
|
||||
.locale
|
||||
.map(|dl| {
|
||||
// TODO: ugly hack to accept locales that fallback to "und" in the collator service
|
||||
if M::KEY.path() == CollationMetadataV1Marker::KEY.path() && dl.is_empty() {
|
||||
candidate.clone()
|
||||
} else {
|
||||
dl.into_locale().id
|
||||
}
|
||||
})
|
||||
.or(Some(candidate))
|
||||
.filter(|loc| loc != &LanguageIdentifier::UND)
|
||||
}
|
||||
|
||||
/// Abstract operation [`LookupMatcher ( availableLocales, requestedLocales )`][spec]
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This differs a bit from the spec, since we don't have an `[[AvailableLocales]]`
|
||||
/// list to compare with. However, we can do data requests to a [`DataProvider`]
|
||||
/// in order to see if a certain [`Locale`] is supported.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-lookupmatcher
|
||||
fn lookup_matcher<'provider, M: KeyedDataMarker>(
|
||||
requested_locales: &[Locale],
|
||||
icu: &Icu<'provider>,
|
||||
) -> Locale
|
||||
where
|
||||
BoaProvider<'provider>: DataProvider<M>,
|
||||
{
|
||||
// 1. Let result be a new Record.
|
||||
// 2. For each element locale of requestedLocales, do
|
||||
for locale in requested_locales {
|
||||
// a. Let noExtensionsLocale be the String value that is locale with any Unicode locale
|
||||
// extension sequences removed.
|
||||
let mut locale = locale.clone();
|
||||
let id = std::mem::take(&mut locale.id);
|
||||
|
||||
// b. Let availableLocale be ! BestAvailableLocale(availableLocales, noExtensionsLocale).
|
||||
let available_locale = best_available_locale::<M>(id, &icu.provider());
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
locale.id = available_locale;
|
||||
|
||||
// iii. Return result.
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Let defLocale be ! DefaultLocale().
|
||||
// 4. Set result.[[locale]] to defLocale.
|
||||
// 5. Return result.
|
||||
default_locale(icu.locale_canonicalizer())
|
||||
}
|
||||
|
||||
/// Abstract operation [`BestFitMatcher ( availableLocales, requestedLocales )`][spec]
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-bestfitmatcher
|
||||
fn best_fit_matcher<'provider, M: KeyedDataMarker>(
|
||||
requested_locales: &[Locale],
|
||||
icu: &Icu<'provider>,
|
||||
) -> Locale
|
||||
where
|
||||
BoaProvider<'provider>: DataProvider<M>,
|
||||
{
|
||||
for mut locale in requested_locales
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(std::iter::once_with(|| {
|
||||
default_locale(icu.locale_canonicalizer())
|
||||
}))
|
||||
{
|
||||
let id = std::mem::take(&mut locale.id);
|
||||
|
||||
if let Some(available) = best_locale_for_provider(id, &icu.provider()) {
|
||||
locale.id = available;
|
||||
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
Locale::default()
|
||||
}
|
||||
|
||||
/// 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
|
||||
pub(in crate::builtins::intl) fn resolve_locale<'provider, S>(
|
||||
requested_locales: &[Locale],
|
||||
options: &mut IntlOptions<S::LocaleOptions>,
|
||||
icu: &Icu<'provider>,
|
||||
) -> Locale
|
||||
where
|
||||
S: Service,
|
||||
BoaProvider<'provider>: DataProvider<S::LangMarker>,
|
||||
{
|
||||
// 1. Let matcher be options.[[localeMatcher]].
|
||||
// 2. If matcher is "lookup", then
|
||||
// a. Let r be ! LookupMatcher(availableLocales, requestedLocales).
|
||||
// 3. Else,
|
||||
// a. Let r be ! BestFitMatcher(availableLocales, requestedLocales).
|
||||
// 4. Let foundLocale be r.[[locale]].
|
||||
let mut found_locale = if options.matcher == LocaleMatcher::Lookup {
|
||||
lookup_matcher::<S::LangMarker>(requested_locales, icu)
|
||||
} else {
|
||||
best_fit_matcher::<S::LangMarker>(requested_locales, icu)
|
||||
};
|
||||
|
||||
// From here, the spec differs significantly from the implementation,
|
||||
// since ICU4X allows us to skip some steps and modularize the
|
||||
// extension resolution algorithm. However, the original spec is left here
|
||||
// for completion purposes.
|
||||
|
||||
// 5. Let result be a new Record.
|
||||
// 6. Set result.[[dataLocale]] to foundLocale.
|
||||
// 7. If r has an [[extension]] field, then
|
||||
// a. Let components be ! UnicodeExtensionComponents(r.[[extension]]).
|
||||
// b. Let keywords be components.[[Keywords]].
|
||||
// 9. For each element key of relevantExtensionKeys, do
|
||||
// a. Let foundLocaleData be localeData.[[<foundLocale>]].
|
||||
// b. Assert: Type(foundLocaleData) is Record.
|
||||
// c. Let keyLocaleData be foundLocaleData.[[<key>]].
|
||||
// d. Assert: Type(keyLocaleData) is List.
|
||||
// e. Let value be keyLocaleData[0].
|
||||
// f. Assert: Type(value) is either String or Null.
|
||||
// g. Let supportedExtensionAddition be "".
|
||||
// h. If r has an [[extension]] field, then
|
||||
// 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.
|
||||
// 2. Let requestedValue be entry.[[Value]].
|
||||
// 3. If requestedValue is not the empty String, then
|
||||
// a. If keyLocaleData contains requestedValue, then
|
||||
// i. Let value be requestedValue.
|
||||
// ii. Let supportedExtensionAddition be the string-concatenation of "-", key, "-", and value.
|
||||
// 4. Else if keyLocaleData contains "true", then
|
||||
// a. Let value be "true".
|
||||
// b. Let supportedExtensionAddition be the string-concatenation of "-" and key.
|
||||
// i. If options has a field [[<key>]], then
|
||||
// i. Let optionsValue be options.[[<key>]].
|
||||
// ii. Assert: Type(optionsValue) is either String, Undefined, or Null.
|
||||
// iii. If Type(optionsValue) is String, then
|
||||
// 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.
|
||||
// 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
|
||||
// a. Let optionsValue be "true".
|
||||
// iv. If keyLocaleData contains optionsValue, then
|
||||
// 1. If SameValue(optionsValue, value) is false, then
|
||||
// a. Let value be optionsValue.
|
||||
// b. Let supportedExtensionAddition be "".
|
||||
// j. Set result.[[<key>]] to value.
|
||||
// k. Append supportedExtensionAddition to supportedExtension.
|
||||
// 10. If the number of elements in supportedExtension is greater than 2, then
|
||||
// a. Let foundLocale be InsertUnicodeExtensionAndCanonicalize(foundLocale, supportedExtension).
|
||||
// 11. Set result.[[locale]] to foundLocale.
|
||||
|
||||
// 12. Return result.
|
||||
S::resolve(
|
||||
&mut found_locale,
|
||||
&mut options.service_options,
|
||||
icu.provider(),
|
||||
);
|
||||
icu.locale_canonicalizer().canonicalize(&mut found_locale);
|
||||
found_locale
|
||||
}
|
||||
|
||||
/// Abstract operation [`LookupSupportedLocales ( availableLocales, requestedLocales )`][spec]
|
||||
///
|
||||
/// Returns the subset of the provided BCP 47 language priority list requestedLocales for which
|
||||
/// `availableLocales` has a matching locale when using the BCP 47 Lookup algorithm. Locales appear
|
||||
/// in the same order in the returned list as in `requestedLocales`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This differs a bit from the spec, since we don't have an `[[AvailableLocales]]`
|
||||
/// list to compare with. However, we can do data requests to a [`DataProvider`]
|
||||
/// in order to see if a certain [`Locale`] is supported.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-lookupsupportedlocales
|
||||
fn lookup_supported_locales<M: KeyedDataMarker>(
|
||||
requested_locales: &[Locale],
|
||||
provider: &impl DataProvider<M>,
|
||||
) -> Vec<Locale> {
|
||||
// 1. Let subset be a new empty List.
|
||||
// 2. For each element locale of requestedLocales, do
|
||||
// a. Let noExtensionsLocale be the String value that is locale with any Unicode locale extension sequences removed.
|
||||
// b. Let availableLocale be ! BestAvailableLocale(availableLocales, noExtensionsLocale).
|
||||
// c. If availableLocale is not undefined, append locale to the end of subset.
|
||||
// 3. Return subset.
|
||||
requested_locales
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|loc| best_available_locale(loc.id.clone(), provider).is_some())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Abstract operation [`BestFitSupportedLocales ( availableLocales, requestedLocales )`][spec]
|
||||
///
|
||||
/// Returns the subset of the provided BCP 47 language priority list `requestedLocales` for which
|
||||
/// `availableLocales` has a matching locale when using the Best Fit Matcher algorithm. Locales appear
|
||||
/// in the same order in the returned list as in requestedLocales.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-bestfitsupportedlocales
|
||||
fn best_fit_supported_locales<M: KeyedDataMarker>(
|
||||
requested_locales: &[Locale],
|
||||
provider: &impl DataProvider<M>,
|
||||
) -> Vec<Locale> {
|
||||
requested_locales
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|loc| best_locale_for_provider(loc.id.clone(), provider).is_some())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Abstract operation [`SupportedLocales ( availableLocales, requestedLocales, options )`][spec]
|
||||
///
|
||||
/// Returns the subset of the provided BCP 47 language priority list requestedLocales for which
|
||||
/// availableLocales has a matching locale
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-supportedlocales
|
||||
pub(in crate::builtins::intl) fn supported_locales<'ctx, 'icu: 'ctx, M: KeyedDataMarker>(
|
||||
requested_locales: &[Locale],
|
||||
options: &JsValue,
|
||||
context: &'ctx mut Context<'icu>,
|
||||
) -> JsResult<JsObject>
|
||||
where
|
||||
BoaProvider<'icu>: DataProvider<M>,
|
||||
{
|
||||
// 1. Set options to ? CoerceOptionsToObject(options).
|
||||
let options = coerce_options_to_object(options, context)?;
|
||||
|
||||
// 2. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
|
||||
let matcher =
|
||||
get_option::<LocaleMatcher>(&options, "localeMatcher", false, context)?.unwrap_or_default();
|
||||
|
||||
let elements = match matcher {
|
||||
// 4. Else,
|
||||
// a. Let supportedLocales be LookupSupportedLocales(availableLocales, requestedLocales).
|
||||
LocaleMatcher::Lookup => {
|
||||
lookup_supported_locales(requested_locales, &context.icu().provider())
|
||||
}
|
||||
// 3. If matcher is "best fit", then
|
||||
// a. Let supportedLocales be BestFitSupportedLocales(availableLocales, requestedLocales).
|
||||
LocaleMatcher::BestFit => {
|
||||
best_fit_supported_locales(requested_locales, &context.icu().provider())
|
||||
}
|
||||
};
|
||||
|
||||
// 5. Return CreateArrayFromList(supportedLocales).
|
||||
Ok(Array::create_array_from_list(
|
||||
elements.into_iter().map(|loc| loc.to_string().into()),
|
||||
context,
|
||||
))
|
||||
}
|
||||
|
||||
/// Validates that the unicode extension `key` with `value` is a valid extension value for the
|
||||
/// `language`.
|
||||
pub(in crate::builtins::intl) fn validate_extension<M: KeyedDataMarker>(
|
||||
language: LanguageIdentifier,
|
||||
key: Key,
|
||||
value: &Value,
|
||||
provider: &impl DataProvider<M>,
|
||||
) -> bool {
|
||||
let mut locale = DataLocale::from(language);
|
||||
locale.set_unicode_ext(key, value.clone());
|
||||
let request = DataRequest {
|
||||
locale: &locale,
|
||||
metadata: DataRequestMetadata::default(),
|
||||
};
|
||||
|
||||
DataProvider::load(provider, request)
|
||||
.ok()
|
||||
.map(|res| res.metadata.locale.unwrap_or_else(|| locale.clone()))
|
||||
.filter(|loc| loc == &locale)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use icu_locid::{langid, locale, Locale};
|
||||
use icu_plurals::provider::CardinalV1Marker;
|
||||
use icu_provider::AsDeserializingBufferProvider;
|
||||
|
||||
use crate::{
|
||||
builtins::intl::locale::utils::{
|
||||
best_available_locale, best_fit_matcher, default_locale, lookup_matcher,
|
||||
},
|
||||
context::icu::{BoaProvider, Icu},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn best_avail_loc() {
|
||||
let provider = boa_icu_provider::buffer();
|
||||
let provider = provider.as_deserializing();
|
||||
|
||||
assert_eq!(
|
||||
best_available_locale::<CardinalV1Marker>(langid!("en"), &provider),
|
||||
Some(langid!("en"))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
best_available_locale::<CardinalV1Marker>(langid!("es-ES"), &provider),
|
||||
Some(langid!("es"))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
best_available_locale::<CardinalV1Marker>(langid!("kr"), &provider),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_match() {
|
||||
let provider = boa_icu_provider::buffer();
|
||||
let icu = Icu::new(BoaProvider::Buffer(provider)).unwrap();
|
||||
|
||||
// requested: []
|
||||
|
||||
let res = lookup_matcher::<CardinalV1Marker>(&[], &icu);
|
||||
assert_eq!(res, default_locale(icu.locale_canonicalizer()));
|
||||
assert!(res.extensions.is_empty());
|
||||
|
||||
// requested: [fr-FR-u-hc-h12]
|
||||
let requested: Locale = "fr-FR-u-hc-h12".parse().unwrap();
|
||||
|
||||
let result = lookup_matcher::<CardinalV1Marker>(&[requested.clone()], &icu);
|
||||
assert_eq!(result.id, langid!("fr"));
|
||||
assert_eq!(result.extensions, requested.extensions);
|
||||
|
||||
// requested: [kr-KR-u-hc-h12, gr-GR-u-hc-h24-x-4a, es-ES-valencia-u-ca-gregory, uz-Cyrl]
|
||||
let kr = "kr-KR-u-hc-h12".parse().unwrap();
|
||||
let gr = "gr-GR-u-hc-h24-x-4a".parse().unwrap();
|
||||
let es: Locale = "es-ES-valencia-u-ca-gregory".parse().unwrap();
|
||||
let uz = locale!("uz-Cyrl");
|
||||
let requested = vec![kr, gr, es.clone(), uz];
|
||||
|
||||
let res = best_fit_matcher::<CardinalV1Marker>(&requested, &icu);
|
||||
assert_eq!(res.id, langid!("es"));
|
||||
assert_eq!(res.extensions, es.extensions);
|
||||
}
|
||||
}
|
||||
155
javascript-engine/external/boa/boa_engine/src/builtins/intl/mod.rs
vendored
Normal file
155
javascript-engine/external/boa/boa_engine/src/builtins/intl/mod.rs
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
//! Boa's implementation of ECMAScript's 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
|
||||
|
||||
#![allow(clippy::string_lit_as_bytes)]
|
||||
|
||||
use super::JsArgs;
|
||||
use crate::{
|
||||
builtins::intl::date_time_format::DateTimeFormat,
|
||||
builtins::{Array, BuiltIn},
|
||||
context::BoaProvider,
|
||||
object::ObjectInitializer,
|
||||
property::Attribute,
|
||||
symbol::JsSymbol,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
use boa_profiler::Profiler;
|
||||
use icu_provider::KeyedDataMarker;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
pub(crate) mod collator;
|
||||
pub(crate) mod date_time_format;
|
||||
pub(crate) mod list_format;
|
||||
pub(crate) mod locale;
|
||||
mod options;
|
||||
pub(crate) mod segmenter;
|
||||
|
||||
use self::{collator::Collator, list_format::ListFormat, locale::Locale, segmenter::Segmenter};
|
||||
|
||||
/// 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 collator = Collator::init(context).expect("initialization should return a constructor");
|
||||
|
||||
let list_format =
|
||||
ListFormat::init(context).expect("initialization should return a constructor");
|
||||
|
||||
let locale = Locale::init(context).expect("initialization should return a constructor");
|
||||
|
||||
let segmenter =
|
||||
Segmenter::init(context).expect("initialization should return a constructor");
|
||||
|
||||
let date_time_format = DateTimeFormat::init(context);
|
||||
|
||||
ObjectInitializer::new(context)
|
||||
.property(
|
||||
JsSymbol::to_string_tag(),
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
"Collator",
|
||||
collator,
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
"ListFormat",
|
||||
list_format,
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
"DateTimeFormat",
|
||||
date_time_format,
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
"Locale",
|
||||
locale,
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
"Segmenter",
|
||||
segmenter,
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.function(Self::get_canonical_locales, "getCanonicalLocales", 1)
|
||||
.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> {
|
||||
let locales = args.get_or_undefined(0);
|
||||
|
||||
// 1. Let ll be ? CanonicalizeLocaleList(locales).
|
||||
let ll = locale::canonicalize_locale_list(locales, context)?;
|
||||
|
||||
// 2. Return CreateArrayFromList(ll).
|
||||
Ok(JsValue::Object(Array::create_array_from_list(
|
||||
ll.into_iter().map(|loc| loc.to_string().into()),
|
||||
context,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// A service component that is part of the `Intl` API.
|
||||
///
|
||||
/// This needs to be implemented for every `Intl` service in order to use the functions
|
||||
/// defined in `locale::utils`, such as locale resolution and selection.
|
||||
trait Service {
|
||||
/// The data marker used by [`resolve_locale`][locale::resolve_locale] to decide
|
||||
/// which locales are supported by this service.
|
||||
type LangMarker: KeyedDataMarker;
|
||||
|
||||
/// The set of options used in the [`Service::resolve`] method to resolve the provided
|
||||
/// locale.
|
||||
type LocaleOptions;
|
||||
|
||||
/// Resolves the final value of `locale` from a set of `options`.
|
||||
///
|
||||
/// The provided `options` will also be modified with the final values, in case there were
|
||||
/// changes in the resolution algorithm.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// - A correct implementation must ensure `locale` and `options` are both written with the
|
||||
/// new final values.
|
||||
/// - If the implementor service doesn't contain any `[[RelevantExtensionKeys]]`, this can be
|
||||
/// skipped.
|
||||
fn resolve(
|
||||
_locale: &mut icu_locid::Locale,
|
||||
_options: &mut Self::LocaleOptions,
|
||||
_provider: BoaProvider<'_>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
252
javascript-engine/external/boa/boa_engine/src/builtins/intl/options.rs
vendored
Normal file
252
javascript-engine/external/boa/boa_engine/src/builtins/intl/options.rs
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use icu_collator::CaseFirst;
|
||||
|
||||
use crate::{
|
||||
object::{JsObject, ObjectData},
|
||||
Context, JsNativeError, JsResult, JsString, JsValue,
|
||||
};
|
||||
|
||||
/// `IntlOptions` aggregates the `locale_matcher` selector and any other object
|
||||
/// property needed for `Intl` object constructors.
|
||||
///
|
||||
/// It is used as the type of the `options` parameter in the operation `resolve_locale`.
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct IntlOptions<O> {
|
||||
pub(super) matcher: LocaleMatcher,
|
||||
pub(super) service_options: O,
|
||||
}
|
||||
|
||||
/// A type used as an option parameter inside the `Intl` [spec].
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402
|
||||
pub(super) trait OptionType: Sized {
|
||||
/// Parses a [`JsValue`] into an instance of `Self`.
|
||||
///
|
||||
/// Roughly equivalent to the algorithm steps of [9.12.13.3-7][spec], but allows for parsing
|
||||
/// steps instead of returning a pure string, number or boolean.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-getoption
|
||||
fn from_value(value: JsValue, context: &mut Context<'_>) -> JsResult<Self>;
|
||||
}
|
||||
|
||||
pub(super) trait OptionTypeParsable: FromStr {}
|
||||
|
||||
impl<T: OptionTypeParsable> OptionType for T
|
||||
where
|
||||
T::Err: Display,
|
||||
{
|
||||
fn from_value(value: JsValue, context: &mut Context<'_>) -> JsResult<Self> {
|
||||
value
|
||||
.to_string(context)?
|
||||
.to_std_string_escaped()
|
||||
.parse::<Self>()
|
||||
.map_err(|err| JsNativeError::range().with_message(err.to_string()).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionType for bool {
|
||||
fn from_value(value: JsValue, _: &mut Context<'_>) -> JsResult<Self> {
|
||||
// 5. If type is "boolean", then
|
||||
// a. Set value to ! ToBoolean(value).
|
||||
Ok(value.to_boolean())
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionType for JsString {
|
||||
fn from_value(value: JsValue, context: &mut Context<'_>) -> JsResult<Self> {
|
||||
// 6. If type is "string", then
|
||||
// a. Set value to ? ToString(value).
|
||||
value.to_string(context)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub(super) enum LocaleMatcher {
|
||||
Lookup,
|
||||
#[default]
|
||||
BestFit,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ParseLocaleMatcherError;
|
||||
|
||||
impl Display for ParseLocaleMatcherError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
"provided string was not `lookup` or `best fit`".fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for LocaleMatcher {
|
||||
type Err = ParseLocaleMatcherError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"lookup" => Ok(Self::Lookup),
|
||||
"best fit" => Ok(Self::BestFit),
|
||||
_ => Err(ParseLocaleMatcherError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionTypeParsable for LocaleMatcher {}
|
||||
|
||||
impl OptionType for CaseFirst {
|
||||
fn from_value(value: JsValue, context: &mut Context<'_>) -> JsResult<Self> {
|
||||
match value.to_string(context)?.to_std_string_escaped().as_str() {
|
||||
"upper" => Ok(CaseFirst::UpperFirst),
|
||||
"lower" => Ok(CaseFirst::LowerFirst),
|
||||
"false" => Ok(CaseFirst::Off),
|
||||
_ => Err(JsNativeError::range()
|
||||
.with_message("provided string was not `upper`, `lower` or `false`")
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract operation [`GetOption ( options, property, type, values, fallback )`][spec]
|
||||
///
|
||||
/// Extracts the value of the property named `property` from the provided `options` object,
|
||||
/// converts it to the required `type` and checks whether it is one of a `List` of allowed
|
||||
/// `values`. If `values` is undefined, there is no fixed set of values and any is permitted.
|
||||
/// If the value is `undefined`, `required` determines if the function should return `None` or
|
||||
/// an `Err`. Use [`Option::unwrap_or`] and friends to manage the default value.
|
||||
///
|
||||
/// This is a safer alternative to `GetOption`, which tries to parse from the
|
||||
/// provided property a valid variant of the provided type `T`. It doesn't accept
|
||||
/// a `type` parameter since the type can specify in its implementation of [`TryFrom`] whether
|
||||
/// it wants to parse from a [`str`] or convert directly from a boolean or number.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-getoption
|
||||
pub(super) fn get_option<T: OptionType>(
|
||||
options: &JsObject,
|
||||
property: &str,
|
||||
required: bool,
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<Option<T>> {
|
||||
// 1. Let value be ? Get(options, property).
|
||||
let value = options.get(property, context)?;
|
||||
|
||||
// 2. If value is undefined, then
|
||||
if value.is_undefined() {
|
||||
return if required {
|
||||
// a. If default is required, throw a RangeError exception.
|
||||
Err(JsNativeError::range()
|
||||
.with_message("GetOption: option value cannot be undefined")
|
||||
.into())
|
||||
} else {
|
||||
// b. Return default.
|
||||
Ok(None)
|
||||
};
|
||||
}
|
||||
|
||||
// The steps 3 to 7 must be made for each `OptionType`.
|
||||
T::from_value(value, context).map(Some)
|
||||
}
|
||||
|
||||
/// 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(super) 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 )`][spec]
|
||||
///
|
||||
/// Converts `value` to a `Number value`, checks whether it is in the allowed range,
|
||||
/// and fills in a `fallback` value if necessary.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-defaultnumberoption
|
||||
#[allow(unused)]
|
||||
pub(super) 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 Err(JsNativeError::range()
|
||||
.with_message("DefaultNumberOption: value is out of range.")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 4. Return floor(value).
|
||||
Ok(Some(value.floor()))
|
||||
}
|
||||
|
||||
/// Abstract operation [`GetOptionsObject ( options )`][spec]
|
||||
///
|
||||
/// Returns a [`JsObject`] suitable for use with [`get_option`], either `options` itself or a default empty
|
||||
/// `JsObject`. It throws a `TypeError` if `options` is not undefined and not a `JsObject`.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-getoptionsobject
|
||||
pub(super) fn get_options_object(options: &JsValue) -> JsResult<JsObject> {
|
||||
match options {
|
||||
// If options is undefined, then
|
||||
JsValue::Undefined => {
|
||||
// a. Return OrdinaryObjectCreate(null).
|
||||
Ok(JsObject::from_proto_and_data(None, ObjectData::ordinary()))
|
||||
}
|
||||
// 2. If Type(options) is Object, then
|
||||
JsValue::Object(obj) => {
|
||||
// a. Return options.
|
||||
Ok(obj.clone())
|
||||
}
|
||||
// 3. Throw a TypeError exception.
|
||||
_ => Err(JsNativeError::typ()
|
||||
.with_message("GetOptionsObject: provided options is not an object")
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract operation [`CoerceOptionsToObject ( options )`][spec]
|
||||
///
|
||||
/// Coerces `options` into a [`JsObject`] suitable for use with [`get_option`], defaulting to an empty
|
||||
/// `JsObject`.
|
||||
/// Because it coerces non-null primitive values into objects, its use is discouraged for new
|
||||
/// functionality in favour of [`get_options_object`].
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma402/#sec-coerceoptionstoobject
|
||||
pub(super) fn coerce_options_to_object(
|
||||
options: &JsValue,
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsObject> {
|
||||
// If options is undefined, then
|
||||
if options.is_undefined() {
|
||||
// a. Return OrdinaryObjectCreate(null).
|
||||
return Ok(JsObject::from_proto_and_data(None, ObjectData::ordinary()));
|
||||
}
|
||||
|
||||
// 2. Return ? ToObject(options).
|
||||
options.to_object(context)
|
||||
}
|
||||
45
javascript-engine/external/boa/boa_engine/src/builtins/intl/segmenter/mod.rs
vendored
Normal file
45
javascript-engine/external/boa/boa_engine/src/builtins/intl/segmenter/mod.rs
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// TODO: implement `Segmenter` when https://github.com/unicode-org/icu4x/issues/2259 closes.
|
||||
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use crate::{builtins::BuiltIn, object::ConstructorBuilder, Context, JsResult, JsValue};
|
||||
|
||||
mod options;
|
||||
#[allow(unused)]
|
||||
pub(crate) use options::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Segmenter;
|
||||
|
||||
impl BuiltIn for Segmenter {
|
||||
const NAME: &'static str = "Segmenter";
|
||||
|
||||
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().segmenter().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Segmenter {
|
||||
pub(crate) const LENGTH: usize = 0;
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn constructor(
|
||||
_: &JsValue,
|
||||
_: &[JsValue],
|
||||
_: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
Ok(JsValue::Undefined)
|
||||
}
|
||||
}
|
||||
29
javascript-engine/external/boa/boa_engine/src/builtins/intl/segmenter/options.rs
vendored
Normal file
29
javascript-engine/external/boa/boa_engine/src/builtins/intl/segmenter/options.rs
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub(crate) enum Granularity {
|
||||
#[default]
|
||||
Grapheme,
|
||||
Word,
|
||||
Sentence,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParseGranularityError;
|
||||
|
||||
impl std::fmt::Display for ParseGranularityError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("provided string was not `grapheme`, `word` or `sentence`")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Granularity {
|
||||
type Err = ParseGranularityError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"grapheme" => Ok(Self::Grapheme),
|
||||
"word" => Ok(Self::Word),
|
||||
"sentence" => Ok(Self::Sentence),
|
||||
_ => Err(ParseGranularityError),
|
||||
}
|
||||
}
|
||||
}
|
||||
444
javascript-engine/external/boa/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs
vendored
Normal file
444
javascript-engine/external/boa/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs
vendored
Normal file
@@ -0,0 +1,444 @@
|
||||
use crate::{
|
||||
builtins::{
|
||||
iterable::{create_iter_result_object, IteratorRecord, IteratorResult},
|
||||
promise::{if_abrupt_reject_promise, PromiseCapability},
|
||||
JsArgs, Promise,
|
||||
},
|
||||
native_function::NativeFunction,
|
||||
object::{FunctionObjectBuilder, JsObject, ObjectData},
|
||||
property::PropertyDescriptor,
|
||||
Context, JsNativeError, JsResult, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// Create the `%AsyncFromSyncIteratorPrototype%` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%-object
|
||||
pub(crate) fn create_async_from_sync_iterator_prototype(context: &mut Context<'_>) -> JsObject {
|
||||
let _timer = Profiler::global().start_event("AsyncFromSyncIteratorPrototype", "init");
|
||||
|
||||
let prototype = JsObject::from_proto_and_data(
|
||||
context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.async_iterator_prototype(),
|
||||
ObjectData::ordinary(),
|
||||
);
|
||||
|
||||
let next_function = FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_fn_ptr(AsyncFromSyncIterator::next),
|
||||
)
|
||||
.name("next")
|
||||
.length(1)
|
||||
.build();
|
||||
let return_function = FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_fn_ptr(AsyncFromSyncIterator::r#return),
|
||||
)
|
||||
.name("return")
|
||||
.length(1)
|
||||
.build();
|
||||
let throw_function = FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_fn_ptr(AsyncFromSyncIterator::throw),
|
||||
)
|
||||
.name("throw")
|
||||
.length(1)
|
||||
.build();
|
||||
|
||||
{
|
||||
let mut prototype_mut = prototype.borrow_mut();
|
||||
|
||||
prototype_mut.insert(
|
||||
"next",
|
||||
PropertyDescriptor::builder()
|
||||
.value(next_function)
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
);
|
||||
prototype_mut.insert(
|
||||
"return",
|
||||
PropertyDescriptor::builder()
|
||||
.value(return_function)
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
);
|
||||
prototype_mut.insert(
|
||||
"throw",
|
||||
PropertyDescriptor::builder()
|
||||
.value(throw_function)
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
);
|
||||
}
|
||||
|
||||
prototype
|
||||
}
|
||||
|
||||
/// The internal data for `%AsyncFromSyncIterator%` objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-properties-of-async-from-sync-iterator-instances
|
||||
#[derive(Clone, Debug, Finalize, Trace)]
|
||||
pub struct AsyncFromSyncIterator {
|
||||
// The [[SyncIteratorRecord]] internal slot.
|
||||
sync_iterator_record: IteratorRecord,
|
||||
}
|
||||
|
||||
impl AsyncFromSyncIterator {
|
||||
/// `CreateAsyncFromSyncIterator ( syncIteratorRecord )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createasyncfromsynciterator
|
||||
pub(crate) fn create(
|
||||
sync_iterator_record: IteratorRecord,
|
||||
context: &mut Context<'_>,
|
||||
) -> IteratorRecord {
|
||||
// 1. Let asyncIterator be OrdinaryObjectCreate(%AsyncFromSyncIteratorPrototype%, « [[SyncIteratorRecord]] »).
|
||||
// 2. Set asyncIterator.[[SyncIteratorRecord]] to syncIteratorRecord.
|
||||
let async_iterator = JsObject::from_proto_and_data(
|
||||
context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.async_from_sync_iterator_prototype(),
|
||||
ObjectData::async_from_sync_iterator(Self {
|
||||
sync_iterator_record,
|
||||
}),
|
||||
);
|
||||
|
||||
// 3. Let nextMethod be ! Get(asyncIterator, "next").
|
||||
let next_method = async_iterator
|
||||
.get("next", context)
|
||||
.expect("async from sync iterator prototype must have next method");
|
||||
|
||||
// 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: asyncIterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
|
||||
// 5. Return iteratorRecord.
|
||||
IteratorRecord::new(async_iterator, next_method, false)
|
||||
}
|
||||
|
||||
/// `%AsyncFromSyncIteratorPrototype%.next ( [ value ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.next
|
||||
fn next(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
|
||||
// 1. Let O be the this value.
|
||||
// 2. Assert: O is an Object that has a [[SyncIteratorRecord]] internal slot.
|
||||
// 4. Let syncIteratorRecord be O.[[SyncIteratorRecord]].
|
||||
let sync_iterator_record = this
|
||||
.as_object()
|
||||
.expect("async from sync iterator prototype must be object")
|
||||
.borrow()
|
||||
.as_async_from_sync_iterator()
|
||||
.expect("async from sync iterator prototype must be object")
|
||||
.sync_iterator_record
|
||||
.clone();
|
||||
|
||||
// 3. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
let promise_capability = PromiseCapability::new(
|
||||
&context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.promise()
|
||||
.constructor()
|
||||
.into(),
|
||||
context,
|
||||
)
|
||||
.expect("cannot fail with promise constructor");
|
||||
|
||||
// 5. If value is present, then
|
||||
// a. Let result be Completion(IteratorNext(syncIteratorRecord, value)).
|
||||
// 6. Else,
|
||||
// a. Let result be Completion(IteratorNext(syncIteratorRecord)).
|
||||
let result = sync_iterator_record.next(args.get(0).cloned(), context);
|
||||
|
||||
// 7. IfAbruptRejectPromise(result, promiseCapability).
|
||||
if_abrupt_reject_promise!(result, promise_capability, context);
|
||||
|
||||
// 8. Return AsyncFromSyncIteratorContinuation(result, promiseCapability).
|
||||
Self::continuation(&result, &promise_capability, context)
|
||||
}
|
||||
|
||||
/// `%AsyncFromSyncIteratorPrototype%.return ( [ value ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.return
|
||||
fn r#return(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
|
||||
// 1. Let O be the this value.
|
||||
// 2. Assert: O is an Object that has a [[SyncIteratorRecord]] internal slot.
|
||||
// 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]].
|
||||
let sync_iterator = this
|
||||
.as_object()
|
||||
.expect("async from sync iterator prototype must be object")
|
||||
.borrow()
|
||||
.as_async_from_sync_iterator()
|
||||
.expect("async from sync iterator prototype must be object")
|
||||
.sync_iterator_record
|
||||
.iterator()
|
||||
.clone();
|
||||
|
||||
// 3. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
let promise_capability = PromiseCapability::new(
|
||||
&context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.promise()
|
||||
.constructor()
|
||||
.into(),
|
||||
context,
|
||||
)
|
||||
.expect("cannot fail with promise constructor");
|
||||
|
||||
// 5. Let return be Completion(GetMethod(syncIterator, "return")).
|
||||
let r#return = sync_iterator.get_method("return", context);
|
||||
|
||||
// 6. IfAbruptRejectPromise(return, promiseCapability).
|
||||
if_abrupt_reject_promise!(r#return, promise_capability, context);
|
||||
|
||||
let result = match (r#return, args.get(0)) {
|
||||
// 7. If return is undefined, then
|
||||
(None, _) => {
|
||||
// a. Let iterResult be CreateIterResultObject(value, true).
|
||||
let iter_result =
|
||||
create_iter_result_object(args.get_or_undefined(0).clone(), true, context);
|
||||
|
||||
// b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iterResult »).
|
||||
promise_capability
|
||||
.resolve()
|
||||
.call(&JsValue::Undefined, &[iter_result], context)
|
||||
.expect("cannot fail according to spec");
|
||||
|
||||
// c. Return promiseCapability.[[Promise]].
|
||||
return Ok(promise_capability.promise().clone().into());
|
||||
}
|
||||
// 8. If value is present, then
|
||||
(Some(r#return), Some(value)) => {
|
||||
// a. Let result be Completion(Call(return, syncIterator, « value »)).
|
||||
r#return.call(&sync_iterator.clone().into(), &[value.clone()], context)
|
||||
}
|
||||
// 9. Else,
|
||||
(Some(r#return), None) => {
|
||||
// a. Let result be Completion(Call(return, syncIterator)).
|
||||
r#return.call(&sync_iterator.clone().into(), &[], context)
|
||||
}
|
||||
};
|
||||
|
||||
// 10. IfAbruptRejectPromise(result, promiseCapability).
|
||||
if_abrupt_reject_promise!(result, promise_capability, context);
|
||||
|
||||
let Some(result) = result.as_object() else {
|
||||
// 11. If Type(result) is not Object, then
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
|
||||
promise_capability
|
||||
.reject()
|
||||
.call(
|
||||
&JsValue::Undefined,
|
||||
&[JsNativeError::typ()
|
||||
.with_message("iterator return function returned non-object")
|
||||
.to_opaque(context)
|
||||
.into()],
|
||||
context,
|
||||
)
|
||||
.expect("cannot fail according to spec");
|
||||
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return Ok(promise_capability.promise().clone().into());
|
||||
};
|
||||
|
||||
// 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability).
|
||||
Self::continuation(
|
||||
&IteratorResult {
|
||||
object: result.clone(),
|
||||
},
|
||||
&promise_capability,
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
/// `%AsyncFromSyncIteratorPrototype%.throw ( [ value ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.throw
|
||||
fn throw(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
|
||||
// 1. Let O be the this value.
|
||||
// 2. Assert: O is an Object that has a [[SyncIteratorRecord]] internal slot.
|
||||
// 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]].
|
||||
let sync_iterator = this
|
||||
.as_object()
|
||||
.expect("async from sync iterator prototype must be object")
|
||||
.borrow()
|
||||
.as_async_from_sync_iterator()
|
||||
.expect("async from sync iterator prototype must be object")
|
||||
.sync_iterator_record
|
||||
.iterator()
|
||||
.clone();
|
||||
|
||||
// 3. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
let promise_capability = PromiseCapability::new(
|
||||
&context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.promise()
|
||||
.constructor()
|
||||
.into(),
|
||||
context,
|
||||
)
|
||||
.expect("cannot fail with promise constructor");
|
||||
|
||||
// 5. Let throw be Completion(GetMethod(syncIterator, "throw")).
|
||||
let throw = sync_iterator.get_method("throw", context);
|
||||
|
||||
// 6. IfAbruptRejectPromise(throw, promiseCapability).
|
||||
if_abrupt_reject_promise!(throw, promise_capability, context);
|
||||
|
||||
let result = match (throw, args.get(0)) {
|
||||
// 7. If throw is undefined, then
|
||||
(None, _) => {
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »).
|
||||
promise_capability
|
||||
.reject()
|
||||
.call(
|
||||
&JsValue::Undefined,
|
||||
&[args.get_or_undefined(0).clone()],
|
||||
context,
|
||||
)
|
||||
.expect("cannot fail according to spec");
|
||||
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return Ok(promise_capability.promise().clone().into());
|
||||
}
|
||||
// 8. If value is present, then
|
||||
(Some(throw), Some(value)) => {
|
||||
// a. Let result be Completion(Call(throw, syncIterator, « value »)).
|
||||
throw.call(&sync_iterator.clone().into(), &[value.clone()], context)
|
||||
}
|
||||
// 9. Else,
|
||||
(Some(throw), None) => {
|
||||
// a. Let result be Completion(Call(throw, syncIterator)).
|
||||
throw.call(&sync_iterator.clone().into(), &[], context)
|
||||
}
|
||||
};
|
||||
|
||||
// 10. IfAbruptRejectPromise(result, promiseCapability).
|
||||
if_abrupt_reject_promise!(result, promise_capability, context);
|
||||
|
||||
let Some(result) = result.as_object() else {
|
||||
// 11. If Type(result) is not Object, then
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
|
||||
promise_capability
|
||||
.reject()
|
||||
.call(
|
||||
&JsValue::Undefined,
|
||||
&[JsNativeError::typ()
|
||||
.with_message("iterator throw function returned non-object")
|
||||
.to_opaque(context)
|
||||
.into()],
|
||||
context,
|
||||
)
|
||||
.expect("cannot fail according to spec");
|
||||
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return Ok(promise_capability.promise().clone().into());
|
||||
};
|
||||
|
||||
// 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability).
|
||||
Self::continuation(
|
||||
&IteratorResult {
|
||||
object: result.clone(),
|
||||
},
|
||||
&promise_capability,
|
||||
context,
|
||||
)
|
||||
}
|
||||
|
||||
/// `AsyncFromSyncIteratorContinuation ( result, promiseCapability )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncfromsynciteratorcontinuation
|
||||
fn continuation(
|
||||
result: &IteratorResult,
|
||||
promise_capability: &PromiseCapability,
|
||||
context: &mut Context<'_>,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. NOTE: Because promiseCapability is derived from the intrinsic %Promise%,
|
||||
// the calls to promiseCapability.[[Reject]] entailed by the
|
||||
// use IfAbruptRejectPromise below are guaranteed not to throw.
|
||||
|
||||
// 2. Let done be Completion(IteratorComplete(result)).
|
||||
let done = result.complete(context);
|
||||
|
||||
// 3. IfAbruptRejectPromise(done, promiseCapability).
|
||||
if_abrupt_reject_promise!(done, promise_capability, context);
|
||||
|
||||
// 4. Let value be Completion(IteratorValue(result)).
|
||||
let value = result.value(context);
|
||||
|
||||
// 5. IfAbruptRejectPromise(value, promiseCapability).
|
||||
if_abrupt_reject_promise!(value, promise_capability, context);
|
||||
|
||||
// 6. Let valueWrapper be Completion(PromiseResolve(%Promise%, value)).
|
||||
let value_wrapper = Promise::promise_resolve(
|
||||
context.intrinsics().constructors().promise().constructor(),
|
||||
value,
|
||||
context,
|
||||
);
|
||||
|
||||
// 7. IfAbruptRejectPromise(valueWrapper, promiseCapability).
|
||||
if_abrupt_reject_promise!(value_wrapper, promise_capability, context);
|
||||
|
||||
// 8. Let unwrap be a new Abstract Closure with parameters (value)
|
||||
// that captures done and performs the following steps when called:
|
||||
// 9. Let onFulfilled be CreateBuiltinFunction(unwrap, 1, "", « »).
|
||||
let on_fulfilled = FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_copy_closure(move |_this, args, context| {
|
||||
// a. Return CreateIterResultObject(value, done).
|
||||
Ok(create_iter_result_object(
|
||||
args.get_or_undefined(0).clone(),
|
||||
done,
|
||||
context,
|
||||
))
|
||||
}),
|
||||
)
|
||||
.name("")
|
||||
.length(1)
|
||||
.build();
|
||||
|
||||
// 10. NOTE: onFulfilled is used when processing the "value" property of an
|
||||
// IteratorResult object in order to wait for its value if it is a promise and
|
||||
// re-package the result in a new "unwrapped" IteratorResult object.
|
||||
|
||||
// 11. Perform PerformPromiseThen(valueWrapper, onFulfilled, undefined, promiseCapability).
|
||||
Promise::perform_promise_then(
|
||||
&value_wrapper,
|
||||
&on_fulfilled.into(),
|
||||
&JsValue::Undefined,
|
||||
Some(promise_capability.clone()),
|
||||
context,
|
||||
);
|
||||
|
||||
// 12. Return promiseCapability.[[Promise]].
|
||||
Ok(promise_capability.promise().clone().into())
|
||||
}
|
||||
}
|
||||
573
javascript-engine/external/boa/boa_engine/src/builtins/iterable/mod.rs
vendored
Normal file
573
javascript-engine/external/boa/boa_engine/src/builtins/iterable/mod.rs
vendored
Normal file
@@ -0,0 +1,573 @@
|
||||
//! Boa's implementation of ECMAScript's `IteratorRecord` and iterator prototype objects.
|
||||
|
||||
mod async_from_sync_iterator;
|
||||
|
||||
use crate::{
|
||||
builtins::{
|
||||
regexp::regexp_string_iterator::RegExpStringIterator,
|
||||
string::string_iterator::StringIterator, ArrayIterator, ForInIterator, MapIterator,
|
||||
SetIterator,
|
||||
},
|
||||
error::JsNativeError,
|
||||
object::{JsObject, ObjectInitializer},
|
||||
symbol::JsSymbol,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use async_from_sync_iterator::create_async_from_sync_iterator_prototype;
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
pub(crate) use async_from_sync_iterator::AsyncFromSyncIterator;
|
||||
|
||||
/// The built-in iterator prototypes.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IteratorPrototypes {
|
||||
/// The `IteratorPrototype` object.
|
||||
iterator_prototype: JsObject,
|
||||
|
||||
/// The `AsyncIteratorPrototype` object.
|
||||
async_iterator_prototype: JsObject,
|
||||
|
||||
/// The `AsyncFromSyncIteratorPrototype` prototype object.
|
||||
async_from_sync_iterator_prototype: JsObject,
|
||||
|
||||
/// The `ArrayIteratorPrototype` prototype object.
|
||||
array_iterator: JsObject,
|
||||
|
||||
/// The `SetIteratorPrototype` prototype object.
|
||||
set_iterator: JsObject,
|
||||
|
||||
/// The `StringIteratorPrototype` prototype object.
|
||||
string_iterator: JsObject,
|
||||
|
||||
/// The `RegExpStringIteratorPrototype` prototype object.
|
||||
regexp_string_iterator: JsObject,
|
||||
|
||||
/// The `MapIteratorPrototype` prototype object.
|
||||
map_iterator: JsObject,
|
||||
|
||||
/// The `ForInIteratorPrototype` prototype object.
|
||||
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);
|
||||
let async_iterator_prototype = create_async_iterator_prototype(context);
|
||||
let async_from_sync_iterator_prototype = create_async_from_sync_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,
|
||||
async_iterator_prototype,
|
||||
async_from_sync_iterator_prototype,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `ArrayIteratorPrototype` object.
|
||||
#[inline]
|
||||
pub fn array_iterator(&self) -> JsObject {
|
||||
self.array_iterator.clone()
|
||||
}
|
||||
|
||||
/// Returns the `IteratorPrototype` object.
|
||||
#[inline]
|
||||
pub fn iterator_prototype(&self) -> JsObject {
|
||||
self.iterator_prototype.clone()
|
||||
}
|
||||
|
||||
/// Returns the `AsyncIteratorPrototype` object.
|
||||
#[inline]
|
||||
pub fn async_iterator_prototype(&self) -> JsObject {
|
||||
self.async_iterator_prototype.clone()
|
||||
}
|
||||
|
||||
/// Returns the `AsyncFromSyncIteratorPrototype` object.
|
||||
#[inline]
|
||||
pub fn async_from_sync_iterator_prototype(&self) -> JsObject {
|
||||
self.async_from_sync_iterator_prototype.clone()
|
||||
}
|
||||
|
||||
/// Returns the `SetIteratorPrototype` object.
|
||||
#[inline]
|
||||
pub fn set_iterator(&self) -> JsObject {
|
||||
self.set_iterator.clone()
|
||||
}
|
||||
|
||||
/// Returns the `StringIteratorPrototype` object.
|
||||
#[inline]
|
||||
pub fn string_iterator(&self) -> JsObject {
|
||||
self.string_iterator.clone()
|
||||
}
|
||||
|
||||
/// Returns the `RegExpStringIteratorPrototype` object.
|
||||
#[inline]
|
||||
pub fn regexp_string_iterator(&self) -> JsObject {
|
||||
self.regexp_string_iterator.clone()
|
||||
}
|
||||
|
||||
/// Returns the `MapIteratorPrototype` object.
|
||||
#[inline]
|
||||
pub fn map_iterator(&self) -> JsObject {
|
||||
self.map_iterator.clone()
|
||||
}
|
||||
|
||||
/// Returns the `ForInIteratorPrototype` object.
|
||||
#[inline]
|
||||
pub fn for_in_iterator(&self) -> JsObject {
|
||||
self.for_in_iterator.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// `CreateIterResultObject( value, done )`
|
||||
///
|
||||
/// Generates an object supporting the `IteratorResult` interface.
|
||||
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 = JsObject::with_object_proto(context);
|
||||
|
||||
// 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 {
|
||||
/// Hints that the iterator should be sync.
|
||||
Sync,
|
||||
|
||||
/// Hints that the iterator should be async.
|
||||
Async,
|
||||
}
|
||||
|
||||
impl JsValue {
|
||||
/// `GetIterator ( obj [ , hint [ , method ] ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-getiterator
|
||||
pub fn get_iterator(
|
||||
&self,
|
||||
context: &mut Context<'_>,
|
||||
hint: Option<IteratorHint>,
|
||||
method: Option<JsObject>,
|
||||
) -> 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 method.is_some() {
|
||||
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(JsSymbol::async_iterator(), context)? {
|
||||
Some(method)
|
||||
} else {
|
||||
// ii. If method is undefined, then
|
||||
// 1. Let syncMethod be ? GetMethod(obj, @@iterator).
|
||||
let sync_method = self.get_method(JsSymbol::iterator(), context)?;
|
||||
|
||||
// 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod).
|
||||
let sync_iterator_record =
|
||||
self.get_iterator(context, Some(IteratorHint::Sync), sync_method)?;
|
||||
|
||||
// 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord).
|
||||
return Ok(AsyncFromSyncIterator::create(sync_iterator_record, context));
|
||||
}
|
||||
} else {
|
||||
// b. Otherwise, set method to ? GetMethod(obj, @@iterator).
|
||||
self.get_method(JsSymbol::iterator(), context)?
|
||||
}
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
JsNativeError::typ().with_message(format!(
|
||||
"value with type `{}` is not iterable",
|
||||
self.type_of()
|
||||
))
|
||||
})?;
|
||||
|
||||
// 3. Let iterator be ? Call(method, obj).
|
||||
let iterator = method.call(self, &[], context)?;
|
||||
|
||||
// 4. If Type(iterator) is not Object, throw a TypeError exception.
|
||||
let iterator_obj = iterator.as_object().ok_or_else(|| {
|
||||
JsNativeError::typ().with_message("returned 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
|
||||
fn create_iterator_prototype(context: &mut Context<'_>) -> JsObject {
|
||||
let _timer = Profiler::global().start_event("Iterator Prototype", "init");
|
||||
|
||||
let symbol_iterator = JsSymbol::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 {
|
||||
/// Create a new `IteratorResult`.
|
||||
pub(crate) fn new(object: JsObject) -> Self {
|
||||
Self { object }
|
||||
}
|
||||
|
||||
/// `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(Clone, Debug, Finalize, Trace)]
|
||||
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`.
|
||||
pub(crate) const fn iterator(&self) -> &JsObject {
|
||||
&self.iterator
|
||||
}
|
||||
|
||||
/// Get the `[[NextMethod]]` field of the `IteratorRecord`.
|
||||
pub(crate) const fn next_method(&self) -> &JsValue {
|
||||
&self.next_method
|
||||
}
|
||||
|
||||
/// Get the `[[Done]]` field of the `IteratorRecord`.
|
||||
pub(crate) const fn done(&self) -> bool {
|
||||
self.done
|
||||
}
|
||||
|
||||
/// Sets the `[[Done]]` field of the `IteratorRecord`.
|
||||
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
|
||||
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 = self.next_method.as_callable().ok_or_else(|| {
|
||||
JsNativeError::typ().with_message("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.
|
||||
result
|
||||
.as_object()
|
||||
.map(|o| IteratorResult { object: o.clone() })
|
||||
.ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("next value should be an object")
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
/// `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
|
||||
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.
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("inner result was not an object")
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `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<JsObject>,
|
||||
) -> JsResult<Vec<JsValue>> {
|
||||
let _timer = Profiler::global().start_event("iterable_to_list", "iterator");
|
||||
|
||||
// 1. If method is present, then
|
||||
// a. Let iteratorRecord be ? GetIterator(items, sync, method).
|
||||
// 2. Else,
|
||||
// a. Let iteratorRecord be ? GetIterator(items, sync).
|
||||
let iterator_record = items.get_iterator(context, Some(IteratorHint::Sync), method)?;
|
||||
|
||||
// 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;
|
||||
|
||||
/// Create the `%AsyncIteratorPrototype%` object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asynciteratorprototype
|
||||
fn create_async_iterator_prototype(context: &mut Context<'_>) -> JsObject {
|
||||
let _timer = Profiler::global().start_event("AsyncIteratorPrototype", "init");
|
||||
|
||||
let symbol_iterator = JsSymbol::async_iterator();
|
||||
let iterator_prototype = ObjectInitializer::new(context)
|
||||
.function(
|
||||
|v, _, _| Ok(v.clone()),
|
||||
(symbol_iterator, "[Symbol.asyncIterator]"),
|
||||
0,
|
||||
)
|
||||
.build();
|
||||
iterator_prototype
|
||||
}
|
||||
881
javascript-engine/external/boa/boa_engine/src/builtins/json/mod.rs
vendored
Normal file
881
javascript-engine/external/boa/boa_engine/src/builtins/json/mod.rs
vendored
Normal file
@@ -0,0 +1,881 @@
|
||||
//! Boa's implementation of ECMAScript's 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 std::{
|
||||
borrow::Cow,
|
||||
iter::{once, FusedIterator},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
builtins::BuiltIn,
|
||||
error::JsNativeError,
|
||||
js_string,
|
||||
object::{JsObject, ObjectInitializer, RecursionLimiter},
|
||||
property::{Attribute, PropertyNameKind},
|
||||
string::{utf16, CodePoint},
|
||||
symbol::JsSymbol,
|
||||
value::IntegerOrInfinity,
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
use boa_parser::Parser;
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
// `Intersperse` impl taken from `itertools`
|
||||
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
|
||||
#[derive(Clone, Debug)]
|
||||
struct Intersperse<I>
|
||||
where
|
||||
I: Iterator,
|
||||
{
|
||||
element: I::Item,
|
||||
iter: std::iter::Fuse<I>,
|
||||
peek: Option<I::Item>,
|
||||
}
|
||||
|
||||
fn intersperse<I>(iter: I, element: I::Item) -> Intersperse<I>
|
||||
where
|
||||
I: Iterator,
|
||||
{
|
||||
let mut iter = iter.fuse();
|
||||
Intersperse {
|
||||
peek: iter.next(),
|
||||
iter,
|
||||
element,
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Iterator for Intersperse<I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Clone,
|
||||
{
|
||||
type Item = I::Item;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.peek.is_some() {
|
||||
self.peek.take()
|
||||
} else {
|
||||
self.peek = self.iter.next();
|
||||
if self.peek.is_some() {
|
||||
Some(self.element.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
type SizeHint = (usize, Option<usize>);
|
||||
const fn add(a: SizeHint, b: SizeHint) -> SizeHint {
|
||||
let min = a.0.saturating_add(b.0);
|
||||
let max = match (a.1, b.1) {
|
||||
(Some(x), Some(y)) => x.checked_add(y),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
(min, max)
|
||||
}
|
||||
|
||||
fn add_scalar(sh: SizeHint, x: usize) -> SizeHint {
|
||||
let (mut low, mut hi) = sh;
|
||||
low = low.saturating_add(x);
|
||||
hi = hi.and_then(|elt| elt.checked_add(x));
|
||||
(low, hi)
|
||||
}
|
||||
// 2 * SH + { 1 or 0 }
|
||||
let has_peek = usize::from(self.peek.is_some());
|
||||
let sh = self.iter.size_hint();
|
||||
add_scalar(add(sh, sh), has_peek)
|
||||
}
|
||||
|
||||
fn fold<B, F>(mut self, init: B, mut f: F) -> B
|
||||
where
|
||||
Self: Sized,
|
||||
F: FnMut(B, Self::Item) -> B,
|
||||
{
|
||||
let mut accum = init;
|
||||
|
||||
if let Some(x) = self.peek.take() {
|
||||
accum = f(accum, x);
|
||||
}
|
||||
|
||||
let element = &mut self.element;
|
||||
|
||||
self.iter.fold(accum, |accum, x| {
|
||||
let accum = f(accum, element.clone());
|
||||
f(accum, x)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> FusedIterator for Intersperse<I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Clone,
|
||||
{
|
||||
}
|
||||
|
||||
/// 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 = JsSymbol::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)?
|
||||
.to_std_string()
|
||||
.map_err(|e| JsNativeError::syntax().with_message(e.to_string()))?;
|
||||
|
||||
// 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::<serde_json::Value>(&json_string) {
|
||||
return Err(JsNativeError::syntax().with_message(e.to_string()).into());
|
||||
}
|
||||
|
||||
// 3. Let scriptString be the string-concatenation of "(", jsonString, and ");".
|
||||
// TODO: fix script read for eval
|
||||
let script_string = format!("({json_string});");
|
||||
|
||||
// 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 mut parser = Parser::new(script_string.as_bytes());
|
||||
parser.set_json_parse();
|
||||
let statement_list = parser.parse_all(context.interner_mut())?;
|
||||
let code_block = context.compile_json_parse(&statement_list)?;
|
||||
let unfiltered = context.execute(code_block)?;
|
||||
|
||||
// 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 = JsObject::with_object_proto(context);
|
||||
|
||||
// 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()? {
|
||||
// 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")
|
||||
.clone();
|
||||
|
||||
// 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.into(), context)?;
|
||||
}
|
||||
// 3. Else,
|
||||
else {
|
||||
// a. Perform ? CreateDataProperty(val, P, newElement).
|
||||
obj.create_data_property(p, 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 = js_string!();
|
||||
|
||||
// 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()? {
|
||||
// 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 => js_string!(" "),
|
||||
IntegerOrInfinity::NegativeInfinity => js_string!(),
|
||||
IntegerOrInfinity::Integer(i) if i < 1 => js_string!(),
|
||||
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.
|
||||
js_string!(s.get(..10).unwrap_or(s))
|
||||
// 8. Else,
|
||||
} else {
|
||||
// a. Let gap be the empty String.
|
||||
js_string!()
|
||||
};
|
||||
|
||||
// 9. Let wrapper be ! OrdinaryObjectCreate(%Object.prototype%).
|
||||
let wrapper = JsObject::with_object_proto(context);
|
||||
|
||||
// 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, js_string!(), &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(js_string!("null")));
|
||||
}
|
||||
|
||||
// 6. If value is true, return "true".
|
||||
// 7. If value is false, return "false".
|
||||
if value.is_boolean() {
|
||||
return Ok(Some(js_string!(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(js_string!("null")));
|
||||
}
|
||||
|
||||
// 10. If Type(value) is BigInt, throw a TypeError exception.
|
||||
if value.is_bigint() {
|
||||
return Err(JsNativeError::typ()
|
||||
.with_message("cannot serialize bigint to JSON")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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()? {
|
||||
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 {
|
||||
let mut buf = [0; 2];
|
||||
// 1. Let product be the String value consisting solely of the code unit 0x0022 (QUOTATION MARK).
|
||||
let mut product = vec!['"' as u16];
|
||||
|
||||
// 2. For each code point C of ! StringToCodePoints(value), do
|
||||
for code_point in value.code_points() {
|
||||
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.
|
||||
CodePoint::Unicode('\u{0008}') => product.extend_from_slice(utf16!(r"\b")),
|
||||
CodePoint::Unicode('\u{0009}') => product.extend_from_slice(utf16!(r"\t")),
|
||||
CodePoint::Unicode('\u{000A}') => product.extend_from_slice(utf16!(r"\n")),
|
||||
CodePoint::Unicode('\u{000C}') => product.extend_from_slice(utf16!(r"\f")),
|
||||
CodePoint::Unicode('\u{000D}') => product.extend_from_slice(utf16!(r"\r")),
|
||||
CodePoint::Unicode('\u{0022}') => product.extend_from_slice(utf16!(r#"\""#)),
|
||||
CodePoint::Unicode('\u{005C}') => product.extend_from_slice(utf16!(r"\\")),
|
||||
// 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
|
||||
// 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).
|
||||
CodePoint::Unicode(c) if c < '\u{0020}' => {
|
||||
product.extend(format!("\\u{:04x}", c as u32).encode_utf16());
|
||||
}
|
||||
CodePoint::UnpairedSurrogate(surr) => {
|
||||
product.extend(format!("\\u{surr:04x}").encode_utf16());
|
||||
}
|
||||
// c. Else,
|
||||
CodePoint::Unicode(c) => {
|
||||
// i. Set product to the string-concatenation of product and ! UTF16EncodeCodePoint(C).
|
||||
product.extend_from_slice(c.encode_utf16(&mut buf));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK).
|
||||
product.push('"' as u16);
|
||||
|
||||
// 4. Return product.
|
||||
js_string!(&product[..])
|
||||
}
|
||||
|
||||
/// `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 Err(JsNativeError::typ()
|
||||
.with_message("cyclic object value")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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 = js_string!(&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).
|
||||
let mut member = Self::quote_json_string(p).to_vec();
|
||||
|
||||
// ii. Set member to the string-concatenation of member and ":".
|
||||
member.push(':' as u16);
|
||||
|
||||
// iii. If state.[[Gap]] is not the empty String, then
|
||||
if !state.gap.is_empty() {
|
||||
// 1. Set member to the string-concatenation of member and the code unit 0x0020 (SPACE).
|
||||
member.push(' ' as u16);
|
||||
}
|
||||
|
||||
// iv. Set member to the string-concatenation of member and strP.
|
||||
member.extend_from_slice(&str_p);
|
||||
|
||||
// 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 "{}".
|
||||
js_string!("{}")
|
||||
// 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 "}".
|
||||
let separator = utf16!(",");
|
||||
let result = once(utf16!("{"))
|
||||
.chain(intersperse(partial.iter().map(Vec::as_slice), separator))
|
||||
.chain(once(utf16!("}")))
|
||||
.flatten()
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
js_string!(&result[..])
|
||||
// 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 mut separator = utf16!(",\n").to_vec();
|
||||
separator.extend_from_slice(&state.indent);
|
||||
// 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.
|
||||
// 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 "}".
|
||||
let result = [utf16!("{\n"), &state.indent[..]]
|
||||
.into_iter()
|
||||
.chain(intersperse(partial.iter().map(Vec::as_slice), &separator))
|
||||
.chain([utf16!("\n"), &stepback[..], utf16!("}")].into_iter())
|
||||
.flatten()
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
js_string!(&result[..])
|
||||
}
|
||||
};
|
||||
|
||||
// 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 Err(JsNativeError::typ()
|
||||
.with_message("cyclic object value")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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 = js_string!(&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(Cow::Owned(str_p.to_vec()));
|
||||
// c. Else,
|
||||
} else {
|
||||
// i. Append "null" to partial.
|
||||
partial.push(Cow::Borrowed(utf16!("null")));
|
||||
}
|
||||
|
||||
// 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 "[]".
|
||||
js_string!("[]")
|
||||
// 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 "]".
|
||||
let separator = utf16!(",");
|
||||
let result = once(utf16!("["))
|
||||
.chain(intersperse(partial.iter().map(Cow::as_ref), separator))
|
||||
.chain(once(utf16!("]")))
|
||||
.flatten()
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
js_string!(&result[..])
|
||||
// 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 mut separator = utf16!(",\n").to_vec();
|
||||
separator.extend_from_slice(&state.indent);
|
||||
// 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.
|
||||
// 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 "]".
|
||||
let result = [utf16!("[\n"), &state.indent[..]]
|
||||
.into_iter()
|
||||
.chain(intersperse(partial.iter().map(Cow::as_ref), &separator))
|
||||
.chain([utf16!("\n"), &stepback[..], utf16!("]")].into_iter())
|
||||
.flatten()
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
js_string!(&result[..])
|
||||
}
|
||||
};
|
||||
|
||||
// 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>>,
|
||||
}
|
||||
468
javascript-engine/external/boa/boa_engine/src/builtins/json/tests.rs
vendored
Normal file
468
javascript-engine/external/boa/boa_engine/src/builtins/json/tests.rs
vendored
Normal file
@@ -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"));
|
||||
}
|
||||
159
javascript-engine/external/boa/boa_engine/src/builtins/map/map_iterator.rs
vendored
Normal file
159
javascript-engine/external/boa/boa_engine/src/builtins/map/map_iterator.rs
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
//! This module implements the `MapIterator` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-map-iterator-objects
|
||||
|
||||
use super::ordered_map::MapLock;
|
||||
use crate::{
|
||||
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue},
|
||||
error::JsNativeError,
|
||||
object::{JsObject, ObjectData},
|
||||
property::{PropertyDescriptor, PropertyNameKind},
|
||||
symbol::JsSymbol,
|
||||
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-map-iterator-objects
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct MapIterator {
|
||||
iterated_map: Option<JsObject>,
|
||||
map_next_index: usize,
|
||||
#[unsafe_ignore_trace]
|
||||
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());
|
||||
}
|
||||
}
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("`this` is not a Map")
|
||||
.into())
|
||||
}
|
||||
|
||||
/// %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(|| JsNativeError::typ().with_message("`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().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 = JsSymbol::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
|
||||
}
|
||||
}
|
||||
598
javascript-engine/external/boa/boa_engine/src/builtins/map/mod.rs
vendored
Normal file
598
javascript-engine/external/boa/boa_engine/src/builtins/map/mod.rs
vendored
Normal file
@@ -0,0 +1,598 @@
|
||||
//! Boa's implementation of ECMAScript's global `Map` object.
|
||||
//!
|
||||
//! The ECMAScript `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
|
||||
|
||||
use self::{map_iterator::MapIterator, ordered_map::OrderedMap};
|
||||
use super::JsArgs;
|
||||
use crate::{
|
||||
builtins::BuiltIn,
|
||||
context::intrinsics::StandardConstructors,
|
||||
error::JsNativeError,
|
||||
native_function::NativeFunction,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder,
|
||||
FunctionObjectBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::{Attribute, PropertyNameKind},
|
||||
symbol::JsSymbol,
|
||||
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;
|
||||
|
||||
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 =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species))
|
||||
.name("get [Symbol.species]")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let get_size =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_size))
|
||||
.name("get size")
|
||||
.length(0)
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let entries_function =
|
||||
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(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(
|
||||
JsSymbol::species(),
|
||||
Some(get_species),
|
||||
None,
|
||||
Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
"entries",
|
||||
entries_function.clone(),
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
JsSymbol::iterator(),
|
||||
entries_function,
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.property(
|
||||
JsSymbol::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 Err(JsNativeError::typ()
|
||||
.with_message("calling a builtin Map constructor without new is forbidden")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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], _: &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());
|
||||
}
|
||||
}
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("'this' is not a Map")
|
||||
.into())
|
||||
}
|
||||
|
||||
/// `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],
|
||||
_: &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());
|
||||
}
|
||||
}
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("'this' is not a Map")
|
||||
.into())
|
||||
}
|
||||
|
||||
/// `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],
|
||||
_: &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());
|
||||
}
|
||||
}
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("'this' is not a Map")
|
||||
.into())
|
||||
}
|
||||
|
||||
/// `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], _: &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() {
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("'this' is not a Map")
|
||||
.into())
|
||||
}
|
||||
|
||||
/// `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], _: &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());
|
||||
}
|
||||
}
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("'this' is not a Map")
|
||||
.into())
|
||||
}
|
||||
|
||||
/// `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], _: &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() {
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("'this' is not a Map")
|
||||
.into())
|
||||
}
|
||||
|
||||
/// `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(|| JsNativeError::typ().with_message("`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(|| {
|
||||
JsNativeError::typ().with_message(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().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(|| {
|
||||
JsNativeError::typ().with_message("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 Some(next_item) = next else {
|
||||
return Ok(target.clone().into());
|
||||
};
|
||||
|
||||
let next_item = next_item.value(context)?;
|
||||
|
||||
let Some(next_item) = next_item.as_object() else {
|
||||
// d. If Type(nextItem) is not Object, then
|
||||
// i. Let error be ThrowCompletion(a newly created TypeError object).
|
||||
let err = Err(JsNativeError::typ()
|
||||
.with_message("cannot get key and value from primitive item of `iterable`")
|
||||
.into());
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
235
javascript-engine/external/boa/boa_engine/src/builtins/map/ordered_map.rs
vendored
Normal file
235
javascript-engine/external/boa/boa_engine/src/builtins/map/ordered_map.rs
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
//! Implements a map type that preserves insertion order.
|
||||
|
||||
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 {
|
||||
Self::Key(v) => v.hash(state),
|
||||
Self::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> {
|
||||
/// Creates a new empty `OrderedMap`.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: IndexMap::new(),
|
||||
lock: 0,
|
||||
empty_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new empty `OrderedMap` with the specified capacity.
|
||||
#[must_use]
|
||||
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.
|
||||
#[must_use]
|
||||
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.
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.len() - self.empty_count
|
||||
}
|
||||
|
||||
/// Returns true if the map contains no elements.
|
||||
///
|
||||
/// Computes in **O(1)** time.
|
||||
#[must_use]
|
||||
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.
|
||||
#[must_use]
|
||||
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();
|
||||
}
|
||||
}
|
||||
406
javascript-engine/external/boa/boa_engine/src/builtins/map/tests.rs
vendored
Normal file
406
javascript-engine/external/boa/boa_engine/src/builtins/map/tests.rs
vendored
Normal file
@@ -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\"");
|
||||
}
|
||||
1021
javascript-engine/external/boa/boa_engine/src/builtins/math/mod.rs
vendored
Normal file
1021
javascript-engine/external/boa/boa_engine/src/builtins/math/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
729
javascript-engine/external/boa/boa_engine/src/builtins/math/tests.rs
vendored
Normal file
729
javascript-engine/external/boa/boa_engine/src/builtins/math/tests.rs
vendored
Normal file
@@ -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.774_964_387_392_123);
|
||||
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);
|
||||
}
|
||||
227
javascript-engine/external/boa/boa_engine/src/builtins/mod.rs
vendored
Normal file
227
javascript-engine/external/boa/boa_engine/src/builtins/mod.rs
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
//! Boa's ECMAScript built-in object implementations, e.g. Object, String, Math, Array, etc.
|
||||
//!
|
||||
//! This module also contains a JavaScript Console implementation.
|
||||
|
||||
pub mod array;
|
||||
pub mod array_buffer;
|
||||
pub mod async_function;
|
||||
pub mod async_generator;
|
||||
pub mod async_generator_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;
|
||||
pub mod uri;
|
||||
pub mod weak;
|
||||
|
||||
#[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, async_generator::AsyncGenerator,
|
||||
async_generator_function::AsyncGeneratorFunction, generator::Generator,
|
||||
generator_function::GeneratorFunction, typed_array::TypedArray, uri::Uri, weak::WeakRef,
|
||||
},
|
||||
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 ECMAScript/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 ECMAScript/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 ECMAScript/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.
|
||||
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
|
||||
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,
|
||||
AsyncGenerator,
|
||||
AsyncGeneratorFunction,
|
||||
Uri,
|
||||
WeakRef,
|
||||
};
|
||||
|
||||
#[cfg(feature = "intl")]
|
||||
init_builtin::<intl::Intl>(context);
|
||||
|
||||
#[cfg(feature = "console")]
|
||||
init_builtin::<console::Console>(context);
|
||||
}
|
||||
|
||||
/// A utility trait to make working with function arguments easier.
|
||||
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)
|
||||
}
|
||||
}
|
||||
35
javascript-engine/external/boa/boa_engine/src/builtins/nan/mod.rs
vendored
Normal file
35
javascript-engine/external/boa/boa_engine/src/builtins/nan/mod.rs
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
//! Boa's implementation of ECMAScript's 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())
|
||||
}
|
||||
}
|
||||
10
javascript-engine/external/boa/boa_engine/src/builtins/nan/tests.rs
vendored
Normal file
10
javascript-engine/external/boa/boa_engine/src/builtins/nan/tests.rs
vendored
Normal file
@@ -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");
|
||||
}
|
||||
80
javascript-engine/external/boa/boa_engine/src/builtins/number/conversions.rs
vendored
Normal file
80
javascript-engine/external/boa/boa_engine/src/builtins/number/conversions.rs
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
/// Converts a 64-bit floating point number to an `i32` according to the [`ToInt32`][ToInt32] algorithm.
|
||||
///
|
||||
/// [ToInt32]: https://tc39.es/ecma262/#sec-toint32
|
||||
#[allow(clippy::float_cmp)]
|
||||
pub(crate) fn f64_to_int32(number: f64) -> i32 {
|
||||
const SIGN_MASK: u64 = 0x8000_0000_0000_0000;
|
||||
const EXPONENT_MASK: u64 = 0x7FF0_0000_0000_0000;
|
||||
const SIGNIFICAND_MASK: u64 = 0x000F_FFFF_FFFF_FFFF;
|
||||
const HIDDEN_BIT: u64 = 0x0010_0000_0000_0000;
|
||||
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;
|
||||
|
||||
fn is_denormal(number: f64) -> bool {
|
||||
(number.to_bits() & EXPONENT_MASK) == 0
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn significand(number: f64) -> u64 {
|
||||
let d64 = number.to_bits();
|
||||
let significand = d64 & SIGNIFICAND_MASK;
|
||||
|
||||
if is_denormal(number) {
|
||||
significand
|
||||
} else {
|
||||
significand + HIDDEN_BIT
|
||||
}
|
||||
}
|
||||
|
||||
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) & 0xFFFF_FFFF
|
||||
};
|
||||
|
||||
(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
|
||||
pub(crate) fn f64_to_uint32(number: f64) -> u32 {
|
||||
f64_to_int32(number) as u32
|
||||
}
|
||||
1198
javascript-engine/external/boa/boa_engine/src/builtins/number/mod.rs
vendored
Normal file
1198
javascript-engine/external/boa/boa_engine/src/builtins/number/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
887
javascript-engine/external/boa/boa_engine/src/builtins/number/tests.rs
vendored
Normal file
887
javascript-engine/external/boa/boa_engine/src/builtins/number/tests.rs
vendored
Normal file
@@ -0,0 +1,887 @@
|
||||
#![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))")
|
||||
);
|
||||
}
|
||||
160
javascript-engine/external/boa/boa_engine/src/builtins/object/for_in_iterator.rs
vendored
Normal file
160
javascript-engine/external/boa/boa_engine/src/builtins/object/for_in_iterator.rs
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
//! This module implements the `ForInIterator` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-for-in-iterator-objects
|
||||
|
||||
use crate::{
|
||||
builtins::{function::make_builtin_fn, iterable::create_iter_result_object},
|
||||
error::JsNativeError,
|
||||
object::{JsObject, ObjectData},
|
||||
property::PropertyDescriptor,
|
||||
property::PropertyKey,
|
||||
symbol::JsSymbol,
|
||||
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(|| JsNativeError::typ().with_message("`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), 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 = JsSymbol::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
|
||||
}
|
||||
}
|
||||
1396
javascript-engine/external/boa/boa_engine/src/builtins/object/mod.rs
vendored
Normal file
1396
javascript-engine/external/boa/boa_engine/src/builtins/object/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
422
javascript-engine/external/boa/boa_engine/src/builtins/object/tests.rs
vendored
Normal file
422
javascript-engine/external/boa/boa_engine/src/builtins/object/tests.rs
vendored
Normal file
@@ -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 = "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 = "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 = "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"),
|
||||
]);
|
||||
}
|
||||
2297
javascript-engine/external/boa/boa_engine/src/builtins/promise/mod.rs
vendored
Normal file
2297
javascript-engine/external/boa/boa_engine/src/builtins/promise/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
21
javascript-engine/external/boa/boa_engine/src/builtins/promise/tests.rs
vendored
Normal file
21
javascript-engine/external/boa/boa_engine/src/builtins/promise/tests.rs
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
use crate::{context::ContextBuilder, forward, job::SimpleJobQueue};
|
||||
|
||||
#[test]
|
||||
fn promise() {
|
||||
let queue = SimpleJobQueue::new();
|
||||
let mut context = ContextBuilder::new().job_queue(&queue).build();
|
||||
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));
|
||||
context.run_jobs();
|
||||
let after_completion = forward(&mut context, "count");
|
||||
assert_eq!(after_completion, String::from("3"));
|
||||
}
|
||||
196
javascript-engine/external/boa/boa_engine/src/builtins/proxy/mod.rs
vendored
Normal file
196
javascript-engine/external/boa/boa_engine/src/builtins/proxy/mod.rs
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
//! Boa's implementation of ECMAScript's 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 std::cell::Cell;
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
error::JsNativeError,
|
||||
native_function::NativeFunction,
|
||||
object::{ConstructorBuilder, FunctionObjectBuilder, 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) -> JsResult<(JsObject, JsObject)> {
|
||||
self.data.clone().ok_or_else(|| {
|
||||
JsNativeError::typ()
|
||||
.with_message("Proxy object has empty handler and target")
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
/// `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 Err(JsNativeError::typ()
|
||||
.with_message("Proxy constructor called on undefined new target")
|
||||
.into());
|
||||
}
|
||||
|
||||
// 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(|| {
|
||||
JsNativeError::typ().with_message("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(|| {
|
||||
JsNativeError::typ().with_message("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.
|
||||
FunctionObjectBuilder::new(
|
||||
context,
|
||||
NativeFunction::from_copy_closure_with_captures(
|
||||
|_, _, 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())
|
||||
},
|
||||
Cell::new(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 = JsObject::with_object_proto(context);
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
420
javascript-engine/external/boa/boa_engine/src/builtins/reflect/mod.rs
vendored
Normal file
420
javascript-engine/external/boa/boa_engine/src/builtins/reflect/mod.rs
vendored
Normal file
@@ -0,0 +1,420 @@
|
||||
//! Boa's implementation of ECMAScript's global `Reflect` object.
|
||||
//!
|
||||
//! The `Reflect` global object is a built-in object that provides methods for interceptable
|
||||
//! ECMAScript 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},
|
||||
error::JsNativeError,
|
||||
object::ObjectInitializer,
|
||||
property::Attribute,
|
||||
symbol::JsSymbol,
|
||||
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 = JsSymbol::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(|| JsNativeError::typ().with_message("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 Err(JsNativeError::typ()
|
||||
.with_message("target must be a function")
|
||||
.into());
|
||||
}
|
||||
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(|| JsNativeError::typ().with_message("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.
|
||||
new_target.as_constructor().ok_or_else(|| {
|
||||
JsNativeError::typ().with_message("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(|| JsNativeError::typ().with_message("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(|| {
|
||||
JsNativeError::typ().with_message("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(|| JsNativeError::typ().with_message("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(|| JsNativeError::typ().with_message("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
|
||||
// 3.a. Set receiver to target.
|
||||
let receiver = args
|
||||
.get(2)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| 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 {
|
||||
Err(JsNativeError::typ()
|
||||
.with_message("target must be an object")
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(|| JsNativeError::typ().with_message("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(|| JsNativeError::typ().with_message("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(|| JsNativeError::typ().with_message("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(|| JsNativeError::typ().with_message("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(|| JsNativeError::typ().with_message("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(|| JsNativeError::typ().with_message("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 = args
|
||||
.get(3)
|
||||
.cloned()
|
||||
.unwrap_or_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(|| JsNativeError::typ().with_message("target must be an object"))?;
|
||||
let proto = match args.get_or_undefined(1) {
|
||||
JsValue::Object(obj) => Some(obj.clone()),
|
||||
JsValue::Null => None,
|
||||
_ => {
|
||||
return Err(JsNativeError::typ()
|
||||
.with_message("proto must be an object or null")
|
||||
.into())
|
||||
}
|
||||
};
|
||||
Ok(target.__set_prototype_of__(proto, context)?.into())
|
||||
}
|
||||
}
|
||||
191
javascript-engine/external/boa/boa_engine/src/builtins/reflect/tests.rs
vendored
Normal file
191
javascript-engine/external/boa/boa_engine/src/builtins/reflect/tests.rs
vendored
Normal file
@@ -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\"");
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user