feat: works
This commit is contained in:
@@ -10,7 +10,10 @@ pub struct MyContainer;
|
||||
|
||||
impl Container for MyContainer {
|
||||
fn fetch(&mut self, s: &str) -> String {
|
||||
format!("FETCHED: {}", s)
|
||||
// format!("FETCHED: {}", s)
|
||||
let r = "{\"result\":\"{}\"}".into();
|
||||
println!(">>>> {}", r);
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +28,12 @@ fn main() -> Result<()> {
|
||||
|linker| container::add_to_linker(linker, |cx| -> &mut MyContainer { &mut cx.imports }),
|
||||
|store, module, linker| Exports::instantiate(store, module, linker, |cx| &mut cx.exports),
|
||||
)?;
|
||||
let a = exports.eval_javascript(&mut store, "[script]");
|
||||
let a = exports.eval_javascript(&mut store, "let a = [];fetch('aaa')");
|
||||
|
||||
println!("Hello, world! {:?}", a);
|
||||
match a {
|
||||
Ok(a) => println!("Hello, world! {:?}", a),
|
||||
Err(e) => println!("ERROR: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
416
__wasm/wit-bindgen-sample/engine/Cargo.lock
generated
416
__wasm/wit-bindgen-sample/engine/Cargo.lock
generated
@@ -19,20 +19,152 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "boa_engine"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"boa_gc",
|
||||
"boa_interner",
|
||||
"boa_profiler",
|
||||
"boa_unicode",
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
"fast-float",
|
||||
"gc",
|
||||
"indexmap",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"regress",
|
||||
"rustc-hash",
|
||||
"ryu-js",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tap",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boa_gc"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"gc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boa_interner"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"phf",
|
||||
"rustc-hash",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boa_profiler"
|
||||
version = "0.15.0"
|
||||
|
||||
[[package]]
|
||||
name = "boa_unicode"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"unicode-general-category",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d07a982d1fb29db01e5a59b1918e03da4df7297eaeee7686ac45542fd4e59c8"
|
||||
|
||||
[[package]]
|
||||
name = "engine"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"boa_engine",
|
||||
"getrandom",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wit-bindgen-gen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast-float"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c"
|
||||
|
||||
[[package]]
|
||||
name = "gc"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3edaac0f5832202ebc99520cb77c932248010c4645d20be1dc62d6579f5b3752"
|
||||
dependencies = [
|
||||
"gc_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gc_derive"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60df8444f094ff7885631d80e78eb7d88c3c2361a98daaabb06256e4500db941"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
@@ -48,12 +180,127 @@ version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.40"
|
||||
@@ -83,6 +330,106 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regress"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a92ff21fe8026ce3f2627faaf43606f0b67b014dbc9ccf027181a804f75d92e"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
|
||||
|
||||
[[package]]
|
||||
name = "ryu-js"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.98"
|
||||
@@ -94,6 +441,35 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
@@ -118,6 +494,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-general-category"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1218098468b8085b19a2824104c70d976491d247ce194bbd9dc77181150cdfd6"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.2"
|
||||
@@ -151,6 +533,40 @@ version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-gen-core"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -8,6 +8,20 @@ edition = "2021"
|
||||
[lib]
|
||||
crate-type = ['cdylib']
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
incremental = true
|
||||
lto = true
|
||||
opt-level = "z"
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen-gen-core = { path = '../wit-bindgen/crates/gen-core' }
|
||||
wit-bindgen-rust = { path = '../wit-bindgen/crates/rust-wasm' }
|
||||
boa_engine = "0.15.0"
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[patch.crates-io]
|
||||
getrandom = { path = "./getrandom" }
|
||||
boa_engine = { path = "./boa/boa_engine" }
|
||||
11
__wasm/wit-bindgen-sample/engine/boa/.editorconfig
Normal file
11
__wasm/wit-bindgen-sample/engine/boa/.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[{Makefile,**.mk}]
|
||||
# Use tabs for indentation (Makefiles require tabs)
|
||||
indent_style = tab
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
30
__wasm/wit-bindgen-sample/engine/boa/.gitattributes
vendored
Normal file
30
__wasm/wit-bindgen-sample/engine/boa/.gitattributes
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Handle line endings automatically for files detected as text
|
||||
# and leave all files detected as binary untouched.
|
||||
* text=auto
|
||||
|
||||
#
|
||||
# The above will handle all files NOT found below
|
||||
#
|
||||
# These files are text and should be normalized (Convert crlf => lf)
|
||||
*.css eol=lf
|
||||
*.htm eol=lf
|
||||
*.html eol=lf
|
||||
*.js eol=lf
|
||||
*.json eol=lf
|
||||
*.sh eol=lf
|
||||
*.txt eol=lf
|
||||
*.yml eol=lf
|
||||
*.rs eol=lf
|
||||
*.toml eol=lf
|
||||
*.lock eol=lf
|
||||
*.md eol=lf
|
||||
*.svg eol=lf
|
||||
|
||||
# These files are binary and should be left untouched
|
||||
# (binary is a macro for -text -diff)
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.jar binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.png binary
|
||||
1
__wasm/wit-bindgen-sample/engine/boa/.github/FUNDING.yml
vendored
Normal file
1
__wasm/wit-bindgen-sample/engine/boa/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
open_collective: boa
|
||||
52
__wasm/wit-bindgen-sample/engine/boa/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
52
__wasm/wit-bindgen-sample/engine/boa/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: "\U0001F41B Bug report"
|
||||
about: Create a report to help us improve
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for reporting a bug in Boa! This will make us improve the engine. But first, fill the following template so that we better understand what's happening. Feel free to add or remove sections as you feel appropriate.
|
||||
-->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
<!-- E.g.:
|
||||
The variable statement is not working as expected, it always adds 10 when assigning a number to a variable"
|
||||
-->
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the issue, or JavaScript code that causes this failure.
|
||||
|
||||
<!-- E.g.:
|
||||
This JavaScript code reproduces the issue:
|
||||
```javascript
|
||||
var a = 10;
|
||||
a;
|
||||
```
|
||||
-->
|
||||
|
||||
**Expected behavior**
|
||||
Explain what you expected to happen, and what is happening instead.
|
||||
|
||||
<!-- E.g.:
|
||||
Running this code, `a` should be set to `10` and printed, but `a` is instead set to `20`. The expected behaviour can be found in the [ECMAScript specification][spec].
|
||||
|
||||
[spec]: https://tc39.es/ecma262/#sec-variable-statement-runtime-semantics-evaluation
|
||||
-->
|
||||
|
||||
**Build environment (please complete the following information):**
|
||||
|
||||
- OS: [e.g. Fedora Linux]
|
||||
- Version: [e.g. 32]
|
||||
- Target triple: [e.g. x86_64-unknown-linux-gnu]
|
||||
- Rustc version: [e.g. rustc 1.43.0 (4fb7144ed 2020-04-20), running `rustc -V`]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
<!-- E.g.:
|
||||
You can find more information in [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var).
|
||||
-->
|
||||
5
__wasm/wit-bindgen-sample/engine/boa/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
__wasm/wit-bindgen-sample/engine/boa/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discord channel
|
||||
url: https://discord.gg/tUFFk9Y
|
||||
about: Please ask and answer questions here.
|
||||
13
__wasm/wit-bindgen-sample/engine/boa/.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
13
__wasm/wit-bindgen-sample/engine/boa/.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: Custom
|
||||
about: Open an issue in the repo that is neither a bug or a feature, such a new idea
|
||||
title: ""
|
||||
labels: ""
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for contributing to Boa! Please, let us know how can we help you.
|
||||
-->
|
||||
|
||||
E.g.: I think we should improve the way the JavaScript interpreter works by...
|
||||
43
__wasm/wit-bindgen-sample/engine/boa/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
43
__wasm/wit-bindgen-sample/engine/boa/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: "\U0001F680 Feature request"
|
||||
about: Suggest a new ECMAScript feature to be implemented, or a new capability of the engine.
|
||||
title: ""
|
||||
labels: enhancement
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for adding a feature request to Boa! As this is an experimental JavaScript engine, there will probably be many ECMAScript features left to implement. In order to understand the feature request as best as possible, please fill the following template. Feel free to add or remove sections as needed.
|
||||
-->
|
||||
|
||||
**ECMASCript feature**
|
||||
Explain the ECMAScript feature that you'd like to see implemented.
|
||||
|
||||
<!-- E.g.:
|
||||
I would like to see `switch` statement parsing and execution implemented. [ECMAScript specification][spec].
|
||||
|
||||
[spec]: https://tc39.es/ecma262/#sec-switch-statement
|
||||
-->
|
||||
|
||||
**Example code**
|
||||
Give a code example that should work after the implementation of this feature.
|
||||
|
||||
<!-- E.g.:
|
||||
This code should now work and give the expected result:
|
||||
```javascript
|
||||
let a = "hello";
|
||||
let b;
|
||||
switch (a) {
|
||||
case 'hello':
|
||||
b = 'world';
|
||||
break;
|
||||
case 'world':
|
||||
b = 'hello';
|
||||
break;
|
||||
default:
|
||||
b = 'hello world';
|
||||
}
|
||||
b;
|
||||
```
|
||||
The expected output is `world`.
|
||||
-->
|
||||
12
__wasm/wit-bindgen-sample/engine/boa/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
12
__wasm/wit-bindgen-sample/engine/boa/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<!---
|
||||
Thank you for contributing to Boa! Please fill out the template below, and remove or add any
|
||||
information as you feel neccesary.
|
||||
--->
|
||||
|
||||
This Pull Request fixes/closes #{issue_num}.
|
||||
|
||||
It changes the following:
|
||||
|
||||
-
|
||||
-
|
||||
-
|
||||
10
__wasm/wit-bindgen-sample/engine/boa/.github/codecov.yml
vendored
Normal file
10
__wasm/wit-bindgen-sample/engine/boa/.github/codecov.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
github_checks:
|
||||
annotations: false
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 5% # allow 5% coverage variance
|
||||
|
||||
patch: off
|
||||
50
__wasm/wit-bindgen-sample/engine/boa/.github/dependabot.yml
vendored
Normal file
50
__wasm/wit-bindgen-sample/engine/boa/.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: cargo
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: cargo
|
||||
directory: /boa_cli/
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: cargo
|
||||
directory: /boa_engine/
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: cargo
|
||||
directory: /boa_gc/
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: cargo
|
||||
directory: /boa_interner/
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: cargo
|
||||
directory: /boa_profiler/
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: cargo
|
||||
directory: /boa_tester/
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: cargo
|
||||
directory: /boa_unicode/
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: cargo
|
||||
directory: /boa_wasm/
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: gitsubmodule
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
16
__wasm/wit-bindgen-sample/engine/boa/.github/release.yml
vendored
Normal file
16
__wasm/wit-bindgen-sample/engine/boa/.github/release.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# .github/release.yml
|
||||
|
||||
changelog:
|
||||
exclude:
|
||||
authors:
|
||||
- dependabot
|
||||
categories:
|
||||
- title: Feature Enhancements
|
||||
labels:
|
||||
- enhancement
|
||||
- title: Bug Fixes
|
||||
labels:
|
||||
- bug
|
||||
- title: Internal Improvements
|
||||
labels:
|
||||
- Internal
|
||||
162
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/bors.yml
vendored
Normal file
162
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/bors.yml
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
name: bors
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- staging
|
||||
- trying
|
||||
|
||||
jobs:
|
||||
test_on_linux:
|
||||
name: Tests on Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: -v
|
||||
|
||||
test_on_windows:
|
||||
name: Tests on Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: -v
|
||||
|
||||
test_on_macos:
|
||||
name: Tests on MacOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: -v
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
components: rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
components: clippy
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }}
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- --verbose
|
||||
|
||||
examples:
|
||||
name: Examples
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-examples-${{ hashFiles('**/Cargo.lock') }}
|
||||
- run: cd boa_examples
|
||||
- name: Build examples
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
- name: Run example classes
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: --bin classes
|
||||
|
||||
doc:
|
||||
name: Documentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-doc-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Generate documentation
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: -v --document-private-items --all-features
|
||||
54
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/master.yml
vendored
Normal file
54
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/master.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: Main workflows
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||
name: Upload docs and run benchmarks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
!target/doc_upload
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-doc-bench-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Generate documentation
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: -v --document-private-items --all-features
|
||||
- run: echo "<meta http-equiv=refresh content=0;url=boa_engine/index.html>" > target/doc/index.html
|
||||
- run: |
|
||||
if [ -d target/doc_upload ]; then rm -rf target/doc_upload; fi
|
||||
mkdir target/doc_upload && mv target/doc target/doc_upload/doc
|
||||
- name: Upload documentation
|
||||
uses: crazy-max/ghaction-github-pages@v3.0.0
|
||||
with:
|
||||
target_branch: gh-pages
|
||||
keep_history: true
|
||||
build_dir: target/doc_upload
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run benchmark
|
||||
run: cargo bench -p boa_engine -- --output-format bencher | tee output.txt
|
||||
- name: Store benchmark result
|
||||
uses: benchmark-action/github-action-benchmark@v1.14.0
|
||||
with:
|
||||
name: Boa Benchmarks
|
||||
tool: "cargo"
|
||||
output-file-path: output.txt
|
||||
auto-push: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
34
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/pull_request.yml
vendored
Normal file
34
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/pull_request.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Benchmarks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
runBenchmark:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'run-benchmark')
|
||||
name: run benchmark
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- uses: boa-dev/criterion-compare-action@v3.2.2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branchName: ${{ github.base_ref }}
|
||||
cwd: ./boa_engine
|
||||
62
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/release.yml
vendored
Normal file
62
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: Publish Release
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Install cargo-workspaces
|
||||
uses: actions-rs/install@v0.1
|
||||
with:
|
||||
crate: cargo-workspaces
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
PATCH: ${{ github.run_number }}
|
||||
shell: bash
|
||||
run: |
|
||||
git config --global user.email "runner@gha.local"
|
||||
git config --global user.name "Github Action"
|
||||
cargo workspaces publish --from-git --yes minor
|
||||
doc-publish:
|
||||
# needs: publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
- run: npm ci
|
||||
- name: Cache npm build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
target
|
||||
boa_wasm/pkg
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-npm-build-target-${{ hashFiles('**/package-lock.json') }}
|
||||
- run: npm run build:prod
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
publish_dir: ./dist
|
||||
destination_dir: playground
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
166
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/rust.yml
vendored
Normal file
166
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/rust.yml
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: Continuous integration
|
||||
|
||||
jobs:
|
||||
test_on_linux:
|
||||
name: Tests on Linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Run cargo-tarpaulin
|
||||
uses: actions-rs/tarpaulin@v0.1
|
||||
with:
|
||||
args: --features intl --ignore-tests
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
test_on_windows:
|
||||
name: Tests on Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: -v --features intl
|
||||
|
||||
test_on_macos:
|
||||
name: Tests on MacOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: -v --features intl
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
components: rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
components: clippy
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }}
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- --verbose
|
||||
|
||||
examples:
|
||||
name: Examples
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-examples-${{ hashFiles('**/Cargo.lock') }}
|
||||
- run: cd boa_examples
|
||||
- name: Build examples
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
- name: Run example classes
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: run
|
||||
args: --bin classes
|
||||
|
||||
doc:
|
||||
name: Documentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-doc-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Generate documentation
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: -v --document-private-items --all-features
|
||||
12
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/security_audit.yml
vendored
Normal file
12
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/security_audit.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Security audit
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/audit-check@v1.2.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
119
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/test262.yml
vendored
Normal file
119
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/test262.yml
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
name: EcmaScript official test suite (test262)
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
run_test262:
|
||||
name: Run the test262 test suite
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
path: boa
|
||||
- name: Install the Rust toolchain
|
||||
uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
key: ${{ runner.os }}-cargo-test262-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
# Run the test suite and upload the results
|
||||
- name: Checkout GitHub pages
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: gh-pages
|
||||
path: gh-pages
|
||||
|
||||
- name: Run the test262 test suite
|
||||
run: |
|
||||
cd boa
|
||||
mkdir ../results
|
||||
cargo run --release --bin boa_tester -- run -v -o ../results/test262
|
||||
cd ..
|
||||
|
||||
# Run the results comparison
|
||||
- name: Compare results
|
||||
if: github.event_name == 'pull_request'
|
||||
id: compare-non-vm
|
||||
shell: bash
|
||||
run: |
|
||||
cd boa
|
||||
comment="$(./target/release/boa_tester compare ../gh-pages/test262/refs/heads/main/latest.json ../results/test262/pull/latest.json -m)"
|
||||
comment="${comment//'%'/'%25'}"
|
||||
comment="${comment//$'\n'/'%0A'}"
|
||||
comment="${comment//$'\r'/'%0D'}"
|
||||
echo "::set-output name=comment::$comment"
|
||||
|
||||
- name: Get the PR number
|
||||
if: github.event_name == 'pull_request'
|
||||
id: pr-number
|
||||
uses: kkak10/pr-number-action@v1.3
|
||||
|
||||
- name: Find Previous Comment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: previous-comment
|
||||
with:
|
||||
issue-number: ${{ steps.pr-number.outputs.pr }}
|
||||
body-includes: Test262 conformance changes
|
||||
|
||||
- name: Update comment
|
||||
if: github.event_name == 'pull_request' && steps.previous-comment.outputs.comment-id
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
comment-id: ${{ steps.previous-comment.outputs.comment-id }}
|
||||
body: |
|
||||
### Test262 conformance changes
|
||||
|
||||
${{ steps.compare-non-vm.outputs.comment }}
|
||||
${{ steps.compare-vm.outputs.comment }}
|
||||
edit-mode: replace
|
||||
|
||||
- name: Write a new comment
|
||||
if: github.event_name == 'pull_request' && !steps.previous-comment.outputs.comment-id
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
issue-number: ${{ steps.pr-number.outputs.pr }}
|
||||
body: |
|
||||
### Test262 conformance changes
|
||||
|
||||
${{ steps.compare-non-vm.outputs.comment }}
|
||||
${{ steps.compare-vm.outputs.comment }}
|
||||
|
||||
# Commit changes to GitHub pages.
|
||||
- name: Commit files
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
cp -r ./results/test262/* ./gh-pages/test262/
|
||||
cd gh-pages
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add test262
|
||||
git commit -m "Add new test262 results" -a
|
||||
cd ..
|
||||
- name: Upload results
|
||||
if: github.event_name == 'push'
|
||||
uses: ad-m/github-push-action@v0.6.0
|
||||
with:
|
||||
directory: gh-pages
|
||||
branch: gh-pages
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
49
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/webassembly.yml
vendored
Normal file
49
__wasm/wit-bindgen-sample/engine/boa/.github/workflows/webassembly.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: Webassembly demo
|
||||
|
||||
jobs:
|
||||
check_style:
|
||||
name: Check webassembly demo style
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Check code formatting
|
||||
run: npx prettier --check .
|
||||
|
||||
build:
|
||||
name: Build webassembly demo
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
WASM_PACK_PATH: ~/.cargo/bin/wasm-pack
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1.0.7
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
profile: minimal
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- name: Cache npm build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
target
|
||||
~/.cargo/git
|
||||
~/.cargo/registry
|
||||
boa_wasm/pkg
|
||||
key: ${{ runner.os }}-npm-build-target-${{ hashFiles('**/package-lock.json') }}
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
34
__wasm/wit-bindgen-sample/engine/boa/.gitignore
vendored
Normal file
34
__wasm/wit-bindgen-sample/engine/boa/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# IDE
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# Vim
|
||||
*.*.swp
|
||||
*.*.swo
|
||||
|
||||
# Build
|
||||
target
|
||||
dist
|
||||
**/*.rs.bk
|
||||
node_modules
|
||||
.DS_Store
|
||||
yarn-error.log
|
||||
.vscode/settings.json
|
||||
|
||||
# tests/js/test.js is used for testing changes locally
|
||||
tests/js/test.js
|
||||
.boa_history
|
||||
|
||||
# Profiling
|
||||
*.string_data
|
||||
*.string_index
|
||||
*.events
|
||||
chrome_profiler.json
|
||||
*.mm_profdata
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Yarn
|
||||
.yarn
|
||||
.yarnrc.yml
|
||||
3
__wasm/wit-bindgen-sample/engine/boa/.gitmodules
vendored
Normal file
3
__wasm/wit-bindgen-sample/engine/boa/.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "test262"]
|
||||
path = test262
|
||||
url = https://github.com/tc39/test262.git
|
||||
10
__wasm/wit-bindgen-sample/engine/boa/.prettierignore
Normal file
10
__wasm/wit-bindgen-sample/engine/boa/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
||||
# Ignore artifacts:
|
||||
*.rs
|
||||
target
|
||||
node_modules
|
||||
boa_engine/benches/bench_scripts/mini_js.js
|
||||
boa_engine/benches/bench_scripts/clean_js.js
|
||||
boa_wasm/pkg
|
||||
dist
|
||||
test262
|
||||
tests/js/test.js
|
||||
35
__wasm/wit-bindgen-sample/engine/boa/.vscode/launch.json
vendored
Normal file
35
__wasm/wit-bindgen-sample/engine/boa/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Launch",
|
||||
"windows": {
|
||||
"program": "${workspaceFolder}/target/debug/boa.exe"
|
||||
},
|
||||
"program": "${workspaceFolder}/target/debug/boa",
|
||||
"args": ["${workspaceFolder}/tests/js/test.js"],
|
||||
"sourceLanguages": ["rust"]
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Launch (VM)",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"run",
|
||||
"--manifest-path",
|
||||
"./boa_cli/Cargo.toml",
|
||||
"--features",
|
||||
"vm"
|
||||
]
|
||||
},
|
||||
"args": ["-t", "${workspaceFolder}/tests/js/test.js"],
|
||||
"sourceLanguages": ["rust"]
|
||||
}
|
||||
]
|
||||
}
|
||||
129
__wasm/wit-bindgen-sample/engine/boa/.vscode/tasks.json
vendored
Normal file
129
__wasm/wit-bindgen-sample/engine/boa/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Cargo Run",
|
||||
"command": "cargo",
|
||||
"args": ["run", "--bin", "boa", "./tests/js/test.js"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
"RUST_BACKTRACE": "1"
|
||||
}
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Cargo Run (VM)",
|
||||
"command": "cargo",
|
||||
"args": ["run", "--", "-t", "../tests/js/test.js"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/boa_cli",
|
||||
"env": {
|
||||
"RUST_BACKTRACE": "1"
|
||||
}
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Cargo Run (Profiler)",
|
||||
"command": "cargo",
|
||||
"args": ["run", "--features", "Boa/profiler", "../tests/js/test.js"],
|
||||
"group": "build",
|
||||
"options": {
|
||||
"env": {
|
||||
"RUST_BACKTRACE": "full"
|
||||
},
|
||||
"cwd": "${workspaceFolder}/boa_cli"
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Cargo Run (Profiler & VM)",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"run",
|
||||
"--features",
|
||||
"Boa/profiler",
|
||||
"--features",
|
||||
"vm",
|
||||
"../tests/js/test.js"
|
||||
],
|
||||
"group": "build",
|
||||
"options": {
|
||||
"env": {
|
||||
"RUST_BACKTRACE": "full"
|
||||
},
|
||||
"cwd": "${workspaceFolder}/boa_cli"
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Get Tokens",
|
||||
"command": "cargo",
|
||||
"args": ["run", "--bin", "boa", "--", "-t=Debug", "./tests/js/test.js"],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Get AST",
|
||||
"command": "cargo",
|
||||
"args": ["run", "--bin", "boa", "--", "-a=Debug", "./tests/js/test.js"],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Cargo Test",
|
||||
"command": "cargo",
|
||||
"args": ["test"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "process",
|
||||
"label": "Cargo Test Build",
|
||||
"command": "cargo",
|
||||
"args": ["test", "--no-run"],
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
1209
__wasm/wit-bindgen-sample/engine/boa/CHANGELOG.md
Normal file
1209
__wasm/wit-bindgen-sample/engine/boa/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
128
__wasm/wit-bindgen-sample/engine/boa/CODE_OF_CONDUCT.md
Normal file
128
__wasm/wit-bindgen-sample/engine/boa/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[discord](https://discord.gg/tUFFk9Y) by contacting _JaseW_.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
115
__wasm/wit-bindgen-sample/engine/boa/CONTRIBUTING.md
Normal file
115
__wasm/wit-bindgen-sample/engine/boa/CONTRIBUTING.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Contributing to Boa
|
||||
|
||||
Boa welcomes contribution from everyone. Here are the guidelines if you are
|
||||
thinking of helping out:
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions to Boa or its dependencies should be made in the form of GitHub
|
||||
pull requests. Each pull request will be reviewed by a core contributor
|
||||
(someone with permission to land patches) and either landed in the main tree or
|
||||
given feedback for changes that would be required. All contributions should
|
||||
follow this format.
|
||||
|
||||
Should you wish to work on an issue, please claim it first by commenting on
|
||||
the GitHub issue that you want to work on it. This is to prevent duplicated
|
||||
efforts from contributors on the same issue.
|
||||
|
||||
Head over to [issues][issues] and check for "good first issue" labels to find
|
||||
good tasks to start with. If you come across words or jargon that do not make
|
||||
sense, please ask!
|
||||
|
||||
If you don't already have Rust installed [_rustup_][rustup] is the recommended
|
||||
tool to use. It will install Rust and allow you to switch between _nightly_,
|
||||
_stable_ and _beta_. You can also install additional components. In Linux, you
|
||||
can run:
|
||||
|
||||
```shell
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
```
|
||||
|
||||
Then simply clone this project and `cargo build`.
|
||||
|
||||
### Running the compiler
|
||||
|
||||
You can execute a Boa console by running `cargo run`, and you can compile a list
|
||||
of JavaScript files by running `cargo run -- file1.js file2.js` and so on.
|
||||
|
||||
### Debugging
|
||||
|
||||
Knowing how to debug the interpreter should help you resolve problems quite quickly.
|
||||
See [Debugging](./docs/debugging.md).
|
||||
|
||||
### Web Assembly
|
||||
|
||||
If you want to develop on the web assembly side you can run `yarn serve` and then go
|
||||
to <http://localhost:8080>.
|
||||
|
||||
### boa-unicode
|
||||
|
||||
Boa uses the library `boa-unicode` to query Unicode character properties and classes in lexer and parser. See [boa_unicode/README.md](./boa_unicode/README.md) for development and more information.
|
||||
|
||||
### Setup
|
||||
|
||||
#### VSCode Plugins
|
||||
|
||||
Either the [Rust (RLS)][rls_vscode] or the [Rust Analyzer][rust-analyzer_vscode]
|
||||
extensions are preferred. RLS is easier to set up but some of the development is
|
||||
moving towards Rust Analyzer. Both of these plugins will help you with your Rust
|
||||
Development
|
||||
|
||||
#### Tasks
|
||||
|
||||
There are some pre-defined tasks in [tasks.json](.vscode/tasks.json)
|
||||
|
||||
- Build - shift+cmd/ctrl+b should build and run cargo. You should be able to make changes and run this task.
|
||||
- Test - (there is no shortcut, you'll need to make one) - Runs `Cargo Test`.
|
||||
I personally set a shortcut of shift+cmd+option+T (or shift+ctrl+alt+T)
|
||||
|
||||
If you don't want to install everything on your machine, you can use the Dockerfile.
|
||||
Start VSCode in container mode (you may need the docker container plugin) and use the Dockerfile.
|
||||
|
||||
## Testing
|
||||
|
||||
Boa provides its own test suite, and can also run the official ECMAScript test suite. To run the Boa test
|
||||
suite, you can just run the normal `cargo test`, and to run the full ECMAScript test suite, you can run it
|
||||
with this command:
|
||||
|
||||
```shell
|
||||
cargo run --release --bin boa_tester -- run -v 2> error.log
|
||||
```
|
||||
|
||||
Note that this requires the `test262` submodule to be checked out, so you will need to run the following first:
|
||||
|
||||
```shell
|
||||
git submodule init && git submodule update
|
||||
```
|
||||
|
||||
This will run the test suite in verbose mode (you can remove the `-v` part to run it in non-verbose mode),
|
||||
and output nice colorings in the terminal. It will also output any panic information into the `error.log` file.
|
||||
|
||||
You can get some more verbose information that tells you the exact name of each test that is being run, useful
|
||||
for debugging purposes by setting up the verbose flag twice, for example `-vv`. If you want to know the output of
|
||||
each test that is executed, you can use the triple verbose (`-vvv`) flag.
|
||||
|
||||
If you want to only run one sub-suite or even one test (to just check if you fixed/broke something specific),
|
||||
you can do it with the `-s` parameter, and then passing the path to the sub-suite or test that you want to run. Note
|
||||
that the `-s` parameter value should be a path relative to the `test262` directory. For example, to run the number
|
||||
type tests, use `-s test/language/types/number`.
|
||||
|
||||
Finally, if you're using the verbose flag and running a sub suite with a small number of tests, then the output will
|
||||
be more readable if you disable parallelism with the `-d` flag. All together it might look something like:
|
||||
|
||||
```shell
|
||||
cargo run --release --bin boa_tester -- run -vv -d -s test/language/types/number 2> error.log
|
||||
```
|
||||
|
||||
## Communication
|
||||
|
||||
We have a Discord server, feel free to ask questions here:
|
||||
<https://discord.gg/tUFFk9Y>
|
||||
|
||||
[issues]: https://github.com/boa-dev/boa/issues
|
||||
[rustup]: https://rustup.rs/
|
||||
[rls_vscode]: https://marketplace.visualstudio.com/items?itemName=rust-lang.rust
|
||||
[rust-analyzer_vscode]: https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer
|
||||
1983
__wasm/wit-bindgen-sample/engine/boa/Cargo.lock
generated
Normal file
1983
__wasm/wit-bindgen-sample/engine/boa/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
__wasm/wit-bindgen-sample/engine/boa/Cargo.toml
Normal file
34
__wasm/wit-bindgen-sample/engine/boa/Cargo.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"boa_cli",
|
||||
"boa_engine",
|
||||
"boa_gc",
|
||||
"boa_interner",
|
||||
"boa_profiler",
|
||||
"boa_tester",
|
||||
"boa_unicode",
|
||||
"boa_wasm",
|
||||
"boa_examples",
|
||||
]
|
||||
|
||||
[workspace.metadata.workspaces]
|
||||
allow_branch = "main"
|
||||
|
||||
# The release profile, used for `cargo build --release`.
|
||||
[profile.release]
|
||||
# Enables "fat" LTO, for faster release builds
|
||||
lto = "fat"
|
||||
# Makes sure that all code is compiled together, for LTO
|
||||
codegen-units = 1
|
||||
|
||||
# The test profile, used for `cargo test`.
|
||||
[profile.test]
|
||||
# Enables thin local LTO and some optimizations.
|
||||
opt-level = 1
|
||||
|
||||
# The benchmark profile, used for `cargo bench`.
|
||||
[profile.bench]
|
||||
# Enables "fat" LTO, for faster benchmark builds
|
||||
lto = "fat"
|
||||
# Makes sure that all code is compiled together, for LTO
|
||||
codegen-units = 1
|
||||
21
__wasm/wit-bindgen-sample/engine/boa/LICENSE-MIT
Normal file
21
__wasm/wit-bindgen-sample/engine/boa/LICENSE-MIT
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Jason Williams
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
24
__wasm/wit-bindgen-sample/engine/boa/LICENSE-UNLICENSE
Normal file
24
__wasm/wit-bindgen-sample/engine/boa/LICENSE-UNLICENSE
Normal file
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org>
|
||||
115
__wasm/wit-bindgen-sample/engine/boa/README.md
Normal file
115
__wasm/wit-bindgen-sample/engine/boa/README.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Boa
|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
alt="Boa Logo"
|
||||
src="./assets/logo.svg"
|
||||
width="30%"
|
||||
/>
|
||||
</p>
|
||||
|
||||
This is an experimental Javascript lexer, parser and interpreter written in Rust.
|
||||
Currently, it has support for some of the language.
|
||||
|
||||
[![Build Status][build_badge]][build_link]
|
||||
[](https://codecov.io/gh/boa-dev/boa)
|
||||
[](https://crates.io/crates/Boa)
|
||||
[](https://docs.rs/Boa/)
|
||||
[](https://discord.gg/tUFFk9Y)
|
||||
|
||||
[build_badge]: https://github.com/boa-dev/boa/actions/workflows/rust.yml/badge.svg?event=push&branch=main
|
||||
[build_link]: https://github.com/boa-dev/boa/actions/workflows/rust.yml?query=event%3Apush+branch%3Amain
|
||||
|
||||
## Live Demo (WASM)
|
||||
|
||||
<https://boa-dev.github.io/boa/playground/>
|
||||
|
||||
You can get more verbose errors when running from the command line.
|
||||
|
||||
## Development documentation
|
||||
|
||||
You can check the internal development docs at <https://boa-dev.github.io/boa/doc>.
|
||||
|
||||
## Conformance
|
||||
|
||||
To know how much of the _ECMAScript_ specification does Boa cover, you can check out results running the _ECMASCript Test262_ test suite [here](https://boa-dev.github.io/boa/test262/).
|
||||
|
||||
## Contributing
|
||||
|
||||
Please, check the [CONTRIBUTING.md](CONTRIBUTING.md) file to know how to
|
||||
contribute in the project. You will need Rust installed and an editor. We have
|
||||
some configurations ready for VSCode.
|
||||
|
||||
### Debugging
|
||||
|
||||
Check [debugging.md](./docs/debugging.md) for more info on debugging.
|
||||
|
||||
### Web Assembly
|
||||
|
||||
This interpreter can be exposed to JavaScript!
|
||||
You can build the example locally with:
|
||||
|
||||
```shell
|
||||
npm run build
|
||||
```
|
||||
|
||||
In the console you can use `window.evaluate` to pass JavaScript in.
|
||||
To develop on the web assembly side you can run:
|
||||
|
||||
```shell
|
||||
npm run serve
|
||||
```
|
||||
|
||||
then go to `http://localhost:8080`.
|
||||
|
||||
## Usage
|
||||
|
||||
- Clone this repo.
|
||||
- Run with `cargo run -- test.js` where `test.js` is an existing JS file with any JS valid code.
|
||||
- If any JS doesn't work then it's a bug. Please raise an [issue](https://github.com/boa-dev/boa/issues/)!
|
||||
|
||||
### Example
|
||||
|
||||

|
||||
|
||||
## Command-line Options
|
||||
|
||||
```shell
|
||||
USAGE:
|
||||
boa [OPTIONS] [FILE]...
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-a, --dump-ast <FORMAT> Dump the abstract syntax tree (ast) to stdout with the given format [possible values: Debug, Json,
|
||||
JsonPretty]
|
||||
|
||||
ARGS:
|
||||
<FILE>... The JavaScript file(s) to be evaluated
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
See [Milestones](https://github.com/boa-dev/boa/milestones).
|
||||
|
||||
## Benchmarks
|
||||
|
||||
See [Benchmarks](https://boa-dev.github.io/boa/dev/bench/).
|
||||
|
||||
## Profiling
|
||||
|
||||
See [Profiling](./docs/profiling.md).
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG.md](./CHANGELOG.md).
|
||||
|
||||
## Communication
|
||||
|
||||
Feel free to contact us on [Discord](https://discord.gg/tUFFk9Y).
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [Unlicense](./LICENSE-UNLICENSE) or [MIT](./LICENSE-MIT) licenses, at your option.
|
||||
BIN
__wasm/wit-bindgen-sample/engine/boa/assets/01_rust_loves_js.png
Normal file
BIN
__wasm/wit-bindgen-sample/engine/boa/assets/01_rust_loves_js.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
184
__wasm/wit-bindgen-sample/engine/boa/assets/logo.svg
Normal file
184
__wasm/wit-bindgen-sample/engine/boa/assets/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 135 KiB |
31
__wasm/wit-bindgen-sample/engine/boa/boa_cli/Cargo.toml
Normal file
31
__wasm/wit-bindgen-sample/engine/boa/boa_cli/Cargo.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "boa_cli"
|
||||
version = "0.15.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.60"
|
||||
authors = ["boa-dev"]
|
||||
description = "Boa is a Javascript lexer, parser and Just-in-Time compiler written in Rust. Currently, it has support for some of the language."
|
||||
repository = "https://github.com/boa-dev/boa"
|
||||
keywords = ["javascript", "compiler", "lexer", "parser", "js", "cli"]
|
||||
categories = ["command-line-utilities"]
|
||||
license = "Unlicense/MIT"
|
||||
default-run = "boa"
|
||||
|
||||
[dependencies]
|
||||
boa_engine = { path = "../boa_engine", features = ["deser", "console"], version = "0.15.0" }
|
||||
boa_interner = { path = "../boa_interner", version = "0.15.0" }
|
||||
rustyline = "9.1.2"
|
||||
rustyline-derive = "0.6.0"
|
||||
clap = { version = "3.2.12", features = ["derive"] }
|
||||
serde_json = "1.0.82"
|
||||
colored = "2.0.0"
|
||||
regex = "1.6.0"
|
||||
phf = { version = "0.10.1", features = ["macros"] }
|
||||
|
||||
[target.x86_64-unknown-linux-gnu.dependencies]
|
||||
jemallocator = "0.5.0"
|
||||
|
||||
[[bin]]
|
||||
name = "boa"
|
||||
doc = false
|
||||
path = "src/main.rs"
|
||||
173
__wasm/wit-bindgen-sample/engine/boa/boa_cli/src/helper.rs
Normal file
173
__wasm/wit-bindgen-sample/engine/boa/boa_cli/src/helper.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use colored::{Color, Colorize};
|
||||
use phf::{phf_set, Set};
|
||||
use regex::{Captures, Regex};
|
||||
use rustyline::{
|
||||
error::ReadlineError,
|
||||
highlight::Highlighter,
|
||||
validate::{MatchingBracketValidator, ValidationContext, ValidationResult, Validator},
|
||||
};
|
||||
use rustyline_derive::{Completer, Helper, Hinter};
|
||||
use std::borrow::Cow;
|
||||
|
||||
const STRING_COLOR: Color = Color::Green;
|
||||
const KEYWORD_COLOR: Color = Color::Yellow;
|
||||
const PROPERTY_COLOR: Color = Color::Magenta;
|
||||
const OPERATOR_COLOR: Color = Color::TrueColor {
|
||||
r: 214,
|
||||
g: 95,
|
||||
b: 26,
|
||||
};
|
||||
const UNDEFINED_COLOR: Color = Color::TrueColor {
|
||||
r: 100,
|
||||
g: 100,
|
||||
b: 100,
|
||||
};
|
||||
const NUMBER_COLOR: Color = Color::TrueColor {
|
||||
r: 26,
|
||||
g: 214,
|
||||
b: 175,
|
||||
};
|
||||
const IDENTIFIER_COLOR: Color = Color::TrueColor {
|
||||
r: 26,
|
||||
g: 160,
|
||||
b: 214,
|
||||
};
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Completer, Helper, Hinter)]
|
||||
pub(crate) struct RLHelper {
|
||||
highlighter: LineHighlighter,
|
||||
validator: MatchingBracketValidator,
|
||||
}
|
||||
|
||||
impl RLHelper {
|
||||
#[inline]
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
highlighter: LineHighlighter,
|
||||
validator: MatchingBracketValidator::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator for RLHelper {
|
||||
fn validate(
|
||||
&self,
|
||||
context: &mut ValidationContext<'_>,
|
||||
) -> Result<ValidationResult, ReadlineError> {
|
||||
self.validator.validate(context)
|
||||
}
|
||||
|
||||
fn validate_while_typing(&self) -> bool {
|
||||
self.validator.validate_while_typing()
|
||||
}
|
||||
}
|
||||
|
||||
impl Highlighter for RLHelper {
|
||||
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
|
||||
hint.into()
|
||||
}
|
||||
|
||||
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
|
||||
self.highlighter.highlight(line, pos)
|
||||
}
|
||||
|
||||
fn highlight_candidate<'c>(
|
||||
&self,
|
||||
candidate: &'c str,
|
||||
_completion: rustyline::CompletionType,
|
||||
) -> Cow<'c, str> {
|
||||
self.highlighter.highlight(candidate, 0)
|
||||
}
|
||||
|
||||
fn highlight_char(&self, line: &str, _: usize) -> bool {
|
||||
!line.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
static KEYWORDS: Set<&'static str> = phf_set! {
|
||||
"break",
|
||||
"case",
|
||||
"catch",
|
||||
"class",
|
||||
"const",
|
||||
"continue",
|
||||
"default",
|
||||
"delete",
|
||||
"do",
|
||||
"else",
|
||||
"export",
|
||||
"extends",
|
||||
"finally",
|
||||
"for",
|
||||
"function",
|
||||
"if",
|
||||
"import",
|
||||
"instanceof",
|
||||
"new",
|
||||
"return",
|
||||
"super",
|
||||
"switch",
|
||||
"this",
|
||||
"throw",
|
||||
"try",
|
||||
"typeof",
|
||||
"var",
|
||||
"void",
|
||||
"while",
|
||||
"with",
|
||||
"yield",
|
||||
"await",
|
||||
"enum",
|
||||
"let",
|
||||
};
|
||||
|
||||
struct LineHighlighter;
|
||||
|
||||
impl Highlighter for LineHighlighter {
|
||||
fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> {
|
||||
let mut coloured = line.to_string();
|
||||
|
||||
let reg = Regex::new(
|
||||
r#"(?x)
|
||||
(?P<identifier>\b[$_\p{ID_Start}][$_\p{ID_Continue}\u{200C}\u{200D}]*\b) |
|
||||
(?P<string_double_quote>"([^"\\]|\\.)*") |
|
||||
(?P<string_single_quote>'([^'\\]|\\.)*') |
|
||||
(?P<template_literal>`([^`\\]|\\.)*`) |
|
||||
(?P<op>[+\-/*%~^!&|=<>;:]) |
|
||||
(?P<number>0[bB][01](_?[01])*n?|0[oO][0-7](_?[0-7])*n?|0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?|(([0-9](_?[0-9])*\.([0-9](_?[0-9])*)?)|(([0-9](_?[0-9])*)?\.[0-9](_?[0-9])*)|([0-9](_?[0-9])*))([eE][+-]?[0-9](_?[0-9])*)?n?)"#,
|
||||
)
|
||||
.expect("could not compile regular expression");
|
||||
|
||||
coloured = reg
|
||||
.replace_all(&coloured, |caps: &Captures<'_>| {
|
||||
if let Some(cap) = caps.name("identifier") {
|
||||
match cap.as_str() {
|
||||
"true" | "false" | "null" | "Infinity" | "globalThis" => {
|
||||
cap.as_str().color(PROPERTY_COLOR).to_string()
|
||||
}
|
||||
"undefined" => cap.as_str().color(UNDEFINED_COLOR).to_string(),
|
||||
identifier if KEYWORDS.contains(identifier) => {
|
||||
cap.as_str().color(KEYWORD_COLOR).bold().to_string()
|
||||
}
|
||||
_ => cap.as_str().color(IDENTIFIER_COLOR).to_string(),
|
||||
}
|
||||
} else if let Some(cap) = caps.name("string_double_quote") {
|
||||
cap.as_str().color(STRING_COLOR).to_string()
|
||||
} else if let Some(cap) = caps.name("string_single_quote") {
|
||||
cap.as_str().color(STRING_COLOR).to_string()
|
||||
} else if let Some(cap) = caps.name("template_literal") {
|
||||
cap.as_str().color(STRING_COLOR).to_string()
|
||||
} else if let Some(cap) = caps.name("op") {
|
||||
cap.as_str().color(OPERATOR_COLOR).to_string()
|
||||
} else if let Some(cap) = caps.name("number") {
|
||||
cap.as_str().color(NUMBER_COLOR).to_string()
|
||||
} else {
|
||||
caps[0].to_string()
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
coloured.into()
|
||||
}
|
||||
}
|
||||
264
__wasm/wit-bindgen-sample/engine/boa/boa_cli/src/main.rs
Normal file
264
__wasm/wit-bindgen-sample/engine/boa/boa_cli/src/main.rs
Normal file
@@ -0,0 +1,264 @@
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg",
|
||||
html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg"
|
||||
)]
|
||||
#![cfg_attr(not(test), deny(clippy::unwrap_used))]
|
||||
#![warn(
|
||||
clippy::perf,
|
||||
clippy::single_match_else,
|
||||
clippy::dbg_macro,
|
||||
clippy::doc_markdown,
|
||||
clippy::wildcard_imports,
|
||||
clippy::struct_excessive_bools,
|
||||
clippy::doc_markdown,
|
||||
clippy::semicolon_if_nothing_returned,
|
||||
clippy::pedantic
|
||||
)]
|
||||
#![deny(
|
||||
clippy::all,
|
||||
clippy::cast_lossless,
|
||||
clippy::redundant_closure_for_method_calls,
|
||||
clippy::use_self,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::trivially_copy_pass_by_ref,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::map_unwrap_or,
|
||||
unused_qualifications,
|
||||
unused_import_braces,
|
||||
unused_lifetimes,
|
||||
unreachable_pub,
|
||||
trivial_numeric_casts,
|
||||
// rustdoc,
|
||||
missing_debug_implementations,
|
||||
missing_copy_implementations,
|
||||
deprecated_in_future,
|
||||
meta_variable_misuse,
|
||||
non_ascii_idents,
|
||||
rust_2018_compatibility,
|
||||
rust_2018_idioms,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
)]
|
||||
#![allow(
|
||||
clippy::module_name_repetitions,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::cast_possible_wrap,
|
||||
clippy::cast_ptr_alignment,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::too_many_lines,
|
||||
clippy::unreadable_literal,
|
||||
clippy::missing_inline_in_public_items,
|
||||
clippy::cognitive_complexity,
|
||||
clippy::must_use_candidate,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::as_conversions,
|
||||
clippy::let_unit_value,
|
||||
rustdoc::missing_doc_code_examples
|
||||
)]
|
||||
|
||||
use boa_engine::{syntax::ast::node::StatementList, Context};
|
||||
use clap::{ArgEnum, Parser};
|
||||
use colored::{Color, Colorize};
|
||||
use rustyline::{config::Config, error::ReadlineError, EditMode, Editor};
|
||||
use std::{fs::read, io, path::PathBuf};
|
||||
mod helper;
|
||||
|
||||
#[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;
|
||||
|
||||
/// CLI configuration for Boa.
|
||||
static CLI_HISTORY: &str = ".boa_history";
|
||||
|
||||
const READLINE_COLOR: Color = Color::Cyan;
|
||||
|
||||
// Added #[allow(clippy::option_option)] because to StructOpt an Option<Option<T>>
|
||||
// is an optional argument that optionally takes a value ([--opt=[val]]).
|
||||
// https://docs.rs/structopt/0.3.11/structopt/#type-magic
|
||||
#[allow(clippy::option_option)]
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(author, about, name = "boa")]
|
||||
struct Opt {
|
||||
/// The JavaScript file(s) to be evaluated.
|
||||
#[clap(name = "FILE", parse(from_os_str))]
|
||||
files: Vec<PathBuf>,
|
||||
|
||||
/// Dump the AST to stdout with the given format.
|
||||
#[clap(long, short = 'a', value_name = "FORMAT", ignore_case = true, arg_enum)]
|
||||
dump_ast: Option<Option<DumpFormat>>,
|
||||
|
||||
/// Dump the AST to stdout with the given format.
|
||||
#[clap(long = "trace", short = 't')]
|
||||
trace: bool,
|
||||
|
||||
/// Use vi mode in the REPL
|
||||
#[clap(long = "vi")]
|
||||
vi_mode: bool,
|
||||
}
|
||||
|
||||
impl Opt {
|
||||
/// Returns whether a dump flag has been used.
|
||||
fn has_dump_flag(&self) -> bool {
|
||||
self.dump_ast.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, ArgEnum)]
|
||||
enum DumpFormat {
|
||||
/// The different types of format available for dumping.
|
||||
///
|
||||
// NOTE: This can easily support other formats just by
|
||||
// adding a field to this enum and adding the necessary
|
||||
// implementation. Example: Toml, Html, etc.
|
||||
//
|
||||
// NOTE: The fields of this enum are not doc comments because
|
||||
// arg_enum! macro does not support it.
|
||||
|
||||
// This is the default format that you get from std::fmt::Debug.
|
||||
Debug,
|
||||
|
||||
// This is a minified json format.
|
||||
Json,
|
||||
|
||||
// This is a pretty printed json format.
|
||||
JsonPretty,
|
||||
}
|
||||
|
||||
/// Parses the the token stream into an AST and returns it.
|
||||
///
|
||||
/// Returns a error of type String with a message,
|
||||
/// if the token stream has a parsing error.
|
||||
fn parse_tokens<S>(src: S, context: &mut Context) -> Result<StatementList, String>
|
||||
where
|
||||
S: AsRef<[u8]>,
|
||||
{
|
||||
use boa_engine::syntax::parser::Parser;
|
||||
|
||||
let src_bytes = src.as_ref();
|
||||
Parser::new(src_bytes)
|
||||
.parse_all(context)
|
||||
.map_err(|e| format!("ParsingError: {e}"))
|
||||
}
|
||||
|
||||
/// Dumps the AST to stdout with format controlled by the given arguments.
|
||||
///
|
||||
/// Returns a error of type String with a error message,
|
||||
/// if the source has a syntax or parsing error.
|
||||
fn dump<S>(src: S, args: &Opt, context: &mut Context) -> Result<(), String>
|
||||
where
|
||||
S: AsRef<[u8]>,
|
||||
{
|
||||
if let Some(ref arg) = args.dump_ast {
|
||||
let ast = parse_tokens(src, context)?;
|
||||
|
||||
match arg {
|
||||
Some(format) => match format {
|
||||
DumpFormat::Debug => println!("{ast:#?}"),
|
||||
DumpFormat::Json => println!(
|
||||
"{}",
|
||||
serde_json::to_string(&ast).expect("could not convert AST to a JSON string")
|
||||
),
|
||||
DumpFormat::JsonPretty => {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&ast)
|
||||
.expect("could not convert AST to a pretty JSON string")
|
||||
);
|
||||
}
|
||||
},
|
||||
// Default ast dumping format.
|
||||
None => println!("{ast:#?}"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn main() -> Result<(), std::io::Error> {
|
||||
let args = Opt::parse();
|
||||
|
||||
let mut context = Context::default();
|
||||
|
||||
// Trace Output
|
||||
context.set_trace(args.trace);
|
||||
|
||||
for file in &args.files {
|
||||
let buffer = read(file)?;
|
||||
|
||||
if args.has_dump_flag() {
|
||||
if let Err(e) = dump(&buffer, &args, &mut context) {
|
||||
eprintln!("{e}");
|
||||
}
|
||||
} else {
|
||||
match context.eval(&buffer) {
|
||||
Ok(v) => println!("{}", v.display()),
|
||||
Err(v) => eprintln!("Uncaught {}", v.display()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if args.files.is_empty() {
|
||||
let config = Config::builder()
|
||||
.keyseq_timeout(1)
|
||||
.edit_mode(if args.vi_mode {
|
||||
EditMode::Vi
|
||||
} else {
|
||||
EditMode::Emacs
|
||||
})
|
||||
.build();
|
||||
|
||||
let mut editor = Editor::with_config(config);
|
||||
editor.load_history(CLI_HISTORY).map_err(|err| match err {
|
||||
ReadlineError::Io(e) => e,
|
||||
e => io::Error::new(io::ErrorKind::Other, e),
|
||||
})?;
|
||||
editor.set_helper(Some(helper::RLHelper::new()));
|
||||
|
||||
let readline = ">> ".color(READLINE_COLOR).bold().to_string();
|
||||
|
||||
loop {
|
||||
match editor.readline(&readline) {
|
||||
Ok(line) if line == ".exit" => break,
|
||||
Err(ReadlineError::Interrupted | ReadlineError::Eof) => break,
|
||||
|
||||
Ok(line) => {
|
||||
editor.add_history_entry(&line);
|
||||
|
||||
if args.has_dump_flag() {
|
||||
if let Err(e) = dump(&line, &args, &mut context) {
|
||||
eprintln!("{e}");
|
||||
}
|
||||
} else {
|
||||
match context.eval(line.trim_end()) {
|
||||
Ok(v) => println!("{}", v.display()),
|
||||
Err(v) => {
|
||||
eprintln!(
|
||||
"{}: {}",
|
||||
"Uncaught".red(),
|
||||
v.display().to_string().red()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
eprintln!("Unknown error: {err:?}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editor
|
||||
.save_history(CLI_HISTORY)
|
||||
.expect("could not save CLI history");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
75
__wasm/wit-bindgen-sample/engine/boa/boa_engine/Cargo.toml
Normal file
75
__wasm/wit-bindgen-sample/engine/boa/boa_engine/Cargo.toml
Normal file
@@ -0,0 +1,75 @@
|
||||
[package]
|
||||
name = "boa_engine"
|
||||
version = "0.15.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.60"
|
||||
authors = ["boa-dev"]
|
||||
description = "Boa is a Javascript lexer, parser and Just-in-Time compiler written in Rust. Currently, it has support for some of the language."
|
||||
repository = "https://github.com/boa-dev/boa"
|
||||
keywords = ["javascript", "js", "compiler", "lexer", "parser"]
|
||||
categories = ["parser-implementations", "compilers"]
|
||||
license = "Unlicense/MIT"
|
||||
readme = "../README.md"
|
||||
|
||||
[features]
|
||||
profiler = ["boa_profiler/profiler"]
|
||||
deser = ["boa_interner/serde"]
|
||||
intl = [
|
||||
"dep:icu_locale_canonicalizer",
|
||||
"dep:icu_locid",
|
||||
"dep:icu_datetime",
|
||||
"dep:icu_plurals",
|
||||
"dep:icu_provider",
|
||||
"dep:icu_testdata",
|
||||
"dep:sys-locale"
|
||||
]
|
||||
|
||||
# Enable Boa's WHATWG console object implementation.
|
||||
console = []
|
||||
|
||||
[dependencies]
|
||||
boa_unicode = { path = "../boa_unicode", version = "0.15.0" }
|
||||
boa_interner = { path = "../boa_interner", version = "0.15.0" }
|
||||
boa_gc = { path = "../boa_gc", version = "0.15.0" }
|
||||
gc = "0.4.1"
|
||||
boa_profiler = { path = "../boa_profiler", version = "0.15.0" }
|
||||
serde = { version = "1.0.139", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.82"
|
||||
rand = "0.8.5"
|
||||
num-traits = "0.2.15"
|
||||
regress = "0.4.1"
|
||||
rustc-hash = "1.1.0"
|
||||
num-bigint = { version = "0.4.3", features = ["serde"] }
|
||||
num-integer = "0.1.45"
|
||||
bitflags = "1.3.2"
|
||||
indexmap = "1.9.1"
|
||||
ryu-js = "0.2.2"
|
||||
chrono = "0.4.19"
|
||||
fast-float = "0.2.0"
|
||||
unicode-normalization = "0.1.21"
|
||||
dyn-clone = "1.0.7"
|
||||
once_cell = "1.13.0"
|
||||
tap = "1.0.1"
|
||||
icu_locale_canonicalizer = { version = "0.6.0", features = ["serde"], optional = true }
|
||||
icu_locid = { version = "0.6.0", features = ["serde"], optional = true }
|
||||
icu_datetime = { version = "0.6.0", features = ["serde"], optional = true }
|
||||
icu_plurals = { version = "0.6.0", features = ["serde"], optional = true }
|
||||
icu_provider = { version = "0.6.0", optional = true }
|
||||
icu_testdata = { version = "0.6.0", optional = true }
|
||||
sys-locale = { version = "0.2.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.5"
|
||||
float-cmp = "0.9.0"
|
||||
|
||||
[target.x86_64-unknown-linux-gnu.dev-dependencies]
|
||||
jemallocator = "0.5.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "boa_engine"
|
||||
bench = false
|
||||
|
||||
[[bench]]
|
||||
name = "full"
|
||||
harness = false
|
||||
@@ -0,0 +1,10 @@
|
||||
# Boa Benchmarks
|
||||
|
||||
For each js script in the `bench_scripts` folder, we create three benchmarks:
|
||||
|
||||
- Parser => lexing and parsing of the source code
|
||||
- Compiler => compilation of the parsed statement list into bytecode
|
||||
- Execution => execution of the bytecode in the vm
|
||||
|
||||
The idea is to check the performance of Boa in different scenarios.
|
||||
Different parts of Boa are benchmarked separately to make the impact of local changes visible.
|
||||
@@ -0,0 +1 @@
|
||||
((2 + 2) ** 3 / 100 - 5 ** 3 * -1000) ** 2 + 100 - 8;
|
||||
@@ -0,0 +1,7 @@
|
||||
(function () {
|
||||
let testArr = [1, 2, 3, 4, 5];
|
||||
|
||||
let res = testArr[2];
|
||||
|
||||
return res;
|
||||
})();
|
||||
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let testArr = [];
|
||||
for (let a = 0; a <= 500; a++) {
|
||||
testArr[a] = "p" + a;
|
||||
}
|
||||
|
||||
return testArr;
|
||||
})();
|
||||
@@ -0,0 +1,22 @@
|
||||
(function () {
|
||||
let testArray = [
|
||||
83, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83,
|
||||
62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93,
|
||||
17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77,
|
||||
32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32,
|
||||
56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234,
|
||||
23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29,
|
||||
2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28,
|
||||
93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62,
|
||||
99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17,
|
||||
28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32,
|
||||
45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56,
|
||||
67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28,
|
||||
];
|
||||
|
||||
while (testArray.length > 0) {
|
||||
testArray.pop();
|
||||
}
|
||||
|
||||
return testArray;
|
||||
})();
|
||||
@@ -0,0 +1,7 @@
|
||||
new Boolean(
|
||||
!new Boolean(
|
||||
new Boolean(
|
||||
!new Boolean(false).valueOf() && new Boolean(true).valueOf()
|
||||
).valueOf()
|
||||
).valueOf()
|
||||
).valueOf();
|
||||
@@ -0,0 +1,15 @@
|
||||
!function () {
|
||||
var M = new Array();
|
||||
for (i = 0; i < 100; i++) {
|
||||
M.push(Math.floor(Math.random() * 100));
|
||||
}
|
||||
var test = [];
|
||||
for (i = 0; i < 100; i++) {
|
||||
if (M[i] > 50) {
|
||||
test.push(M[i]);
|
||||
}
|
||||
}
|
||||
test.forEach(elem => {
|
||||
0
|
||||
});
|
||||
}();
|
||||
@@ -0,0 +1,10 @@
|
||||
(function () {
|
||||
let num = 12;
|
||||
|
||||
function fib(n) {
|
||||
if (n <= 1) return 1;
|
||||
return fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
|
||||
return fib(num);
|
||||
})();
|
||||
@@ -0,0 +1,10 @@
|
||||
(function () {
|
||||
let b = "hello";
|
||||
for (let a = 10; a < 100; a += 5) {
|
||||
if (a < 50) {
|
||||
b += "world";
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
})();
|
||||
@@ -0,0 +1 @@
|
||||
!function(){var r=new Array();for(i=0;i<100;i++)r.push(Math.floor(100*Math.random()));var a=[];for(i=0;i<100;i++)r[i]>50&&a.push(r[i]);a.forEach(i=>{0})}();
|
||||
@@ -0,0 +1,5 @@
|
||||
new Number(
|
||||
new Number(
|
||||
new Number(new Number(100).valueOf() - 10.5).valueOf() + 100
|
||||
).valueOf() * 1.6
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let test = {
|
||||
my_prop: "hello",
|
||||
another: 65,
|
||||
};
|
||||
|
||||
return test;
|
||||
})();
|
||||
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let test = {
|
||||
my_prop: "hello",
|
||||
another: 65,
|
||||
};
|
||||
|
||||
return test.my_prop;
|
||||
})();
|
||||
@@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
let test = {
|
||||
my_prop: "hello",
|
||||
another: 65,
|
||||
};
|
||||
|
||||
return test["my" + "_prop"];
|
||||
})();
|
||||
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = new RegExp("hello", "i");
|
||||
|
||||
return regExp.test("Hello World");
|
||||
})();
|
||||
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = new RegExp("hello", "i");
|
||||
|
||||
return regExp;
|
||||
})();
|
||||
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = /hello/i;
|
||||
|
||||
return regExp.test("Hello World");
|
||||
})();
|
||||
@@ -0,0 +1,5 @@
|
||||
(function () {
|
||||
let regExp = /hello/i;
|
||||
|
||||
return regExp;
|
||||
})();
|
||||
@@ -0,0 +1,9 @@
|
||||
(function () {
|
||||
var a = "hello";
|
||||
var b = "world";
|
||||
|
||||
var c = a == b;
|
||||
|
||||
var d = b;
|
||||
var e = d == b;
|
||||
})();
|
||||
@@ -0,0 +1,6 @@
|
||||
(function () {
|
||||
var a = "hello";
|
||||
var b = "world";
|
||||
|
||||
var c = a + b;
|
||||
})();
|
||||
@@ -0,0 +1,4 @@
|
||||
(function () {
|
||||
var a = "hello";
|
||||
var b = a;
|
||||
})();
|
||||
@@ -0,0 +1,7 @@
|
||||
new String(
|
||||
new String(
|
||||
new String(
|
||||
new String("Hello").valueOf() + new String(", world").valueOf()
|
||||
).valueOf() + "!"
|
||||
).valueOf()
|
||||
).valueOf();
|
||||
@@ -0,0 +1,3 @@
|
||||
(function () {
|
||||
return Symbol();
|
||||
})();
|
||||
@@ -0,0 +1,94 @@
|
||||
//! Benchmarks of the whole execution engine in Boa.
|
||||
|
||||
use boa_engine::{realm::Realm, Context};
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
|
||||
#[cfg_attr(
|
||||
all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"),
|
||||
global_allocator
|
||||
)]
|
||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||
|
||||
fn create_realm(c: &mut Criterion) {
|
||||
c.bench_function("Create Realm", move |b| b.iter(Realm::create));
|
||||
}
|
||||
|
||||
macro_rules! full_benchmarks {
|
||||
($({$id:literal, $name:ident}),*) => {
|
||||
fn bench_parser(c: &mut Criterion) {
|
||||
$(
|
||||
{
|
||||
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
|
||||
let mut context = Context::default();
|
||||
c.bench_function(concat!($id, " (Parser)"), move |b| {
|
||||
b.iter(|| context.parse(black_box(CODE)))
|
||||
});
|
||||
}
|
||||
)*
|
||||
}
|
||||
fn bench_compile(c: &mut Criterion) {
|
||||
$(
|
||||
{
|
||||
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
|
||||
let mut context = Context::default();
|
||||
let statement_list = context.parse(CODE).expect("parsing failed");
|
||||
c.bench_function(concat!($id, " (Compiler)"), move |b| {
|
||||
b.iter(|| {
|
||||
context.compile(black_box(&statement_list))
|
||||
})
|
||||
});
|
||||
}
|
||||
)*
|
||||
}
|
||||
fn bench_execution(c: &mut Criterion) {
|
||||
$(
|
||||
{
|
||||
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
|
||||
let mut context = Context::default();
|
||||
let statement_list = context.parse(CODE).expect("parsing failed");
|
||||
let code_block = context.compile(&statement_list).unwrap();
|
||||
c.bench_function(concat!($id, " (Execution)"), move |b| {
|
||||
b.iter(|| {
|
||||
context.execute(black_box(code_block.clone())).unwrap()
|
||||
})
|
||||
});
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
full_benchmarks!(
|
||||
{"Symbols", symbol_creation},
|
||||
{"For loop", for_loop},
|
||||
{"Fibonacci", fibonacci},
|
||||
{"Object Creation", object_creation},
|
||||
{"Static Object Property Access", object_prop_access_const},
|
||||
{"Dynamic Object Property Access", object_prop_access_dyn},
|
||||
{"RegExp Literal Creation", regexp_literal_creation},
|
||||
{"RegExp Creation", regexp_creation},
|
||||
{"RegExp Literal", regexp_literal},
|
||||
{"RegExp", regexp},
|
||||
{"Array access", array_access},
|
||||
{"Array creation", array_create},
|
||||
{"Array pop", array_pop},
|
||||
{"String concatenation", string_concat},
|
||||
{"String comparison", string_compare},
|
||||
{"String copy", string_copy},
|
||||
{"Number Object Access", number_object_access},
|
||||
{"Boolean Object Access", boolean_object_access},
|
||||
{"String Object Access", string_object_access},
|
||||
{"Arithmetic operations", arithmetic_operations},
|
||||
{"Clean js", clean_js},
|
||||
{"Mini js", mini_js}
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
create_realm,
|
||||
bench_parser,
|
||||
bench_compile,
|
||||
bench_execution,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
453
__wasm/wit-bindgen-sample/engine/boa/boa_engine/src/bigint.rs
Normal file
453
__wasm/wit-bindgen-sample/engine/boa/boa_engine/src/bigint.rs
Normal file
@@ -0,0 +1,453 @@
|
||||
//! This module implements the JavaScript bigint primitive rust type.
|
||||
|
||||
use crate::{builtins::Number, Context, JsValue};
|
||||
use boa_gc::{unsafe_empty_trace, Finalize, Trace};
|
||||
use num_integer::Integer;
|
||||
use num_traits::{pow::Pow, FromPrimitive, One, ToPrimitive, Zero};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// The raw bigint type.
|
||||
pub type RawBigInt = num_bigint::BigInt;
|
||||
|
||||
#[cfg(feature = "deser")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// JavaScript bigint primitive rust type.
|
||||
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Finalize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct JsBigInt {
|
||||
inner: Rc<RawBigInt>,
|
||||
}
|
||||
|
||||
// Safety: BigInt does not contain any objects which needs to be traced,
|
||||
// so this is safe.
|
||||
unsafe impl Trace for JsBigInt {
|
||||
unsafe_empty_trace!();
|
||||
}
|
||||
|
||||
impl JsBigInt {
|
||||
/// Create a new [`JsBigInt`].
|
||||
#[inline]
|
||||
pub fn new<T: Into<Self>>(value: T) -> Self {
|
||||
value.into()
|
||||
}
|
||||
|
||||
/// Create a [`JsBigInt`] with value `0`.
|
||||
#[inline]
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::zero()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if is zero.
|
||||
#[inline]
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.inner.is_zero()
|
||||
}
|
||||
|
||||
/// Create a [`JsBigInt`] with value `1`.
|
||||
#[inline]
|
||||
pub fn one() -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::one()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if is one.
|
||||
#[inline]
|
||||
pub fn is_one(&self) -> bool {
|
||||
self.inner.is_one()
|
||||
}
|
||||
|
||||
/// Convert bigint to string with radix.
|
||||
#[inline]
|
||||
pub fn to_string_radix(&self, radix: u32) -> String {
|
||||
self.inner.to_str_radix(radix)
|
||||
}
|
||||
|
||||
/// Converts the `BigInt` to a f64 type.
|
||||
///
|
||||
/// Returns `f64::INFINITY` if the `BigInt` is too big.
|
||||
#[inline]
|
||||
pub fn to_f64(&self) -> f64 {
|
||||
self.inner.to_f64().unwrap_or(f64::INFINITY)
|
||||
}
|
||||
|
||||
/// Converts a string to a `BigInt` with the specified radix.
|
||||
#[inline]
|
||||
pub fn from_string_radix(buf: &str, radix: u32) -> Option<Self> {
|
||||
Some(Self {
|
||||
inner: Rc::new(RawBigInt::parse_bytes(buf.as_bytes(), radix)?),
|
||||
})
|
||||
}
|
||||
|
||||
/// This function takes a string and converts it to `BigInt` type.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-stringtobigint
|
||||
#[inline]
|
||||
pub fn from_string(mut string: &str) -> Option<Self> {
|
||||
string = string.trim();
|
||||
|
||||
if string.is_empty() {
|
||||
return Some(Self::zero());
|
||||
}
|
||||
|
||||
let mut radix = 10;
|
||||
if string.starts_with("0b") || string.starts_with("0B") {
|
||||
radix = 2;
|
||||
string = &string[2..];
|
||||
} else if string.starts_with("0x") || string.starts_with("0X") {
|
||||
radix = 16;
|
||||
string = &string[2..];
|
||||
} else if string.starts_with("0o") || string.starts_with("0O") {
|
||||
radix = 8;
|
||||
string = &string[2..];
|
||||
}
|
||||
|
||||
Self::from_string_radix(string, radix)
|
||||
}
|
||||
|
||||
/// Checks for `SameValueZero` equality.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-equal
|
||||
#[inline]
|
||||
pub fn same_value_zero(x: &Self, y: &Self) -> bool {
|
||||
// Return BigInt::equal(x, y)
|
||||
Self::equal(x, y)
|
||||
}
|
||||
|
||||
/// Checks for `SameValue` equality.
|
||||
///
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValue
|
||||
#[inline]
|
||||
pub fn same_value(x: &Self, y: &Self) -> bool {
|
||||
// Return BigInt::equal(x, y)
|
||||
Self::equal(x, y)
|
||||
}
|
||||
|
||||
/// Checks for mathematical equality.
|
||||
///
|
||||
/// The abstract operation `BigInt::equal` takes arguments x (a `BigInt`) and y (a `BigInt`).
|
||||
/// It returns `true` if x and y have the same mathematical integer value and false otherwise.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValueZero
|
||||
#[inline]
|
||||
pub fn equal(x: &Self, y: &Self) -> bool {
|
||||
x == y
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pow(x: &Self, y: &Self, context: &mut Context) -> Result<Self, JsValue> {
|
||||
let y = if let Some(y) = y.inner.to_biguint() {
|
||||
y
|
||||
} else {
|
||||
return context.throw_range_error("BigInt negative exponent");
|
||||
};
|
||||
|
||||
let num_bits = (x.inner.bits() as f64
|
||||
* y.to_f64().expect("Unable to convert from BigUInt to f64"))
|
||||
.floor()
|
||||
+ 1f64;
|
||||
|
||||
if num_bits > 1_000_000_000f64 {
|
||||
return context.throw_range_error("Maximum BigInt size exceeded");
|
||||
}
|
||||
|
||||
Ok(Self::new(x.inner.as_ref().clone().pow(y)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_right(x: &Self, y: &Self, context: &mut Context) -> Result<Self, JsValue> {
|
||||
if let Some(n) = y.inner.to_i32() {
|
||||
let inner = if n > 0 {
|
||||
x.inner.as_ref().clone().shr(n as usize)
|
||||
} else {
|
||||
x.inner.as_ref().clone().shl(n.unsigned_abs())
|
||||
};
|
||||
|
||||
Ok(Self::new(inner))
|
||||
} else {
|
||||
context.throw_range_error("Maximum BigInt size exceeded")
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shift_left(x: &Self, y: &Self, context: &mut Context) -> Result<Self, JsValue> {
|
||||
if let Some(n) = y.inner.to_i32() {
|
||||
let inner = if n > 0 {
|
||||
x.inner.as_ref().clone().shl(n as usize)
|
||||
} else {
|
||||
x.inner.as_ref().clone().shr(n.unsigned_abs())
|
||||
};
|
||||
|
||||
Ok(Self::new(inner))
|
||||
} else {
|
||||
context.throw_range_error("Maximum BigInt size exceeded")
|
||||
}
|
||||
}
|
||||
|
||||
/// Floored integer modulo.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use num_integer::Integer;
|
||||
/// assert_eq!((8).mod_floor(&3), 2);
|
||||
/// assert_eq!((8).mod_floor(&-3), -1);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn mod_floor(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.mod_floor(&y.inner))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().add(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sub(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().sub(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mul(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().mul(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn div(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().div(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rem(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().rem(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bitand(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().bitand(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bitor(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().bitor(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bitxor(x: &Self, y: &Self) -> Self {
|
||||
Self::new(x.inner.as_ref().clone().bitxor(y.inner.as_ref()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn neg(x: &Self) -> Self {
|
||||
Self::new(x.as_inner().neg())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn not(x: &Self) -> Self {
|
||||
Self::new(!x.as_inner())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn as_inner(&self) -> &RawBigInt {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for JsBigInt {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawBigInt> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: RawBigInt) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<RawBigInt>> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: Box<RawBigInt>) -> Self {
|
||||
Self {
|
||||
inner: value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i8> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: i8) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: u8) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: i16) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: u16) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: i32) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: u32) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: i64) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: u64) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<isize> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: isize) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for JsBigInt {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RawBigInt::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TryFromF64Error;
|
||||
|
||||
impl Display for TryFromF64Error {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Could not convert f64 value to a BigInt type")
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<f64> for JsBigInt {
|
||||
type Error = TryFromF64Error;
|
||||
|
||||
#[inline]
|
||||
fn try_from(n: f64) -> Result<Self, Self::Error> {
|
||||
// If the truncated version of the number is not the
|
||||
// same as the non-truncated version then the floating-point
|
||||
// number conains a fractional part.
|
||||
if !Number::equal(n.trunc(), n) {
|
||||
return Err(TryFromF64Error);
|
||||
}
|
||||
match RawBigInt::from_f64(n) {
|
||||
Some(bigint) => Ok(Self::new(bigint)),
|
||||
None => Err(TryFromF64Error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<i32> for JsBigInt {
|
||||
#[inline]
|
||||
fn eq(&self, other: &i32) -> bool {
|
||||
self.inner.as_ref() == &RawBigInt::from(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<JsBigInt> for i32 {
|
||||
#[inline]
|
||||
fn eq(&self, other: &JsBigInt) -> bool {
|
||||
&RawBigInt::from(*self) == other.inner.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<f64> for JsBigInt {
|
||||
#[inline]
|
||||
fn eq(&self, other: &f64) -> bool {
|
||||
if other.fract() != 0.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.inner.as_ref() == &RawBigInt::from(*other as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<JsBigInt> for f64 {
|
||||
#[inline]
|
||||
fn eq(&self, other: &JsBigInt) -> bool {
|
||||
if self.fract() != 0.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
&RawBigInt::from(*self as i64) == other.inner.as_ref()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
use crate::{
|
||||
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue},
|
||||
object::{JsObject, ObjectData},
|
||||
property::{PropertyDescriptor, PropertyNameKind},
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// The Array Iterator object represents an iteration over an array. It implements the iterator protocol.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct ArrayIterator {
|
||||
array: JsObject,
|
||||
next_index: usize,
|
||||
kind: PropertyNameKind,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl ArrayIterator {
|
||||
pub(crate) const NAME: &'static str = "ArrayIterator";
|
||||
|
||||
fn new(array: JsObject, kind: PropertyNameKind) -> Self {
|
||||
Self {
|
||||
array,
|
||||
kind,
|
||||
next_index: 0,
|
||||
done: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// `CreateArrayIterator( array, kind )`
|
||||
///
|
||||
/// Creates a new iterator over the given array.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createarrayiterator
|
||||
pub(crate) fn create_array_iterator(
|
||||
array: JsObject,
|
||||
kind: PropertyNameKind,
|
||||
context: &Context,
|
||||
) -> JsValue {
|
||||
let array_iterator = JsObject::from_proto_and_data(
|
||||
context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.array_iterator(),
|
||||
ObjectData::array_iterator(Self::new(array, kind)),
|
||||
);
|
||||
array_iterator.into()
|
||||
}
|
||||
|
||||
/// %ArrayIteratorPrototype%.next( )
|
||||
///
|
||||
/// Gets the next result in the array.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next
|
||||
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let mut array_iterator = this.as_object().map(JsObject::borrow_mut);
|
||||
let array_iterator = array_iterator
|
||||
.as_mut()
|
||||
.and_then(|obj| obj.as_array_iterator_mut())
|
||||
.ok_or_else(|| context.construct_type_error("`this` is not an ArrayIterator"))?;
|
||||
let index = array_iterator.next_index;
|
||||
if array_iterator.done {
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
));
|
||||
}
|
||||
|
||||
let len = if let Some(f) = array_iterator.array.borrow().as_typed_array() {
|
||||
if f.is_detached() {
|
||||
return context.throw_type_error(
|
||||
"Cannot get value from typed array that has a detached array buffer",
|
||||
);
|
||||
}
|
||||
|
||||
f.array_length()
|
||||
} else {
|
||||
array_iterator.array.length_of_array_like(context)?
|
||||
};
|
||||
|
||||
if index >= len {
|
||||
array_iterator.done = true;
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
));
|
||||
}
|
||||
array_iterator.next_index = index + 1;
|
||||
match array_iterator.kind {
|
||||
PropertyNameKind::Key => Ok(create_iter_result_object(index.into(), false, context)),
|
||||
PropertyNameKind::Value => {
|
||||
let element_value = array_iterator.array.get(index, context)?;
|
||||
Ok(create_iter_result_object(element_value, false, context))
|
||||
}
|
||||
PropertyNameKind::KeyAndValue => {
|
||||
let element_value = array_iterator.array.get(index, context)?;
|
||||
let result = Array::create_array_from_list([index.into(), element_value], context);
|
||||
Ok(create_iter_result_object(result.into(), false, context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the `%ArrayIteratorPrototype%` object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
|
||||
pub(crate) fn create_prototype(
|
||||
iterator_prototype: JsObject,
|
||||
context: &mut Context,
|
||||
) -> JsObject {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
// Create prototype
|
||||
let array_iterator =
|
||||
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
|
||||
make_builtin_fn(Self::next, "next", &array_iterator, 0, context);
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
let to_string_tag_property = PropertyDescriptor::builder()
|
||||
.value("Array Iterator")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
array_iterator.insert(to_string_tag, to_string_tag_property);
|
||||
array_iterator
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,818 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{
|
||||
builtins::{typed_array::TypedArrayKind, BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
|
||||
JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
symbol::WellKnownSymbols,
|
||||
value::{IntegerOrInfinity, Numeric},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
use num_traits::{Signed, ToPrimitive};
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct ArrayBuffer {
|
||||
pub array_buffer_data: Option<Vec<u8>>,
|
||||
pub array_buffer_byte_length: usize,
|
||||
pub array_buffer_detach_key: JsValue,
|
||||
}
|
||||
|
||||
impl ArrayBuffer {
|
||||
pub(crate) fn array_buffer_byte_length(&self) -> usize {
|
||||
self.array_buffer_byte_length
|
||||
}
|
||||
}
|
||||
|
||||
impl BuiltIn for ArrayBuffer {
|
||||
const NAME: &'static str = "ArrayBuffer";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
|
||||
|
||||
let get_species = FunctionBuilder::native(context, Self::get_species)
|
||||
.name("get [Symbol.species]")
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
let get_byte_length = FunctionBuilder::native(context, Self::get_byte_length)
|
||||
.name("get byteLength")
|
||||
.build();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().array_buffer().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.accessor("byteLength", Some(get_byte_length), None, flag_attributes)
|
||||
.static_accessor(
|
||||
WellKnownSymbols::species(),
|
||||
Some(get_species),
|
||||
None,
|
||||
Attribute::CONFIGURABLE,
|
||||
)
|
||||
.static_method(Self::is_view, "isView", 1)
|
||||
.method(Self::slice, "slice", 2)
|
||||
.property(
|
||||
WellKnownSymbols::to_string_tag(),
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrayBuffer {
|
||||
const LENGTH: usize = 1;
|
||||
|
||||
/// `25.1.3.1 ArrayBuffer ( length )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer-length
|
||||
fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if new_target.is_undefined() {
|
||||
return context
|
||||
.throw_type_error("ArrayBuffer.constructor called with undefined new target");
|
||||
}
|
||||
|
||||
// 2. Let byteLength be ? ToIndex(length).
|
||||
let byte_length = args.get_or_undefined(0).to_index(context)?;
|
||||
|
||||
// 3. Return ? AllocateArrayBuffer(NewTarget, byteLength).
|
||||
Ok(Self::allocate(new_target, byte_length, context)?.into())
|
||||
}
|
||||
|
||||
/// `25.1.4.3 get ArrayBuffer [ @@species ]`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer-@@species
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Return the this value.
|
||||
Ok(this.clone())
|
||||
}
|
||||
|
||||
/// `25.1.4.1 ArrayBuffer.isView ( arg )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.isview
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn is_view(_: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. If Type(arg) is not Object, return false.
|
||||
// 2. If arg has a [[ViewedArrayBuffer]] internal slot, return true.
|
||||
// 3. Return false.
|
||||
Ok(args
|
||||
.get_or_undefined(0)
|
||||
.as_object()
|
||||
.map(|obj| obj.borrow().has_viewed_array_buffer())
|
||||
.unwrap_or_default()
|
||||
.into())
|
||||
}
|
||||
|
||||
/// `25.1.5.1 get ArrayBuffer.prototype.byteLength`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength
|
||||
pub(crate) fn get_byte_length(
|
||||
this: &JsValue,
|
||||
_args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
let obj = if let Some(obj) = this.as_object() {
|
||||
obj
|
||||
} else {
|
||||
return context.throw_type_error("ArrayBuffer.byteLength called with non-object value");
|
||||
};
|
||||
let obj = obj.borrow();
|
||||
let o = if let Some(o) = obj.as_array_buffer() {
|
||||
o
|
||||
} else {
|
||||
return context.throw_type_error("ArrayBuffer.byteLength called with invalid object");
|
||||
};
|
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
|
||||
// 4. If IsDetachedBuffer(O) is true, return +0𝔽.
|
||||
if Self::is_detached_buffer(o) {
|
||||
return Ok(0.into());
|
||||
}
|
||||
|
||||
// 5. Let length be O.[[ArrayBufferByteLength]].
|
||||
// 6. Return 𝔽(length).
|
||||
Ok(o.array_buffer_byte_length.into())
|
||||
}
|
||||
|
||||
/// `25.1.5.3 ArrayBuffer.prototype.slice ( start, end )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice
|
||||
fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
let obj = if let Some(obj) = this.as_object() {
|
||||
obj
|
||||
} else {
|
||||
return context.throw_type_error("ArrayBuffer.slice called with non-object value");
|
||||
};
|
||||
let obj_borrow = obj.borrow();
|
||||
let o = if let Some(o) = obj_borrow.as_array_buffer() {
|
||||
o
|
||||
} else {
|
||||
return context.throw_type_error("ArrayBuffer.slice called with invalid object");
|
||||
};
|
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
|
||||
// 4. If IsDetachedBuffer(O) is true, throw a TypeError exception.
|
||||
if Self::is_detached_buffer(o) {
|
||||
return context.throw_type_error("ArrayBuffer.slice called with detached buffer");
|
||||
}
|
||||
|
||||
// 5. Let len be O.[[ArrayBufferByteLength]].
|
||||
let len = o.array_buffer_byte_length as i64;
|
||||
|
||||
// 6. Let relativeStart be ? ToIntegerOrInfinity(start).
|
||||
let relative_start = args.get_or_undefined(0).to_integer_or_infinity(context)?;
|
||||
|
||||
let first = match relative_start {
|
||||
// 7. If relativeStart is -∞, let first be 0.
|
||||
IntegerOrInfinity::NegativeInfinity => 0,
|
||||
// 8. Else if relativeStart < 0, let first be max(len + relativeStart, 0).
|
||||
IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0),
|
||||
// 9. Else, let first be min(relativeStart, len).
|
||||
IntegerOrInfinity::Integer(i) => std::cmp::min(i, len),
|
||||
IntegerOrInfinity::PositiveInfinity => len,
|
||||
};
|
||||
|
||||
// 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
|
||||
let end = args.get_or_undefined(1);
|
||||
let relative_end = if end.is_undefined() {
|
||||
IntegerOrInfinity::Integer(len)
|
||||
} else {
|
||||
end.to_integer_or_infinity(context)?
|
||||
};
|
||||
|
||||
let r#final = match relative_end {
|
||||
// 11. If relativeEnd is -∞, let final be 0.
|
||||
IntegerOrInfinity::NegativeInfinity => 0,
|
||||
// 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
|
||||
IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0),
|
||||
// 13. Else, let final be min(relativeEnd, len).
|
||||
IntegerOrInfinity::Integer(i) => std::cmp::min(i, len),
|
||||
IntegerOrInfinity::PositiveInfinity => len,
|
||||
};
|
||||
|
||||
// 14. Let newLen be max(final - first, 0).
|
||||
let new_len = std::cmp::max(r#final - first, 0) as usize;
|
||||
|
||||
// 15. Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%).
|
||||
let ctor = obj.species_constructor(StandardConstructors::array_buffer, context)?;
|
||||
|
||||
// 16. Let new be ? Construct(ctor, « 𝔽(newLen) »).
|
||||
let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?;
|
||||
|
||||
{
|
||||
let new_obj = new.borrow();
|
||||
// 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]).
|
||||
let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| {
|
||||
context.construct_type_error("ArrayBuffer constructor returned invalid object")
|
||||
})?;
|
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception.
|
||||
|
||||
// 19. If IsDetachedBuffer(new) is true, throw a TypeError exception.
|
||||
if new_array_buffer.is_detached_buffer() {
|
||||
return context
|
||||
.throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer");
|
||||
}
|
||||
}
|
||||
// 20. If SameValue(new, O) is true, throw a TypeError exception.
|
||||
if this
|
||||
.as_object()
|
||||
.map(|obj| JsObject::equals(obj, &new))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer");
|
||||
}
|
||||
|
||||
{
|
||||
let mut new_obj_borrow = new.borrow_mut();
|
||||
let new_array_buffer = new_obj_borrow
|
||||
.as_array_buffer_mut()
|
||||
.expect("Already checked that `new_obj` was an `ArrayBuffer`");
|
||||
|
||||
// 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception.
|
||||
if new_array_buffer.array_buffer_byte_length < new_len {
|
||||
return context.throw_type_error("New ArrayBuffer length too small");
|
||||
}
|
||||
|
||||
// 22. NOTE: Side-effects of the above steps may have detached O.
|
||||
// 23. If IsDetachedBuffer(O) is true, throw a TypeError exception.
|
||||
if Self::is_detached_buffer(o) {
|
||||
return context
|
||||
.throw_type_error("ArrayBuffer detached while ArrayBuffer.slice was running");
|
||||
}
|
||||
|
||||
// 24. Let fromBuf be O.[[ArrayBufferData]].
|
||||
let from_buf = o
|
||||
.array_buffer_data
|
||||
.as_ref()
|
||||
.expect("ArrayBuffer cannot be detached here");
|
||||
|
||||
// 25. Let toBuf be new.[[ArrayBufferData]].
|
||||
let to_buf = new_array_buffer
|
||||
.array_buffer_data
|
||||
.as_mut()
|
||||
.expect("ArrayBuffer cannot be detached here");
|
||||
|
||||
// 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
|
||||
copy_data_block_bytes(to_buf, 0, from_buf, first as usize, new_len);
|
||||
}
|
||||
|
||||
// 27. Return new.
|
||||
Ok(new.into())
|
||||
}
|
||||
|
||||
/// `25.1.2.1 AllocateArrayBuffer ( constructor, byteLength )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-allocatearraybuffer
|
||||
pub(crate) fn allocate(
|
||||
constructor: &JsValue,
|
||||
byte_length: usize,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject> {
|
||||
// 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »).
|
||||
let prototype = get_prototype_from_constructor(
|
||||
constructor,
|
||||
StandardConstructors::array_buffer,
|
||||
context,
|
||||
)?;
|
||||
let obj = context.construct_object();
|
||||
obj.set_prototype(prototype.into());
|
||||
|
||||
// 2. Let block be ? CreateByteDataBlock(byteLength).
|
||||
let block = create_byte_data_block(byte_length, context)?;
|
||||
|
||||
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||
obj.borrow_mut().data = ObjectData::array_buffer(Self {
|
||||
array_buffer_data: Some(block),
|
||||
array_buffer_byte_length: byte_length,
|
||||
array_buffer_detach_key: JsValue::Undefined,
|
||||
});
|
||||
|
||||
// 5. Return obj.
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
/// `25.1.2.2 IsDetachedBuffer ( arrayBuffer )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer
|
||||
pub(crate) fn is_detached_buffer(&self) -> bool {
|
||||
// 1. If arrayBuffer.[[ArrayBufferData]] is null, return true.
|
||||
// 2. Return false.
|
||||
self.array_buffer_data.is_none()
|
||||
}
|
||||
|
||||
/// `25.1.2.4 CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength, cloneConstructor )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-clonearraybuffer
|
||||
pub(crate) fn clone_array_buffer(
|
||||
&self,
|
||||
src_byte_offset: usize,
|
||||
src_length: usize,
|
||||
clone_constructor: &JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsObject> {
|
||||
// 1. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength).
|
||||
let target_buffer = Self::allocate(clone_constructor, src_length, context)?;
|
||||
|
||||
// 2. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception.
|
||||
// 3. Let srcBlock be srcBuffer.[[ArrayBufferData]].
|
||||
let src_block = if let Some(b) = &self.array_buffer_data {
|
||||
b
|
||||
} else {
|
||||
return context.throw_syntax_error("Cannot clone detached array buffer");
|
||||
};
|
||||
|
||||
{
|
||||
// 4. Let targetBlock be targetBuffer.[[ArrayBufferData]].
|
||||
let mut target_buffer_mut = target_buffer.borrow_mut();
|
||||
let target_block = target_buffer_mut
|
||||
.as_array_buffer_mut()
|
||||
.expect("This must be an ArrayBuffer");
|
||||
|
||||
// 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength).
|
||||
copy_data_block_bytes(
|
||||
target_block
|
||||
.array_buffer_data
|
||||
.as_mut()
|
||||
.expect("ArrayBuffer cannot me detached here"),
|
||||
0,
|
||||
src_block,
|
||||
src_byte_offset,
|
||||
src_length,
|
||||
);
|
||||
}
|
||||
|
||||
// 6. Return targetBuffer.
|
||||
Ok(target_buffer)
|
||||
}
|
||||
|
||||
/// `25.1.2.6 IsUnclampedIntegerElementType ( type )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isunclampedintegerelementtype
|
||||
fn is_unclamped_integer_element_type(t: TypedArrayKind) -> bool {
|
||||
// 1. If type is Int8, Uint8, Int16, Uint16, Int32, or Uint32, return true.
|
||||
// 2. Return false.
|
||||
matches!(
|
||||
t,
|
||||
TypedArrayKind::Int8
|
||||
| TypedArrayKind::Uint8
|
||||
| TypedArrayKind::Int16
|
||||
| TypedArrayKind::Uint16
|
||||
| TypedArrayKind::Int32
|
||||
| TypedArrayKind::Uint32
|
||||
)
|
||||
}
|
||||
|
||||
/// `25.1.2.7 IsBigIntElementType ( type )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isbigintelementtype
|
||||
fn is_big_int_element_type(t: TypedArrayKind) -> bool {
|
||||
// 1. If type is BigUint64 or BigInt64, return true.
|
||||
// 2. Return false.
|
||||
matches!(t, TypedArrayKind::BigUint64 | TypedArrayKind::BigInt64)
|
||||
}
|
||||
|
||||
/// `25.1.2.8 IsNoTearConfiguration ( type, order )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isnotearconfiguration
|
||||
// TODO: Allow unused function until shared array buffers are implemented.
|
||||
#[allow(dead_code)]
|
||||
fn is_no_tear_configuration(t: TypedArrayKind, order: SharedMemoryOrder) -> bool {
|
||||
// 1. If ! IsUnclampedIntegerElementType(type) is true, return true.
|
||||
if Self::is_unclamped_integer_element_type(t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. If ! IsBigIntElementType(type) is true and order is not Init or Unordered, return true.
|
||||
if Self::is_big_int_element_type(t)
|
||||
&& !matches!(
|
||||
order,
|
||||
SharedMemoryOrder::Init | SharedMemoryOrder::Unordered
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Return false.
|
||||
false
|
||||
}
|
||||
|
||||
/// `25.1.2.9 RawBytesToNumeric ( type, rawBytes, isLittleEndian )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-rawbytestonumeric
|
||||
fn raw_bytes_to_numeric(t: TypedArrayKind, bytes: &[u8], is_little_endian: bool) -> JsValue {
|
||||
let n: Numeric = match t {
|
||||
TypedArrayKind::Int8 => {
|
||||
if is_little_endian {
|
||||
i8::from_le_bytes(bytes.try_into().expect("slice with incorrect length")).into()
|
||||
} else {
|
||||
i8::from_be_bytes(bytes.try_into().expect("slice with incorrect length")).into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => {
|
||||
if is_little_endian {
|
||||
u8::from_le_bytes(bytes.try_into().expect("slice with incorrect length")).into()
|
||||
} else {
|
||||
u8::from_be_bytes(bytes.try_into().expect("slice with incorrect length")).into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Int16 => {
|
||||
if is_little_endian {
|
||||
i16::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
i16::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Uint16 => {
|
||||
if is_little_endian {
|
||||
u16::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
u16::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Int32 => {
|
||||
if is_little_endian {
|
||||
i32::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
i32::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Uint32 => {
|
||||
if is_little_endian {
|
||||
u32::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
u32::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::BigInt64 => {
|
||||
if is_little_endian {
|
||||
i64::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
i64::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::BigUint64 => {
|
||||
if is_little_endian {
|
||||
u64::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
u64::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Float32 => {
|
||||
if is_little_endian {
|
||||
f32::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
f32::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
TypedArrayKind::Float64 => {
|
||||
if is_little_endian {
|
||||
f64::from_le_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
} else {
|
||||
f64::from_be_bytes(bytes.try_into().expect("slice with incorrect length"))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
n.into()
|
||||
}
|
||||
|
||||
/// `25.1.2.10 GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-getvaluefrombuffer
|
||||
pub(crate) fn get_value_from_buffer(
|
||||
&self,
|
||||
byte_index: usize,
|
||||
t: TypedArrayKind,
|
||||
_is_typed_array: bool,
|
||||
_order: SharedMemoryOrder,
|
||||
is_little_endian: Option<bool>,
|
||||
) -> JsValue {
|
||||
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
|
||||
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
|
||||
// 3. Let block be arrayBuffer.[[ArrayBufferData]].
|
||||
let block = self
|
||||
.array_buffer_data
|
||||
.as_ref()
|
||||
.expect("ArrayBuffer cannot be detached here");
|
||||
|
||||
// 4. Let elementSize be the Element Size value specified in Table 73 for Element Type type.
|
||||
let element_size = t.element_size();
|
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 5. If IsSharedArrayBuffer(arrayBuffer) is true, then
|
||||
|
||||
// 6. Else, let rawValue be a List whose elements are bytes from block at indices byteIndex (inclusive) through byteIndex + elementSize (exclusive).
|
||||
// 7. Assert: The number of elements in rawValue is elementSize.
|
||||
let raw_value = &block[byte_index..byte_index + element_size];
|
||||
|
||||
// TODO: Agent Record [[LittleEndian]] filed
|
||||
// 8. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
|
||||
let is_little_endian = is_little_endian.unwrap_or(true);
|
||||
|
||||
// 9. Return RawBytesToNumeric(type, rawValue, isLittleEndian).
|
||||
Self::raw_bytes_to_numeric(t, raw_value, is_little_endian)
|
||||
}
|
||||
|
||||
/// `25.1.2.11 NumericToRawBytes ( type, value, isLittleEndian )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-numerictorawbytes
|
||||
fn numeric_to_raw_bytes(
|
||||
t: TypedArrayKind,
|
||||
value: &JsValue,
|
||||
is_little_endian: bool,
|
||||
context: &mut Context,
|
||||
) -> JsResult<Vec<u8>> {
|
||||
Ok(match t {
|
||||
TypedArrayKind::Int8 if is_little_endian => {
|
||||
value.to_int8(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Int8 => value.to_int8(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Uint8 if is_little_endian => {
|
||||
value.to_uint8(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Uint8 => value.to_uint8(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Uint8Clamped if is_little_endian => {
|
||||
value.to_uint8_clamp(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Uint8Clamped => value.to_uint8_clamp(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Int16 if is_little_endian => {
|
||||
value.to_int16(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Int16 => value.to_int16(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Uint16 if is_little_endian => {
|
||||
value.to_uint16(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Uint16 => value.to_uint16(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Int32 if is_little_endian => {
|
||||
value.to_i32(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Int32 => value.to_i32(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::Uint32 if is_little_endian => {
|
||||
value.to_u32(context)?.to_le_bytes().to_vec()
|
||||
}
|
||||
TypedArrayKind::Uint32 => value.to_u32(context)?.to_be_bytes().to_vec(),
|
||||
TypedArrayKind::BigInt64 if is_little_endian => {
|
||||
let big_int = value.to_big_int64(context)?;
|
||||
big_int
|
||||
.to_i64()
|
||||
.unwrap_or_else(|| {
|
||||
if big_int.is_positive() {
|
||||
i64::MAX
|
||||
} else {
|
||||
i64::MIN
|
||||
}
|
||||
})
|
||||
.to_le_bytes()
|
||||
.to_vec()
|
||||
}
|
||||
TypedArrayKind::BigInt64 => {
|
||||
let big_int = value.to_big_int64(context)?;
|
||||
big_int
|
||||
.to_i64()
|
||||
.unwrap_or_else(|| {
|
||||
if big_int.is_positive() {
|
||||
i64::MAX
|
||||
} else {
|
||||
i64::MIN
|
||||
}
|
||||
})
|
||||
.to_be_bytes()
|
||||
.to_vec()
|
||||
}
|
||||
TypedArrayKind::BigUint64 if is_little_endian => value
|
||||
.to_big_uint64(context)?
|
||||
.to_u64()
|
||||
.unwrap_or(u64::MAX)
|
||||
.to_le_bytes()
|
||||
.to_vec(),
|
||||
TypedArrayKind::BigUint64 => value
|
||||
.to_big_uint64(context)?
|
||||
.to_u64()
|
||||
.unwrap_or(u64::MAX)
|
||||
.to_be_bytes()
|
||||
.to_vec(),
|
||||
TypedArrayKind::Float32 => match value.to_number(context)? {
|
||||
f if is_little_endian => (f as f32).to_le_bytes().to_vec(),
|
||||
f => (f as f32).to_be_bytes().to_vec(),
|
||||
},
|
||||
TypedArrayKind::Float64 => match value.to_number(context)? {
|
||||
f if is_little_endian => f.to_le_bytes().to_vec(),
|
||||
f => f.to_be_bytes().to_vec(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// `25.1.2.12 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-setvalueinbuffer
|
||||
pub(crate) fn set_value_in_buffer(
|
||||
&mut self,
|
||||
byte_index: usize,
|
||||
t: TypedArrayKind,
|
||||
value: &JsValue,
|
||||
_order: SharedMemoryOrder,
|
||||
is_little_endian: Option<bool>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
|
||||
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
|
||||
// 3. Assert: Type(value) is BigInt if ! IsBigIntElementType(type) is true; otherwise, Type(value) is Number.
|
||||
// 4. Let block be arrayBuffer.[[ArrayBufferData]].
|
||||
let block = self
|
||||
.array_buffer_data
|
||||
.as_mut()
|
||||
.expect("ArrayBuffer cannot be detached here");
|
||||
|
||||
// 5. Let elementSize be the Element Size value specified in Table 73 for Element Type type.
|
||||
|
||||
// TODO: Agent Record [[LittleEndian]] filed
|
||||
// 6. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
|
||||
let is_little_endian = is_little_endian.unwrap_or(true);
|
||||
|
||||
// 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian).
|
||||
let raw_bytes = Self::numeric_to_raw_bytes(t, value, is_little_endian, context)?;
|
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 8. If IsSharedArrayBuffer(arrayBuffer) is true, then
|
||||
|
||||
// 9. Else, store the individual bytes of rawBytes into block, starting at block[byteIndex].
|
||||
for (i, raw_byte) in raw_bytes.iter().enumerate() {
|
||||
block[byte_index + i] = *raw_byte;
|
||||
}
|
||||
|
||||
// 10. Return NormalCompletion(undefined).
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
}
|
||||
|
||||
/// `CreateByteDataBlock ( size )` abstract operation.
|
||||
///
|
||||
/// The abstract operation `CreateByteDataBlock` takes argument `size` (a non-negative
|
||||
/// integer). For more information, check the [spec][spec].
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createbytedatablock
|
||||
pub fn create_byte_data_block(size: usize, context: &mut Context) -> JsResult<Vec<u8>> {
|
||||
// 1. Let db be a new Data Block value consisting of size bytes. If it is impossible to
|
||||
// create such a Data Block, throw a RangeError exception.
|
||||
let mut data_block = Vec::new();
|
||||
data_block.try_reserve(size).map_err(|e| {
|
||||
context.construct_range_error(format!("couldn't allocate the data block: {e}"))
|
||||
})?;
|
||||
|
||||
// 2. Set all of the bytes of db to 0.
|
||||
data_block.resize(size, 0);
|
||||
|
||||
// 3. Return db.
|
||||
Ok(data_block)
|
||||
}
|
||||
|
||||
/// `6.2.8.3 CopyDataBlockBytes ( toBlock, toIndex, fromBlock, fromIndex, count )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-copydatablockbytes
|
||||
fn copy_data_block_bytes(
|
||||
to_block: &mut [u8],
|
||||
mut to_index: usize,
|
||||
from_block: &[u8],
|
||||
mut from_index: usize,
|
||||
mut count: usize,
|
||||
) {
|
||||
// 1. Assert: fromBlock and toBlock are distinct values.
|
||||
// 2. Let fromSize be the number of bytes in fromBlock.
|
||||
let from_size = from_block.len();
|
||||
|
||||
// 3. Assert: fromIndex + count ≤ fromSize.
|
||||
assert!(from_index + count <= from_size);
|
||||
|
||||
// 4. Let toSize be the number of bytes in toBlock.
|
||||
let to_size = to_block.len();
|
||||
|
||||
// 5. Assert: toIndex + count ≤ toSize.
|
||||
assert!(to_index + count <= to_size);
|
||||
|
||||
// 6. Repeat, while count > 0,
|
||||
while count > 0 {
|
||||
// a. If fromBlock is a Shared Data Block, then
|
||||
// TODO: Shared Data Block
|
||||
|
||||
// b. Else,
|
||||
// i. Assert: toBlock is not a Shared Data Block.
|
||||
// ii. Set toBlock[toIndex] to fromBlock[fromIndex].
|
||||
to_block[to_index] = from_block[from_index];
|
||||
|
||||
// c. Set toIndex to toIndex + 1.
|
||||
to_index += 1;
|
||||
|
||||
// d. Set fromIndex to fromIndex + 1.
|
||||
from_index += 1;
|
||||
|
||||
// e. Set count to count - 1.
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
// 7. Return NormalCompletion(empty).
|
||||
}
|
||||
|
||||
// TODO: Allow unused variants until shared array buffers are implemented.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub(crate) enum SharedMemoryOrder {
|
||||
Init,
|
||||
SeqCst,
|
||||
Unordered,
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ut_sunny_day_create_byte_data_block() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert!(create_byte_data_block(100, &mut context).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ut_rainy_day_create_byte_data_block() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert!(create_byte_data_block(usize::MAX, &mut context).is_err());
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
//! This module implements the global `AsyncFunction` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-async-function-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction
|
||||
|
||||
use crate::{
|
||||
builtins::{
|
||||
function::{ConstructorKind, Function},
|
||||
BuiltIn,
|
||||
},
|
||||
object::ObjectData,
|
||||
property::PropertyDescriptor,
|
||||
symbol::WellKnownSymbols,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AsyncFunction;
|
||||
|
||||
impl BuiltIn for AsyncFunction {
|
||||
const NAME: &'static str = "AsyncFunction";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let prototype = &context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_function()
|
||||
.prototype;
|
||||
let constructor = &context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.async_function()
|
||||
.constructor;
|
||||
|
||||
constructor.set_prototype(Some(
|
||||
context.intrinsics().constructors().function().constructor(),
|
||||
));
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(1)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
constructor.borrow_mut().insert("length", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(Self::NAME)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
constructor.borrow_mut().insert("name", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(prototype.clone())
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(false);
|
||||
constructor.borrow_mut().insert("prototype", property);
|
||||
constructor.borrow_mut().data = ObjectData::function(Function::Native {
|
||||
function: Self::constructor,
|
||||
constructor: Some(ConstructorKind::Base),
|
||||
});
|
||||
|
||||
prototype.set_prototype(Some(
|
||||
context.intrinsics().constructors().function().prototype(),
|
||||
));
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(constructor.clone())
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype.borrow_mut().insert("constructor", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(Self::NAME)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype
|
||||
.borrow_mut()
|
||||
.insert(WellKnownSymbols::to_string_tag(), property);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncFunction {
|
||||
/// `AsyncFunction ( p1, p2, … , pn, body )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-async-function-constructor-arguments
|
||||
fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
crate::builtins::function::BuiltInFunctionObject::create_dynamic_function(
|
||||
new_target, args, true, false, context,
|
||||
)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
//! This module implements the global `BigInt` object.
|
||||
//!
|
||||
//! `BigInt` is a built-in object that provides a way to represent whole numbers larger
|
||||
//! than the largest number JavaScript can reliably represent with the Number primitive
|
||||
//! and represented by the `Number.MAX_SAFE_INTEGER` constant.
|
||||
//! `BigInt` can be used for arbitrarily large integers.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-bigint-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
object::ConstructorBuilder,
|
||||
property::Attribute,
|
||||
symbol::WellKnownSymbols,
|
||||
value::{IntegerOrInfinity, PreferredType},
|
||||
Context, JsBigInt, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use num_bigint::ToBigInt;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// `BigInt` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BigInt;
|
||||
|
||||
impl BuiltIn for BigInt {
|
||||
const NAME: &'static str = "BigInt";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().bigint_object().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.callable(true)
|
||||
.constructor(true)
|
||||
.method(Self::to_string, "toString", 0)
|
||||
.method(Self::value_of, "valueOf", 0)
|
||||
.static_method(Self::as_int_n, "asIntN", 2)
|
||||
.static_method(Self::as_uint_n, "asUintN", 2)
|
||||
.property(
|
||||
to_string_tag,
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl BigInt {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// `BigInt()`
|
||||
///
|
||||
/// The `BigInt()` constructor is used to create `BigInt` objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bigint-objects
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt
|
||||
fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is not undefined, throw a TypeError exception.
|
||||
if !new_target.is_undefined() {
|
||||
return context.throw_type_error("BigInt is not a constructor");
|
||||
}
|
||||
|
||||
let value = args.get_or_undefined(0);
|
||||
|
||||
// 2. Let prim be ? ToPrimitive(value, number).
|
||||
let prim = value.to_primitive(context, PreferredType::Number)?;
|
||||
|
||||
// 3. If Type(prim) is Number, return ? NumberToBigInt(prim).
|
||||
if let Some(number) = prim.as_number() {
|
||||
return Self::number_to_bigint(number, context);
|
||||
}
|
||||
|
||||
// 4. Otherwise, return ? ToBigInt(value).
|
||||
Ok(value.to_bigint(context)?.into())
|
||||
}
|
||||
|
||||
/// `NumberToBigInt ( number )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-numbertobigint
|
||||
#[inline]
|
||||
fn number_to_bigint(number: f64, context: &mut Context) -> JsResult<JsValue> {
|
||||
// 1. If IsIntegralNumber(number) is false, throw a RangeError exception.
|
||||
if number.is_nan() || number.is_infinite() || number.fract() != 0.0 {
|
||||
return context.throw_range_error(format!("Cannot convert {number} to BigInt"));
|
||||
}
|
||||
|
||||
// 2. Return the BigInt value that represents ℝ(number).
|
||||
Ok(JsBigInt::from(number.to_bigint().expect("This conversion must be safe")).into())
|
||||
}
|
||||
|
||||
/// The abstract operation `thisBigIntValue` takes argument value.
|
||||
///
|
||||
/// The phrase “this `BigInt` value” within the specification of a method refers to the
|
||||
/// result returned by calling the abstract operation `thisBigIntValue` with the `this` value
|
||||
/// of the method invocation passed as the argument.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-thisbigintvalue
|
||||
#[inline]
|
||||
fn this_bigint_value(value: &JsValue, context: &mut Context) -> JsResult<JsBigInt> {
|
||||
value
|
||||
// 1. If Type(value) is BigInt, return value.
|
||||
.as_bigint()
|
||||
.cloned()
|
||||
// 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then
|
||||
// a. Assert: Type(value.[[BigIntData]]) is BigInt.
|
||||
// b. Return value.[[BigIntData]].
|
||||
.or_else(|| {
|
||||
value
|
||||
.as_object()
|
||||
.and_then(|obj| obj.borrow().as_bigint().cloned())
|
||||
})
|
||||
// 3. Throw a TypeError exception.
|
||||
.ok_or_else(|| context.construct_type_error("'this' is not a BigInt"))
|
||||
}
|
||||
|
||||
/// `BigInt.prototype.toString( [radix] )`
|
||||
///
|
||||
/// The `toString()` method returns a string representing the specified `BigInt` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.tostring
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn to_string(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let x be ? thisBigIntValue(this value).
|
||||
let x = Self::this_bigint_value(this, context)?;
|
||||
|
||||
let radix = args.get_or_undefined(0);
|
||||
|
||||
// 2. If radix is undefined, let radixMV be 10.
|
||||
let radix_mv = if radix.is_undefined() {
|
||||
// 5. If radixMV = 10, return ! ToString(x).
|
||||
// Note: early return optimization.
|
||||
return Ok(x.to_string().into());
|
||||
// 3. Else, let radixMV be ? ToIntegerOrInfinity(radix).
|
||||
} else {
|
||||
radix.to_integer_or_infinity(context)?
|
||||
};
|
||||
|
||||
// 4. If radixMV < 2 or radixMV > 36, throw a RangeError exception.
|
||||
let radix_mv = match radix_mv {
|
||||
IntegerOrInfinity::Integer(i) if (2..=36).contains(&i) => i,
|
||||
_ => {
|
||||
return context.throw_range_error(
|
||||
"radix must be an integer at least 2 and no greater than 36",
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// 5. If radixMV = 10, return ! ToString(x).
|
||||
if radix_mv == 10 {
|
||||
return Ok(x.to_string().into());
|
||||
}
|
||||
|
||||
// 1. Let x be ? thisBigIntValue(this value).
|
||||
// 6. Return the String representation of this Number value using the radix specified by radixMV.
|
||||
// Letters a-z are used for digits with values 10 through 35.
|
||||
// The precise algorithm is implementation-defined, however the algorithm should be a generalization of that specified in 6.1.6.2.23.
|
||||
Ok(JsValue::new(x.to_string_radix(radix_mv as u32)))
|
||||
}
|
||||
|
||||
/// `BigInt.prototype.valueOf()`
|
||||
///
|
||||
/// The `valueOf()` method returns the wrapped primitive value of a Number object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.valueof
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/valueOf
|
||||
pub(crate) fn value_of(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
Ok(JsValue::new(Self::this_bigint_value(this, context)?))
|
||||
}
|
||||
|
||||
/// `BigInt.asIntN()`
|
||||
///
|
||||
/// The `BigInt.asIntN()` method wraps the value of a `BigInt` to a signed integer between `-2**(width - 1)` and `2**(width-1) - 1`.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bigint.asintn
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asIntN
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn as_int_n(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let (modulo, bits) = Self::calculate_as_uint_n(args, context)?;
|
||||
|
||||
if bits > 0
|
||||
&& modulo
|
||||
>= JsBigInt::pow(
|
||||
&JsBigInt::new(2),
|
||||
&JsBigInt::new(i64::from(bits) - 1),
|
||||
context,
|
||||
)?
|
||||
{
|
||||
Ok(JsValue::new(JsBigInt::sub(
|
||||
&modulo,
|
||||
&JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)), context)?,
|
||||
)))
|
||||
} else {
|
||||
Ok(JsValue::new(modulo))
|
||||
}
|
||||
}
|
||||
|
||||
/// `BigInt.asUintN()`
|
||||
///
|
||||
/// The `BigInt.asUintN()` method wraps the value of a `BigInt` to an unsigned integer between `0` and `2**(width) - 1`.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bigint.asuintn
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asUintN
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn as_uint_n(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let (modulo, _) = Self::calculate_as_uint_n(args, context)?;
|
||||
|
||||
Ok(JsValue::new(modulo))
|
||||
}
|
||||
|
||||
/// Helper function to wrap the value of a `BigInt` to an unsigned integer.
|
||||
///
|
||||
/// This function expects the same arguments as `as_uint_n` and wraps the value of a `BigInt`.
|
||||
/// Additionally to the wrapped unsigned value it returns the converted `bits` argument, so it
|
||||
/// can be reused from the `as_int_n` method.
|
||||
fn calculate_as_uint_n(args: &[JsValue], context: &mut Context) -> JsResult<(JsBigInt, u32)> {
|
||||
let bits_arg = args.get_or_undefined(0);
|
||||
let bigint_arg = args.get_or_undefined(1);
|
||||
|
||||
let bits = bits_arg.to_index(context)?;
|
||||
let bits = u32::try_from(bits).unwrap_or(u32::MAX);
|
||||
|
||||
let bigint = bigint_arg.to_bigint(context)?;
|
||||
|
||||
Ok((
|
||||
JsBigInt::mod_floor(
|
||||
&bigint,
|
||||
&JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(i64::from(bits)), context)?,
|
||||
),
|
||||
bits,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
use crate::{forward, Context};
|
||||
|
||||
#[test]
|
||||
fn equality() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "0n == 0n"), "true");
|
||||
assert_eq!(forward(&mut context, "1n == 0n"), "false");
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"1000000000000000000000000000000000n == 1000000000000000000000000000000000n"
|
||||
),
|
||||
"true"
|
||||
);
|
||||
|
||||
assert_eq!(forward(&mut context, "0n == ''"), "true");
|
||||
assert_eq!(forward(&mut context, "100n == '100'"), "true");
|
||||
assert_eq!(forward(&mut context, "100n == '100.5'"), "false");
|
||||
assert_eq!(
|
||||
forward(&mut context, "10000000000000000n == '10000000000000000'"),
|
||||
"true"
|
||||
);
|
||||
|
||||
assert_eq!(forward(&mut context, "'' == 0n"), "true");
|
||||
assert_eq!(forward(&mut context, "'100' == 100n"), "true");
|
||||
assert_eq!(forward(&mut context, "'100.5' == 100n"), "false");
|
||||
assert_eq!(
|
||||
forward(&mut context, "'10000000000000000' == 10000000000000000n"),
|
||||
"true"
|
||||
);
|
||||
|
||||
assert_eq!(forward(&mut context, "0n == 0"), "true");
|
||||
assert_eq!(forward(&mut context, "0n == 0.0"), "true");
|
||||
assert_eq!(forward(&mut context, "100n == 100"), "true");
|
||||
assert_eq!(forward(&mut context, "100n == 100.0"), "true");
|
||||
assert_eq!(forward(&mut context, "100n == '100.5'"), "false");
|
||||
assert_eq!(forward(&mut context, "100n == '1005'"), "false");
|
||||
assert_eq!(
|
||||
forward(&mut context, "10000000000000000n == 10000000000000000"),
|
||||
"true"
|
||||
);
|
||||
|
||||
assert_eq!(forward(&mut context, "0 == 0n"), "true");
|
||||
assert_eq!(forward(&mut context, "0.0 == 0n"), "true");
|
||||
assert_eq!(forward(&mut context, "100 == 100n"), "true");
|
||||
assert_eq!(forward(&mut context, "100.0 == 100n"), "true");
|
||||
assert_eq!(forward(&mut context, "100.5 == 100n"), "false");
|
||||
assert_eq!(forward(&mut context, "1005 == 100n"), "false");
|
||||
assert_eq!(
|
||||
forward(&mut context, "10000000000000000 == 10000000000000000n"),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_integer() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt(1000)"), "1000n");
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt(20000000000000000)"),
|
||||
"20000000000000000n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt(1000000000000000000000000000000000)"),
|
||||
"999999999999999945575230987042816n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_rational() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt(0.0)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt(1.0)"), "1n");
|
||||
assert_eq!(forward(&mut context, "BigInt(10000.0)"), "10000n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_rational_with_fractional_part() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let scenario = r#"
|
||||
try {
|
||||
BigInt(0.1);
|
||||
} catch (e) {
|
||||
e.toString();
|
||||
}
|
||||
"#;
|
||||
assert_eq!(
|
||||
forward(&mut context, scenario),
|
||||
"\"RangeError: Cannot convert 0.1 to BigInt\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_null() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let scenario = r#"
|
||||
try {
|
||||
BigInt(null);
|
||||
} catch (e) {
|
||||
e.toString();
|
||||
}
|
||||
"#;
|
||||
assert_eq!(
|
||||
forward(&mut context, scenario),
|
||||
"\"TypeError: cannot convert null to a BigInt\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_undefined() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let scenario = r#"
|
||||
try {
|
||||
BigInt(undefined);
|
||||
} catch (e) {
|
||||
e.toString();
|
||||
}
|
||||
"#;
|
||||
assert_eq!(
|
||||
forward(&mut context, scenario),
|
||||
"\"TypeError: cannot convert undefined to a BigInt\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigint_function_conversion_from_string() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt('')"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt(' ')"), "0n");
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt('200000000000000000')"),
|
||||
"200000000000000000n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt('1000000000000000000000000000000000')"),
|
||||
"1000000000000000000000000000000000n"
|
||||
);
|
||||
assert_eq!(forward(&mut context, "BigInt('0b1111')"), "15n");
|
||||
assert_eq!(forward(&mut context, "BigInt('0o70')"), "56n");
|
||||
assert_eq!(forward(&mut context, "BigInt('0xFF')"), "255n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "10000n + 1000n"), "11000n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "10000n - 1000n"), "9000n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, "123456789n * 102030n"),
|
||||
"12596296181670n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "15000n / 50n"), "300n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_with_truncation() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "15001n / 50n"), "300n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn r#mod() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "15007n % 10n"), "7n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, "100n ** 10n"),
|
||||
"100000000000000000000n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_negative_exponent() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "10n ** (-10n)", "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shl() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "8n << 2n"), "32n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shl_out_of_range() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "1000n << 1000000000000000n", "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shr() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "8n >> 2n"), "2n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shr_out_of_range() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "1000n >> 1000000000000000n", "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "1000n.toString()"), "\"1000\"");
|
||||
assert_eq!(forward(&mut context, "1000n.toString(2)"), "\"1111101000\"");
|
||||
assert_eq!(forward(&mut context, "255n.toString(16)"), "\"ff\"");
|
||||
assert_eq!(forward(&mut context, "1000n.toString(36)"), "\"rs\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string_invalid_radix() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "10n.toString(null)", "RangeError");
|
||||
assert_throws(&mut context, "10n.toString(-1)", "RangeError");
|
||||
assert_throws(&mut context, "10n.toString(37)", "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_int_n() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN(0, 1n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN(1, 1n)"), "-1n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN(3, 10n)"), "2n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN({}, 1n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN(2, 0n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asIntN(2, -0n)"), "0n");
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asIntN(2, -123456789012345678901n)"),
|
||||
"-1n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asIntN(2, -123456789012345678900n)"),
|
||||
"0n"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asIntN(2, 123456789012345678900n)"),
|
||||
"0n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asIntN(2, 123456789012345678901n)"),
|
||||
"1n"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asIntN(200, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)"
|
||||
),
|
||||
"-1n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asIntN(201, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)"
|
||||
),
|
||||
"1606938044258990275541962092341162602522202993782792835301375n"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asIntN(200, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)"
|
||||
),
|
||||
"-741470203160010616172516490008037905920749803227695190508460n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asIntN(201, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)"
|
||||
),
|
||||
"865467841098979659369445602333124696601453190555097644792916n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_int_n_errors() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "BigInt.asIntN(-1, 0n)", "RangeError");
|
||||
assert_throws(&mut context, "BigInt.asIntN(-2.5, 0n)", "RangeError");
|
||||
assert_throws(
|
||||
&mut context,
|
||||
"BigInt.asIntN(9007199254740992, 0n)",
|
||||
"RangeError",
|
||||
);
|
||||
assert_throws(&mut context, "BigInt.asIntN(0n, 0n)", "TypeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_uint_n() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(0, -2n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(0, -1n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(0, 0n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(0, 1n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(0, 2n)"), "0n");
|
||||
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, -3n)"), "1n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, -2n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, -1n)"), "1n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 0n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 1n)"), "1n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 2n)"), "0n");
|
||||
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 3n)"), "1n");
|
||||
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asUintN(1, -123456789012345678901n)"),
|
||||
"1n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asUintN(1, -123456789012345678900n)"),
|
||||
"0n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asUintN(1, 123456789012345678900n)"),
|
||||
"0n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "BigInt.asUintN(1, 123456789012345678901n)"),
|
||||
"1n"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asUintN(200, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)"
|
||||
),
|
||||
"1606938044258990275541962092341162602522202993782792835301375n"
|
||||
);
|
||||
assert_eq!(
|
||||
forward(
|
||||
&mut context,
|
||||
"BigInt.asUintN(201, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)"
|
||||
),
|
||||
"3213876088517980551083924184682325205044405987565585670602751n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_uint_n_errors() {
|
||||
let mut context = Context::default();
|
||||
|
||||
assert_throws(&mut context, "BigInt.asUintN(-1, 0n)", "RangeError");
|
||||
assert_throws(&mut context, "BigInt.asUintN(-2.5, 0n)", "RangeError");
|
||||
assert_throws(
|
||||
&mut context,
|
||||
"BigInt.asUintN(9007199254740992, 0n)",
|
||||
"RangeError",
|
||||
);
|
||||
assert_throws(&mut context, "BigInt.asUintN(0n, 0n)", "TypeError");
|
||||
}
|
||||
|
||||
fn assert_throws(context: &mut Context, src: &str, error_type: &str) {
|
||||
let result = forward(context, src);
|
||||
assert!(result.contains(error_type));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn division_by_zero() {
|
||||
let mut context = Context::default();
|
||||
assert_throws(&mut context, "1n/0n", "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remainder_by_zero() {
|
||||
let mut context = Context::default();
|
||||
assert_throws(&mut context, "1n % 0n", "RangeError");
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//! This module implements the global `Boolean` object.
|
||||
//!
|
||||
//! The `Boolean` object is an object wrapper for a boolean value.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-boolean-object
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{
|
||||
builtins::BuiltIn,
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
/// Boolean implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Boolean;
|
||||
|
||||
impl BuiltIn for Boolean {
|
||||
/// The name of the object.
|
||||
const NAME: &'static str = "Boolean";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().boolean().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.method(Self::to_string, "toString", 0)
|
||||
.method(Self::value_of, "valueOf", 0)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Boolean {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// `[[Construct]]` Create a new boolean object
|
||||
///
|
||||
/// `[[Call]]` Creates a new boolean primitive
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// Get the argument, if any
|
||||
let data = args.get(0).map_or(false, JsValue::to_boolean);
|
||||
if new_target.is_undefined() {
|
||||
return Ok(JsValue::new(data));
|
||||
}
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::boolean, context)?;
|
||||
let boolean = JsObject::from_proto_and_data(prototype, ObjectData::boolean(data));
|
||||
|
||||
Ok(boolean.into())
|
||||
}
|
||||
|
||||
/// An Utility function used to get the internal `[[BooleanData]]`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue
|
||||
fn this_boolean_value(value: &JsValue, context: &mut Context) -> JsResult<bool> {
|
||||
value
|
||||
.as_boolean()
|
||||
.or_else(|| value.as_object().and_then(|obj| obj.borrow().as_boolean()))
|
||||
.ok_or_else(|| context.construct_type_error("'this' is not a boolean"))
|
||||
}
|
||||
|
||||
/// The `toString()` method returns a string representing the specified `Boolean` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-boolean-object
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn to_string(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let boolean = Self::this_boolean_value(this, context)?;
|
||||
Ok(JsValue::new(boolean.to_string()))
|
||||
}
|
||||
|
||||
/// The valueOf() method returns the primitive value of a `Boolean` object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf
|
||||
#[inline]
|
||||
pub(crate) fn value_of(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
Ok(JsValue::new(Self::this_boolean_value(this, context)?))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use crate::{forward, forward_val, Context};
|
||||
|
||||
/// Test the correct type is returned from call and construct
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[test]
|
||||
fn construct_and_call() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var one = new Boolean(1);
|
||||
var zero = Boolean(0);
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let one = forward_val(&mut context, "one").unwrap();
|
||||
let zero = forward_val(&mut context, "zero").unwrap();
|
||||
|
||||
assert!(one.is_object());
|
||||
assert!(zero.is_boolean());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constructor_gives_true_instance() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var trueVal = new Boolean(true);
|
||||
var trueNum = new Boolean(1);
|
||||
var trueString = new Boolean("true");
|
||||
var trueBool = new Boolean(trueVal);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let true_val = forward_val(&mut context, "trueVal").expect("value expected");
|
||||
let true_num = forward_val(&mut context, "trueNum").expect("value expected");
|
||||
let true_string = forward_val(&mut context, "trueString").expect("value expected");
|
||||
let true_bool = forward_val(&mut context, "trueBool").expect("value expected");
|
||||
|
||||
// Values should all be objects
|
||||
assert!(true_val.is_object());
|
||||
assert!(true_num.is_object());
|
||||
assert!(true_string.is_object());
|
||||
assert!(true_bool.is_object());
|
||||
|
||||
// Values should all be truthy
|
||||
assert!(true_val.to_boolean());
|
||||
assert!(true_num.to_boolean());
|
||||
assert!(true_string.to_boolean());
|
||||
assert!(true_bool.to_boolean());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instances_have_correct_proto_set() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
var boolInstance = new Boolean(true);
|
||||
var boolProto = Boolean.prototype;
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
let bool_instance = forward_val(&mut context, "boolInstance").expect("value expected");
|
||||
let bool_prototype = forward_val(&mut context, "boolProto").expect("value expected");
|
||||
|
||||
assert_eq!(
|
||||
&*bool_instance.as_object().unwrap().prototype(),
|
||||
&bool_prototype.as_object().cloned()
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,577 @@
|
||||
//! This module implements the global `console` object.
|
||||
//!
|
||||
//! The `console` object can be accessed from any global object.
|
||||
//!
|
||||
//! The specifics of how it works varies from browser to browser, but there is a de facto set of features that are typically provided.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [WHATWG `console` specification][spec]
|
||||
//!
|
||||
//! [spec]: https://console.spec.whatwg.org/
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Console
|
||||
|
||||
#![allow(clippy::print_stdout)]
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
object::ObjectInitializer,
|
||||
value::{display::display_obj, JsValue, Numeric},
|
||||
Context, JsResult, JsString,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::time::SystemTime;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
/// This represents the different types of log messages.
|
||||
#[derive(Debug)]
|
||||
pub enum LogMessage {
|
||||
Log(String),
|
||||
Info(String),
|
||||
Warn(String),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
/// Helper function for logging messages.
|
||||
pub(crate) fn logger(msg: LogMessage, console_state: &Console) {
|
||||
let indent = 2 * console_state.groups.len();
|
||||
|
||||
match msg {
|
||||
LogMessage::Error(msg) => {
|
||||
eprintln!("{msg:>indent$}");
|
||||
}
|
||||
LogMessage::Log(msg) | LogMessage::Info(msg) | LogMessage::Warn(msg) => {
|
||||
println!("{msg:>indent$}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents the `console` formatter.
|
||||
pub fn formatter(data: &[JsValue], context: &mut Context) -> JsResult<String> {
|
||||
let target = data
|
||||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.to_string(context)?;
|
||||
|
||||
match data.len() {
|
||||
0 => Ok(String::new()),
|
||||
1 => Ok(target.to_string()),
|
||||
_ => {
|
||||
let mut formatted = String::new();
|
||||
let mut arg_index = 1;
|
||||
let mut chars = target.chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if c == '%' {
|
||||
let fmt = chars.next().unwrap_or('%');
|
||||
match fmt {
|
||||
/* integer */
|
||||
'd' | 'i' => {
|
||||
let arg = match data.get_or_undefined(arg_index).to_numeric(context)? {
|
||||
Numeric::Number(r) => (r.floor() + 0.0).to_string(),
|
||||
Numeric::BigInt(int) => int.to_string(),
|
||||
};
|
||||
formatted.push_str(&arg);
|
||||
arg_index += 1;
|
||||
}
|
||||
/* float */
|
||||
'f' => {
|
||||
let arg = data.get_or_undefined(arg_index).to_number(context)?;
|
||||
formatted.push_str(&format!("{arg:.6}"));
|
||||
arg_index += 1;
|
||||
}
|
||||
/* object, FIXME: how to render this properly? */
|
||||
'o' | 'O' => {
|
||||
let arg = data.get_or_undefined(arg_index);
|
||||
formatted.push_str(&arg.display().to_string());
|
||||
arg_index += 1;
|
||||
}
|
||||
/* string */
|
||||
's' => {
|
||||
let arg = data
|
||||
.get(arg_index)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.to_string(context)?;
|
||||
formatted.push_str(&arg);
|
||||
arg_index += 1;
|
||||
}
|
||||
'%' => formatted.push('%'),
|
||||
/* TODO: %c is not implemented */
|
||||
c => {
|
||||
formatted.push('%');
|
||||
formatted.push(c);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
formatted.push(c);
|
||||
};
|
||||
}
|
||||
|
||||
/* unformatted data */
|
||||
for rest in data.iter().skip(arg_index) {
|
||||
formatted.push_str(&format!(" {}", rest.to_string(context)?));
|
||||
}
|
||||
|
||||
Ok(formatted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the internal console object state.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Console {
|
||||
count_map: FxHashMap<JsString, u32>,
|
||||
timer_map: FxHashMap<JsString, u128>,
|
||||
groups: Vec<String>,
|
||||
}
|
||||
|
||||
impl BuiltIn for Console {
|
||||
const NAME: &'static str = "console";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
ObjectInitializer::new(context)
|
||||
.function(Self::assert, "assert", 0)
|
||||
.function(Self::clear, "clear", 0)
|
||||
.function(Self::debug, "debug", 0)
|
||||
.function(Self::error, "error", 0)
|
||||
.function(Self::info, "info", 0)
|
||||
.function(Self::log, "log", 0)
|
||||
.function(Self::trace, "trace", 0)
|
||||
.function(Self::warn, "warn", 0)
|
||||
.function(Self::error, "exception", 0)
|
||||
.function(Self::count, "count", 0)
|
||||
.function(Self::count_reset, "countReset", 0)
|
||||
.function(Self::group, "group", 0)
|
||||
.function(Self::group, "groupCollapsed", 0)
|
||||
.function(Self::group_end, "groupEnd", 0)
|
||||
.function(Self::time, "time", 0)
|
||||
.function(Self::time_log, "timeLog", 0)
|
||||
.function(Self::time_end, "timeEnd", 0)
|
||||
.function(Self::dir, "dir", 0)
|
||||
.function(Self::dir, "dirxml", 0)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Console {
|
||||
/// The name of the object.
|
||||
pub(crate) const NAME: &'static str = "console";
|
||||
|
||||
/// `console.assert(condition, ...data)`
|
||||
///
|
||||
/// Prints a JavaScript value to the standard error if first argument evaluates to `false` or there
|
||||
/// were no arguments.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#assert
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/assert
|
||||
pub(crate) fn assert(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let assertion = args.get(0).map_or(false, JsValue::to_boolean);
|
||||
|
||||
if !assertion {
|
||||
let mut args: Vec<JsValue> = args.iter().skip(1).cloned().collect();
|
||||
let message = "Assertion failed".to_string();
|
||||
if args.is_empty() {
|
||||
args.push(JsValue::new(message));
|
||||
} else if !args[0].is_string() {
|
||||
args.insert(0, JsValue::new(message));
|
||||
} else {
|
||||
let concat = format!("{message}: {}", args[0].display());
|
||||
args[0] = JsValue::new(concat);
|
||||
}
|
||||
|
||||
logger(
|
||||
LogMessage::Error(formatter(&args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.clear()`
|
||||
///
|
||||
/// Removes all groups and clears console if possible.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#clear
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/clear
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn clear(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
context.console_mut().groups.clear();
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.debug(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "debug" logLevel.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#debug
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/debug
|
||||
pub(crate) fn debug(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Log(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.error(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "error" logLevel.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#error
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/error
|
||||
pub(crate) fn error(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Error(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.info(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "info" logLevel.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#info
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/info
|
||||
pub(crate) fn info(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Info(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.log(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "log" logLevel.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#log
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/log
|
||||
pub(crate) fn log(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Log(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
fn get_stack_trace(context: &mut Context) -> Vec<String> {
|
||||
let mut stack_trace: Vec<String> = vec![];
|
||||
|
||||
for frame in context.vm.frames.iter().rev() {
|
||||
stack_trace.push(
|
||||
context
|
||||
.interner()
|
||||
.resolve_expect(frame.code.name)
|
||||
.to_owned(),
|
||||
);
|
||||
}
|
||||
|
||||
stack_trace
|
||||
}
|
||||
|
||||
/// `console.trace(...data)`
|
||||
///
|
||||
/// Prints a stack trace with "trace" logLevel, optionally labelled by data.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#trace
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/trace
|
||||
pub(crate) fn trace(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
if !args.is_empty() {
|
||||
logger(
|
||||
LogMessage::Log(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
|
||||
let stack_trace_dump = Self::get_stack_trace(context).join("\n");
|
||||
logger(LogMessage::Log(stack_trace_dump), context.console());
|
||||
}
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.warn(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "warn" logLevel.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#warn
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/warn
|
||||
pub(crate) fn warn(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Warn(formatter(args, context)?),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.count(label)`
|
||||
///
|
||||
/// Prints number of times the function was called with that particular label.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#count
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/count
|
||||
pub(crate) fn count(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let label = match args.get(0) {
|
||||
Some(value) => value.to_string(context)?,
|
||||
None => "default".into(),
|
||||
};
|
||||
|
||||
let msg = format!("count {label}:");
|
||||
let c = context.console_mut().count_map.entry(label).or_insert(0);
|
||||
*c += 1;
|
||||
|
||||
logger(LogMessage::Info(format!("{msg} {c}")), context.console());
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.countReset(label)`
|
||||
///
|
||||
/// Resets the counter for label.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#countreset
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/countReset
|
||||
pub(crate) fn count_reset(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let label = match args.get(0) {
|
||||
Some(value) => value.to_string(context)?,
|
||||
None => "default".into(),
|
||||
};
|
||||
|
||||
context.console_mut().count_map.remove(&label);
|
||||
|
||||
logger(
|
||||
LogMessage::Warn(format!("countReset {label}")),
|
||||
context.console(),
|
||||
);
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// Returns current system time in ms.
|
||||
fn system_time_in_ms() -> u128 {
|
||||
let now = SystemTime::now();
|
||||
now.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.expect("negative duration")
|
||||
.as_millis()
|
||||
}
|
||||
|
||||
/// `console.time(label)`
|
||||
///
|
||||
/// Starts the timer for given label.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#time
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/time
|
||||
pub(crate) fn time(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let label = match args.get(0) {
|
||||
Some(value) => value.to_string(context)?,
|
||||
None => "default".into(),
|
||||
};
|
||||
|
||||
if context.console().timer_map.get(&label).is_some() {
|
||||
logger(
|
||||
LogMessage::Warn(format!("Timer '{label}' already exist")),
|
||||
context.console(),
|
||||
);
|
||||
} else {
|
||||
let time = Self::system_time_in_ms();
|
||||
context.console_mut().timer_map.insert(label, time);
|
||||
}
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.timeLog(label, ...data)`
|
||||
///
|
||||
/// Prints elapsed time for timer with given label.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#timelog
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeLog
|
||||
pub(crate) fn time_log(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let label = match args.get(0) {
|
||||
Some(value) => value.to_string(context)?,
|
||||
None => "default".into(),
|
||||
};
|
||||
|
||||
if let Some(t) = context.console().timer_map.get(&label) {
|
||||
let time = Self::system_time_in_ms();
|
||||
let mut concat = format!("{label}: {} ms", time - t);
|
||||
for msg in args.iter().skip(1) {
|
||||
concat = concat + " " + &msg.display().to_string();
|
||||
}
|
||||
logger(LogMessage::Log(concat), context.console());
|
||||
} else {
|
||||
logger(
|
||||
LogMessage::Warn(format!("Timer '{label}' doesn't exist")),
|
||||
context.console(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.timeEnd(label)`
|
||||
///
|
||||
/// Removes the timer with given label.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#timeend
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeEnd
|
||||
pub(crate) fn time_end(
|
||||
_: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let label = match args.get(0) {
|
||||
Some(value) => value.to_string(context)?,
|
||||
None => "default".into(),
|
||||
};
|
||||
|
||||
if let Some(t) = context.console_mut().timer_map.remove(label.as_str()) {
|
||||
let time = Self::system_time_in_ms();
|
||||
logger(
|
||||
LogMessage::Info(format!("{label}: {} ms - timer removed", time - t)),
|
||||
context.console(),
|
||||
);
|
||||
} else {
|
||||
logger(
|
||||
LogMessage::Warn(format!("Timer '{label}' doesn't exist")),
|
||||
context.console(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.group(...data)`
|
||||
///
|
||||
/// Adds new group with name from formatted data to stack.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#group
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/group
|
||||
pub(crate) fn group(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
let group_label = formatter(args, context)?;
|
||||
|
||||
logger(
|
||||
LogMessage::Info(format!("group: {group_label}")),
|
||||
context.console(),
|
||||
);
|
||||
context.console_mut().groups.push(group_label);
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.groupEnd(label)`
|
||||
///
|
||||
/// Removes the last group from the stack.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#groupend
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/groupEnd
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn group_end(
|
||||
_: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
context.console_mut().groups.pop();
|
||||
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `console.dir(item, options)`
|
||||
///
|
||||
/// Prints info about item
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [WHATWG `console` specification][spec]
|
||||
///
|
||||
/// [spec]: https://console.spec.whatwg.org/#dir
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/dir
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn dir(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
logger(
|
||||
LogMessage::Info(display_obj(args.get_or_undefined(0), true)),
|
||||
context.console(),
|
||||
);
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use crate::{builtins::console::formatter, Context, JsValue};
|
||||
|
||||
#[test]
|
||||
fn formatter_no_args_is_empty_string() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(formatter(&[], &mut context).unwrap(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatter_empty_format_string_is_empty_string() {
|
||||
let mut context = Context::default();
|
||||
let val = JsValue::new("");
|
||||
assert_eq!(formatter(&[val], &mut context).unwrap(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatter_format_without_args_renders_verbatim() {
|
||||
let mut context = Context::default();
|
||||
let val = [JsValue::new("%d %s %% %f")];
|
||||
let res = formatter(&val, &mut context).unwrap();
|
||||
assert_eq!(res, "%d %s %% %f");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatter_empty_format_string_concatenates_rest_of_args() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let val = [
|
||||
JsValue::new(""),
|
||||
JsValue::new("to powinno zostać"),
|
||||
JsValue::new("połączone"),
|
||||
];
|
||||
let res = formatter(&val, &mut context).unwrap();
|
||||
assert_eq!(res, " to powinno zostać połączone");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatter_utf_8_checks() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let val = [
|
||||
JsValue::new("Są takie chwile %dą %są tu%sów %привет%ź".to_string()),
|
||||
JsValue::new(123),
|
||||
JsValue::new(1.23),
|
||||
JsValue::new("ł"),
|
||||
];
|
||||
let res = formatter(&val, &mut context).unwrap();
|
||||
assert_eq!(res, "Są takie chwile 123ą 1.23ą tułów %привет%ź");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatter_trailing_format_leader_renders() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let val = [JsValue::new("%%%%%"), JsValue::new("|")];
|
||||
let res = formatter(&val, &mut context).unwrap();
|
||||
assert_eq!(res, "%%% |");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::approx_constant)]
|
||||
fn formatter_float_format_works() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let val = [JsValue::new("%f"), JsValue::new(3.1415)];
|
||||
let res = formatter(&val, &mut context).unwrap();
|
||||
assert_eq!(res, "3.141500");
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,116 @@
|
||||
//! This module implements the global `AggregateError` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-aggregate-error
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError
|
||||
|
||||
use crate::{
|
||||
builtins::{iterable::iterable_to_list, Array, BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::{Attribute, PropertyDescriptorBuilder},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct AggregateError;
|
||||
|
||||
impl BuiltIn for AggregateError {
|
||||
const NAME: &'static str = "AggregateError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.aggregate_error()
|
||||
.clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl AggregateError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 2;
|
||||
|
||||
/// Create a new aggregate error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%", « [[ErrorData]] »).
|
||||
let prototype = get_prototype_from_constructor(
|
||||
new_target,
|
||||
StandardConstructors::aggregate_error,
|
||||
context,
|
||||
)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(1);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(2), context)?;
|
||||
|
||||
// 5. Let errorsList be ? IterableToList(errors).
|
||||
let errors = args.get_or_undefined(0);
|
||||
let errors_list = iterable_to_list(context, errors, None)?;
|
||||
// 6. Perform ! DefinePropertyOrThrow(O, "errors",
|
||||
// PropertyDescriptor {
|
||||
// [[Configurable]]: true,
|
||||
// [[Enumerable]]: false,
|
||||
// [[Writable]]: true,
|
||||
// [[Value]]: CreateArrayFromList(errorsList)
|
||||
// }).
|
||||
o.define_property_or_throw(
|
||||
"errors",
|
||||
PropertyDescriptorBuilder::new()
|
||||
.configurable(true)
|
||||
.enumerable(false)
|
||||
.writable(true)
|
||||
.value(Array::create_array_from_list(errors_list, context))
|
||||
.build(),
|
||||
context,
|
||||
)
|
||||
.expect("should not fail according to spec");
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//! This module implements the global `EvalError` object.
|
||||
//!
|
||||
//! Indicates an error regarding the global `eval()` function.
|
||||
//! This exception is not thrown by JavaScript anymore, however
|
||||
//! the `EvalError` object remains for compatibility.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// JavaScript `EvalError` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct EvalError;
|
||||
|
||||
impl BuiltIn for EvalError {
|
||||
const NAME: &'static str = "EvalError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().eval_error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl EvalError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::eval_error, context)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
//! This module implements the global `Error` object.
|
||||
//!
|
||||
//! Error objects are thrown when runtime errors occur.
|
||||
//! The Error object can also be used as a base object for user-defined exceptions.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-error-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
|
||||
use crate::{
|
||||
builtins::BuiltIn,
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsString, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
pub(crate) mod aggregate;
|
||||
pub(crate) mod eval;
|
||||
pub(crate) mod range;
|
||||
pub(crate) mod reference;
|
||||
pub(crate) mod syntax;
|
||||
pub(crate) mod r#type;
|
||||
pub(crate) mod uri;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub(crate) use self::aggregate::AggregateError;
|
||||
pub(crate) use self::eval::EvalError;
|
||||
pub(crate) use self::r#type::TypeError;
|
||||
pub(crate) use self::range::RangeError;
|
||||
pub(crate) use self::reference::ReferenceError;
|
||||
pub(crate) use self::syntax::SyntaxError;
|
||||
pub(crate) use self::uri::UriError;
|
||||
|
||||
use super::JsArgs;
|
||||
|
||||
/// Built-in `Error` object.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Error;
|
||||
|
||||
impl BuiltIn for Error {
|
||||
const NAME: &'static str = "Error";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.method(Self::to_string, "toString", 0)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
pub(crate) fn install_error_cause(
|
||||
o: &JsObject,
|
||||
options: &JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<()> {
|
||||
// 1. If Type(options) is Object and ? HasProperty(options, "cause") is true, then
|
||||
if let Some(options) = options.as_object() {
|
||||
if options.has_property("cause", context)? {
|
||||
// a. Let cause be ? Get(options, "cause").
|
||||
let cause = options.get("cause", context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause).
|
||||
o.create_non_enumerable_data_property_or_throw("cause", cause, context);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Return unused.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `Error( message [ , options ] )`
|
||||
///
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%", « [[ErrorData]] »).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::error, context)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Self::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
|
||||
/// `Error.prototype.toString()`
|
||||
///
|
||||
/// The toString() method returns a string representing the specified Error object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn to_string(
|
||||
this: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let O be the this value.
|
||||
let o = if let Some(o) = this.as_object() {
|
||||
o
|
||||
// 2. If Type(O) is not Object, throw a TypeError exception.
|
||||
} else {
|
||||
return context.throw_type_error("'this' is not an Object");
|
||||
};
|
||||
|
||||
// 3. Let name be ? Get(O, "name").
|
||||
let name = o.get("name", context)?;
|
||||
|
||||
// 4. If name is undefined, set name to "Error"; otherwise set name to ? ToString(name).
|
||||
let name = if name.is_undefined() {
|
||||
JsString::new("Error")
|
||||
} else {
|
||||
name.to_string(context)?
|
||||
};
|
||||
|
||||
// 5. Let msg be ? Get(O, "message").
|
||||
let msg = o.get("message", context)?;
|
||||
|
||||
// 6. If msg is undefined, set msg to the empty String; otherwise set msg to ? ToString(msg).
|
||||
let msg = if msg.is_undefined() {
|
||||
JsString::empty()
|
||||
} else {
|
||||
msg.to_string(context)?
|
||||
};
|
||||
|
||||
// 7. If name is the empty String, return msg.
|
||||
if name.is_empty() {
|
||||
return Ok(msg.into());
|
||||
}
|
||||
|
||||
// 8. If msg is the empty String, return name.
|
||||
if msg.is_empty() {
|
||||
return Ok(name.into());
|
||||
}
|
||||
|
||||
// 9. Return the string-concatenation of name, the code unit 0x003A (COLON),
|
||||
// the code unit 0x0020 (SPACE), and msg.
|
||||
Ok(format!("{name}: {msg}").into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//! This module implements the global `RangeError` object.
|
||||
//!
|
||||
//! Indicates a value that is not in the set or range of allowable values.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// JavaScript `RangeError` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct RangeError;
|
||||
|
||||
impl BuiltIn for RangeError {
|
||||
const NAME: &'static str = "RangeError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().range_error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl RangeError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::range_error, context)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//! This module implements the global `ReferenceError` object.
|
||||
//!
|
||||
//! Indicates an error that occurs when de-referencing an invalid reference
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct ReferenceError;
|
||||
|
||||
impl BuiltIn for ReferenceError {
|
||||
const NAME: &'static str = "ReferenceError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.reference_error()
|
||||
.clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReferenceError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype = get_prototype_from_constructor(
|
||||
new_target,
|
||||
StandardConstructors::reference_error,
|
||||
context,
|
||||
)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//! This module implements the global `SyntaxError` object.
|
||||
//!
|
||||
//! The `SyntaxError` object represents an error when trying to interpret syntactically invalid code.
|
||||
//! It is thrown when the JavaScript context encounters tokens or token order that does not conform
|
||||
//! to the syntax of the language when parsing code.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// JavaScript `SyntaxError` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct SyntaxError;
|
||||
|
||||
impl BuiltIn for SyntaxError {
|
||||
const NAME: &'static str = "SyntaxError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().syntax_error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl SyntaxError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype = get_prototype_from_constructor(
|
||||
new_target,
|
||||
StandardConstructors::syntax_error,
|
||||
context,
|
||||
)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use crate::{forward, Context};
|
||||
|
||||
#[test]
|
||||
fn error_to_string() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
let e = new Error('1');
|
||||
let name = new Error();
|
||||
let message = new Error('message');
|
||||
message.name = '';
|
||||
let range_e = new RangeError('2');
|
||||
let ref_e = new ReferenceError('3');
|
||||
let syntax_e = new SyntaxError('4');
|
||||
let type_e = new TypeError('5');
|
||||
"#;
|
||||
forward(&mut context, init);
|
||||
assert_eq!(forward(&mut context, "e.toString()"), "\"Error: 1\"");
|
||||
assert_eq!(forward(&mut context, "name.toString()"), "\"Error\"");
|
||||
assert_eq!(forward(&mut context, "message.toString()"), "\"message\"");
|
||||
assert_eq!(
|
||||
forward(&mut context, "range_e.toString()"),
|
||||
"\"RangeError: 2\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "ref_e.toString()"),
|
||||
"\"ReferenceError: 3\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "syntax_e.toString()"),
|
||||
"\"SyntaxError: 4\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "type_e.toString()"),
|
||||
"\"TypeError: 5\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_error_name() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(forward(&mut context, "EvalError.name"), "\"EvalError\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_error_length() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(forward(&mut context, "EvalError.length"), "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_error_to_string() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(
|
||||
forward(&mut context, "new EvalError('hello').toString()"),
|
||||
"\"EvalError: hello\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "new EvalError().toString()"),
|
||||
"\"EvalError\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_error_name() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(forward(&mut context, "URIError.name"), "\"URIError\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_error_length() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(forward(&mut context, "URIError.length"), "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_error_to_string() {
|
||||
let mut context = Context::default();
|
||||
assert_eq!(
|
||||
forward(&mut context, "new URIError('hello').toString()"),
|
||||
"\"URIError: hello\""
|
||||
);
|
||||
assert_eq!(
|
||||
forward(&mut context, "new URIError().toString()"),
|
||||
"\"URIError\""
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
//! This module implements the global `TypeError` object.
|
||||
//!
|
||||
//! The `TypeError` object represents an error when an operation could not be performed,
|
||||
//! typically (but not exclusively) when a value is not of the expected type.
|
||||
//!
|
||||
//! A `TypeError` may be thrown when:
|
||||
//! - an operand or argument passed to a function is incompatible with the type expected by that operator or function.
|
||||
//! - when attempting to modify a value that cannot be changed.
|
||||
//! - when attempting to use a value in an inappropriate way.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-typeerror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
|
||||
|
||||
use crate::{
|
||||
builtins::{function::Function, BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::{Attribute, PropertyDescriptor},
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// JavaScript `TypeError` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct TypeError;
|
||||
|
||||
impl BuiltIn for TypeError {
|
||||
const NAME: &'static str = "TypeError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().type_error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::type_error, context)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_throw_type_error(context: &mut Context) -> JsObject {
|
||||
fn throw_type_error(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
context.throw_type_error("'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them")
|
||||
}
|
||||
|
||||
let function = JsObject::from_proto_and_data(
|
||||
context.intrinsics().constructors().function().prototype(),
|
||||
ObjectData::function(Function::Native {
|
||||
function: throw_type_error,
|
||||
constructor: None,
|
||||
}),
|
||||
);
|
||||
|
||||
let property = PropertyDescriptor::builder()
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(false);
|
||||
function.insert_property("name", property.clone().value("ThrowTypeError"));
|
||||
function.insert_property("length", property.value(0));
|
||||
|
||||
function
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//! This module implements the global `URIError` object.
|
||||
//!
|
||||
//! The `URIError` object represents an error when a global URI handling
|
||||
//! function was used in a wrong way.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
context::intrinsics::StandardConstructors,
|
||||
object::{
|
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
|
||||
},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use tap::{Conv, Pipe};
|
||||
|
||||
use super::Error;
|
||||
|
||||
/// JavaScript `URIError` implementation.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct UriError;
|
||||
|
||||
impl BuiltIn for UriError {
|
||||
const NAME: &'static str = "URIError";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let error_constructor = context.intrinsics().constructors().error().constructor();
|
||||
let error_prototype = context.intrinsics().constructors().error().prototype();
|
||||
|
||||
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().uri_error().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.inherit(error_prototype)
|
||||
.custom_prototype(error_constructor)
|
||||
.property("name", Self::NAME, attribute)
|
||||
.property("message", "", attribute)
|
||||
.build()
|
||||
.conv::<JsValue>()
|
||||
.pipe(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl UriError {
|
||||
/// The amount of arguments this function object takes.
|
||||
pub(crate) const LENGTH: usize = 1;
|
||||
|
||||
/// Create a new error object.
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
let prototype =
|
||||
get_prototype_from_constructor(new_target, StandardConstructors::uri_error, context)?;
|
||||
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
|
||||
|
||||
// 3. If message is not undefined, then
|
||||
let message = args.get_or_undefined(0);
|
||||
if !message.is_undefined() {
|
||||
// a. Let msg be ? ToString(message).
|
||||
let msg = message.to_string(context)?;
|
||||
|
||||
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
|
||||
o.create_non_enumerable_data_property_or_throw("message", msg, context);
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
|
||||
|
||||
// 5. Return O.
|
||||
Ok(o.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
//! This module implements the global `eval` function.
|
||||
//!
|
||||
//! The `eval()` function evaluates JavaScript code represented as a string.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-eval-x
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
|
||||
|
||||
use crate::{
|
||||
builtins::{BuiltIn, JsArgs},
|
||||
object::FunctionBuilder,
|
||||
property::Attribute,
|
||||
Context, JsValue,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Eval;
|
||||
|
||||
impl BuiltIn for Eval {
|
||||
const NAME: &'static str = "eval";
|
||||
|
||||
const ATTRIBUTE: Attribute = Attribute::CONFIGURABLE
|
||||
.union(Attribute::NON_ENUMERABLE)
|
||||
.union(Attribute::WRITABLE);
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let object = FunctionBuilder::native(context, Self::eval)
|
||||
.name("eval")
|
||||
.length(1)
|
||||
.constructor(false)
|
||||
.build();
|
||||
|
||||
Some(object.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval {
|
||||
/// `19.2.1 eval ( x )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-eval-x
|
||||
fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result<JsValue, JsValue> {
|
||||
// 1. Return ? PerformEval(x, false, false).
|
||||
Self::perform_eval(args.get_or_undefined(0), false, false, context)
|
||||
}
|
||||
|
||||
/// `19.2.1.1 PerformEval ( x, strictCaller, direct )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-performeval
|
||||
pub(crate) fn perform_eval(
|
||||
x: &JsValue,
|
||||
direct: bool,
|
||||
strict: bool,
|
||||
context: &mut Context,
|
||||
) -> Result<JsValue, JsValue> {
|
||||
// 1. Assert: If direct is false, then strictCaller is also false.
|
||||
if !direct {
|
||||
debug_assert!(!strict);
|
||||
}
|
||||
|
||||
// 2. If Type(x) is not String, return x.
|
||||
let x = if let Some(x) = x.as_string() {
|
||||
x.clone()
|
||||
} else {
|
||||
return Ok(x.clone());
|
||||
};
|
||||
|
||||
// Because of implementation details the following code differs from the spec.
|
||||
|
||||
// Parse the script body and handle early errors (6 - 11)
|
||||
let body = match context.parse_eval(x.as_bytes(), direct, strict) {
|
||||
Ok(body) => body,
|
||||
Err(e) => return context.throw_syntax_error(e.to_string()),
|
||||
};
|
||||
|
||||
// 12 - 13 are implicit in the call of `Context::compile_with_new_declarative`.
|
||||
|
||||
// Because our environment model does not map directly to the spec this section looks very different.
|
||||
// 14 - 33 are in the following section, together with EvalDeclarationInstantiation.
|
||||
if direct {
|
||||
// If the call to eval is direct, the code is executed in the current environment.
|
||||
|
||||
// Poison the current environment, because it may contain new declarations after/during eval.
|
||||
context.realm.environments.poison_current();
|
||||
|
||||
// Set the compile time environment to the current running environment and save the number of current environments.
|
||||
context.realm.compile_env = context.realm.environments.current_compile_environment();
|
||||
let environments_len = context.realm.environments.len();
|
||||
|
||||
// Error if any var declaration in the eval code already exists as a let/const declaration in the current running environment.
|
||||
let mut vars = FxHashSet::default();
|
||||
body.var_declared_names_new(&mut vars);
|
||||
if let Some(name) = context
|
||||
.realm
|
||||
.environments
|
||||
.has_lex_binding_until_function_environment(&vars)
|
||||
{
|
||||
let name = context.interner().resolve_expect(name);
|
||||
let msg = format!("variable declaration {name} in eval function already exists as lexically declaration");
|
||||
return context.throw_syntax_error(msg);
|
||||
}
|
||||
|
||||
// Compile and execute the eval statement list.
|
||||
let code_block = context.compile_with_new_declarative(&body, strict)?;
|
||||
context
|
||||
.realm
|
||||
.environments
|
||||
.extend_outer_function_environment();
|
||||
let result = context.execute(code_block);
|
||||
|
||||
// Pop any added runtime environments that where not removed during the eval execution.
|
||||
context.realm.environments.truncate(environments_len);
|
||||
|
||||
result
|
||||
} else {
|
||||
// If the call to eval is indirect, the code is executed in the global environment.
|
||||
|
||||
// Poison all environments, because the global environment may contain new declarations after/during eval.
|
||||
context.realm.environments.poison_all();
|
||||
|
||||
// Pop all environments before the eval execution.
|
||||
let environments = context.realm.environments.pop_to_global();
|
||||
let environments_len = context.realm.environments.len();
|
||||
context.realm.compile_env = context.realm.environments.current_compile_environment();
|
||||
|
||||
// Compile and execute the eval statement list.
|
||||
let code_block = context.compile_with_new_declarative(&body, false)?;
|
||||
let result = context.execute(code_block);
|
||||
|
||||
// Restore all environments to the state from before the eval execution.
|
||||
context.realm.environments.truncate(environments_len);
|
||||
context.realm.environments.extend(environments);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
use crate::{
|
||||
builtins::Array,
|
||||
environments::DeclarativeEnvironment,
|
||||
object::{JsObject, ObjectData},
|
||||
property::PropertyDescriptor,
|
||||
symbol::{self, WellKnownSymbols},
|
||||
syntax::ast::node::FormalParameterList,
|
||||
Context, JsValue,
|
||||
};
|
||||
use boa_gc::{Finalize, Gc, Trace};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
/// `ParameterMap` represents the `[[ParameterMap]]` internal slot on a Arguments exotic object.
|
||||
///
|
||||
/// This struct stores all the data to access mapped function parameters in their environment.
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub struct ParameterMap {
|
||||
binding_indices: Vec<Option<usize>>,
|
||||
environment: Gc<DeclarativeEnvironment>,
|
||||
}
|
||||
|
||||
impl ParameterMap {
|
||||
/// Deletes the binding with the given index from the parameter map.
|
||||
pub(crate) fn delete(&mut self, index: usize) {
|
||||
if let Some(binding) = self.binding_indices.get_mut(index) {
|
||||
*binding = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value of the binding at the given index from the function environment.
|
||||
///
|
||||
/// Note: This function is the abstract getter closure described in 10.4.4.7.1 `MakeArgGetter ( name, env )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-makearggetter
|
||||
pub(crate) fn get(&self, index: usize) -> Option<JsValue> {
|
||||
if let Some(Some(binding_index)) = self.binding_indices.get(index) {
|
||||
return Some(self.environment.get(*binding_index));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Set the value of the binding at the given index in the function environment.
|
||||
///
|
||||
/// Note: This function is the abstract setter closure described in 10.4.4.7.2 `MakeArgSetter ( name, env )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-makeargsetter
|
||||
pub(crate) fn set(&self, index: usize, value: &JsValue) {
|
||||
if let Some(Some(binding_index)) = self.binding_indices.get(index) {
|
||||
self.environment.set(*binding_index, value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Trace, Finalize)]
|
||||
pub enum Arguments {
|
||||
Unmapped,
|
||||
Mapped(ParameterMap),
|
||||
}
|
||||
|
||||
impl Arguments {
|
||||
/// Creates a new unmapped Arguments ordinary object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createunmappedargumentsobject
|
||||
pub(crate) fn create_unmapped_arguments_object(
|
||||
arguments_list: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsObject {
|
||||
// 1. Let len be the number of elements in argumentsList.
|
||||
let len = arguments_list.len();
|
||||
|
||||
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »).
|
||||
let obj = context.construct_object();
|
||||
|
||||
// 3. Set obj.[[ParameterMap]] to undefined.
|
||||
// skipped because the `Arguments` enum ensures ordinary argument objects don't have a `[[ParameterMap]]`
|
||||
obj.borrow_mut().data = ObjectData::arguments(Self::Unmapped);
|
||||
|
||||
// 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len),
|
||||
// [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
|
||||
obj.define_property_or_throw(
|
||||
"length",
|
||||
PropertyDescriptor::builder()
|
||||
.value(len)
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// 5. Let index be 0.
|
||||
// 6. Repeat, while index < len,
|
||||
for (index, value) in arguments_list.iter().cloned().enumerate() {
|
||||
// a. Let val be argumentsList[index].
|
||||
// b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val).
|
||||
obj.create_data_property_or_throw(index, value, context)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// c. Set index to index + 1.
|
||||
}
|
||||
|
||||
// 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor {
|
||||
// [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false,
|
||||
// [[Configurable]]: true }).
|
||||
obj.define_property_or_throw(
|
||||
symbol::WellKnownSymbols::iterator(),
|
||||
PropertyDescriptor::builder()
|
||||
.value(Array::values_intrinsic(context))
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
let throw_type_error = context.intrinsics().objects().throw_type_error();
|
||||
|
||||
// 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor {
|
||||
// [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false,
|
||||
// [[Configurable]]: false }).
|
||||
obj.define_property_or_throw(
|
||||
"callee",
|
||||
PropertyDescriptor::builder()
|
||||
.get(throw_type_error.clone())
|
||||
.set(throw_type_error)
|
||||
.enumerable(false)
|
||||
.configurable(false),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// 9. Return obj.
|
||||
obj
|
||||
}
|
||||
|
||||
/// Creates a new mapped Arguments exotic object.
|
||||
///
|
||||
/// <https://tc39.es/ecma262/#sec-createmappedargumentsobject>
|
||||
pub(crate) fn create_mapped_arguments_object(
|
||||
func: &JsObject,
|
||||
formals: &FormalParameterList,
|
||||
arguments_list: &[JsValue],
|
||||
env: &Gc<DeclarativeEnvironment>,
|
||||
context: &mut Context,
|
||||
) -> JsObject {
|
||||
// 1. Assert: formals does not contain a rest parameter, any binding patterns, or any initializers.
|
||||
// It may contain duplicate identifiers.
|
||||
// 2. Let len be the number of elements in argumentsList.
|
||||
let len = arguments_list.len();
|
||||
|
||||
// 3. Let obj be ! MakeBasicObject(« [[Prototype]], [[Extensible]], [[ParameterMap]] »).
|
||||
// 4. Set obj.[[GetOwnProperty]] as specified in 10.4.4.1.
|
||||
// 5. Set obj.[[DefineOwnProperty]] as specified in 10.4.4.2.
|
||||
// 6. Set obj.[[Get]] as specified in 10.4.4.3.
|
||||
// 7. Set obj.[[Set]] as specified in 10.4.4.4.
|
||||
// 8. Set obj.[[Delete]] as specified in 10.4.4.5.
|
||||
// 9. Set obj.[[Prototype]] to %Object.prototype%.
|
||||
|
||||
// Section 17-19 are done first, for easier object creation in 11.
|
||||
//
|
||||
// The section 17-19 differs from the spec, due to the way the runtime environments work.
|
||||
//
|
||||
// This section creates getters and setters for all mapped arguments.
|
||||
// Getting and setting values on the `arguments` object will actually access the bindings in the environment:
|
||||
// ```
|
||||
// function f(a) {console.log(a); arguments[0] = 1; console.log(a)};
|
||||
// f(0) // 0, 1
|
||||
// ```
|
||||
//
|
||||
// The spec assumes, that identifiers are used at runtime to reference bindings in the environment.
|
||||
// We use indices to access environment bindings at runtime.
|
||||
// To map to function parameters to binding indices, we use the fact, that bindings in a
|
||||
// function environment start with all of the arguments in order:
|
||||
// `function f (a,b,c)`
|
||||
// | binding index | `arguments` property key | identifier |
|
||||
// | 0 | 0 | a |
|
||||
// | 1 | 1 | b |
|
||||
// | 2 | 2 | c |
|
||||
//
|
||||
// Notice that the binding index does not correspond to the argument index:
|
||||
// `function f (a,a,b)` => binding indices 0 (a), 1 (b), 2 (c)
|
||||
// | binding index | `arguments` property key | identifier |
|
||||
// | - | 0 | - |
|
||||
// | 0 | 1 | a |
|
||||
// | 1 | 2 | b |
|
||||
// While the `arguments` object contains all arguments, they must not be all bound.
|
||||
// In the case of duplicate parameter names, the last one is bound as the environment binding.
|
||||
//
|
||||
// The following logic implements the steps 17-19 adjusted for our environment structure.
|
||||
|
||||
let mut bindings = FxHashMap::default();
|
||||
let mut property_index = 0;
|
||||
'outer: for formal in formals.parameters.iter() {
|
||||
for name in formal.names() {
|
||||
if property_index >= len {
|
||||
break 'outer;
|
||||
}
|
||||
let binding_index = bindings.len() + 1;
|
||||
let entry = bindings
|
||||
.entry(name)
|
||||
.or_insert((binding_index, property_index));
|
||||
entry.1 = property_index;
|
||||
property_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut map = ParameterMap {
|
||||
binding_indices: vec![None; property_index],
|
||||
environment: env.clone(),
|
||||
};
|
||||
|
||||
for (binding_index, property_index) in bindings.values() {
|
||||
map.binding_indices[*property_index] = Some(*binding_index);
|
||||
}
|
||||
|
||||
// 11. Set obj.[[ParameterMap]] to map.
|
||||
let obj = JsObject::from_proto_and_data(
|
||||
context.intrinsics().constructors().object().prototype(),
|
||||
ObjectData::arguments(Self::Mapped(map)),
|
||||
);
|
||||
|
||||
// 14. Let index be 0.
|
||||
// 15. Repeat, while index < len,
|
||||
for (index, val) in arguments_list.iter().cloned().enumerate() {
|
||||
// a. Let val be argumentsList[index].
|
||||
// b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val).
|
||||
// Note: Insert is used here because `CreateDataPropertyOrThrow` would cause a panic while executing
|
||||
// exotic argument object set methods before the variables in the environment are initialized.
|
||||
obj.insert(
|
||||
index,
|
||||
PropertyDescriptor::builder()
|
||||
.value(val)
|
||||
.writable(true)
|
||||
.enumerable(true)
|
||||
.configurable(true)
|
||||
.build(),
|
||||
);
|
||||
// c. Set index to index + 1.
|
||||
}
|
||||
|
||||
// 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len),
|
||||
// [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
|
||||
obj.define_property_or_throw(
|
||||
"length",
|
||||
PropertyDescriptor::builder()
|
||||
.value(len)
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// 20. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor {
|
||||
// [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false,
|
||||
// [[Configurable]]: true }).
|
||||
obj.define_property_or_throw(
|
||||
WellKnownSymbols::iterator(),
|
||||
PropertyDescriptor::builder()
|
||||
.value(Array::values_intrinsic(context))
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor {
|
||||
// [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
|
||||
obj.define_property_or_throw(
|
||||
"callee",
|
||||
PropertyDescriptor::builder()
|
||||
.value(func.clone())
|
||||
.writable(true)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
context,
|
||||
)
|
||||
.expect("Defining new own properties for a new ordinary object cannot fail");
|
||||
|
||||
// 22. Return obj.
|
||||
obj
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,262 @@
|
||||
use crate::{
|
||||
forward, forward_val,
|
||||
object::FunctionBuilder,
|
||||
property::{Attribute, PropertyDescriptor},
|
||||
Context, JsString,
|
||||
};
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
#[test]
|
||||
fn arguments_object() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let init = r#"
|
||||
function jason(a, b) {
|
||||
return arguments[0];
|
||||
}
|
||||
var val = jason(100, 6);
|
||||
"#;
|
||||
|
||||
eprintln!("{}", forward(&mut context, init));
|
||||
|
||||
let return_val = forward_val(&mut context, "val").expect("value expected");
|
||||
assert!(return_val.is_integer());
|
||||
assert_eq!(
|
||||
return_val
|
||||
.to_i32(&mut context)
|
||||
.expect("Could not convert value to i32"),
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_mutating_function_when_calling() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
function x() {
|
||||
x.y = 3;
|
||||
}
|
||||
x();
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, func));
|
||||
let y = forward_val(&mut context, "x.y").expect("value expected");
|
||||
assert!(y.is_integer());
|
||||
assert_eq!(
|
||||
y.to_i32(&mut context)
|
||||
.expect("Could not convert value to i32"),
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_mutating_function_when_constructing() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
function x() {
|
||||
x.y = 3;
|
||||
}
|
||||
new x();
|
||||
"#;
|
||||
eprintln!("{}", forward(&mut context, func));
|
||||
let y = forward_val(&mut context, "x.y").expect("value expected");
|
||||
assert!(y.is_integer());
|
||||
assert_eq!(
|
||||
y.to_i32(&mut context)
|
||||
.expect("Could not convert value to i32"),
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_function_prototype() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
Function.prototype()
|
||||
"#;
|
||||
let value = forward_val(&mut context, func).unwrap();
|
||||
assert!(value.is_undefined());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_function_prototype_with_arguments() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
Function.prototype(1, "", new String(""))
|
||||
"#;
|
||||
let value = forward_val(&mut context, func).unwrap();
|
||||
assert!(value.is_undefined());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_function_prototype_with_new() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
new Function.prototype()
|
||||
"#;
|
||||
let value = forward_val(&mut context, func);
|
||||
assert!(value.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_name() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
Function.prototype.name
|
||||
"#;
|
||||
let value = forward_val(&mut context, func).unwrap();
|
||||
assert!(value.is_string());
|
||||
assert!(value.as_string().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)]
|
||||
fn function_prototype_length() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
Function.prototype.length
|
||||
"#;
|
||||
let value = forward_val(&mut context, func).unwrap();
|
||||
assert!(value.is_number());
|
||||
assert_eq!(value.as_number().unwrap(), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_call() {
|
||||
let mut context = Context::default();
|
||||
let func = r#"
|
||||
let e = new Error()
|
||||
Object.prototype.toString.call(e)
|
||||
"#;
|
||||
let value = forward_val(&mut context, func).unwrap();
|
||||
assert!(value.is_string());
|
||||
assert_eq!(value.as_string().unwrap(), "[object Error]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_call_throw() {
|
||||
let mut context = Context::default();
|
||||
let throw = r#"
|
||||
let call = Function.prototype.call;
|
||||
call(call)
|
||||
"#;
|
||||
let value = forward_val(&mut context, throw).unwrap_err();
|
||||
assert!(value.is_object());
|
||||
let string = value.to_string(&mut context).unwrap();
|
||||
assert!(string.starts_with("TypeError"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_call_multiple_args() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
function f(a, b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
let o = {a: 0, b: 0};
|
||||
f.call(o, 1, 2);
|
||||
"#;
|
||||
forward_val(&mut context, init).unwrap();
|
||||
let boolean = forward_val(&mut context, "o.a == 1")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
let boolean = forward_val(&mut context, "o.b == 2")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_apply() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
const numbers = [6, 7, 3, 4, 2];
|
||||
const max = Math.max.apply(null, numbers);
|
||||
const min = Math.min.apply(null, numbers);
|
||||
"#;
|
||||
forward_val(&mut context, init).unwrap();
|
||||
|
||||
let boolean = forward_val(&mut context, "max == 7")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
|
||||
let boolean = forward_val(&mut context, "min == 2")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_prototype_apply_on_object() {
|
||||
let mut context = Context::default();
|
||||
let init = r#"
|
||||
function f(a, b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
let o = {a: 0, b: 0};
|
||||
f.apply(o, [1, 2]);
|
||||
"#;
|
||||
forward_val(&mut context, init).unwrap();
|
||||
|
||||
let boolean = forward_val(&mut context, "o.a == 1")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
|
||||
let boolean = forward_val(&mut context, "o.b == 2")
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap();
|
||||
assert!(boolean);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_capture_clone() {
|
||||
let mut context = Context::default();
|
||||
|
||||
let string = JsString::from("Hello");
|
||||
let object = context.construct_object();
|
||||
object
|
||||
.define_property_or_throw(
|
||||
"key",
|
||||
PropertyDescriptor::builder()
|
||||
.value(" world!")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(false),
|
||||
&mut context,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let func = FunctionBuilder::closure_with_captures(
|
||||
&mut context,
|
||||
|_, _, captures, context| {
|
||||
let (string, object) = &captures;
|
||||
|
||||
let hw = JsString::concat(
|
||||
string,
|
||||
object
|
||||
.__get_own_property__(&"key".into(), context)?
|
||||
.and_then(|prop| prop.value().cloned())
|
||||
.and_then(|val| val.as_string().cloned())
|
||||
.ok_or_else(|| context.construct_type_error("invalid `key` property"))?,
|
||||
);
|
||||
Ok(hw.into())
|
||||
},
|
||||
(string, object),
|
||||
)
|
||||
.name("closure")
|
||||
.build();
|
||||
|
||||
context.register_global_property("closure", func, Attribute::default());
|
||||
|
||||
assert_eq!(forward(&mut context, "closure()"), "\"Hello world!\"");
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
//! This module implements the global `Generator` object.
|
||||
//!
|
||||
//! A Generator is an instance of a generator function and conforms to both the Iterator and Iterable interfaces.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-generator-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator
|
||||
|
||||
use crate::{
|
||||
builtins::{iterable::create_iter_result_object, BuiltIn, JsArgs},
|
||||
environments::DeclarativeEnvironmentStack,
|
||||
object::{ConstructorBuilder, JsObject, ObjectData},
|
||||
property::{Attribute, PropertyDescriptor},
|
||||
symbol::WellKnownSymbols,
|
||||
value::JsValue,
|
||||
vm::{CallFrame, GeneratorResumeKind, ReturnType},
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_gc::{Cell, Finalize, Gc, Trace};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// Indicates the state of a generator.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) enum GeneratorState {
|
||||
Undefined,
|
||||
SuspendedStart,
|
||||
SuspendedYield,
|
||||
Executing,
|
||||
Completed,
|
||||
}
|
||||
|
||||
/// Holds all information that a generator needs to continue it's execution.
|
||||
///
|
||||
/// All of the fields must be changed with those that are currently present in the
|
||||
/// context/vm before the generator execution starts/resumes and after it has ended/yielded.
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub(crate) struct GeneratorContext {
|
||||
pub(crate) environments: DeclarativeEnvironmentStack,
|
||||
pub(crate) call_frame: CallFrame,
|
||||
pub(crate) stack: Vec<JsValue>,
|
||||
}
|
||||
|
||||
/// The internal representation on a `Generator` object.
|
||||
#[derive(Debug, Clone, Finalize, Trace)]
|
||||
pub struct Generator {
|
||||
/// The `[[GeneratorState]]` internal slot.
|
||||
#[unsafe_ignore_trace]
|
||||
pub(crate) state: GeneratorState,
|
||||
|
||||
/// The `[[GeneratorContext]]` internal slot.
|
||||
pub(crate) context: Option<Gc<Cell<GeneratorContext>>>,
|
||||
}
|
||||
|
||||
impl BuiltIn for Generator {
|
||||
const NAME: &'static str = "Generator";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let iterator_prototype = context
|
||||
.intrinsics()
|
||||
.objects()
|
||||
.iterator_prototypes()
|
||||
.iterator_prototype();
|
||||
|
||||
let generator_function_prototype = context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator_function()
|
||||
.prototype();
|
||||
|
||||
ConstructorBuilder::with_standard_constructor(
|
||||
context,
|
||||
Self::constructor,
|
||||
context.intrinsics().constructors().generator().clone(),
|
||||
)
|
||||
.name(Self::NAME)
|
||||
.length(Self::LENGTH)
|
||||
.property(
|
||||
WellKnownSymbols::to_string_tag(),
|
||||
Self::NAME,
|
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
)
|
||||
.method(Self::next, "next", 1)
|
||||
.method(Self::r#return, "return", 1)
|
||||
.method(Self::throw, "throw", 1)
|
||||
.inherit(iterator_prototype)
|
||||
.build();
|
||||
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator()
|
||||
.prototype
|
||||
.insert_property(
|
||||
"constructor",
|
||||
PropertyDescriptor::builder()
|
||||
.value(generator_function_prototype)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true),
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Generator {
|
||||
pub(crate) const LENGTH: usize = 0;
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn constructor(
|
||||
_: &JsValue,
|
||||
_: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
let prototype = context.intrinsics().constructors().generator().prototype();
|
||||
|
||||
let this = JsObject::from_proto_and_data(
|
||||
prototype,
|
||||
ObjectData::generator(Self {
|
||||
state: GeneratorState::Undefined,
|
||||
context: None,
|
||||
}),
|
||||
);
|
||||
|
||||
Ok(this.into())
|
||||
}
|
||||
|
||||
/// `Generator.prototype.next ( value )`
|
||||
///
|
||||
/// The `next()` method returns an object with two properties done and value.
|
||||
/// You can also provide a parameter to the next method to send a value to the generator.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.next
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next
|
||||
pub(crate) fn next(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Return ? GeneratorResume(this value, value, empty).
|
||||
match this.as_object() {
|
||||
Some(obj) if obj.is_generator() => {
|
||||
Self::generator_resume(obj, args.get_or_undefined(0), context)
|
||||
}
|
||||
_ => context.throw_type_error("Generator.prototype.next called on non generator"),
|
||||
}
|
||||
}
|
||||
|
||||
/// `Generator.prototype.return ( value )`
|
||||
///
|
||||
/// The `return()` method returns the given value and finishes the generator.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.return
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return
|
||||
pub(crate) fn r#return(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let g be the this value.
|
||||
// 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
|
||||
// 3. Return ? GeneratorResumeAbrupt(g, C, empty).
|
||||
Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0).clone()), context)
|
||||
}
|
||||
|
||||
/// `Generator.prototype.throw ( exception )`
|
||||
///
|
||||
/// The `throw()` method resumes the execution of a generator by throwing an error into it
|
||||
/// and returns an object with two properties done and value.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.throw
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/throw
|
||||
pub(crate) fn throw(
|
||||
this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let g be the this value.
|
||||
// 2. Let C be ThrowCompletion(exception).
|
||||
// 3. Return ? GeneratorResumeAbrupt(g, C, empty).
|
||||
Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0).clone()), context)
|
||||
}
|
||||
|
||||
/// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generatorresume
|
||||
pub(crate) fn generator_resume(
|
||||
generator_obj: &JsObject,
|
||||
value: &JsValue,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let state be ? GeneratorValidate(generator, generatorBrand).
|
||||
let mut generator_obj_mut = generator_obj.borrow_mut();
|
||||
let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| {
|
||||
context.construct_type_error("generator resumed on non generator object")
|
||||
})?;
|
||||
let state = generator.state;
|
||||
|
||||
if state == GeneratorState::Executing {
|
||||
return Err(context.construct_type_error("Generator should not be executing"));
|
||||
}
|
||||
|
||||
// 2. If state is completed, return CreateIterResultObject(undefined, true).
|
||||
if state == GeneratorState::Completed {
|
||||
return Ok(create_iter_result_object(
|
||||
JsValue::undefined(),
|
||||
true,
|
||||
context,
|
||||
));
|
||||
}
|
||||
|
||||
// 3. Assert: state is either suspendedStart or suspendedYield.
|
||||
assert!(matches!(
|
||||
state,
|
||||
GeneratorState::SuspendedStart | GeneratorState::SuspendedYield
|
||||
));
|
||||
|
||||
// 4. Let genContext be generator.[[GeneratorContext]].
|
||||
// 5. Let methodContext be the running execution context.
|
||||
// 6. Suspend methodContext.
|
||||
// 7. Set generator.[[GeneratorState]] to executing.
|
||||
generator.state = GeneratorState::Executing;
|
||||
let first_execution = matches!(state, GeneratorState::SuspendedStart);
|
||||
|
||||
let generator_context_cell = generator
|
||||
.context
|
||||
.take()
|
||||
.expect("generator context cannot be empty here");
|
||||
let mut generator_context = generator_context_cell.borrow_mut();
|
||||
drop(generator_obj_mut);
|
||||
|
||||
std::mem::swap(
|
||||
&mut context.realm.environments,
|
||||
&mut generator_context.environments,
|
||||
);
|
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
|
||||
context.vm.push_frame(generator_context.call_frame.clone());
|
||||
if !first_execution {
|
||||
context.vm.push(value);
|
||||
}
|
||||
|
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal;
|
||||
|
||||
let result = context.run();
|
||||
|
||||
generator_context.call_frame = context
|
||||
.vm
|
||||
.pop_frame()
|
||||
.expect("generator call frame must exist");
|
||||
std::mem::swap(
|
||||
&mut context.realm.environments,
|
||||
&mut generator_context.environments,
|
||||
);
|
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
|
||||
|
||||
let mut generator_obj_mut = generator_obj.borrow_mut();
|
||||
let generator = generator_obj_mut
|
||||
.as_generator_mut()
|
||||
.expect("already checked this object type");
|
||||
|
||||
match result {
|
||||
Ok((value, ReturnType::Yield)) => {
|
||||
generator.state = GeneratorState::SuspendedYield;
|
||||
drop(generator_context);
|
||||
generator.context = Some(generator_context_cell);
|
||||
Ok(create_iter_result_object(value, false, context))
|
||||
}
|
||||
Ok((value, _)) => {
|
||||
generator.state = GeneratorState::Completed;
|
||||
Ok(create_iter_result_object(value, true, context))
|
||||
}
|
||||
Err(value) => {
|
||||
generator.state = GeneratorState::Completed;
|
||||
Err(value)
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Push genContext onto the execution context stack; genContext is now the running execution context.
|
||||
// 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the result of the operation that suspended it. Let result be the value returned by the resumed computation.
|
||||
// 10. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context.
|
||||
// 11. Return Completion(result).
|
||||
}
|
||||
|
||||
/// `27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generatorresumeabrupt
|
||||
pub(crate) fn generator_resume_abrupt(
|
||||
this: &JsValue,
|
||||
abrupt_completion: JsResult<JsValue>,
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
// 1. Let state be ? GeneratorValidate(generator, generatorBrand).
|
||||
let generator_obj = this.as_object().ok_or_else(|| {
|
||||
context.construct_type_error("generator resumed on non generator object")
|
||||
})?;
|
||||
let mut generator_obj_mut = generator_obj.borrow_mut();
|
||||
let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| {
|
||||
context.construct_type_error("generator resumed on non generator object")
|
||||
})?;
|
||||
let mut state = generator.state;
|
||||
|
||||
if state == GeneratorState::Executing {
|
||||
return Err(context.construct_type_error("Generator should not be executing"));
|
||||
}
|
||||
|
||||
// 2. If state is suspendedStart, then
|
||||
if state == GeneratorState::SuspendedStart {
|
||||
// a. Set generator.[[GeneratorState]] to completed.
|
||||
generator.state = GeneratorState::Completed;
|
||||
// b. Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with generator can be discarded at this point.
|
||||
generator.context = None;
|
||||
// c. Set state to completed.
|
||||
state = GeneratorState::Completed;
|
||||
}
|
||||
|
||||
// 3. If state is completed, then
|
||||
if state == GeneratorState::Completed {
|
||||
// a. If abruptCompletion.[[Type]] is return, then
|
||||
if let Ok(value) = abrupt_completion {
|
||||
// i. Return CreateIterResultObject(abruptCompletion.[[Value]], true).
|
||||
return Ok(create_iter_result_object(value, true, context));
|
||||
}
|
||||
// b. Return Completion(abruptCompletion).
|
||||
return abrupt_completion;
|
||||
}
|
||||
|
||||
// 4. Assert: state is suspendedYield.
|
||||
// 5. Let genContext be generator.[[GeneratorContext]].
|
||||
// 6. Let methodContext be the running execution context.
|
||||
// 7. Suspend methodContext.
|
||||
// 8. Set generator.[[GeneratorState]] to executing.
|
||||
// 9. Push genContext onto the execution context stack; genContext is now the running execution context.
|
||||
// 10. Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation.
|
||||
// 11. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context.
|
||||
// 12. Return Completion(result).
|
||||
let generator_context_cell = generator
|
||||
.context
|
||||
.take()
|
||||
.expect("generator context cannot be empty here");
|
||||
let mut generator_context = generator_context_cell.borrow_mut();
|
||||
|
||||
generator.state = GeneratorState::Executing;
|
||||
drop(generator_obj_mut);
|
||||
|
||||
std::mem::swap(
|
||||
&mut context.realm.environments,
|
||||
&mut generator_context.environments,
|
||||
);
|
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
|
||||
context.vm.push_frame(generator_context.call_frame.clone());
|
||||
|
||||
let result = match abrupt_completion {
|
||||
Ok(value) => {
|
||||
context.vm.push(value);
|
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return;
|
||||
context.run()
|
||||
}
|
||||
Err(value) => {
|
||||
context.vm.push(value);
|
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw;
|
||||
context.run()
|
||||
}
|
||||
};
|
||||
generator_context.call_frame = context
|
||||
.vm
|
||||
.pop_frame()
|
||||
.expect("generator call frame must exist");
|
||||
std::mem::swap(
|
||||
&mut context.realm.environments,
|
||||
&mut generator_context.environments,
|
||||
);
|
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack);
|
||||
|
||||
let mut generator_obj_mut = generator_obj.borrow_mut();
|
||||
let generator = generator_obj_mut
|
||||
.as_generator_mut()
|
||||
.expect("already checked this object type");
|
||||
|
||||
match result {
|
||||
Ok((value, ReturnType::Yield)) => {
|
||||
generator.state = GeneratorState::SuspendedYield;
|
||||
drop(generator_context);
|
||||
generator.context = Some(generator_context_cell);
|
||||
Ok(create_iter_result_object(value, false, context))
|
||||
}
|
||||
Ok((value, _)) => {
|
||||
generator.state = GeneratorState::Completed;
|
||||
Ok(create_iter_result_object(value, true, context))
|
||||
}
|
||||
Err(value) => {
|
||||
generator.state = GeneratorState::Completed;
|
||||
Err(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
//! This module implements the global `GeneratorFunction` object.
|
||||
//!
|
||||
//! The `GeneratorFunction` constructor creates a new generator function object.
|
||||
//! In JavaScript, every generator function is actually a `GeneratorFunction` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-generatorfunction-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/GeneratorFunction
|
||||
|
||||
use crate::{
|
||||
builtins::{
|
||||
function::{BuiltInFunctionObject, ConstructorKind, Function},
|
||||
BuiltIn,
|
||||
},
|
||||
object::ObjectData,
|
||||
property::PropertyDescriptor,
|
||||
symbol::WellKnownSymbols,
|
||||
value::JsValue,
|
||||
Context, JsResult,
|
||||
};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// The internal representation on a `Generator` object.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GeneratorFunction;
|
||||
|
||||
impl BuiltIn for GeneratorFunction {
|
||||
const NAME: &'static str = "GeneratorFunction";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
let prototype = &context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator_function()
|
||||
.prototype;
|
||||
let constructor = &context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator_function()
|
||||
.constructor;
|
||||
|
||||
constructor.set_prototype(Some(
|
||||
context.intrinsics().constructors().function().constructor(),
|
||||
));
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(1)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
constructor.borrow_mut().insert("length", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value("GeneratorFunction")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
constructor.borrow_mut().insert("name", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator_function()
|
||||
.prototype(),
|
||||
)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(false);
|
||||
constructor.borrow_mut().insert("prototype", property);
|
||||
constructor.borrow_mut().data = ObjectData::function(Function::Native {
|
||||
function: Self::constructor,
|
||||
constructor: Some(ConstructorKind::Base),
|
||||
});
|
||||
|
||||
prototype.set_prototype(Some(
|
||||
context.intrinsics().constructors().function().prototype(),
|
||||
));
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(
|
||||
context
|
||||
.intrinsics()
|
||||
.constructors()
|
||||
.generator_function()
|
||||
.constructor(),
|
||||
)
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype.borrow_mut().insert("constructor", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value(context.intrinsics().constructors().generator().prototype())
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype.borrow_mut().insert("prototype", property);
|
||||
let property = PropertyDescriptor::builder()
|
||||
.value("GeneratorFunction")
|
||||
.writable(false)
|
||||
.enumerable(false)
|
||||
.configurable(true);
|
||||
prototype
|
||||
.borrow_mut()
|
||||
.insert(WellKnownSymbols::to_string_tag(), property);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl GeneratorFunction {
|
||||
/// `GeneratorFunction ( p1, p2, … , pn, body )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generatorfunction
|
||||
pub(crate) fn constructor(
|
||||
new_target: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
BuiltInFunctionObject::create_dynamic_function(new_target, args, false, true, context)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//! This module implements the global `globalThis` property.
|
||||
//!
|
||||
//! The global globalThis property contains the global this value,
|
||||
//! which is akin to the global object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-globalthis
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis
|
||||
|
||||
use crate::{builtins::BuiltIn, Context, JsValue};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// The JavaScript `globalThis`.
|
||||
pub(crate) struct GlobalThis;
|
||||
|
||||
impl BuiltIn for GlobalThis {
|
||||
const NAME: &'static str = "globalThis";
|
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
Some(context.global_object().clone().into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
use crate::exec;
|
||||
|
||||
#[test]
|
||||
fn global_this_exists_on_global_object_and_evaluates_to_an_object() {
|
||||
let scenario = r#"
|
||||
typeof globalThis;
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "\"object\"");
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//! This module implements the global `Infinity` property.
|
||||
//!
|
||||
//! The global property `Infinity` is a numeric value representing infinity.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-value-properties-of-the-global-object-infinity
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::{builtins::BuiltIn, property::Attribute, Context, JsValue};
|
||||
use boa_profiler::Profiler;
|
||||
|
||||
/// JavaScript global `Infinity` property.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) struct Infinity;
|
||||
|
||||
impl BuiltIn for Infinity {
|
||||
const NAME: &'static str = "Infinity";
|
||||
|
||||
const ATTRIBUTE: Attribute = Attribute::READONLY
|
||||
.union(Attribute::NON_ENUMERABLE)
|
||||
.union(Attribute::PERMANENT);
|
||||
|
||||
fn init(_: &mut Context) -> Option<JsValue> {
|
||||
let _timer = Profiler::global().start_event(Self::NAME, "init");
|
||||
|
||||
Some(f64::INFINITY.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use crate::exec;
|
||||
|
||||
#[test]
|
||||
fn infinity_exists_on_global_object_and_evaluates_to_infinity_value() {
|
||||
let scenario = r#"
|
||||
Infinity;
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "Infinity");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infinity_exists_and_equals_to_number_positive_infinity_value() {
|
||||
let scenario = r#"
|
||||
Number.POSITIVE_INFINITY === Infinity;
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "true");
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user