feat: works

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

View File

@@ -10,7 +10,10 @@ pub struct MyContainer;
impl Container for MyContainer { impl Container for MyContainer {
fn fetch(&mut self, s: &str) -> String { 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 }), |linker| container::add_to_linker(linker, |cx| -> &mut MyContainer { &mut cx.imports }),
|store, module, linker| Exports::instantiate(store, module, linker, |cx| &mut cx.exports), |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(()) Ok(())
} }

View File

@@ -19,20 +19,152 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 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]] [[package]]
name = "engine" name = "engine"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"boa_engine",
"getrandom",
"serde",
"serde_json",
"wit-bindgen-gen-core", "wit-bindgen-gen-core",
"wit-bindgen-rust", "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]] [[package]]
name = "heck" name = "heck"
version = "0.3.3" version = "0.3.3"
@@ -48,12 +180,127 @@ version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 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]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.40" version = "1.0.40"
@@ -83,6 +330,106 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "syn" name = "syn"
version = "1.0.98" version = "1.0.98"
@@ -94,6 +441,35 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@@ -118,6 +494,12 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "unicode-general-category"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1218098468b8085b19a2824104c70d976491d247ce194bbd9dc77181150cdfd6"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.2" version = "1.0.2"
@@ -151,6 +533,40 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 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]] [[package]]
name = "wit-bindgen-gen-core" name = "wit-bindgen-gen-core"
version = "0.1.0" version = "0.1.0"

View File

@@ -8,6 +8,20 @@ edition = "2021"
[lib] [lib]
crate-type = ['cdylib'] crate-type = ['cdylib']
[profile.release]
codegen-units = 1
incremental = true
lto = true
opt-level = "z"
[dependencies] [dependencies]
wit-bindgen-gen-core = { path = '../wit-bindgen/crates/gen-core' } wit-bindgen-gen-core = { path = '../wit-bindgen/crates/gen-core' }
wit-bindgen-rust = { path = '../wit-bindgen/crates/rust-wasm' } 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" }

View 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

View 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

View File

@@ -0,0 +1 @@
open_collective: boa

View 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).
-->

View 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.

View 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...

