Browse Source

Added a Boa runtime (#2743)

This Pull Request fixes/closes #718.

It changes the following:

- Adds a new `boa_runtime` crate, that will only include `console` for now
- Changes the `boa_cli` crate to use the new `boa_runtime` crate for the console, instead of the `console` feature of `boa_engine`
- Removes the `console` feature in `boa_engine`
- Adds a new `boa_testing` helper crate with some useful functions for testing `boa`. This part duplicates the code from `boa_engine`, but I could not make `boa_engine` work with this crate as a dependency due to circular dependencies. Maybe doing it a bit generic could work, but didn't have enough time to check it.

To be checked: wether the WASM example works as expected with the console.
pull/2866/head
Iban Eguia Moraza 2 years ago
parent
commit
63d9d67ecb
  1. 83
      Cargo.lock
  2. 12
      Cargo.toml
  3. 2
      boa_ast/Cargo.toml
  4. 3
      boa_cli/Cargo.toml
  5. 13
      boa_cli/src/main.rs
  6. 5
      boa_engine/Cargo.toml
  7. 3
      boa_engine/src/builtins/function/tests.rs
  8. 20
      boa_engine/src/builtins/mod.rs
  9. 17
      boa_engine/src/context/mod.rs
  10. 43
      boa_engine/src/lib.rs
  11. 90
      boa_engine/src/value/display.rs
  12. 10
      boa_engine/src/value/tests.rs
  13. 9
      boa_engine/src/vm/call_frame/mod.rs
  14. 12
      boa_engine/src/vm/code_block.rs
  15. 3
      boa_examples/Cargo.toml
  16. 19
      boa_examples/src/bin/classes.rs
  17. 22
      boa_examples/src/bin/futures.rs
  18. 2
      boa_examples/src/bin/loadstring.rs
  19. 2
      boa_parser/Cargo.toml
  20. 21
      boa_runtime/Cargo.toml
  21. 188
      boa_runtime/src/console/mod.rs
  22. 24
      boa_runtime/src/console/tests.rs
  23. 277
      boa_runtime/src/lib.rs
  24. 2
      boa_tester/Cargo.toml
  25. 2
      boa_wasm/Cargo.toml
  26. 2
      test262

83
Cargo.lock generated

@ -326,9 +326,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.2.0"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07d1372b52fa64b8af7cf6e3848cfa2dadb81c21e810f144bdd9c26e21895235"
checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
dependencies = [
"serde",
]
@ -362,7 +362,7 @@ name = "boa_ast"
version = "0.16.0"
dependencies = [
"arbitrary",
"bitflags 2.2.0",
"bitflags 2.1.0",
"boa_interner",
"boa_macros",
"num-bigint",
@ -379,6 +379,7 @@ dependencies = [
"boa_gc",
"boa_interner",
"boa_parser",
"boa_runtime",
"clap 4.2.4",
"colored",
"jemallocator",
@ -393,7 +394,7 @@ dependencies = [
name = "boa_engine"
version = "0.16.0"
dependencies = [
"bitflags 2.2.0",
"bitflags 2.1.0",
"boa_ast",
"boa_gc",
"boa_icu_provider",
@ -454,6 +455,7 @@ dependencies = [
"boa_gc",
"boa_interner",
"boa_parser",
"boa_runtime",
"futures-util",
"smol",
]
@ -521,7 +523,7 @@ dependencies = [
name = "boa_parser"
version = "0.16.0"
dependencies = [
"bitflags 2.2.0",
"bitflags 2.1.0",
"boa_ast",
"boa_interner",
"boa_macros",
@ -543,11 +545,22 @@ dependencies = [
"rustc-hash",
]
[[package]]
name = "boa_runtime"
version = "0.16.0"
dependencies = [
"boa_engine",
"boa_gc",
"indoc",
"rustc-hash",
"textwrap",
]
[[package]]
name = "boa_tester"
version = "0.16.0"
dependencies = [
"bitflags 2.2.0",
"bitflags 2.1.0",
"boa_engine",
"boa_gc",
"clap 4.2.4",
@ -585,9 +598,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.12.0"
version = "3.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
[[package]]
name = "bytecheck"
@ -955,9 +968,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181"
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
dependencies = [
"libc",
]
@ -2031,9 +2044,9 @@ dependencies = [
[[package]]
name = "icu_datetime"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a552f80b0f07c4cf45f9548e8b0b89ee4adb3be5fb2f49fb792cc178ed42be73"
checksum = "72d61014bb8604505baa84ed522aa039951bd81177828d165e80ea8a0543c8a7"
dependencies = [
"databake",
"displaydoc",
@ -2232,9 +2245,9 @@ dependencies = [
[[package]]
name = "icu_provider_fs"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "741a8b57997e99ee8a20da1219b6dad8e4e243f3b05d2397b931f432b2797ebc"
checksum = "b861a88223338dcb70466b04918f1d8bc136575e6d252d1dc349ef4848d388e1"
dependencies = [
"bincode",
"crlify",
@ -2465,9 +2478,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "libc"
version = "0.2.141"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "libloading"
@ -2496,9 +2509,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
[[package]]
name = "litemap"
@ -2560,9 +2573,9 @@ dependencies = [
[[package]]
name = "matrixmultiply"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84"
checksum = "bb99c395ae250e1bf9133673f03ca9f97b7e71b705436bf8f089453445d1e9fe"
dependencies = [
"rawpointer",
]
@ -2841,9 +2854,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.50"
version = "0.10.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1"
checksum = "97ea2d98598bf9ada7ea6ee8a30fb74f9156b63bbe495d64ec2b87c269d2dda3"
dependencies = [
"bitflags 1.3.2",
"cfg-if 1.0.0",
@ -2873,9 +2886,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.85"
version = "0.9.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0"
checksum = "992bac49bdbab4423199c654a5515bd2a6c6a23bf03f2dd3bdb7e5ae6259bc69"
dependencies = [
"cc",
"libc",
@ -3077,9 +3090,9 @@ dependencies = [
[[package]]
name = "polling"
version = "2.7.0"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be1c66a6add46bff50935c313dae30a5030cf8385c5206e8a95e9e9def974aa"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg",
"bitflags 1.3.2",
@ -3290,13 +3303,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac6cf59af1067a3fb53fbe5c88c053764e930f932be1d71d3ffe032cbe147f59"
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.7.0",
"regex-syntax 0.7.1",
]
[[package]]
@ -3317,9 +3330,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.7.0"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6868896879ba532248f33598de5181522d8b3d9d724dfd230911e1a7d4822f5"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
[[package]]
name = "region"
@ -3437,9 +3450,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.37.11"
version = "0.37.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f"
dependencies = [
"bitflags 1.3.2",
"errno",
@ -4344,9 +4357,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.16"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
dependencies = [
"sharded-slab",
"thread_local",

12
Cargo.toml

@ -11,6 +11,7 @@ members = [
"boa_macros_tests",
"boa_parser",
"boa_profiler",
"boa_runtime",
"boa_tester",
"boa_unicode",
"boa_wasm",
@ -26,15 +27,16 @@ license = "Unlicense/MIT"
description = "Boa is a Javascript lexer, parser and compiler written in Rust. Currently, it has support for some of the language."
[workspace.dependencies]
boa_ast = { version = "0.16.0", path = "boa_ast" }
boa_engine = { version = "0.16.0", path = "boa_engine" }
boa_interner = { version = "0.16.0", path = "boa_interner" }
boa_gc = { version = "0.16.0", path = "boa_gc" }
boa_profiler = { version = "0.16.0", path = "boa_profiler" }
boa_unicode = { version = "0.16.0", path = "boa_unicode" }
boa_icu_provider = { version = "0.16.0", path = "boa_icu_provider" }
boa_interner = { version = "0.16.0", path = "boa_interner" }
boa_macros = { version = "0.16.0", path = "boa_macros" }
boa_ast = { version = "0.16.0", path = "boa_ast" }
boa_parser = { version = "0.16.0", path = "boa_parser" }
boa_icu_provider = { version = "0.16.0", path = "boa_icu_provider" }
boa_profiler = { version = "0.16.0", path = "boa_profiler" }
boa_runtime = { version = "0.16.0", path = "boa_runtime" }
boa_unicode = { version = "0.16.0", path = "boa_unicode" }
[workspace.metadata.workspaces]
allow_branch = "main"

2
boa_ast/Cargo.toml

@ -18,7 +18,7 @@ arbitrary = ["dep:arbitrary", "boa_interner/arbitrary", "num-bigint/arbitrary"]
boa_interner.workspace = true
boa_macros.workspace = true
rustc-hash = "1.1.0"
bitflags = "2.2.0"
bitflags = "2.1.0"
num-bigint = "0.4.3"
serde = { version = "1.0.160", features = ["derive"], optional = true }
arbitrary = { version = "1", features = ["derive"], optional = true }

3
boa_cli/Cargo.toml

@ -12,11 +12,12 @@ repository.workspace = true
rust-version.workspace = true
[dependencies]
boa_engine = { workspace = true, features = ["deser", "flowgraph", "trace", "console"] }
boa_engine = { workspace = true, features = ["deser", "flowgraph", "trace"] }
boa_ast = { workspace = true, features = ["serde"] }
boa_parser.workspace = true
boa_gc.workspace = true
boa_interner.workspace = true
boa_runtime.workspace = true
rustyline = { version = "11.0.0", features = ["derive"]}
clap = { version = "4.2.4", features = ["derive"] }
serde_json = "1.0.96"

13
boa_cli/src/main.rs

@ -66,9 +66,11 @@ use boa_engine::{
context::ContextBuilder,
job::{FutureJob, JobQueue, NativeJob},
optimizer::OptimizerOptions,
property::Attribute,
vm::flowgraph::{Direction, Graph},
Context, JsResult, Source,
};
use boa_runtime::Console;
use clap::{Parser, ValueEnum, ValueHint};
use colored::{Color, Colorize};
use debug::init_boa_debug_object;
@ -311,6 +313,9 @@ fn main() -> Result<(), io::Error> {
// Strict mode
context.strict(args.strict);
// Add `console`.
add_runtime(&mut context);
// Trace Output
context.set_trace(args.trace);
@ -404,6 +409,14 @@ fn main() -> Result<(), io::Error> {
Ok(())
}
/// Adds the CLI runtime to the context.
fn add_runtime(context: &mut Context<'_>) {
let console = Console::init(context);
context
.register_global_property(Console::NAME, console, Attribute::all())
.expect("the console object shouldn't exist");
}
#[derive(Default)]
struct Jobs(RefCell<VecDeque<NativeJob>>);

5
boa_engine/Cargo.toml

@ -42,9 +42,6 @@ flowgraph = []
# Enable Boa's VM instruction tracing.
trace = []
# Enable Boa's WHATWG console object implementation.
console = []
# Enable Boa's additional ECMAScript features for web browsers.
annex-b = ["boa_parser/annex-b"]
@ -64,7 +61,7 @@ regress = "0.5.0"
rustc-hash = "1.1.0"
num-bigint = { version = "0.4.3", features = ["serde"] }
num-integer = "0.1.45"
bitflags = "2.2.0"
bitflags = "2.1.0"
indexmap = "1.9.3"
ryu-js = "0.2.2"
chrono = { version = "0.4.24", default-features = false, features = ["clock", "std"] }

3
boa_engine/src/builtins/function/tests.rs

@ -1,5 +1,3 @@
use indoc::indoc;
use crate::{
builtins::error::ErrorKind,
error::JsNativeError,
@ -9,6 +7,7 @@ use crate::{
property::{Attribute, PropertyDescriptor},
run_test_actions, JsValue, TestAction,
};
use indoc::indoc;
#[allow(clippy::float_cmp)]
#[test]

20
boa_engine/src/builtins/mod.rs

@ -1,6 +1,4 @@
//! Boa's ECMAScript built-in object implementations, e.g. Object, String, Math, Array, etc.
//!
//! This module also contains a JavaScript Console implementation.
pub mod array;
pub mod array_buffer;
@ -106,9 +104,6 @@ use crate::{
Context, JsResult, JsString, JsValue,
};
#[cfg(feature = "console")]
use crate::console::Console;
/// A [Well-Known Intrinsic Object].
///
/// Well-known intrinsics are built-in objects that are explicitly referenced by the algorithms of
@ -373,21 +368,6 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult
#[cfg(feature = "intl")]
global_binding::<intl::Intl>(context)?;
#[cfg(feature = "console")]
{
let object = Console::init(context);
let global_object = context.global_object();
global_object.define_property_or_throw(
utf16!("console"),
PropertyDescriptor::builder()
.value(object)
.writable(true)
.enumerable(true)
.configurable(true),
context,
)?;
}
Ok(())
}

17
boa_engine/src/context/mod.rs

@ -300,6 +300,8 @@ impl<'host> Context<'host> {
/// Register a global property.
///
/// It will return an error if the property is already defined.
///
/// # Example
/// ```
/// use boa_engine::{
@ -310,13 +312,17 @@ impl<'host> Context<'host> {
///
/// let mut context = Context::default();
///
/// context.register_global_property("myPrimitiveProperty", 10, Attribute::all());
/// context
/// .register_global_property("myPrimitiveProperty", 10, Attribute::all())
/// .expect("property shouldn't exist");
///
/// let object = ObjectInitializer::new(&mut context)
/// .property("x", 0, Attribute::all())
/// .property("y", 1, Attribute::all())
/// .build();
/// context.register_global_property("myObjectProperty", object, Attribute::all());
/// context
/// .register_global_property("myObjectProperty", object, Attribute::all())
/// .expect("property shouldn't exist");
/// ```
pub fn register_global_property<K, V>(
&mut self,
@ -411,6 +417,8 @@ impl<'host> Context<'host> {
/// Register a global class of type `T`, where `T` implements `Class`.
///
/// It will return an error if the global property is already defined.
///
/// # Example
/// ```ignore
/// #[derive(Debug, Trace, Finalize)]
@ -515,6 +523,11 @@ impl<'host> Context<'host> {
self.kept_alive.clear();
}
/// Retrieves the current stack trace of the context.
pub fn stack_trace(&mut self) -> impl Iterator<Item = &CallFrame> {
self.vm.frames.iter().rev()
}
/// Replaces the currently active realm with `realm`, and returns the old realm.
pub fn enter_realm(&mut self, realm: Realm) -> Realm {
self.vm

43
boa_engine/src/lib.rs

@ -42,7 +42,6 @@
//! # Crate Features
//!
//! - **serde** - Enables serialization and deserialization of the AST (Abstract Syntax Tree).
//! - **console** - Enables `boa`'s [WHATWG `console`][whatwg] object implementation.
//! - **profiler** - Enables profiling with measureme (this is mostly internal).
//! - **intl** - Enables `boa`'s [ECMA-402 Internationalization API][ecma-402] (`Intl` object)
//!
@ -56,7 +55,6 @@
//! - **`boa_unicode`** - Boa's Unicode identifier.
//! - **`boa_icu_provider`** - Boa's ICU4X data provider.
//!
//! [whatwg]: https://console.spec.whatwg.org
//! [ecma-402]: https://tc39.es/ecma402
//! [boa-conformance]: https://boajs.dev/boa/test262/
//! [boa-web]: https://boajs.dev/
@ -156,15 +154,12 @@ pub mod property;
pub mod realm;
pub mod string;
pub mod symbol;
pub mod value;
pub mod vm;
#[cfg(feature = "console")]
pub mod console;
pub(crate) mod tagged;
// pub(crate) mod tagged;
mod tagged;
#[cfg(test)]
mod tests;
pub mod value;
pub mod vm;
/// A convenience module that re-exports the most commonly-used Boa APIs
pub mod prelude {
@ -224,7 +219,7 @@ use std::borrow::Cow;
/// A test action executed in a test function.
#[cfg(test)]
#[derive(Clone)]
pub(crate) struct TestAction(Inner);
struct TestAction(Inner);
#[cfg(test)]
#[derive(Clone)]
@ -264,12 +259,12 @@ enum Inner {
#[cfg(test)]
impl TestAction {
/// Evaluates some utility functions used in tests.
pub(crate) const fn run_harness() -> Self {
const fn run_harness() -> Self {
Self(Inner::RunHarness)
}
/// Runs `source`, panicking if the execution throws.
pub(crate) fn run(source: impl Into<Cow<'static, str>>) -> Self {
fn run(source: impl Into<Cow<'static, str>>) -> Self {
Self(Inner::Run {
source: source.into(),
})
@ -278,22 +273,19 @@ impl TestAction {
/// Executes `op` with the currently active context.
///
/// Useful to make custom assertions that must be done from Rust code.
pub(crate) fn inspect_context(op: fn(&mut Context<'_>)) -> Self {
fn inspect_context(op: fn(&mut Context<'_>)) -> Self {
Self(Inner::InspectContext { op })
}
/// Asserts that evaluating `source` returns the `true` value.
pub(crate) fn assert(source: impl Into<Cow<'static, str>>) -> Self {
fn assert(source: impl Into<Cow<'static, str>>) -> Self {
Self(Inner::Assert {
source: source.into(),
})
}
/// Asserts that the script returns `expected` when evaluating `source`.
pub(crate) fn assert_eq(
source: impl Into<Cow<'static, str>>,
expected: impl Into<JsValue>,
) -> Self {
fn assert_eq(source: impl Into<Cow<'static, str>>, expected: impl Into<JsValue>) -> Self {
Self(Inner::AssertEq {
source: source.into(),
expected: expected.into(),
@ -303,7 +295,7 @@ impl TestAction {
/// Asserts that calling `op` with the value obtained from evaluating `source` returns `true`.
///
/// Useful to check properties of the obtained value that cannot be checked from JS code.
pub(crate) fn assert_with_op(
fn assert_with_op(
source: impl Into<Cow<'static, str>>,
op: fn(JsValue, &mut Context<'_>) -> bool,
) -> Self {
@ -314,7 +306,7 @@ impl TestAction {
}
/// Asserts that evaluating `source` throws the opaque error `value`.
pub(crate) fn assert_opaque_error(
fn assert_opaque_error(
source: impl Into<Cow<'static, str>>,
value: impl Into<JsValue>,
) -> Self {
@ -325,7 +317,7 @@ impl TestAction {
}
/// Asserts that evaluating `source` throws a native error of `kind` and `message`.
pub(crate) fn assert_native_error(
fn assert_native_error(
source: impl Into<Cow<'static, str>>,
kind: builtins::error::ErrorKind,
message: &'static str,
@ -338,7 +330,7 @@ impl TestAction {
}
/// Asserts that calling `op` with the currently executing context returns `true`.
pub(crate) fn assert_context(op: fn(&mut Context<'_>) -> bool) -> Self {
fn assert_context(op: fn(&mut Context<'_>) -> bool) -> Self {
Self(Inner::AssertContext { op })
}
}
@ -346,7 +338,7 @@ impl TestAction {
/// Executes a list of test actions on a new, default context.
#[cfg(test)]
#[track_caller]
pub(crate) fn run_test_actions(actions: impl IntoIterator<Item = TestAction>) {
fn run_test_actions(actions: impl IntoIterator<Item = TestAction>) {
let context = &mut Context::default();
run_test_actions_with(actions, context);
}
@ -354,10 +346,7 @@ pub(crate) fn run_test_actions(actions: impl IntoIterator<Item = TestAction>) {
/// Executes a list of test actions on the provided context.
#[cfg(test)]
#[track_caller]
pub(crate) fn run_test_actions_with(
actions: impl IntoIterator<Item = TestAction>,
context: &mut Context<'_>,
) {
fn run_test_actions_with(actions: impl IntoIterator<Item = TestAction>, context: &mut Context<'_>) {
#[track_caller]
fn forward_val(context: &mut Context<'_>, source: &str) -> JsResult<JsValue> {
context.eval_script(Source::from_bytes(source))

90
boa_engine/src/value/display.rs

@ -248,67 +248,69 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children
}
)
}
_ => display_obj(x, print_internals),
_ => x.display_obj(print_internals),
}
}
_ => x.display().to_string(),
}
}
/// A helper function for specifically printing object values
pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String {
// A simple helper for getting the address of a value
// TODO: Find a more general place for this, as it can be used in other situations as well
fn address_of<T>(t: &T) -> usize {
let my_ptr: *const T = t;
my_ptr as usize
}
impl JsValue {
/// A helper function for specifically printing object values
pub fn display_obj(&self, print_internals: bool) -> String {
// A simple helper for getting the address of a value
// TODO: Find a more general place for this, as it can be used in other situations as well
fn address_of<T>(t: &T) -> usize {
let my_ptr: *const T = t;
my_ptr as usize
}
fn display_obj_internal(
data: &JsValue,
encounters: &mut HashSet<usize>,
indent: usize,
print_internals: bool,
) -> String {
if let JsValue::Object(ref v) = *data {
// The in-memory address of the current object
let addr = address_of(v.as_ref());
fn display_obj_internal(
data: &JsValue,
encounters: &mut HashSet<usize>,
indent: usize,
print_internals: bool,
) -> String {
if let JsValue::Object(ref v) = *data {
// The in-memory address of the current object
let addr = address_of(v.as_ref());
// We need not continue if this object has already been
// printed up the current chain
if encounters.contains(&addr) {
return String::from("[Cycle]");
}
// We need not continue if this object has already been
// printed up the current chain
if encounters.contains(&addr) {
return String::from("[Cycle]");
}
// Mark the current object as encountered
encounters.insert(addr);
// Mark the current object as encountered
encounters.insert(addr);
let result = if print_internals {
print_obj_value!(all of v, display_obj_internal, indent, encounters).join(",\n")
} else {
print_obj_value!(props of v, display_obj_internal, indent, encounters, print_internals)
let result = if print_internals {
print_obj_value!(all of v, display_obj_internal, indent, encounters).join(",\n")
} else {
print_obj_value!(props of v, display_obj_internal, indent, encounters, print_internals)
.join(",\n")
};
};
// If the current object is referenced in a different branch,
// it will not cause an infinite printing loop, so it is safe to be printed again
encounters.remove(&addr);
// If the current object is referenced in a different branch,
// it will not cause an infinite printing loop, so it is safe to be printed again
encounters.remove(&addr);
let closing_indent = String::from_utf8(vec![b' '; indent.wrapping_sub(4)])
.expect("Could not create the closing brace's indentation string");
let closing_indent = String::from_utf8(vec![b' '; indent.wrapping_sub(4)])
.expect("Could not create the closing brace's indentation string");
format!("{{\n{result}\n{closing_indent}}}")
} else {
// Every other type of data is printed with the display method
data.display().to_string()
format!("{{\n{result}\n{closing_indent}}}")
} else {
// Every other type of data is printed with the display method
data.display().to_string()
}
}
}
// We keep track of which objects we have encountered by keeping their
// in-memory address in this set
let mut encounters = HashSet::new();
// We keep track of which objects we have encountered by keeping their
// in-memory address in this set
let mut encounters = HashSet::new();
display_obj_internal(v, &mut encounters, 4, print_internals)
display_obj_internal(self, &mut encounters, 4, print_internals)
}
}
impl Display for ValueDisplay<'_> {

10
boa_engine/src/value/tests.rs

@ -788,16 +788,6 @@ mod cyclic_conversions {
0.0,
)]);
}
#[test]
fn console_log_cyclic() {
run_test_actions([TestAction::run(indoc! {r#"
let a = [1];
a[1] = a;
console.log(a);
"#})]);
// Should not stack overflow
}
}
mod abstract_relational_comparison {

9
boa_engine/src/vm/call_frame/mod.rs

@ -41,6 +41,15 @@ pub struct CallFrame {
pub(crate) iterators: ThinVec<(JsObject, bool)>,
}
/// ---- `CallFrame` public API ----
impl CallFrame {
/// Retrieves the [`CodeBlock`] of this call frame.
#[inline]
pub const fn code_block(&self) -> &Gc<CodeBlock> {
&self.code_block
}
}
/// ---- `CallFrame` creation methods ----
impl CallFrame {
/// Creates a new `CallFrame` with the provided `CodeBlock`.

12
boa_engine/src/vm/code_block.rs

@ -139,8 +139,9 @@ pub struct CodeBlock {
pub(crate) trace: std::cell::Cell<bool>,
}
/// ---- `CodeBlock` public API ----
impl CodeBlock {
/// Constructs a new `CodeBlock`.
/// Creates a new `CodeBlock`.
#[must_use]
pub fn new(name: Sym, length: u32, strict: bool) -> Self {
Self {
@ -168,13 +169,22 @@ impl CodeBlock {
}
}
/// Retrieves the name associated with this code block.
#[must_use]
pub const fn name(&self) -> Sym {
self.name
}
/// Enable or disable instruction tracing to `stdout`.
#[cfg(feature = "trace")]
#[inline]
pub fn set_trace(&self, value: bool) {
self.trace.set(value);
}
}
/// ---- `CodeBlock` private API ----
impl CodeBlock {
/// Read type T from code.
///
/// # Safety

3
boa_examples/Cargo.toml

@ -10,10 +10,11 @@ repository.workspace = true
rust-version.workspace = true
[dependencies]
boa_engine = { workspace = true, features = ["console"] }
boa_engine.workspace = true
boa_ast.workspace = true
boa_interner.workspace = true
boa_gc.workspace = true
boa_parser.workspace = true
boa_runtime.workspace = true
smol = "1.3.0"
futures-util = "0.3.28"

19
boa_examples/src/bin/classes.rs

@ -8,6 +8,7 @@ use boa_engine::{
};
use boa_gc::{Finalize, Trace};
use boa_runtime::Console;
// We create a new struct that is going to represent a person.
//
@ -124,12 +125,26 @@ impl Class for Person {
}
}
/// Adds the custom runtime to the context.
fn add_runtime(context: &mut Context<'_>) {
// We first add the `console` object, to be able to call `console.log()`.
let console = Console::init(context);
context
.register_global_property(Console::NAME, console, Attribute::all())
.expect("the console builtin shouldn't exist");
// Then we need to register the global class `Person` inside `context`.
context
.register_global_class::<Person>()
.expect("the Person builtin shouldn't exist");
}
fn main() {
// First we need to create a Javascript context.
let mut context = Context::default();
// Then we need to register the global class `Person` inside `context`.
context.register_global_class::<Person>().unwrap();
// Then, we add our custom runtime.
add_runtime(&mut context);
// Having done all of that, we can execute Javascript code with `eval`,
// and access the `Person` class defined in Rust!

22
boa_examples/src/bin/futures.rs

@ -8,8 +8,10 @@ use boa_engine::{
context::ContextBuilder,
job::{FutureJob, JobQueue, NativeJob},
native_function::NativeFunction,
property::Attribute,
Context, JsArgs, JsResult, JsValue, Source,
};
use boa_runtime::Console;
use futures_util::{stream::FuturesUnordered, Future};
use smol::{future, stream::StreamExt, LocalExecutor};
@ -128,16 +130,28 @@ fn delay(
}
}
/// Adds the custom runtime to the context.
fn add_runtime(context: &mut Context<'_>) {
// First add the `console` object, to be able to call `console.log()`.
let console = Console::init(context);
context
.register_global_property(Console::NAME, console, Attribute::all())
.expect("the console builtin shouldn't exist");
// Then, bind the defined async function to the ECMAScript function "delay".
context
.register_global_builtin_callable("delay", 1, NativeFunction::from_async_fn(delay))
.expect("the delay builtin shouldn't exist");
}
fn main() {
// Initialize the required executors and the context
let executor = LocalExecutor::new();
let queue: &dyn JobQueue = &Queue::new(executor);
let context = &mut ContextBuilder::new().job_queue(queue).build().unwrap();
// Bind the defined async function to the ECMAScript function "delay".
context
.register_global_builtin_callable("delay", 1, NativeFunction::from_async_fn(delay))
.unwrap();
// Then, add a custom runtime.
add_runtime(context);
// Multiple calls to multiple async timers.
let script = r#"

2
boa_examples/src/bin/loadstring.rs

@ -3,7 +3,7 @@
use boa_engine::{Context, Source};
fn main() {
let js_code = "console.log('Hello World from a JS code string!')";
let js_code = "'Hello World ' + 'from a JS code ' + 'string!'";
// Instantiate the execution context
let mut context = Context::default();

2
boa_parser/Cargo.toml

@ -19,7 +19,7 @@ boa_unicode.workspace = true
rustc-hash = "1.1.0"
fast-float = "0.2.0"
num-traits = "0.2.15"
bitflags = "2.2.0"
bitflags = "2.1.0"
num-bigint = "0.4.3"
regress = "0.5.0"

21
boa_runtime/Cargo.toml

@ -0,0 +1,21 @@
[package]
name = "boa_runtime"
description = "Example runtime for the Boa JavaScript engine."
keywords = ["javascript", "js", "runtime"]
categories = ["command-line-utilities"]
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[dependencies]
boa_engine.workspace = true
boa_gc.workspace = true
rustc-hash = "1.1.0"
[dev-dependencies]
indoc = "2.0.1"
textwrap = "0.16.0"

188
boa_engine/src/console/mod.rs → boa_runtime/src/console/mod.rs

@ -16,14 +16,14 @@
#[cfg(test)]
mod tests;
use crate::{
use boa_engine::{
native_function::NativeFunction,
object::{JsObject, ObjectInitializer},
value::{display::display_obj, JsValue, Numeric},
value::{JsValue, Numeric},
Context, JsArgs, JsResult, JsString,
};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
// use boa_profiler::Profiler;
use rustc_hash::FxHashMap;
use std::{cell::RefCell, rc::Rc, time::SystemTime};
@ -51,7 +51,7 @@ fn logger(msg: LogMessage, console_state: &Console) {
}
/// This represents the `console` formatter.
pub fn formatter(data: &[JsValue], context: &mut Context<'_>) -> JsResult<String> {
fn formatter(data: &[JsValue], context: &mut Context<'_>) -> JsResult<String> {
match data {
[] => Ok(String::new()),
[val] => Ok(val.to_string(context)?.to_std_string_escaped()),
@ -124,16 +124,18 @@ pub fn formatter(data: &[JsValue], context: &mut Context<'_>) -> JsResult<String
/// This is the internal console object state.
#[derive(Debug, Default, Trace, Finalize)]
pub(crate) struct Console {
pub struct Console {
count_map: FxHashMap<JsString, u32>,
timer_map: FxHashMap<JsString, u128>,
groups: Vec<String>,
}
impl Console {
const NAME: &'static str = "console";
/// Name of the built-in `console` property.
pub const NAME: &'static str = "console";
pub(crate) fn init(context: &mut Context<'_>) -> JsObject {
/// Initializes the `console` built-in object.
pub fn init(context: &mut Context<'_>) -> JsObject {
fn console_method(
f: fn(&JsValue, &[JsValue], &Console, &mut Context<'_>) -> JsResult<JsValue>,
state: Rc<RefCell<Console>>,
@ -156,11 +158,11 @@ impl Console {
})
}
}
let _timer = Profiler::global().start_event(Self::NAME, "init");
// let _timer = Profiler::global().start_event(Self::NAME, "init");
let state = Rc::new(RefCell::new(Console::default()));
let state = Rc::new(RefCell::new(Self::default()));
ObjectInitializer::with_native(Console::default(), context)
ObjectInitializer::with_native(Self::default(), context)
.function(console_method(Self::assert, state.clone()), "assert", 0)
.function(console_method_mut(Self::clear, state.clone()), "clear", 0)
.function(console_method(Self::debug, state.clone()), "debug", 0)
@ -209,10 +211,10 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#assert
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/assert
pub(crate) fn assert(
fn assert(
_: &JsValue,
args: &[JsValue],
console: &Console,
console: &Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let assertion = args.get(0).map_or(false, JsValue::to_boolean);
@ -246,10 +248,10 @@ impl Console {
/// [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(
fn clear(
_: &JsValue,
_: &[JsValue],
console: &mut Console,
console: &mut Self,
_: &mut Context<'_>,
) -> JsResult<JsValue> {
console.groups.clear();
@ -266,10 +268,10 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#debug
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/debug
pub(crate) fn debug(
fn debug(
_: &JsValue,
args: &[JsValue],
console: &Console,
console: &Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
logger(LogMessage::Log(formatter(args, context)?), console);
@ -286,10 +288,10 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#error
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/error
pub(crate) fn error(
fn error(
_: &JsValue,
args: &[JsValue],
console: &Console,
console: &Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
logger(LogMessage::Error(formatter(args, context)?), console);
@ -306,10 +308,10 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#info
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/info
pub(crate) fn info(
fn info(
_: &JsValue,
args: &[JsValue],
console: &Console,
console: &Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
logger(LogMessage::Info(formatter(args, context)?), console);
@ -326,31 +328,16 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#log
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/log
pub(crate) fn log(
fn log(
_: &JsValue,
args: &[JsValue],
console: &Console,
console: &Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
logger(LogMessage::Log(formatter(args, context)?), console);
Ok(JsValue::undefined())
}
fn get_stack_trace(context: &mut Context<'_>) -> Vec<String> {
let mut stack_trace: Vec<String> = vec![];
for frame in context.vm.frames.iter().rev() {
stack_trace.push(
context
.interner()
.resolve_expect(frame.code_block.name)
.to_string(),
);
}
stack_trace
}
/// `console.trace(...data)`
///
/// Prints a stack trace with "trace" logLevel, optionally labelled by data.
@ -361,16 +348,23 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#trace
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/trace
pub(crate) fn trace(
fn trace(
_: &JsValue,
args: &[JsValue],
console: &Console,
console: &Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
if !args.is_empty() {
logger(LogMessage::Log(formatter(args, context)?), console);
let stack_trace_dump = Self::get_stack_trace(context).join("\n");
let stack_trace_dump = context
.stack_trace()
.map(|frame| frame.code_block().name())
.collect::<Vec<_>>()
.into_iter()
.map(|s| context.interner().resolve_expect(s).to_string())
.collect::<Vec<_>>()
.join("\n");
logger(LogMessage::Log(stack_trace_dump), console);
}
@ -387,10 +381,10 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#warn
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/warn
pub(crate) fn warn(
fn warn(
_: &JsValue,
args: &[JsValue],
console: &Console,
console: &Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
logger(LogMessage::Warn(formatter(args, context)?), console);
@ -407,10 +401,10 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#count
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/count
pub(crate) fn count(
fn count(
_: &JsValue,
args: &[JsValue],
console: &mut Console,
console: &mut Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let label = match args.get(0) {
@ -436,10 +430,10 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#countreset
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/countReset
pub(crate) fn count_reset(
fn count_reset(
_: &JsValue,
args: &[JsValue],
console: &mut Console,
console: &mut Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let label = match args.get(0) {
@ -475,10 +469,10 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#time
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/time
pub(crate) fn time(
fn time(
_: &JsValue,
args: &[JsValue],
console: &mut Console,
console: &mut Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let label = match args.get(0) {
@ -512,10 +506,10 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#timelog
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeLog
pub(crate) fn time_log(
fn time_log(
_: &JsValue,
args: &[JsValue],
console: &Console,
console: &Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let label = match args.get(0) {
@ -523,22 +517,25 @@ impl Console {
None => "default".into(),
};
if let Some(t) = console.timer_map.get(&label) {
let time = Self::system_time_in_ms();
let mut concat = format!("{}: {} ms", label.to_std_string_escaped(), time - t);
for msg in args.iter().skip(1) {
concat = concat + " " + &msg.display().to_string();
}
logger(LogMessage::Log(concat), console);
} else {
logger(
LogMessage::Warn(format!(
"Timer '{}' doesn't exist",
label.to_std_string_escaped()
)),
console,
);
}
console.timer_map.get(&label).map_or_else(
|| {
logger(
LogMessage::Warn(format!(
"Timer '{}' doesn't exist",
label.to_std_string_escaped()
)),
console,
);
},
|t| {
let time = Self::system_time_in_ms();
let mut concat = format!("{}: {} ms", label.to_std_string_escaped(), time - t);
for msg in args.iter().skip(1) {
concat = concat + " " + &msg.display().to_string();
}
logger(LogMessage::Log(concat), console);
},
);
Ok(JsValue::undefined())
}
@ -553,10 +550,10 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#timeend
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeEnd
pub(crate) fn time_end(
fn time_end(
_: &JsValue,
args: &[JsValue],
console: &mut Console,
console: &mut Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let label = match args.get(0) {
@ -564,25 +561,28 @@ impl Console {
None => "default".into(),
};
if let Some(t) = console.timer_map.remove(&label) {
let time = Self::system_time_in_ms();
logger(
LogMessage::Info(format!(
"{}: {} ms - timer removed",
label.to_std_string_escaped(),
time - t
)),
console,
);
} else {
logger(
LogMessage::Warn(format!(
"Timer '{}' doesn't exist",
label.to_std_string_escaped()
)),
console,
);
}
console.timer_map.remove(&label).map_or_else(
|| {
logger(
LogMessage::Warn(format!(
"Timer '{}' doesn't exist",
label.to_std_string_escaped()
)),
console,
);
},
|t| {
let time = Self::system_time_in_ms();
logger(
LogMessage::Info(format!(
"{}: {} ms - timer removed",
label.to_std_string_escaped(),
time - t
)),
console,
);
},
);
Ok(JsValue::undefined())
}
@ -597,10 +597,10 @@ impl Console {
///
/// [spec]: https://console.spec.whatwg.org/#group
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/group
pub(crate) fn group(
fn group(
_: &JsValue,
args: &[JsValue],
console: &mut Console,
console: &mut Self,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let group_label = formatter(args, context)?;
@ -622,10 +622,10 @@ impl Console {
/// [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(
fn group_end(
_: &JsValue,
_: &[JsValue],
console: &mut Console,
console: &mut Self,
_: &mut Context<'_>,
) -> JsResult<JsValue> {
console.groups.pop();
@ -644,14 +644,14 @@ impl Console {
/// [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(
fn dir(
_: &JsValue,
args: &[JsValue],
console: &Console,
console: &Self,
_: &mut Context<'_>,
) -> JsResult<JsValue> {
logger(
LogMessage::Info(display_obj(args.get_or_undefined(0), true)),
LogMessage::Info(args.get_or_undefined(0).display_obj(true)),
console,
);
Ok(JsValue::undefined())

24
boa_engine/src/console/tests.rs → boa_runtime/src/console/tests.rs

@ -1,4 +1,7 @@
use crate::{console::formatter, run_test_actions, JsValue, TestAction};
use super::{formatter, Console};
use crate::test::{run_test_actions, run_test_actions_with, TestAction};
use boa_engine::{property::Attribute, Context, JsValue};
use indoc::indoc;
#[test]
fn formatter_no_args_is_empty_string() {
@ -81,3 +84,22 @@ fn formatter_float_format_works() {
);
})]);
}
#[test]
fn console_log_cyclic() {
let mut context = Context::default();
let console = Console::init(&mut context);
context
.register_global_property(Console::NAME, console, Attribute::all())
.unwrap();
run_test_actions_with(
[TestAction::run(indoc! {r#"
let a = [1];
a[1] = a;
console.log(a);
"#})],
&mut context,
);
// Should not stack overflow
}

277
boa_runtime/src/lib.rs

@ -0,0 +1,277 @@
//! Example runtime for Boa
//!
//! This crate contains an example runtime for the `boa_engine` crate, so that it can be used as a
//! template for runtime implementors. It contains some basic functionality that can be used by
//! other crates.
#![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), forbid(clippy::unwrap_used))]
#![warn(missing_docs, clippy::dbg_macro)]
#![deny(
// rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html
warnings,
future_incompatible,
let_underscore,
nonstandard_style,
rust_2018_compatibility,
rust_2018_idioms,
rust_2021_compatibility,
unused,
// rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html
macro_use_extern_crate,
meta_variable_misuse,
missing_abi,
missing_copy_implementations,
missing_debug_implementations,
non_ascii_idents,
noop_method_call,
single_use_lifetimes,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unsafe_op_in_unsafe_fn,
unused_crate_dependencies,
unused_import_braces,
unused_lifetimes,
unused_qualifications,
unused_tuple_struct_fields,
variant_size_differences,
// rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links,
rustdoc::missing_crate_level_docs,
rustdoc::private_doc_tests,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_rust_codeblocks,
rustdoc::bare_urls,
// clippy categories https://doc.rust-lang.org/clippy/
clippy::all,
clippy::correctness,
clippy::suspicious,
clippy::style,
clippy::complexity,
clippy::perf,
clippy::pedantic,
clippy::nursery,
clippy::undocumented_unsafe_blocks
)]
#![allow(
clippy::module_name_repetitions,
clippy::redundant_pub_crate,
clippy::let_unit_value
)]
mod console;
#[doc(inline)]
pub use console::Console;
#[cfg(test)]
pub(crate) mod test {
use boa_engine::{builtins, Context, JsResult, JsValue, Source};
use std::borrow::Cow;
/// A test action executed in a test function.
#[allow(missing_debug_implementations)]
#[derive(Clone)]
pub(crate) struct TestAction(Inner);
#[derive(Clone)]
#[allow(dead_code)]
enum Inner {
RunHarness,
Run {
source: Cow<'static, str>,
},
InspectContext {
op: fn(&mut Context<'_>),
},
Assert {
source: Cow<'static, str>,
},
AssertEq {
source: Cow<'static, str>,
expected: JsValue,
},
AssertWithOp {
source: Cow<'static, str>,
op: fn(JsValue, &mut Context<'_>) -> bool,
},
AssertOpaqueError {
source: Cow<'static, str>,
expected: JsValue,
},
AssertNativeError {
source: Cow<'static, str>,
kind: builtins::error::ErrorKind,
message: &'static str,
},
AssertContext {
op: fn(&mut Context<'_>) -> bool,
},
}
impl TestAction {
/// Runs `source`, panicking if the execution throws.
pub(crate) fn run(source: impl Into<Cow<'static, str>>) -> Self {
Self(Inner::Run {
source: source.into(),
})
}
/// Executes `op` with the currently active context.
///
/// Useful to make custom assertions that must be done from Rust code.
pub(crate) fn inspect_context(op: fn(&mut Context<'_>)) -> Self {
Self(Inner::InspectContext { op })
}
}
/// Executes a list of test actions on a new, default context.
#[track_caller]
pub(crate) fn run_test_actions(actions: impl IntoIterator<Item = TestAction>) {
let context = &mut Context::default();
run_test_actions_with(actions, context);
}
/// Executes a list of test actions on the provided context.
#[track_caller]
#[allow(clippy::too_many_lines, clippy::missing_panics_doc)]
pub(crate) fn run_test_actions_with(
actions: impl IntoIterator<Item = TestAction>,
context: &mut Context<'_>,
) {
#[track_caller]
fn forward_val(context: &mut Context<'_>, source: &str) -> JsResult<JsValue> {
context.eval_script(Source::from_bytes(source))
}
#[track_caller]
fn fmt_test(source: &str, test: usize) -> String {
format!(
"\n\nTest case {test}: \n```\n{}\n```",
textwrap::indent(source, " ")
)
}
// Some unwrapping patterns look weird because they're replaceable
// by simpler patterns like `unwrap_or_else` or `unwrap_err
let mut i = 1;
for action in actions.into_iter().map(|a| a.0) {
match action {
Inner::RunHarness => {
// add utility functions for testing
// TODO: extract to a file
forward_val(
context,
r#"
function equals(a, b) {
if (Array.isArray(a) && Array.isArray(b)) {
return arrayEquals(a, b);
}
return a === b;
}
function arrayEquals(a, b) {
return Array.isArray(a) &&
Array.isArray(b) &&
a.length === b.length &&
a.every((val, index) => equals(val, b[index]));
}
"#,
)
.expect("failed to evaluate test harness");
}
Inner::Run { source } => {
if let Err(e) = forward_val(context, &source) {
panic!("{}\nUncaught {e}", fmt_test(&source, i));
}
}
Inner::InspectContext { op } => {
op(context);
}
Inner::Assert { source } => {
let val = match forward_val(context, &source) {
Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
Ok(v) => v,
};
let Some(val) = val.as_boolean() else {
panic!(
"{}\nTried to assert with the non-boolean value `{}`",
fmt_test(&source, i),
val.display()
)
};
assert!(val, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertEq { source, expected } => {
let val = match forward_val(context, &source) {
Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
Ok(v) => v,
};
assert_eq!(val, expected, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertWithOp { source, op } => {
let val = match forward_val(context, &source) {
Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)),
Ok(v) => v,
};
assert!(op(val, context), "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertOpaqueError { source, expected } => {
let err = match forward_val(context, &source) {
Ok(v) => panic!(
"{}\nExpected error, got value `{}`",
fmt_test(&source, i),
v.display()
),
Err(e) => e,
};
let Some(err) = err.as_opaque() else {
panic!("{}\nExpected opaque error, got native error `{}`", fmt_test(&source, i), err)
};
assert_eq!(err, &expected, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertNativeError {
source,
kind,
message,
} => {
let err = match forward_val(context, &source) {
Ok(v) => panic!(
"{}\nExpected error, got value `{}`",
fmt_test(&source, i),
v.display()
),
Err(e) => e,
};
let native = match err.try_native(context) {
Ok(err) => err,
Err(e) => panic!(
"{}\nCouldn't obtain a native error: {e}",
fmt_test(&source, i)
),
};
assert_eq!(&native.kind, &kind, "{}", fmt_test(&source, i));
assert_eq!(native.message(), message, "{}", fmt_test(&source, i));
i += 1;
}
Inner::AssertContext { op } => {
assert!(op(context), "Test case {i}");
i += 1;
}
}
}
}
}

2
boa_tester/Cargo.toml

@ -18,7 +18,7 @@ clap = { version = "4.2.4", features = ["derive"] }
serde = { version = "1.0.160", features = ["derive"] }
serde_yaml = "0.9.21"
serde_json = "1.0.96"
bitflags = "2.2.0"
bitflags = "2.1.0"
regex = "1.8.0"
once_cell = "1.17.1"
colored = "2.0.0"

2
boa_wasm/Cargo.toml

@ -12,7 +12,7 @@ repository.workspace = true
rust-version.workspace = true
[dependencies]
boa_engine = { workspace = true, features = ["console"] }
boa_engine.workspace = true
wasm-bindgen = "0.2.84"
getrandom = { version = "0.2.9", features = ["js"] }
chrono = { version = "0.4.24", features = ["clock", "std", "wasmbind"] }

2
test262

@ -1 +1 @@
Subproject commit e5a7450095c52681cd6233035aba7d6f55b6f9af
Subproject commit 2df6c7d29a18cd33120e791bdbe2043980a893fd
Loading…
Cancel
Save