View 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`.
-->

View 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:
-
-
-

View File

@@ -0,0 +1,10 @@
github_checks:
annotations: false
coverage:
status:
project:
default:
threshold: 5% # allow 5% coverage variance
patch: off

View 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

View 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

View 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

View 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 }}

View 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

View 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 }}

View 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

View 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 }}

View 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 }}

View 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

View 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

View File

@@ -0,0 +1,3 @@
[submodule "test262"]
path = test262
url = https://github.com/tc39/test262.git

View 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

View 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"]
}
]
}

View 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"
}
]
}

File diff suppressed because it is too large Load Diff

View 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.

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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.

View 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>

View 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]
[![codecov](https://codecov.io/gh/boa-dev/boa/branch/main/graph/badge.svg)](https://codecov.io/gh/boa-dev/boa)
[![Crates.io](https://img.shields.io/crates/v/Boa.svg)](https://crates.io/crates/Boa)
[![Docs.rs](https://docs.rs/Boa/badge.svg)](https://docs.rs/Boa/)
[![Discord](https://img.shields.io/discord/595323158140158003?logo=discord)](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
![Example](docs/img/latestDemo.gif)
## 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 135 KiB

View 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"

View 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()
}
}

View 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(())
}

View 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

View File

@@ -0,0 +1,10 @@
# Boa Benchmarks
For each js script in the `bench_scripts` folder, we create three benchmarks:
- Parser => lexing and parsing of the source code
- Compiler => compilation of the parsed statement list into bytecode
- Execution => execution of the bytecode in the vm
The idea is to check the performance of Boa in different scenarios.
Different parts of Boa are benchmarked separately to make the impact of local changes visible.

View File

@@ -0,0 +1 @@
((2 + 2) ** 3 / 100 - 5 ** 3 * -1000) ** 2 + 100 - 8;

View File

@@ -0,0 +1,7 @@
(function () {
let testArr = [1, 2, 3, 4, 5];
let res = testArr[2];
return res;
})();

View File

@@ -0,0 +1,8 @@
(function () {
let testArr = [];
for (let a = 0; a <= 500; a++) {
testArr[a] = "p" + a;
}
return testArr;
})();

View File

@@ -0,0 +1,22 @@
(function () {
let testArray = [
83, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83,
62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93,
17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77,
32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32,
56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234,
23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29,
2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28,
93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62,
99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17,
28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32,
45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56,
67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28,
];
while (testArray.length > 0) {
testArray.pop();
}
return testArray;
})();

View File

@@ -0,0 +1,7 @@
new Boolean(
!new Boolean(
new Boolean(
!new Boolean(false).valueOf() && new Boolean(true).valueOf()
).valueOf()
).valueOf()
).valueOf();

View File

@@ -0,0 +1,15 @@
!function () {
var M = new Array();
for (i = 0; i < 100; i++) {
M.push(Math.floor(Math.random() * 100));
}
var test = [];
for (i = 0; i < 100; i++) {
if (M[i] > 50) {
test.push(M[i]);
}
}
test.forEach(elem => {
0
});
}();

View File

@@ -0,0 +1,10 @@
(function () {
let num = 12;
function fib(n) {
if (n <= 1) return 1;
return fib(n - 1) + fib(n - 2);
}
return fib(num);
})();

View File

@@ -0,0 +1,10 @@
(function () {
let b = "hello";
for (let a = 10; a < 100; a += 5) {
if (a < 50) {
b += "world";
}
}
return b;
})();

View File

@@ -0,0 +1 @@
!function(){var r=new Array();for(i=0;i<100;i++)r.push(Math.floor(100*Math.random()));var a=[];for(i=0;i<100;i++)r[i]>50&&a.push(r[i]);a.forEach(i=>{0})}();

View File

@@ -0,0 +1,5 @@
new Number(
new Number(
new Number(new Number(100).valueOf() - 10.5).valueOf() + 100
).valueOf() * 1.6
);

View File

@@ -0,0 +1,8 @@
(function () {
let test = {
my_prop: "hello",
another: 65,
};
return test;
})();

View File

@@ -0,0 +1,8 @@
(function () {
let test = {
my_prop: "hello",
another: 65,
};
return test.my_prop;
})();

View File

@@ -0,0 +1,8 @@
(function () {
let test = {
my_prop: "hello",
another: 65,
};
return test["my" + "_prop"];
})();

View File

@@ -0,0 +1,5 @@
(function () {
let regExp = new RegExp("hello", "i");
return regExp.test("Hello World");
})();

View File

@@ -0,0 +1,5 @@
(function () {
let regExp = new RegExp("hello", "i");
return regExp;
})();

View File

@@ -0,0 +1,5 @@
(function () {
let regExp = /hello/i;
return regExp.test("Hello World");
})();

View File

@@ -0,0 +1,5 @@
(function () {
let regExp = /hello/i;
return regExp;
})();

View File

@@ -0,0 +1,9 @@
(function () {
var a = "hello";
var b = "world";
var c = a == b;
var d = b;
var e = d == b;
})();

View File

@@ -0,0 +1,6 @@
(function () {
var a = "hello";
var b = "world";
var c = a + b;
})();

View File

@@ -0,0 +1,4 @@
(function () {
var a = "hello";
var b = a;
})();

View File

@@ -0,0 +1,7 @@
new String(
new String(
new String(
new String("Hello").valueOf() + new String(", world").valueOf()
).valueOf() + "!"
).valueOf()
).valueOf();

View File

@@ -0,0 +1,3 @@
(function () {
return Symbol();
})();

View File

@@ -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);

View 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()
}
}

View File

@@ -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

View File

@@ -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,
}

View File

@@ -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());
}

View File

@@ -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)
}
}

View File

@@ -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,
))
}
}

View File

@@ -0,0 +1,413 @@
use crate::{forward, Context};
#[test]
fn equality() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "0n == 0n"), "true");
assert_eq!(forward(&mut context, "1n == 0n"), "false");
assert_eq!(
forward(
&mut context,
"1000000000000000000000000000000000n == 1000000000000000000000000000000000n"
),
"true"
);
assert_eq!(forward(&mut context, "0n == ''"), "true");
assert_eq!(forward(&mut context, "100n == '100'"), "true");
assert_eq!(forward(&mut context, "100n == '100.5'"), "false");
assert_eq!(
forward(&mut context, "10000000000000000n == '10000000000000000'"),
"true"
);
assert_eq!(forward(&mut context, "'' == 0n"), "true");
assert_eq!(forward(&mut context, "'100' == 100n"), "true");
assert_eq!(forward(&mut context, "'100.5' == 100n"), "false");
assert_eq!(
forward(&mut context, "'10000000000000000' == 10000000000000000n"),
"true"
);
assert_eq!(forward(&mut context, "0n == 0"), "true");
assert_eq!(forward(&mut context, "0n == 0.0"), "true");
assert_eq!(forward(&mut context, "100n == 100"), "true");
assert_eq!(forward(&mut context, "100n == 100.0"), "true");
assert_eq!(forward(&mut context, "100n == '100.5'"), "false");
assert_eq!(forward(&mut context, "100n == '1005'"), "false");
assert_eq!(
forward(&mut context, "10000000000000000n == 10000000000000000"),
"true"
);
assert_eq!(forward(&mut context, "0 == 0n"), "true");
assert_eq!(forward(&mut context, "0.0 == 0n"), "true");
assert_eq!(forward(&mut context, "100 == 100n"), "true");
assert_eq!(forward(&mut context, "100.0 == 100n"), "true");
assert_eq!(forward(&mut context, "100.5 == 100n"), "false");
assert_eq!(forward(&mut context, "1005 == 100n"), "false");
assert_eq!(
forward(&mut context, "10000000000000000 == 10000000000000000n"),
"true"
);
}
#[test]
fn bigint_function_conversion_from_integer() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "BigInt(1000)"), "1000n");
assert_eq!(
forward(&mut context, "BigInt(20000000000000000)"),
"20000000000000000n"
);
assert_eq!(
forward(&mut context, "BigInt(1000000000000000000000000000000000)"),
"999999999999999945575230987042816n"
);
}
#[test]
fn bigint_function_conversion_from_rational() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "BigInt(0.0)"), "0n");
assert_eq!(forward(&mut context, "BigInt(1.0)"), "1n");
assert_eq!(forward(&mut context, "BigInt(10000.0)"), "10000n");
}
#[test]
fn bigint_function_conversion_from_rational_with_fractional_part() {
let mut context = Context::default();
let scenario = r#"
try {
BigInt(0.1);
} catch (e) {
e.toString();
}
"#;
assert_eq!(
forward(&mut context, scenario),
"\"RangeError: Cannot convert 0.1 to BigInt\""
);
}
#[test]
fn bigint_function_conversion_from_null() {
let mut context = Context::default();
let scenario = r#"
try {
BigInt(null);
} catch (e) {
e.toString();
}
"#;
assert_eq!(
forward(&mut context, scenario),
"\"TypeError: cannot convert null to a BigInt\""
);
}
#[test]
fn bigint_function_conversion_from_undefined() {
let mut context = Context::default();
let scenario = r#"
try {
BigInt(undefined);
} catch (e) {
e.toString();
}
"#;
assert_eq!(
forward(&mut context, scenario),
"\"TypeError: cannot convert undefined to a BigInt\""
);
}
#[test]
fn bigint_function_conversion_from_string() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "BigInt('')"), "0n");
assert_eq!(forward(&mut context, "BigInt(' ')"), "0n");
assert_eq!(
forward(&mut context, "BigInt('200000000000000000')"),
"200000000000000000n"
);
assert_eq!(
forward(&mut context, "BigInt('1000000000000000000000000000000000')"),
"1000000000000000000000000000000000n"
);
assert_eq!(forward(&mut context, "BigInt('0b1111')"), "15n");
assert_eq!(forward(&mut context, "BigInt('0o70')"), "56n");
assert_eq!(forward(&mut context, "BigInt('0xFF')"), "255n");
}
#[test]
fn add() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "10000n + 1000n"), "11000n");
}
#[test]
fn sub() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "10000n - 1000n"), "9000n");
}
#[test]
fn mul() {
let mut context = Context::default();
assert_eq!(
forward(&mut context, "123456789n * 102030n"),
"12596296181670n"
);
}
#[test]
fn div() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "15000n / 50n"), "300n");
}
#[test]
fn div_with_truncation() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "15001n / 50n"), "300n");
}
#[test]
fn r#mod() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "15007n % 10n"), "7n");
}
#[test]
fn pow() {
let mut context = Context::default();
assert_eq!(
forward(&mut context, "100n ** 10n"),
"100000000000000000000n"
);
}
#[test]
fn pow_negative_exponent() {
let mut context = Context::default();
assert_throws(&mut context, "10n ** (-10n)", "RangeError");
}
#[test]
fn shl() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "8n << 2n"), "32n");
}
#[test]
fn shl_out_of_range() {
let mut context = Context::default();
assert_throws(&mut context, "1000n << 1000000000000000n", "RangeError");
}
#[test]
fn shr() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "8n >> 2n"), "2n");
}
#[test]
fn shr_out_of_range() {
let mut context = Context::default();
assert_throws(&mut context, "1000n >> 1000000000000000n", "RangeError");
}
#[test]
fn to_string() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "1000n.toString()"), "\"1000\"");
assert_eq!(forward(&mut context, "1000n.toString(2)"), "\"1111101000\"");
assert_eq!(forward(&mut context, "255n.toString(16)"), "\"ff\"");
assert_eq!(forward(&mut context, "1000n.toString(36)"), "\"rs\"");
}
#[test]
fn to_string_invalid_radix() {
let mut context = Context::default();
assert_throws(&mut context, "10n.toString(null)", "RangeError");
assert_throws(&mut context, "10n.toString(-1)", "RangeError");
assert_throws(&mut context, "10n.toString(37)", "RangeError");
}
#[test]
fn as_int_n() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "BigInt.asIntN(0, 1n)"), "0n");
assert_eq!(forward(&mut context, "BigInt.asIntN(1, 1n)"), "-1n");
assert_eq!(forward(&mut context, "BigInt.asIntN(3, 10n)"), "2n");
assert_eq!(forward(&mut context, "BigInt.asIntN({}, 1n)"), "0n");
assert_eq!(forward(&mut context, "BigInt.asIntN(2, 0n)"), "0n");
assert_eq!(forward(&mut context, "BigInt.asIntN(2, -0n)"), "0n");
assert_eq!(
forward(&mut context, "BigInt.asIntN(2, -123456789012345678901n)"),
"-1n"
);
assert_eq!(
forward(&mut context, "BigInt.asIntN(2, -123456789012345678900n)"),
"0n"
);
assert_eq!(
forward(&mut context, "BigInt.asIntN(2, 123456789012345678900n)"),
"0n"
);
assert_eq!(
forward(&mut context, "BigInt.asIntN(2, 123456789012345678901n)"),
"1n"
);
assert_eq!(
forward(
&mut context,
"BigInt.asIntN(200, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)"
),
"-1n"
);
assert_eq!(
forward(
&mut context,
"BigInt.asIntN(201, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)"
),
"1606938044258990275541962092341162602522202993782792835301375n"
);
assert_eq!(
forward(
&mut context,
"BigInt.asIntN(200, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)"
),
"-741470203160010616172516490008037905920749803227695190508460n"
);
assert_eq!(
forward(
&mut context,
"BigInt.asIntN(201, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)"
),
"865467841098979659369445602333124696601453190555097644792916n"
);
}
#[test]
fn as_int_n_errors() {
let mut context = Context::default();
assert_throws(&mut context, "BigInt.asIntN(-1, 0n)", "RangeError");
assert_throws(&mut context, "BigInt.asIntN(-2.5, 0n)", "RangeError");
assert_throws(
&mut context,
"BigInt.asIntN(9007199254740992, 0n)",
"RangeError",
);
assert_throws(&mut context, "BigInt.asIntN(0n, 0n)", "TypeError");
}
#[test]
fn as_uint_n() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "BigInt.asUintN(0, -2n)"), "0n");
assert_eq!(forward(&mut context, "BigInt.asUintN(0, -1n)"), "0n");
assert_eq!(forward(&mut context, "BigInt.asUintN(0, 0n)"), "0n");
assert_eq!(forward(&mut context, "BigInt.asUintN(0, 1n)"), "0n");
assert_eq!(forward(&mut context, "BigInt.asUintN(0, 2n)"), "0n");
assert_eq!(forward(&mut context, "BigInt.asUintN(1, -3n)"), "1n");
assert_eq!(forward(&mut context, "BigInt.asUintN(1, -2n)"), "0n");
assert_eq!(forward(&mut context, "BigInt.asUintN(1, -1n)"), "1n");
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 0n)"), "0n");
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 1n)"), "1n");
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 2n)"), "0n");
assert_eq!(forward(&mut context, "BigInt.asUintN(1, 3n)"), "1n");
assert_eq!(
forward(&mut context, "BigInt.asUintN(1, -123456789012345678901n)"),
"1n"
);
assert_eq!(
forward(&mut context, "BigInt.asUintN(1, -123456789012345678900n)"),
"0n"
);
assert_eq!(
forward(&mut context, "BigInt.asUintN(1, 123456789012345678900n)"),
"0n"
);
assert_eq!(
forward(&mut context, "BigInt.asUintN(1, 123456789012345678901n)"),
"1n"
);
assert_eq!(
forward(
&mut context,
"BigInt.asUintN(200, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)"
),
"1606938044258990275541962092341162602522202993782792835301375n"
);
assert_eq!(
forward(
&mut context,
"BigInt.asUintN(201, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)"
),
"3213876088517980551083924184682325205044405987565585670602751n"
);
}
#[test]
fn as_uint_n_errors() {
let mut context = Context::default();
assert_throws(&mut context, "BigInt.asUintN(-1, 0n)", "RangeError");
assert_throws(&mut context, "BigInt.asUintN(-2.5, 0n)", "RangeError");
assert_throws(
&mut context,
"BigInt.asUintN(9007199254740992, 0n)",
"RangeError",
);
assert_throws(&mut context, "BigInt.asUintN(0n, 0n)", "TypeError");
}
fn assert_throws(context: &mut Context, src: &str, error_type: &str) {
let result = forward(context, src);
assert!(result.contains(error_type));
}
#[test]
fn division_by_zero() {
let mut context = Context::default();
assert_throws(&mut context, "1n/0n", "RangeError");
}
#[test]
fn remainder_by_zero() {
let mut context = Context::default();
assert_throws(&mut context, "1n % 0n", "RangeError");
}

View File

@@ -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)?))
}
}

View File

@@ -0,0 +1,65 @@
use crate::{forward, forward_val, Context};
/// Test the correct type is returned from call and construct
#[allow(clippy::unwrap_used)]
#[test]
fn construct_and_call() {
let mut context = Context::default();
let init = r#"
var one = new Boolean(1);
var zero = Boolean(0);
"#;
eprintln!("{}", forward(&mut context, init));
let one = forward_val(&mut context, "one").unwrap();
let zero = forward_val(&mut context, "zero").unwrap();
assert!(one.is_object());
assert!(zero.is_boolean());
}
#[test]
fn constructor_gives_true_instance() {
let mut context = Context::default();
let init = r#"
var trueVal = new Boolean(true);
var trueNum = new Boolean(1);
var trueString = new Boolean("true");
var trueBool = new Boolean(trueVal);
"#;
eprintln!("{}", forward(&mut context, init));
let true_val = forward_val(&mut context, "trueVal").expect("value expected");
let true_num = forward_val(&mut context, "trueNum").expect("value expected");
let true_string = forward_val(&mut context, "trueString").expect("value expected");
let true_bool = forward_val(&mut context, "trueBool").expect("value expected");
// Values should all be objects
assert!(true_val.is_object());
assert!(true_num.is_object());
assert!(true_string.is_object());
assert!(true_bool.is_object());
// Values should all be truthy
assert!(true_val.to_boolean());
assert!(true_num.to_boolean());
assert!(true_string.to_boolean());
assert!(true_bool.to_boolean());
}
#[test]
fn instances_have_correct_proto_set() {
let mut context = Context::default();
let init = r#"
var boolInstance = new Boolean(true);
var boolProto = Boolean.prototype;
"#;
eprintln!("{}", forward(&mut context, init));
let bool_instance = forward_val(&mut context, "boolInstance").expect("value expected");
let bool_prototype = forward_val(&mut context, "boolProto").expect("value expected");
assert_eq!(
&*bool_instance.as_object().unwrap().prototype(),
&bool_prototype.as_object().cloned()
);
}

View File

@@ -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())
}
}

View File

@@ -0,0 +1,68 @@
use crate::{builtins::console::formatter, Context, JsValue};
#[test]
fn formatter_no_args_is_empty_string() {
let mut context = Context::default();
assert_eq!(formatter(&[], &mut context).unwrap(), "");
}
#[test]
fn formatter_empty_format_string_is_empty_string() {
let mut context = Context::default();
let val = JsValue::new("");
assert_eq!(formatter(&[val], &mut context).unwrap(), "");
}
#[test]
fn formatter_format_without_args_renders_verbatim() {
let mut context = Context::default();
let val = [JsValue::new("%d %s %% %f")];
let res = formatter(&val, &mut context).unwrap();
assert_eq!(res, "%d %s %% %f");
}
#[test]
fn formatter_empty_format_string_concatenates_rest_of_args() {
let mut context = Context::default();
let val = [
JsValue::new(""),
JsValue::new("to powinno zostać"),
JsValue::new("połączone"),
];
let res = formatter(&val, &mut context).unwrap();
assert_eq!(res, " to powinno zostać połączone");
}
#[test]
fn formatter_utf_8_checks() {
let mut context = Context::default();
let val = [
JsValue::new("Są takie chwile %dą %są tu%sów %привет%ź".to_string()),
JsValue::new(123),
JsValue::new(1.23),
JsValue::new("ł"),
];
let res = formatter(&val, &mut context).unwrap();
assert_eq!(res, "Są takie chwile 123ą 1.23ą tułów %привет%ź");
}
#[test]
fn formatter_trailing_format_leader_renders() {
let mut context = Context::default();
let val = [JsValue::new("%%%%%"), JsValue::new("|")];
let res = formatter(&val, &mut context).unwrap();
assert_eq!(res, "%%% |");
}
#[test]
#[allow(clippy::approx_constant)]
fn formatter_float_format_works() {
let mut context = Context::default();
let val = [JsValue::new("%f"), JsValue::new(3.1415)];
let res = formatter(&val, &mut context).unwrap();
assert_eq!(res, "3.141500");
}

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

View File

@@ -0,0 +1,116 @@
//! This module implements the global `AggregateError` object.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-aggregate-error
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError
use crate::{
builtins::{iterable::iterable_to_list, Array, BuiltIn, JsArgs},
context::intrinsics::StandardConstructors,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
property::{Attribute, PropertyDescriptorBuilder},
Context, JsResult, JsValue,
};
use boa_profiler::Profiler;
use tap::{Conv, Pipe};
use super::Error;
#[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())
}
}

View File

@@ -0,0 +1,91 @@
//! This module implements the global `EvalError` object.
//!
//! Indicates an error regarding the global `eval()` function.
//! This exception is not thrown by JavaScript anymore, however
//! the `EvalError` object remains for compatibility.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError
use crate::{
builtins::{BuiltIn, JsArgs},
context::intrinsics::StandardConstructors,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
property::Attribute,
Context, JsResult, JsValue,
};
use boa_profiler::Profiler;
use tap::{Conv, Pipe};
use super::Error;
/// 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())
}
}

View File

@@ -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())
}
}

View File

@@ -0,0 +1,89 @@
//! This module implements the global `RangeError` object.
//!
//! Indicates a value that is not in the set or range of allowable values.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError
use crate::{
builtins::{BuiltIn, JsArgs},
context::intrinsics::StandardConstructors,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
property::Attribute,
Context, JsResult, JsValue,
};
use boa_profiler::Profiler;
use tap::{Conv, Pipe};
use super::Error;
/// 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())
}
}

View File

@@ -0,0 +1,95 @@
//! This module implements the global `ReferenceError` object.
//!
//! Indicates an error that occurs when de-referencing an invalid reference
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError
use crate::{
builtins::{BuiltIn, JsArgs},
context::intrinsics::StandardConstructors,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
property::Attribute,
Context, JsResult, JsValue,
};
use boa_profiler::Profiler;
use tap::{Conv, Pipe};
use super::Error;
#[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())
}
}

View File

@@ -0,0 +1,94 @@
//! This module implements the global `SyntaxError` object.
//!
//! The `SyntaxError` object represents an error when trying to interpret syntactically invalid code.
//! It is thrown when the JavaScript context encounters tokens or token order that does not conform
//! to the syntax of the language when parsing code.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-syntaxerror
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError
use crate::{
builtins::{BuiltIn, JsArgs},
context::intrinsics::StandardConstructors,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
property::Attribute,
Context, JsResult, JsValue,
};
use boa_profiler::Profiler;
use tap::{Conv, Pipe};
use super::Error;
/// 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())
}
}

View File

@@ -0,0 +1,86 @@
use crate::{forward, Context};
#[test]
fn error_to_string() {
let mut context = Context::default();
let init = r#"
let e = new Error('1');
let name = new Error();
let message = new Error('message');
message.name = '';
let range_e = new RangeError('2');
let ref_e = new ReferenceError('3');
let syntax_e = new SyntaxError('4');
let type_e = new TypeError('5');
"#;
forward(&mut context, init);
assert_eq!(forward(&mut context, "e.toString()"), "\"Error: 1\"");
assert_eq!(forward(&mut context, "name.toString()"), "\"Error\"");
assert_eq!(forward(&mut context, "message.toString()"), "\"message\"");
assert_eq!(
forward(&mut context, "range_e.toString()"),
"\"RangeError: 2\""
);
assert_eq!(
forward(&mut context, "ref_e.toString()"),
"\"ReferenceError: 3\""
);
assert_eq!(
forward(&mut context, "syntax_e.toString()"),
"\"SyntaxError: 4\""
);
assert_eq!(
forward(&mut context, "type_e.toString()"),
"\"TypeError: 5\""
);
}
#[test]
fn eval_error_name() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "EvalError.name"), "\"EvalError\"");
}
#[test]
fn eval_error_length() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "EvalError.length"), "1");
}
#[test]
fn eval_error_to_string() {
let mut context = Context::default();
assert_eq!(
forward(&mut context, "new EvalError('hello').toString()"),
"\"EvalError: hello\""
);
assert_eq!(
forward(&mut context, "new EvalError().toString()"),
"\"EvalError\""
);
}
#[test]
fn uri_error_name() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "URIError.name"), "\"URIError\"");
}
#[test]
fn uri_error_length() {
let mut context = Context::default();
assert_eq!(forward(&mut context, "URIError.length"), "1");
}
#[test]
fn uri_error_to_string() {
let mut context = Context::default();
assert_eq!(
forward(&mut context, "new URIError('hello').toString()"),
"\"URIError: hello\""
);
assert_eq!(
forward(&mut context, "new URIError().toString()"),
"\"URIError\""
);
}

View File

@@ -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
}

View File

@@ -0,0 +1,90 @@
//! This module implements the global `URIError` object.
//!
//! The `URIError` object represents an error when a global URI handling
//! function was used in a wrong way.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError
use crate::{
builtins::{BuiltIn, JsArgs},
context::intrinsics::StandardConstructors,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
property::Attribute,
Context, JsResult, JsValue,
};
use boa_profiler::Profiler;
use tap::{Conv, Pipe};
use super::Error;
/// 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())
}
}

View File

@@ -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
}
}
}

View File

@@ -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

View File

@@ -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!\"");
}

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}

View File

@@ -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())
}
}

View File

@@ -0,0 +1,10 @@
use crate::exec;
#[test]
fn global_this_exists_on_global_object_and_evaluates_to_an_object() {
let scenario = r#"
typeof globalThis;
"#;
assert_eq!(&exec(scenario), "\"object\"");
}

View File

@@ -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())
}
}

View File

@@ -0,0 +1,19 @@
use crate::exec;
#[test]
fn infinity_exists_on_global_object_and_evaluates_to_infinity_value() {
let scenario = r#"
Infinity;
"#;
assert_eq!(&exec(scenario), "Infinity");
}
#[test]
fn infinity_exists_and_equals_to_number_positive_infinity_value() {
let scenario = r#"
Number.POSITIVE_INFINITY === Infinity;
"#;
assert_eq!(&exec(scenario), "true");
}

Some files were not shown because too many files have changed in this diff Show More