Browse Source

Create `Source` to abstract JS code sources (#2579)

Slightly related to #2411 since we need an API to pass module files, but more useful for #1760, #1313 and other error reporting issues.

It changes the following:

- Introduces a new `Source` API to store the path of a provided file or `None` if the source is a plain string.
- Improves the display of `boa_tester` to show the path of the tests being run. This also enables hyperlinks to directly jump to the tested file from the VS terminal.
- Adjusts the repo to this change.

Hopefully, this will improve our error display in the future.
pull/2580/head
José Julián Espina 2 years ago
parent
commit
515d28f0a2
  1. 1
      Cargo.lock
  2. 21
      boa_cli/src/main.rs
  3. 8
      boa_engine/benches/full.rs
  4. 109
      boa_engine/src/builtins/array/tests.rs
  5. 4
      boa_engine/src/builtins/eval/mod.rs
  6. 7
      boa_engine/src/builtins/function/mod.rs
  7. 4
      boa_engine/src/builtins/json/mod.rs
  8. 12
      boa_engine/src/builtins/object/tests.rs
  9. 4
      boa_engine/src/builtins/promise/tests.rs
  10. 10
      boa_engine/src/builtins/weak/weak_ref.rs
  11. 4
      boa_engine/src/context/hooks.rs
  12. 47
      boa_engine/src/context/mod.rs
  13. 23
      boa_engine/src/lib.rs
  14. 6
      boa_engine/src/tests.rs
  15. 26
      boa_engine/src/value/serde_json.rs
  16. 18
      boa_engine/src/vm/tests.rs
  17. 6
      boa_examples/src/bin/classes.rs
  18. 12
      boa_examples/src/bin/closures.rs
  19. 9
      boa_examples/src/bin/commuter_visitor.rs
  20. 6
      boa_examples/src/bin/loadfile.rs
  21. 4
      boa_examples/src/bin/loadstring.rs
  22. 6
      boa_examples/src/bin/modulehandler.rs
  23. 9
      boa_examples/src/bin/symbol_visitor.rs
  24. 2
      boa_parser/src/lib.rs
  25. 71
      boa_parser/src/parser/mod.rs
  26. 5
      boa_parser/src/parser/tests/format/mod.rs
  27. 6
      boa_parser/src/parser/tests/mod.rs
  28. 88
      boa_parser/src/source.rs
  29. 1
      boa_tester/Cargo.toml
  30. 7
      boa_tester/src/exec/js262.rs
  31. 89
      boa_tester/src/exec/mod.rs
  32. 25
      boa_tester/src/main.rs
  33. 49
      boa_tester/src/read.rs
  34. 4
      boa_wasm/src/lib.rs

1
Cargo.lock generated

@ -323,7 +323,6 @@ dependencies = [
"bitflags", "bitflags",
"boa_engine", "boa_engine",
"boa_gc", "boa_gc",
"boa_parser",
"clap 4.1.4", "clap 4.1.4",
"color-eyre", "color-eyre",
"colored", "colored",

21
boa_cli/src/main.rs

@ -65,7 +65,7 @@ use boa_engine::{
context::ContextBuilder, context::ContextBuilder,
job::{JobQueue, NativeJob}, job::{JobQueue, NativeJob},
vm::flowgraph::{Direction, Graph}, vm::flowgraph::{Direction, Graph},
Context, JsResult, Context, JsResult, Source,
}; };
use clap::{Parser, ValueEnum, ValueHint}; use clap::{Parser, ValueEnum, ValueHint};
use colored::{Color, Colorize}; use colored::{Color, Colorize};
@ -189,23 +189,22 @@ enum FlowgraphDirection {
/// ///
/// Returns a error of type String with a message, /// Returns a error of type String with a message,
/// if the token stream has a parsing error. /// if the token stream has a parsing error.
fn parse_tokens<S>(src: S, context: &mut Context<'_>) -> Result<StatementList, String> fn parse_tokens<S>(src: &S, context: &mut Context<'_>) -> Result<StatementList, String>
where where
S: AsRef<[u8]>, S: AsRef<[u8]> + ?Sized,
{ {
let src_bytes = src.as_ref(); boa_parser::Parser::new(Source::from_bytes(&src))
boa_parser::Parser::new(src_bytes)
.parse_all(context.interner_mut()) .parse_all(context.interner_mut())
.map_err(|e| format!("ParsingError: {e}")) .map_err(|e| format!("Uncaught SyntaxError: {e}"))
} }
/// Dumps the AST to stdout with format controlled by the given arguments. /// Dumps the AST to stdout with format controlled by the given arguments.
/// ///
/// Returns a error of type String with a error message, /// Returns a error of type String with a error message,
/// if the source has a syntax or parsing error. /// if the source has a syntax or parsing error.
fn dump<S>(src: S, args: &Opt, context: &mut Context<'_>) -> Result<(), String> fn dump<S>(src: &S, args: &Opt, context: &mut Context<'_>) -> Result<(), String>
where where
S: AsRef<[u8]>, S: AsRef<[u8]> + ?Sized,
{ {
if let Some(ref arg) = args.dump_ast { if let Some(ref arg) = args.dump_ast {
let ast = parse_tokens(src, context)?; let ast = parse_tokens(src, context)?;
@ -233,7 +232,7 @@ fn generate_flowgraph(
format: FlowgraphFormat, format: FlowgraphFormat,
direction: Option<FlowgraphDirection>, direction: Option<FlowgraphDirection>,
) -> JsResult<String> { ) -> JsResult<String> {
let ast = context.parse(src)?; let ast = context.parse(Source::from_bytes(src))?;
let code = context.compile(&ast)?; let code = context.compile(&ast)?;
let direction = match direction { let direction = match direction {
@ -279,7 +278,7 @@ fn main() -> Result<(), io::Error> {
Err(v) => eprintln!("Uncaught {v}"), Err(v) => eprintln!("Uncaught {v}"),
} }
} else { } else {
match context.eval(&buffer) { match context.eval(Source::from_bytes(&buffer)) {
Ok(v) => println!("{}", v.display()), Ok(v) => println!("{}", v.display()),
Err(v) => eprintln!("Uncaught {v}"), Err(v) => eprintln!("Uncaught {v}"),
} }
@ -336,7 +335,7 @@ fn main() -> Result<(), io::Error> {
Err(v) => eprintln!("Uncaught {v}"), Err(v) => eprintln!("Uncaught {v}"),
} }
} else { } else {
match context.eval(line.trim_end()) { match context.eval(Source::from_bytes(line.trim_end())) {
Ok(v) => { Ok(v) => {
println!("{}", v.display()); println!("{}", v.display());
} }

8
boa_engine/benches/full.rs

@ -1,6 +1,6 @@
//! Benchmarks of the whole execution engine in Boa. //! Benchmarks of the whole execution engine in Boa.
use boa_engine::{realm::Realm, Context}; use boa_engine::{realm::Realm, Context, Source};
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use std::hint::black_box; use std::hint::black_box;
@ -23,7 +23,7 @@ macro_rules! full_benchmarks {
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default(); let mut context = Context::default();
c.bench_function(concat!($id, " (Parser)"), move |b| { c.bench_function(concat!($id, " (Parser)"), move |b| {
b.iter(|| context.parse(black_box(CODE))) b.iter(|| context.parse(black_box(Source::from_bytes(CODE))))
}); });
} }
)* )*
@ -33,7 +33,7 @@ macro_rules! full_benchmarks {
{ {
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default(); let mut context = Context::default();
let statement_list = context.parse(CODE).expect("parsing failed"); let statement_list = context.parse(Source::from_bytes(CODE)).expect("parsing failed");
c.bench_function(concat!($id, " (Compiler)"), move |b| { c.bench_function(concat!($id, " (Compiler)"), move |b| {
b.iter(|| { b.iter(|| {
context.compile(black_box(&statement_list)) context.compile(black_box(&statement_list))
@ -47,7 +47,7 @@ macro_rules! full_benchmarks {
{ {
static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js")); static CODE: &str = include_str!(concat!("bench_scripts/", stringify!($name), ".js"));
let mut context = Context::default(); let mut context = Context::default();
let statement_list = context.parse(CODE).expect("parsing failed"); let statement_list = context.parse(Source::from_bytes(CODE)).expect("parsing failed");
let code_block = context.compile(&statement_list).unwrap(); let code_block = context.compile(&statement_list).unwrap();
c.bench_function(concat!($id, " (Execution)"), move |b| { c.bench_function(concat!($id, " (Execution)"), move |b| {
b.iter(|| { b.iter(|| {

109
boa_engine/src/builtins/array/tests.rs

@ -1,3 +1,5 @@
use boa_parser::Source;
use super::Array; use super::Array;
use crate::builtins::Number; use crate::builtins::Number;
use crate::{forward, Context, JsValue}; use crate::{forward, Context, JsValue};
@ -10,60 +12,85 @@ fn is_array() {
var new_arr = new Array(); var new_arr = new Array();
var many = ["a", "b", "c"]; var many = ["a", "b", "c"];
"#; "#;
context.eval(init).unwrap(); context.eval(Source::from_bytes(init)).unwrap();
assert_eq!( assert_eq!(
context.eval("Array.isArray(empty)").unwrap(), context
.eval(Source::from_bytes("Array.isArray(empty)"))
.unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context.eval("Array.isArray(new_arr)").unwrap(), context
.eval(Source::from_bytes("Array.isArray(new_arr)"))
.unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context.eval("Array.isArray(many)").unwrap(), context
.eval(Source::from_bytes("Array.isArray(many)"))
.unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context.eval("Array.isArray([1, 2, 3])").unwrap(), context
.eval(Source::from_bytes("Array.isArray([1, 2, 3])"))
.unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context.eval("Array.isArray([])").unwrap(), context
.eval(Source::from_bytes("Array.isArray([])"))
.unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!( assert_eq!(
context.eval("Array.isArray({})").unwrap(), context
.eval(Source::from_bytes("Array.isArray({})"))
.unwrap(),
JsValue::new(false) JsValue::new(false)
); );
// assert_eq!(context.eval("Array.isArray(new Array)"), "true");
assert_eq!( assert_eq!(
context.eval("Array.isArray()").unwrap(), context
.eval(Source::from_bytes("Array.isArray(new Array)"))
.unwrap(),
JsValue::new(true)
);
assert_eq!(
context.eval(Source::from_bytes("Array.isArray()")).unwrap(),
JsValue::new(false) JsValue::new(false)
); );
assert_eq!( assert_eq!(
context context
.eval("Array.isArray({ constructor: Array })") .eval(Source::from_bytes("Array.isArray({ constructor: Array })"))
.unwrap(), .unwrap(),
JsValue::new(false) JsValue::new(false)
); );
assert_eq!( assert_eq!(
context context
.eval("Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })") .eval(Source::from_bytes(
"Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })"
))
.unwrap(), .unwrap(),
JsValue::new(false) JsValue::new(false)
); );
assert_eq!( assert_eq!(
context.eval("Array.isArray(17)").unwrap(), context
.eval(Source::from_bytes("Array.isArray(17)"))
.unwrap(),
JsValue::new(false) JsValue::new(false)
); );
assert_eq!( assert_eq!(
context context
.eval("Array.isArray({ __proto__: Array.prototype })") .eval(Source::from_bytes(
"Array.isArray({ __proto__: Array.prototype })"
))
.unwrap(), .unwrap(),
JsValue::new(false) JsValue::new(false)
); );
assert_eq!( assert_eq!(
context.eval("Array.isArray({ length: 0 })").unwrap(), context
.eval(Source::from_bytes("Array.isArray({ length: 0 })"))
.unwrap(),
JsValue::new(false) JsValue::new(false)
); );
} }
@ -73,48 +100,68 @@ fn of() {
let mut context = Context::default(); let mut context = Context::default();
assert_eq!( assert_eq!(
context context
.eval("Array.of(1, 2, 3)") .eval(Source::from_bytes("Array.of(1, 2, 3)"))
.unwrap() .unwrap()
.to_string(&mut context) .to_string(&mut context)
.unwrap(), .unwrap(),
context context
.eval("[1, 2, 3]") .eval(Source::from_bytes("[1, 2, 3]"))
.unwrap() .unwrap()
.to_string(&mut context) .to_string(&mut context)
.unwrap() .unwrap()
); );
assert_eq!( assert_eq!(
context context
.eval("Array.of(1, 'a', [], undefined, null)") .eval(Source::from_bytes("Array.of(1, 'a', [], undefined, null)"))
.unwrap() .unwrap()
.to_string(&mut context) .to_string(&mut context)
.unwrap(), .unwrap(),
context context
.eval("[1, 'a', [], undefined, null]") .eval(Source::from_bytes("[1, 'a', [], undefined, null]"))
.unwrap() .unwrap()
.to_string(&mut context) .to_string(&mut context)
.unwrap() .unwrap()
); );
assert_eq!( assert_eq!(
context context
.eval("Array.of()") .eval(Source::from_bytes("Array.of()"))
.unwrap() .unwrap()
.to_string(&mut context) .to_string(&mut context)
.unwrap(), .unwrap(),
context.eval("[]").unwrap().to_string(&mut context).unwrap() context
.eval(Source::from_bytes("[]"))
.unwrap()
.to_string(&mut context)
.unwrap()
); );
context context
.eval(r#"let a = Array.of.call(Date, "a", undefined, 3);"#) .eval(Source::from_bytes(
r#"let a = Array.of.call(Date, "a", undefined, 3);"#,
))
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
context.eval("a instanceof Date").unwrap(), context
.eval(Source::from_bytes("a instanceof Date"))
.unwrap(),
JsValue::new(true) JsValue::new(true)
); );
assert_eq!(context.eval("a[0]").unwrap(), JsValue::new("a")); assert_eq!(
assert_eq!(context.eval("a[1]").unwrap(), JsValue::undefined()); context.eval(Source::from_bytes("a[0]")).unwrap(),
assert_eq!(context.eval("a[2]").unwrap(), JsValue::new(3)); JsValue::new("a")
assert_eq!(context.eval("a.length").unwrap(), JsValue::new(3)); );
assert_eq!(
context.eval(Source::from_bytes("a[1]")).unwrap(),
JsValue::undefined()
);
assert_eq!(
context.eval(Source::from_bytes("a[2]")).unwrap(),
JsValue::new(3)
);
assert_eq!(
context.eval(Source::from_bytes("a.length")).unwrap(),
JsValue::new(3)
);
} }
#[test] #[test]
@ -124,31 +171,31 @@ fn concat() {
var empty = []; var empty = [];
var one = [1]; var one = [1];
"#; "#;
context.eval(init).unwrap(); context.eval(Source::from_bytes(init)).unwrap();
// Empty ++ Empty // Empty ++ Empty
let ee = context let ee = context
.eval("empty.concat(empty)") .eval(Source::from_bytes("empty.concat(empty)"))
.unwrap() .unwrap()
.display() .display()
.to_string(); .to_string();
assert_eq!(ee, "[]"); assert_eq!(ee, "[]");
// Empty ++ NonEmpty // Empty ++ NonEmpty
let en = context let en = context
.eval("empty.concat(one)") .eval(Source::from_bytes("empty.concat(one)"))
.unwrap() .unwrap()
.display() .display()
.to_string(); .to_string();
assert_eq!(en, "[ 1 ]"); assert_eq!(en, "[ 1 ]");
// NonEmpty ++ Empty // NonEmpty ++ Empty
let ne = context let ne = context
.eval("one.concat(empty)") .eval(Source::from_bytes("one.concat(empty)"))
.unwrap() .unwrap()
.display() .display()
.to_string(); .to_string();
assert_eq!(ne, "[ 1 ]"); assert_eq!(ne, "[ 1 ]");
// NonEmpty ++ NonEmpty // NonEmpty ++ NonEmpty
let nn = context let nn = context
.eval("one.concat(one)") .eval(Source::from_bytes("one.concat(one)"))
.unwrap() .unwrap()
.display() .display()
.to_string(); .to_string();

4
boa_engine/src/builtins/eval/mod.rs

@ -22,7 +22,7 @@ use boa_ast::operations::{
contains, contains_arguments, top_level_var_declared_names, ContainsSymbol, contains, contains_arguments, top_level_var_declared_names, ContainsSymbol,
}; };
use boa_gc::Gc; use boa_gc::Gc;
use boa_parser::Parser; use boa_parser::{Parser, Source};
use boa_profiler::Profiler; use boa_profiler::Profiler;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -124,7 +124,7 @@ impl Eval {
// b. If script is a List of errors, throw a SyntaxError exception. // b. If script is a List of errors, throw a SyntaxError exception.
// c. If script Contains ScriptBody is false, return undefined. // c. If script Contains ScriptBody is false, return undefined.
// d. Let body be the ScriptBody of script. // d. Let body be the ScriptBody of script.
let mut parser = Parser::new(x.as_bytes()); let mut parser = Parser::new(Source::from_bytes(&x));
if strict { if strict {
parser.set_strict(); parser.set_strict();
} }

7
boa_engine/src/builtins/function/mod.rs

@ -34,7 +34,7 @@ use boa_ast::{
}; };
use boa_gc::{self, custom_trace, Finalize, Gc, Trace}; use boa_gc::{self, custom_trace, Finalize, Gc, Trace};
use boa_interner::Sym; use boa_interner::Sym;
use boa_parser::Parser; use boa_parser::{Parser, Source};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use tap::{Conv, Pipe}; use tap::{Conv, Pipe};
@ -512,7 +512,8 @@ impl BuiltInFunctionObject {
parameters.push(u16::from(b')')); parameters.push(u16::from(b')'));
// TODO: make parser generic to u32 iterators // TODO: make parser generic to u32 iterators
let parameters = match Parser::new(String::from_utf16_lossy(&parameters).as_bytes()) let parameters =
match Parser::new(Source::from_bytes(&String::from_utf16_lossy(&parameters)))
.parse_formal_parameters(context.interner_mut(), generator, r#async) .parse_formal_parameters(context.interner_mut(), generator, r#async)
{ {
Ok(parameters) => parameters, Ok(parameters) => parameters,
@ -549,7 +550,7 @@ impl BuiltInFunctionObject {
let body_arg = body_arg.to_string(context)?; let body_arg = body_arg.to_string(context)?;
// TODO: make parser generic to u32 iterators // TODO: make parser generic to u32 iterators
let body = match Parser::new(body_arg.to_std_string_escaped().as_bytes()) let body = match Parser::new(Source::from_bytes(&body_arg.to_std_string_escaped()))
.parse_function_body(context.interner_mut(), generator, r#async) .parse_function_body(context.interner_mut(), generator, r#async)
{ {
Ok(statement_list) => statement_list, Ok(statement_list) => statement_list,

4
boa_engine/src/builtins/json/mod.rs

@ -30,7 +30,7 @@ use crate::{
value::IntegerOrInfinity, value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue, Context, JsResult, JsString, JsValue,
}; };
use boa_parser::Parser; use boa_parser::{Parser, Source};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use tap::{Conv, Pipe}; use tap::{Conv, Pipe};
@ -198,7 +198,7 @@ impl Json {
// 8. NOTE: The PropertyDefinitionEvaluation semantics defined in 13.2.5.5 have special handling for the above evaluation. // 8. NOTE: The PropertyDefinitionEvaluation semantics defined in 13.2.5.5 have special handling for the above evaluation.
// 9. Let unfiltered be completion.[[Value]]. // 9. Let unfiltered be completion.[[Value]].
// 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral. // 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral.
let mut parser = Parser::new(script_string.as_bytes()); let mut parser = Parser::new(Source::from_bytes(&script_string));
parser.set_json_parse(); parser.set_json_parse();
let statement_list = parser.parse_all(context.interner_mut())?; let statement_list = parser.parse_all(context.interner_mut())?;
let code_block = context.compile_json_parse(&statement_list)?; let code_block = context.compile_json_parse(&statement_list)?;

12
boa_engine/src/builtins/object/tests.rs

@ -1,3 +1,5 @@
use boa_parser::Source;
use crate::{check_output, forward, Context, JsValue, TestAction}; use crate::{check_output, forward, Context, JsValue, TestAction};
#[test] #[test]
@ -249,7 +251,10 @@ fn get_own_property_descriptor_1_arg_returns_undefined() {
let obj = {a: 2}; let obj = {a: 2};
Object.getOwnPropertyDescriptor(obj) Object.getOwnPropertyDescriptor(obj)
"#; "#;
assert_eq!(context.eval(code).unwrap(), JsValue::undefined()); assert_eq!(
context.eval(Source::from_bytes(code)).unwrap(),
JsValue::undefined()
);
} }
#[test] #[test]
@ -318,7 +323,10 @@ fn object_is_prototype_of() {
Object.prototype.isPrototypeOf(String.prototype) Object.prototype.isPrototypeOf(String.prototype)
"#; "#;
assert_eq!(context.eval(init).unwrap(), JsValue::new(true)); assert_eq!(
context.eval(Source::from_bytes(init)).unwrap(),
JsValue::new(true)
);
} }
#[test] #[test]

4
boa_engine/src/builtins/promise/tests.rs

@ -1,3 +1,5 @@
use boa_parser::Source;
use crate::{context::ContextBuilder, forward, job::SimpleJobQueue}; use crate::{context::ContextBuilder, forward, job::SimpleJobQueue};
#[test] #[test]
@ -13,7 +15,7 @@ fn promise() {
count += 1; count += 1;
count; count;
"#; "#;
let result = context.eval(init).unwrap(); let result = context.eval(Source::from_bytes(init)).unwrap();
assert_eq!(result.as_number(), Some(2_f64)); assert_eq!(result.as_number(), Some(2_f64));
context.run_jobs(); context.run_jobs();
let after_completion = forward(&mut context, "count"); let after_completion = forward(&mut context, "count");

10
boa_engine/src/builtins/weak/weak_ref.rs

@ -141,6 +141,8 @@ impl WeakRef {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use boa_parser::Source;
use crate::{Context, JsValue}; use crate::{Context, JsValue};
#[test] #[test]
@ -148,7 +150,7 @@ mod tests {
let context = &mut Context::default(); let context = &mut Context::default();
assert!(context assert!(context
.eval( .eval(Source::from_bytes(
r#" r#"
var ptr; var ptr;
{ {
@ -157,7 +159,7 @@ mod tests {
} }
ptr.deref() ptr.deref()
"# "#
) ))
.unwrap() .unwrap()
.is_object()); .is_object());
@ -165,11 +167,11 @@ mod tests {
assert_eq!( assert_eq!(
context context
.eval( .eval(Source::from_bytes(
r#" r#"
ptr.deref() ptr.deref()
"# "#
) ))
.unwrap(), .unwrap(),
JsValue::undefined() JsValue::undefined()
); );

4
boa_engine/src/context/hooks.rs

@ -16,7 +16,7 @@ use crate::{
/// need to be redefined: /// need to be redefined:
/// ///
/// ``` /// ```
/// use boa_engine::{JsNativeError, JsResult, context::{Context, ContextBuilder, HostHooks}}; /// use boa_engine::{JsNativeError, JsResult, context::{Context, ContextBuilder, HostHooks}, Source};
/// ///
/// struct Hooks; /// struct Hooks;
/// ///
@ -30,7 +30,7 @@ use crate::{
/// } /// }
/// let hooks = Hooks; // Can have additional state. /// let hooks = Hooks; // Can have additional state.
/// let context = &mut ContextBuilder::new().host_hooks(&hooks).build(); /// let context = &mut ContextBuilder::new().host_hooks(&hooks).build();
/// let result = context.eval(r#"eval("let a = 5")"#); /// let result = context.eval(Source::from_bytes(r#"eval("let a = 5")"#));
/// assert_eq!(result.unwrap_err().to_string(), "TypeError: eval calls not available"); /// assert_eq!(result.unwrap_err().to_string(), "TypeError: eval calls not available");
/// ``` /// ```
/// ///

47
boa_engine/src/context/mod.rs

@ -13,6 +13,7 @@ pub use icu::BoaProvider;
use intrinsics::{IntrinsicObjects, Intrinsics}; use intrinsics::{IntrinsicObjects, Intrinsics};
use std::io::Read;
#[cfg(not(feature = "intl"))] #[cfg(not(feature = "intl"))]
pub use std::marker::PhantomData; pub use std::marker::PhantomData;
@ -28,7 +29,7 @@ use crate::{
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm, realm::Realm,
vm::{CallFrame, CodeBlock, Vm}, vm::{CallFrame, CodeBlock, Vm},
JsResult, JsValue, JsResult, JsValue, Source,
}; };
use boa_ast::StatementList; use boa_ast::StatementList;
@ -52,6 +53,7 @@ use boa_profiler::Profiler;
/// object::ObjectInitializer, /// object::ObjectInitializer,
/// property::{Attribute, PropertyDescriptor}, /// property::{Attribute, PropertyDescriptor},
/// Context, /// Context,
/// Source
/// }; /// };
/// ///
/// let script = r#" /// let script = r#"
@ -66,7 +68,7 @@ use boa_profiler::Profiler;
/// let mut context = Context::default(); /// let mut context = Context::default();
/// ///
/// // Populate the script definition to the context. /// // Populate the script definition to the context.
/// context.eval(script).unwrap(); /// context.eval(Source::from_bytes(script)).unwrap();
/// ///
/// // Create an object that can be used in eval calls. /// // Create an object that can be used in eval calls.
/// let arg = ObjectInitializer::new(&mut context) /// let arg = ObjectInitializer::new(&mut context)
@ -74,7 +76,7 @@ use boa_profiler::Profiler;
/// .build(); /// .build();
/// context.register_global_property("arg", arg, Attribute::all()); /// context.register_global_property("arg", arg, Attribute::all());
/// ///
/// let value = context.eval("test(arg)").unwrap(); /// let value = context.eval(Source::from_bytes("test(arg)")).unwrap();
/// ///
/// assert_eq!(value.as_number(), Some(12.0)) /// assert_eq!(value.as_number(), Some(12.0))
/// ``` /// ```
@ -92,6 +94,9 @@ pub struct Context<'host> {
/// Intrinsic objects /// Intrinsic objects
intrinsics: Intrinsics, intrinsics: Intrinsics,
/// Execute in strict mode,
strict: bool,
/// Number of instructions remaining before a forced exit /// Number of instructions remaining before a forced exit
#[cfg(feature = "fuzz")] #[cfg(feature = "fuzz")]
pub(crate) instructions_remaining: usize, pub(crate) instructions_remaining: usize,
@ -118,6 +123,7 @@ impl std::fmt::Debug for Context<'_> {
.field("interner", &self.interner) .field("interner", &self.interner)
.field("intrinsics", &self.intrinsics) .field("intrinsics", &self.intrinsics)
.field("vm", &self.vm) .field("vm", &self.vm)
.field("strict", &self.strict)
.field("promise_job_queue", &"JobQueue") .field("promise_job_queue", &"JobQueue")
.field("hooks", &"HostHooks"); .field("hooks", &"HostHooks");
@ -147,14 +153,16 @@ impl Context<'_> {
ContextBuilder::default() ContextBuilder::default()
} }
/// Evaluates the given code by compiling down to bytecode, then interpreting the bytecode into a value /// Evaluates the given script `Source` by compiling down to bytecode, then interpreting the
/// bytecode into a value.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
/// # use boa_engine::Context; /// # use boa_engine::{Context, Source};
/// let mut context = Context::default(); /// let mut context = Context::default();
/// ///
/// let value = context.eval("1 + 3").unwrap(); /// let source = Source::from_bytes("1 + 3");
/// let value = context.eval(source).unwrap();
/// ///
/// assert!(value.is_number()); /// assert!(value.is_number());
/// assert_eq!(value.as_number().unwrap(), 4.0); /// assert_eq!(value.as_number().unwrap(), 4.0);
@ -163,14 +171,11 @@ impl Context<'_> {
/// Note that this won't run any scheduled promise jobs; you need to call [`Context::run_jobs`] /// Note that this won't run any scheduled promise jobs; you need to call [`Context::run_jobs`]
/// on the context or [`JobQueue::run_jobs`] on the provided queue to run them. /// on the context or [`JobQueue::run_jobs`] on the provided queue to run them.
#[allow(clippy::unit_arg, clippy::drop_copy)] #[allow(clippy::unit_arg, clippy::drop_copy)]
pub fn eval<S>(&mut self, src: S) -> JsResult<JsValue> pub fn eval<R: Read>(&mut self, src: Source<'_, R>) -> JsResult<JsValue> {
where
S: AsRef<[u8]>,
{
let main_timer = Profiler::global().start_event("Evaluation", "Main"); let main_timer = Profiler::global().start_event("Evaluation", "Main");
let statement_list = self.parse(src)?; let script = self.parse(src)?;
let code_block = self.compile(&statement_list)?; let code_block = self.compile(&script)?;
let result = self.execute(code_block); let result = self.execute(code_block);
// The main_timer needs to be dropped before the Profiler is. // The main_timer needs to be dropped before the Profiler is.
@ -180,13 +185,13 @@ impl Context<'_> {
result result
} }
/// Parse the given source text. /// Parse the given source script.
pub fn parse<S>(&mut self, src: S) -> Result<StatementList, ParseError> pub fn parse<R: Read>(&mut self, src: Source<'_, R>) -> Result<StatementList, ParseError> {
where
S: AsRef<[u8]>,
{
let _timer = Profiler::global().start_event("Parsing", "Main"); let _timer = Profiler::global().start_event("Parsing", "Main");
let mut parser = Parser::new(src.as_ref()); let mut parser = Parser::new(src);
if self.strict {
parser.set_strict();
}
parser.parse_all(&mut self.interner) parser.parse_all(&mut self.interner)
} }
@ -380,6 +385,11 @@ impl Context<'_> {
self.vm.trace = trace; self.vm.trace = trace;
} }
/// Executes all code in strict mode.
pub fn strict(&mut self, strict: bool) {
self.strict = strict;
}
/// Enqueues a [`NativeJob`] on the [`JobQueue`]. /// Enqueues a [`NativeJob`] on the [`JobQueue`].
pub fn enqueue_job(&mut self, job: NativeJob) { pub fn enqueue_job(&mut self, job: NativeJob) {
self.job_queue.enqueue_promise_job(job, self); self.job_queue.enqueue_promise_job(job, self);
@ -605,6 +615,7 @@ impl<'icu, 'hooks, 'queue> ContextBuilder<'icu, 'hooks, 'queue> {
console: Console::default(), console: Console::default(),
intrinsics, intrinsics,
vm: Vm::default(), vm: Vm::default(),
strict: false,
#[cfg(feature = "intl")] #[cfg(feature = "intl")]
icu: self.icu.unwrap_or_else(|| { icu: self.icu.unwrap_or_else(|| {
let provider = BoaProvider::Buffer(boa_icu_provider::buffer()); let provider = BoaProvider::Buffer(boa_icu_provider::buffer());

23
boa_engine/src/lib.rs

@ -135,6 +135,7 @@ pub mod prelude {
object::JsObject, object::JsObject,
Context, JsBigInt, JsResult, JsString, JsValue, Context, JsBigInt, JsResult, JsString, JsValue,
}; };
pub use boa_parser::Source;
} }
use std::result::Result as StdResult; use std::result::Result as StdResult;
@ -149,6 +150,8 @@ pub use crate::{
symbol::JsSymbol, symbol::JsSymbol,
value::JsValue, value::JsValue,
}; };
#[doc(inline)]
pub use boa_parser::Source;
/// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) /// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`)
pub type JsResult<T> = StdResult<T, JsError>; pub type JsResult<T> = StdResult<T, JsError>;
@ -157,12 +160,12 @@ pub type JsResult<T> = StdResult<T, JsError>;
/// ///
/// The state of the `Context` is changed, and a string representation of the result is returned. /// The state of the `Context` is changed, and a string representation of the result is returned.
#[cfg(test)] #[cfg(test)]
pub(crate) fn forward<S>(context: &mut Context<'_>, src: S) -> String pub(crate) fn forward<S>(context: &mut Context<'_>, src: &S) -> String
where where
S: AsRef<[u8]>, S: AsRef<[u8]> + ?Sized,
{ {
context context
.eval(src.as_ref()) .eval(Source::from_bytes(src))
.map_or_else(|e| format!("Uncaught {e}"), |v| v.display().to_string()) .map_or_else(|e| format!("Uncaught {e}"), |v| v.display().to_string())
} }
@ -172,13 +175,15 @@ where
/// If the interpreter fails parsing an error value is returned instead (error object) /// If the interpreter fails parsing an error value is returned instead (error object)
#[allow(clippy::unit_arg, clippy::drop_copy)] #[allow(clippy::unit_arg, clippy::drop_copy)]
#[cfg(test)] #[cfg(test)]
pub(crate) fn forward_val<T: AsRef<[u8]>>(context: &mut Context<'_>, src: T) -> JsResult<JsValue> { pub(crate) fn forward_val<T: AsRef<[u8]> + ?Sized>(
context: &mut Context<'_>,
src: &T,
) -> JsResult<JsValue> {
use boa_profiler::Profiler; use boa_profiler::Profiler;
let main_timer = Profiler::global().start_event("Main", "Main"); let main_timer = Profiler::global().start_event("Main", "Main");
let src_bytes: &[u8] = src.as_ref(); let result = context.eval(Source::from_bytes(src));
let result = context.eval(src_bytes);
// The main_timer needs to be dropped before the Profiler is. // The main_timer needs to be dropped before the Profiler is.
drop(main_timer); drop(main_timer);
@ -189,10 +194,8 @@ pub(crate) fn forward_val<T: AsRef<[u8]>>(context: &mut Context<'_>, src: T) ->
/// Create a clean Context and execute the code /// Create a clean Context and execute the code
#[cfg(test)] #[cfg(test)]
pub(crate) fn exec<T: AsRef<[u8]>>(src: T) -> String { pub(crate) fn exec<T: AsRef<[u8]> + ?Sized>(src: &T) -> String {
let src_bytes: &[u8] = src.as_ref(); match Context::default().eval(Source::from_bytes(src)) {
match Context::default().eval(src_bytes) {
Ok(value) => value.display().to_string(), Ok(value) => value.display().to_string(),
Err(error) => error.to_string(), Err(error) => error.to_string(),
} }

6
boa_engine/src/tests.rs

@ -1,3 +1,5 @@
use boa_parser::Source;
use crate::{ use crate::{
builtins::Number, check_output, exec, forward, forward_val, string::utf16, builtins::Number, check_output, exec, forward, forward_val, string::utf16,
value::IntegerOrInfinity, Context, JsValue, TestAction, value::IntegerOrInfinity, Context, JsValue, TestAction,
@ -454,7 +456,7 @@ fn test_invalid_break_target() {
} }
"#; "#;
assert!(Context::default().eval(src).is_err()); assert!(Context::default().eval(Source::from_bytes(src)).is_err());
} }
#[test] #[test]
@ -2091,7 +2093,7 @@ fn bigger_switch_example() {
"#, "#,
); );
assert_eq!(&exec(scenario), val); assert_eq!(&exec(&scenario), val);
} }
} }

26
boa_engine/src/value/serde_json.rs

@ -168,6 +168,8 @@ impl JsValue {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use boa_parser::Source;
use crate::object::JsArray; use crate::object::JsArray;
use crate::{Context, JsValue}; use crate::{Context, JsValue};
@ -225,61 +227,61 @@ mod tests {
let mut context = Context::default(); let mut context = Context::default();
let add = context let add = context
.eval( .eval(Source::from_bytes(
r#" r#"
1000000 + 500 1000000 + 500
"#, "#,
) ))
.unwrap(); .unwrap();
let add: u32 = serde_json::from_value(add.to_json(&mut context).unwrap()).unwrap(); let add: u32 = serde_json::from_value(add.to_json(&mut context).unwrap()).unwrap();
assert_eq!(add, 1_000_500); assert_eq!(add, 1_000_500);
let sub = context let sub = context
.eval( .eval(Source::from_bytes(
r#" r#"
1000000 - 500 1000000 - 500
"#, "#,
) ))
.unwrap(); .unwrap();
let sub: u32 = serde_json::from_value(sub.to_json(&mut context).unwrap()).unwrap(); let sub: u32 = serde_json::from_value(sub.to_json(&mut context).unwrap()).unwrap();
assert_eq!(sub, 999_500); assert_eq!(sub, 999_500);
let mult = context let mult = context
.eval( .eval(Source::from_bytes(
r#" r#"
1000000 * 500 1000000 * 500
"#, "#,
) ))
.unwrap(); .unwrap();
let mult: u32 = serde_json::from_value(mult.to_json(&mut context).unwrap()).unwrap(); let mult: u32 = serde_json::from_value(mult.to_json(&mut context).unwrap()).unwrap();
assert_eq!(mult, 500_000_000); assert_eq!(mult, 500_000_000);
let div = context let div = context
.eval( .eval(Source::from_bytes(
r#" r#"
1000000 / 500 1000000 / 500
"#, "#,
) ))
.unwrap(); .unwrap();
let div: u32 = serde_json::from_value(div.to_json(&mut context).unwrap()).unwrap(); let div: u32 = serde_json::from_value(div.to_json(&mut context).unwrap()).unwrap();
assert_eq!(div, 2000); assert_eq!(div, 2000);
let rem = context let rem = context
.eval( .eval(Source::from_bytes(
r#" r#"
233894 % 500 233894 % 500
"#, "#,
) ))
.unwrap(); .unwrap();
let rem: u32 = serde_json::from_value(rem.to_json(&mut context).unwrap()).unwrap(); let rem: u32 = serde_json::from_value(rem.to_json(&mut context).unwrap()).unwrap();
assert_eq!(rem, 394); assert_eq!(rem, 394);
let pow = context let pow = context
.eval( .eval(Source::from_bytes(
r#" r#"
36 ** 5 36 ** 5
"#, "#,
) ))
.unwrap(); .unwrap();
let pow: u32 = serde_json::from_value(pow.to_json(&mut context).unwrap()).unwrap(); let pow: u32 = serde_json::from_value(pow.to_json(&mut context).unwrap()).unwrap();

18
boa_engine/src/vm/tests.rs

@ -1,3 +1,5 @@
use boa_parser::Source;
use crate::{check_output, exec, Context, JsValue, TestAction}; use crate::{check_output, exec, Context, JsValue, TestAction};
#[test] #[test]
@ -45,7 +47,7 @@ fn try_catch_finally_from_init() {
assert_eq!( assert_eq!(
Context::default() Context::default()
.eval(source.as_bytes()) .eval(Source::from_bytes(source))
.unwrap_err() .unwrap_err()
.as_opaque() .as_opaque()
.unwrap(), .unwrap(),
@ -68,7 +70,7 @@ fn multiple_catches() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(source.as_bytes()).unwrap(), Context::default().eval(Source::from_bytes(source)).unwrap(),
JsValue::Undefined JsValue::Undefined
); );
} }
@ -87,7 +89,7 @@ fn use_last_expr_try_block() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(source.as_bytes()).unwrap(), Context::default().eval(Source::from_bytes(source)).unwrap(),
JsValue::from("Hello!") JsValue::from("Hello!")
); );
} }
@ -105,7 +107,7 @@ fn use_last_expr_catch_block() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(source.as_bytes()).unwrap(), Context::default().eval(Source::from_bytes(source)).unwrap(),
JsValue::from("Hello!") JsValue::from("Hello!")
); );
} }
@ -121,7 +123,7 @@ fn no_use_last_expr_finally_block() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(source.as_bytes()).unwrap(), Context::default().eval(Source::from_bytes(source)).unwrap(),
JsValue::undefined() JsValue::undefined()
); );
} }
@ -140,7 +142,7 @@ fn finally_block_binding_env() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(source.as_bytes()).unwrap(), Context::default().eval(Source::from_bytes(source)).unwrap(),
JsValue::from("Hey hey people") JsValue::from("Hey hey people")
); );
} }
@ -159,7 +161,7 @@ fn run_super_method_in_object() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(source.as_bytes()).unwrap(), Context::default().eval(Source::from_bytes(source)).unwrap(),
JsValue::from("super") JsValue::from("super")
); );
} }
@ -185,7 +187,7 @@ fn get_reference_by_super() {
"#; "#;
assert_eq!( assert_eq!(
Context::default().eval(source.as_bytes()).unwrap(), Context::default().eval(Source::from_bytes(source)).unwrap(),
JsValue::from("ab") JsValue::from("ab")
); );
} }

6
boa_examples/src/bin/classes.rs

@ -4,7 +4,7 @@ use boa_engine::{
class::{Class, ClassBuilder}, class::{Class, ClassBuilder},
error::JsNativeError, error::JsNativeError,
property::Attribute, property::Attribute,
Context, JsResult, JsString, JsValue, Context, JsResult, JsString, JsValue, Source,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
@ -130,7 +130,7 @@ fn main() {
// Having done all of that, we can execute Javascript code with `eval`, // Having done all of that, we can execute Javascript code with `eval`,
// and access the `Person` class defined in Rust! // and access the `Person` class defined in Rust!
context context
.eval( .eval(Source::from_bytes(
r" r"
let person = new Person('John', 19); let person = new Person('John', 19);
person.sayHello(); person.sayHello();
@ -146,6 +146,6 @@ fn main() {
console.log(person.inheritedProperty); console.log(person.inheritedProperty);
console.log(Person.prototype.inheritedProperty === person.inheritedProperty); console.log(Person.prototype.inheritedProperty === person.inheritedProperty);
", ",
) ))
.unwrap(); .unwrap();
} }

12
boa_examples/src/bin/closures.rs

@ -9,7 +9,7 @@ use boa_engine::{
object::{builtins::JsArray, FunctionObjectBuilder, JsObject}, object::{builtins::JsArray, FunctionObjectBuilder, JsObject},
property::{Attribute, PropertyDescriptor}, property::{Attribute, PropertyDescriptor},
string::utf16, string::utf16,
Context, JsError, JsNativeError, JsString, JsValue, Context, JsError, JsNativeError, JsString, JsValue, Source,
}; };
use boa_gc::{Finalize, GcRefCell, Trace}; use boa_gc::{Finalize, GcRefCell, Trace};
@ -36,7 +36,7 @@ fn main() -> Result<(), JsError> {
}), }),
); );
assert_eq!(context.eval("closure()")?, 255.into()); assert_eq!(context.eval(Source::from_bytes("closure()"))?, 255.into());
// We have created a closure with moved variables and executed that closure // We have created a closure with moved variables and executed that closure
// inside Javascript! // inside Javascript!
@ -117,13 +117,13 @@ fn main() -> Result<(), JsError> {
); );
assert_eq!( assert_eq!(
context.eval("createMessage()")?, context.eval(Source::from_bytes("createMessage()"))?,
"message from `Boa dev`: Hello!".into() "message from `Boa dev`: Hello!".into()
); );
// The data mutates between calls // The data mutates between calls
assert_eq!( assert_eq!(
context.eval("createMessage(); createMessage();")?, context.eval(Source::from_bytes("createMessage(); createMessage();"))?,
"message from `Boa dev`: Hello! Hello! Hello!".into() "message from `Boa dev`: Hello! Hello! Hello!".into()
); );
@ -167,7 +167,7 @@ fn main() -> Result<(), JsError> {
); );
// First call should return the array `[0]`. // First call should return the array `[0]`.
let result = context.eval("enumerate()")?; let result = context.eval(Source::from_bytes("enumerate()"))?;
let object = result let object = result
.as_object() .as_object()
.cloned() .cloned()
@ -178,7 +178,7 @@ fn main() -> Result<(), JsError> {
assert_eq!(array.get(1, &mut context)?, JsValue::undefined()); assert_eq!(array.get(1, &mut context)?, JsValue::undefined());
// First call should return the array `[0, 1]`. // First call should return the array `[0, 1]`.
let result = context.eval("enumerate()")?; let result = context.eval(Source::from_bytes("enumerate()"))?;
let object = result let object = result
.as_object() .as_object()
.cloned() .cloned()

9
boa_examples/src/bin/commuter_visitor.rs

@ -10,11 +10,11 @@ use boa_ast::{
visitor::{VisitWith, VisitorMut}, visitor::{VisitWith, VisitorMut},
Expression, Expression,
}; };
use boa_engine::Context; use boa_engine::{Context, Source};
use boa_interner::ToInternedString; use boa_interner::ToInternedString;
use boa_parser::Parser; use boa_parser::Parser;
use core::ops::ControlFlow; use core::ops::ControlFlow;
use std::{convert::Infallible, fs::File, io::BufReader}; use std::{convert::Infallible, path::Path};
/// Visitor which, when applied to a binary expression, will swap the operands. Use in other /// Visitor which, when applied to a binary expression, will swap the operands. Use in other
/// circumstances is undefined. /// circumstances is undefined.
@ -65,9 +65,8 @@ impl<'ast> VisitorMut<'ast> for CommutorVisitor {
} }
fn main() { fn main() {
let mut parser = Parser::new(BufReader::new( let mut parser =
File::open("boa_examples/scripts/calc.js").unwrap(), Parser::new(Source::from_filepath(Path::new("boa_examples/scripts/calc.js")).unwrap());
));
let mut ctx = Context::default(); let mut ctx = Context::default();
let mut statements = parser.parse_all(ctx.interner_mut()).unwrap(); let mut statements = parser.parse_all(ctx.interner_mut()).unwrap();

6
boa_examples/src/bin/loadfile.rs

@ -1,14 +1,14 @@
// This example shows how to load, parse and execute JS code from a source file // This example shows how to load, parse and execute JS code from a source file
// (./scripts/helloworld.js) // (./scripts/helloworld.js)
use std::fs; use std::path::Path;
use boa_engine::Context; use boa_engine::{Context, Source};
fn main() { fn main() {
let js_file_path = "./scripts/helloworld.js"; let js_file_path = "./scripts/helloworld.js";
match fs::read(js_file_path) { match Source::from_filepath(Path::new(js_file_path)) {
Ok(src) => { Ok(src) => {
// Instantiate the execution context // Instantiate the execution context
let mut context = Context::default(); let mut context = Context::default();

4
boa_examples/src/bin/loadstring.rs

@ -1,6 +1,6 @@
// This example loads, parses and executes a JS code string // This example loads, parses and executes a JS code string
use boa_engine::Context; use boa_engine::{Context, Source};
fn main() { fn main() {
let js_code = "console.log('Hello World from a JS code string!')"; let js_code = "console.log('Hello World from a JS code string!')";
@ -9,7 +9,7 @@ fn main() {
let mut context = Context::default(); let mut context = Context::default();
// Parse the source code // Parse the source code
match context.eval(js_code) { match context.eval(Source::from_bytes(js_code)) {
Ok(res) => { Ok(res) => {
println!( println!(
"{}", "{}",

6
boa_examples/src/bin/modulehandler.rs

@ -3,7 +3,7 @@
use boa_engine::{ use boa_engine::{
native_function::NativeFunction, prelude::JsObject, property::Attribute, Context, JsResult, native_function::NativeFunction, prelude::JsObject, property::Attribute, Context, JsResult,
JsValue, JsValue, Source,
}; };
use std::fs::read_to_string; use std::fs::read_to_string;
@ -31,7 +31,7 @@ fn main() {
// Instantiating the engine with the execution context // Instantiating the engine with the execution context
// Loading, parsing and executing the JS code from the source file // Loading, parsing and executing the JS code from the source file
ctx.eval(&buffer.unwrap()).unwrap(); ctx.eval(Source::from_bytes(&buffer.unwrap())).unwrap();
} }
// Custom implementation that mimics the 'require' module loader // Custom implementation that mimics the 'require' module loader
@ -52,7 +52,7 @@ fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult<JsV
Ok(JsValue::Rational(-1.0)) Ok(JsValue::Rational(-1.0))
} else { } else {
// Load and parse the module source // Load and parse the module source
ctx.eval(&buffer.unwrap()).unwrap(); ctx.eval(Source::from_bytes(&buffer.unwrap())).unwrap();
// Access module.exports and return as ResultValue // Access module.exports and return as ResultValue
let global_obj = ctx.global_object().to_owned(); let global_obj = ctx.global_object().to_owned();

9
boa_examples/src/bin/symbol_visitor.rs

@ -3,11 +3,11 @@
// which mutates the AST. // which mutates the AST.
use boa_ast::visitor::Visitor; use boa_ast::visitor::Visitor;
use boa_engine::Context; use boa_engine::{Context, Source};
use boa_interner::Sym; use boa_interner::Sym;
use boa_parser::Parser; use boa_parser::Parser;
use core::ops::ControlFlow; use core::ops::ControlFlow;
use std::{collections::HashSet, convert::Infallible, fs::File, io::BufReader}; use std::{collections::HashSet, convert::Infallible, path::Path};
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
struct SymbolVisitor { struct SymbolVisitor {
@ -24,9 +24,8 @@ impl<'ast> Visitor<'ast> for SymbolVisitor {
} }
fn main() { fn main() {
let mut parser = Parser::new(BufReader::new( let mut parser =
File::open("boa_examples/scripts/calc.js").unwrap(), Parser::new(Source::from_filepath(Path::new("boa_examples/scripts/calc.js")).unwrap());
));
let mut ctx = Context::default(); let mut ctx = Context::default();
let statements = parser.parse_all(ctx.interner_mut()).unwrap(); let statements = parser.parse_all(ctx.interner_mut()).unwrap();

2
boa_parser/src/lib.rs

@ -95,7 +95,9 @@
pub mod error; pub mod error;
pub mod lexer; pub mod lexer;
pub mod parser; pub mod parser;
mod source;
pub use error::Error; pub use error::Error;
pub use lexer::Lexer; pub use lexer::Lexer;
pub use parser::Parser; pub use parser::Parser;
pub use source::Source;

71
boa_parser/src/parser/mod.rs

@ -15,7 +15,7 @@ use crate::{
cursor::Cursor, cursor::Cursor,
function::{FormalParameters, FunctionStatementList}, function::{FormalParameters, FunctionStatementList},
}, },
Error, Error, Source,
}; };
use boa_ast::{ use boa_ast::{
expression::Identifier, expression::Identifier,
@ -27,7 +27,7 @@ use boa_ast::{
}; };
use boa_interner::Interner; use boa_interner::Interner;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use std::io::Read; use std::{io::Read, path::Path};
/// Trait implemented by parsers. /// Trait implemented by parsers.
/// ///
@ -105,36 +105,21 @@ impl From<bool> for AllowDefault {
/// [label]: https://tc39.es/ecma262/#sec-labelled-function-declarations /// [label]: https://tc39.es/ecma262/#sec-labelled-function-declarations
/// [block]: https://tc39.es/ecma262/#sec-block-duplicates-allowed-static-semantics /// [block]: https://tc39.es/ecma262/#sec-block-duplicates-allowed-static-semantics
#[derive(Debug)] #[derive(Debug)]
pub struct Parser<R> { #[allow(unused)] // Right now the path is not used, but it's better to have it for future improvements.
pub struct Parser<'a, R> {
/// Path to the source being parsed.
path: Option<&'a Path>,
/// Cursor of the parser, pointing to the lexer and used to get tokens for the parser. /// Cursor of the parser, pointing to the lexer and used to get tokens for the parser.
cursor: Cursor<R>, cursor: Cursor<R>,
} }
impl<R> Parser<R> { impl<'a, R: Read> Parser<'a, R> {
/// Create a new `Parser` with a reader as the input to parse. /// Create a new `Parser` with a `Source` as the input to parse.
pub fn new(reader: R) -> Self pub fn new(source: Source<'a, R>) -> Self {
where
R: Read,
{
Self { Self {
cursor: Cursor::new(reader), path: source.path,
} cursor: Cursor::new(source.reader),
}
/// Set the parser strict mode to true.
pub fn set_strict(&mut self)
where
R: Read,
{
self.cursor.set_strict_mode(true);
} }
/// Set the parser strict mode to true.
pub fn set_json_parse(&mut self)
where
R: Read,
{
self.cursor.set_json_parse(true);
} }
/// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation. /// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation.
@ -145,10 +130,7 @@ impl<R> Parser<R> {
/// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed. /// Will return `Err` on any parsing error, including invalid reads of the bytes being parsed.
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-Script /// [spec]: https://tc39.es/ecma262/#prod-Script
pub fn parse_all(&mut self, interner: &mut Interner) -> ParseResult<StatementList> pub fn parse_all(&mut self, interner: &mut Interner) -> ParseResult<StatementList> {
where
R: Read,
{
Script::new(false).parse(&mut self.cursor, interner) Script::new(false).parse(&mut self.cursor, interner)
} }
@ -165,10 +147,7 @@ impl<R> Parser<R> {
&mut self, &mut self,
direct: bool, direct: bool,
interner: &mut Interner, interner: &mut Interner,
) -> ParseResult<StatementList> ) -> ParseResult<StatementList> {
where
R: Read,
{
Script::new(direct).parse(&mut self.cursor, interner) Script::new(direct).parse(&mut self.cursor, interner)
} }
@ -184,10 +163,7 @@ impl<R> Parser<R> {
interner: &mut Interner, interner: &mut Interner,
allow_yield: bool, allow_yield: bool,
allow_await: bool, allow_await: bool,
) -> ParseResult<StatementList> ) -> ParseResult<StatementList> {
where
R: Read,
{
FunctionStatementList::new(allow_yield, allow_await).parse(&mut self.cursor, interner) FunctionStatementList::new(allow_yield, allow_await).parse(&mut self.cursor, interner)
} }
@ -203,11 +179,26 @@ impl<R> Parser<R> {
interner: &mut Interner, interner: &mut Interner,
allow_yield: bool, allow_yield: bool,
allow_await: bool, allow_await: bool,
) -> ParseResult<FormalParameterList> ) -> ParseResult<FormalParameterList> {
FormalParameters::new(allow_yield, allow_await).parse(&mut self.cursor, interner)
}
}
impl<R> Parser<'_, R> {
/// Set the parser strict mode to true.
pub fn set_strict(&mut self)
where where
R: Read, R: Read,
{ {
FormalParameters::new(allow_yield, allow_await).parse(&mut self.cursor, interner) self.cursor.set_strict_mode(true);
}
/// Set the parser JSON mode to true.
pub fn set_json_parse(&mut self)
where
R: Read,
{
self.cursor.set_json_parse(true);
} }
} }

5
boa_parser/src/parser/tests/format/mod.rs

@ -16,7 +16,7 @@ mod statement;
fn test_formatting(source: &'static str) { fn test_formatting(source: &'static str) {
// Remove preceding newline. // Remove preceding newline.
use crate::Parser; use crate::{Parser, Source};
use boa_interner::{Interner, ToInternedString}; use boa_interner::{Interner, ToInternedString};
let source = &source[1..]; let source = &source[1..];
@ -30,8 +30,9 @@ fn test_formatting(source: &'static str) {
.map(|l| &l[characters_to_remove..]) // Remove preceding whitespace from each line .map(|l| &l[characters_to_remove..]) // Remove preceding whitespace from each line
.collect::<Vec<&'static str>>() .collect::<Vec<&'static str>>()
.join("\n"); .join("\n");
let source = Source::from_bytes(source);
let interner = &mut Interner::default(); let interner = &mut Interner::default();
let result = Parser::new(scenario.as_bytes()) let result = Parser::new(source)
.parse_all(interner) .parse_all(interner)
.expect("parsing failed") .expect("parsing failed")
.to_interned_string(interner); .to_interned_string(interner);

6
boa_parser/src/parser/tests/mod.rs

@ -4,7 +4,7 @@ mod format;
use std::convert::TryInto; use std::convert::TryInto;
use crate::Parser; use crate::{Parser, Source};
use boa_ast::{ use boa_ast::{
declaration::{Declaration, LexicalDeclaration, VarDeclaration, Variable}, declaration::{Declaration, LexicalDeclaration, VarDeclaration, Variable},
expression::{ expression::{
@ -36,7 +36,7 @@ where
L: Into<Box<[StatementListItem]>>, L: Into<Box<[StatementListItem]>>,
{ {
assert_eq!( assert_eq!(
Parser::new(js.as_bytes()) Parser::new(Source::from_bytes(js))
.parse_all(interner) .parse_all(interner)
.expect("failed to parse"), .expect("failed to parse"),
StatementList::from(expr.into()) StatementList::from(expr.into())
@ -46,7 +46,7 @@ where
/// Checks that the given javascript string creates a parse error. /// Checks that the given javascript string creates a parse error.
#[track_caller] #[track_caller]
pub(super) fn check_invalid(js: &str) { pub(super) fn check_invalid(js: &str) {
assert!(Parser::new(js.as_bytes()) assert!(Parser::new(Source::from_bytes(js))
.parse_all(&mut Interner::default()) .parse_all(&mut Interner::default())
.is_err()); .is_err());
} }

88
boa_parser/src/source.rs

@ -0,0 +1,88 @@
use std::{
fs::File,
io::{self, BufReader, Read},
path::Path,
};
/// A source of ECMAScript code.
///
/// [`Source`]s can be created from plain [`str`]s, file [`Path`]s or more generally, any [`Read`]
/// instance.
#[derive(Debug)]
pub struct Source<'path, R> {
pub(crate) reader: R,
pub(crate) path: Option<&'path Path>,
}
impl<'bytes> Source<'static, &'bytes [u8]> {
/// Creates a new `Source` from any type equivalent to a slice of bytes e.g. [`&str`][str],
/// <code>[Vec]<[u8]></code>, <code>[Box]<[\[u8\]][slice]></code> or a plain slice
/// <code>[&\[u8\]][slice]</code>.
///
/// # Examples
///
/// ```
/// # use boa_parser::Source;
/// let code = r#"var array = [5, 4, 3, 2, 1];"#;
/// let source = Source::from_bytes(code);
/// ```
///
/// [slice]: std::slice
pub fn from_bytes<T: AsRef<[u8]> + ?Sized>(source: &'bytes T) -> Self {
Self {
reader: source.as_ref(),
path: None,
}
}
}
impl<'path> Source<'path, BufReader<File>> {
/// Creates a new `Source` from a `Path` to a file.
///
/// # Errors
///
/// See [`File::open`].
///
/// # Examples
///
/// ```no_run
/// # use boa_parser::Source;
/// # use std::{fs::File, path::Path};
/// # fn main() -> std::io::Result<()> {
/// let path = Path::new("script.js");
/// let source = Source::from_filepath(path)?;
/// # Ok(())
/// # }
/// ```
pub fn from_filepath(source: &'path Path) -> io::Result<Self> {
let reader = File::open(source)?;
Ok(Self {
reader: BufReader::new(reader),
path: Some(source),
})
}
}
impl<'path, R: Read> Source<'path, R> {
/// Creates a new `Source` from a [`Read`] instance and an optional [`Path`].
///
/// # Examples
///
/// ```no_run
/// # use boa_parser::Source;
/// # use std::{fs::File, io::Read, path::Path};
/// # fn main() -> std::io::Result<()> {
/// let strictler = r#""use strict""#;
///
/// let path = Path::new("no_strict.js");
/// let file = File::open(path)?;
/// let strict = strictler.as_bytes().chain(file);
///
/// let source = Source::from_reader(strict, Some(path));
/// # Ok(())
/// # }
/// ```
pub const fn from_reader(reader: R, path: Option<&'path Path>) -> Self {
Self { reader, path }
}
}

1
boa_tester/Cargo.toml

@ -14,7 +14,6 @@ rust-version.workspace = true
[dependencies] [dependencies]
boa_engine.workspace = true boa_engine.workspace = true
boa_gc.workspace = true boa_gc.workspace = true
boa_parser.workspace = true
clap = { version = "4.1.4", features = ["derive"] } clap = { version = "4.1.4", features = ["derive"] }
serde = { version = "1.0.152", features = ["derive"] } serde = { version = "1.0.152", features = ["derive"] }
serde_yaml = "0.9.17" serde_yaml = "0.9.17"

7
boa_tester/src/exec/js262.rs

@ -2,7 +2,7 @@ use boa_engine::{
builtins::JsArgs, builtins::JsArgs,
object::{JsObject, ObjectInitializer}, object::{JsObject, ObjectInitializer},
property::Attribute, property::Attribute,
Context, JsNativeError, JsResult, JsValue, Context, JsNativeError, JsResult, JsValue, Source,
}; };
/// Initializes the object in the context. /// Initializes the object in the context.
@ -83,14 +83,15 @@ fn detach_array_buffer(
fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> { fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
args.get(0).and_then(JsValue::as_string).map_or_else( args.get(0).and_then(JsValue::as_string).map_or_else(
|| Ok(JsValue::undefined()), || Ok(JsValue::undefined()),
|source_text| match context.parse(source_text.to_std_string_escaped()) { |source_text| match context.parse(Source::from_bytes(&source_text.to_std_string_escaped()))
{
// TODO: check strict // TODO: check strict
Err(e) => Err(JsNativeError::typ() Err(e) => Err(JsNativeError::typ()
.with_message(format!("Uncaught Syntax Error: {e}")) .with_message(format!("Uncaught Syntax Error: {e}"))
.into()), .into()),
// Calling eval here parses the code a second time. // Calling eval here parses the code a second time.
// TODO: We can fix this after we have have defined the public api for the vm executer. // TODO: We can fix this after we have have defined the public api for the vm executer.
Ok(_) => context.eval(source_text.to_std_string_escaped()), Ok(_) => context.eval(Source::from_bytes(&source_text.to_std_string_escaped())),
}, },
) )
} }

89
boa_tester/src/exec/mod.rs

@ -9,18 +9,17 @@ use crate::read::ErrorType;
use boa_engine::{ use boa_engine::{
builtins::JsArgs, context::ContextBuilder, job::SimpleJobQueue, builtins::JsArgs, context::ContextBuilder, job::SimpleJobQueue,
native_function::NativeFunction, object::FunctionObjectBuilder, property::Attribute, Context, native_function::NativeFunction, object::FunctionObjectBuilder, property::Attribute, Context,
JsNativeErrorKind, JsValue, JsNativeErrorKind, JsValue, Source,
}; };
use boa_parser::Parser;
use colored::Colorize; use colored::Colorize;
use rayon::prelude::*; use rayon::prelude::*;
use std::{borrow::Cow, cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
impl TestSuite { impl TestSuite {
/// Runs the test suite. /// Runs the test suite.
pub(crate) fn run(&self, harness: &Harness, verbose: u8, parallel: bool) -> SuiteResult { pub(crate) fn run(&self, harness: &Harness, verbose: u8, parallel: bool) -> SuiteResult {
if verbose != 0 { if verbose != 0 {
println!("Suite {}:", self.name); println!("Suite {}:", self.path.display());
} }
let suites: Vec<_> = if parallel { let suites: Vec<_> = if parallel {
@ -85,7 +84,7 @@ impl TestSuite {
println!( println!(
"Suite {} results: total: {total}, passed: {}, ignored: {}, failed: {} (panics: \ "Suite {} results: total: {total}, passed: {}, ignored: {}, failed: {} (panics: \
{}{}), conformance: {:.2}%", {}{}), conformance: {:.2}%",
self.name, self.path.display(),
passed.to_string().green(), passed.to_string().green(),
ignored.to_string().yellow(), ignored.to_string().yellow(),
(total - passed - ignored).to_string().red(), (total - passed - ignored).to_string().red(),
@ -129,11 +128,29 @@ impl Test {
/// Runs the test once, in strict or non-strict mode /// Runs the test once, in strict or non-strict mode
fn run_once(&self, harness: &Harness, strict: bool, verbose: u8) -> TestResult { fn run_once(&self, harness: &Harness, strict: bool, verbose: u8) -> TestResult {
let Ok(source) = Source::from_filepath(&self.path) else {
if verbose > 1 {
println!(
"`{}`{}: {}",
self.path.display(),
if strict { " (strict mode)" } else { "" },
"Invalid file".red()
);
} else {
print!("{}", "F".red());
}
return TestResult {
name: self.name.clone(),
strict,
result: TestOutcomeResult::Failed,
result_text: Box::from("Could not read test file.")
}
};
if self.ignored { if self.ignored {
if verbose > 1 { if verbose > 1 {
println!( println!(
"`{}`{}: {}", "`{}`{}: {}",
self.name, self.path.display(),
if strict { " (strict mode)" } else { "" }, if strict { " (strict mode)" } else { "" },
"Ignored".yellow() "Ignored".yellow()
); );
@ -150,17 +167,11 @@ impl Test {
if verbose > 1 { if verbose > 1 {
println!( println!(
"`{}`{}: starting", "`{}`{}: starting",
self.name, self.path.display(),
if strict { " (strict mode)" } else { "" } if strict { " (strict mode)" } else { "" }
); );
} }
let test_content = if strict {
Cow::Owned(format!("\"use strict\";\n{}", self.content))
} else {
Cow::Borrowed(&*self.content)
};
let result = std::panic::catch_unwind(|| match self.expected_outcome { let result = std::panic::catch_unwind(|| match self.expected_outcome {
Outcome::Positive => { Outcome::Positive => {
let queue = SimpleJobQueue::new(); let queue = SimpleJobQueue::new();
@ -170,9 +181,10 @@ impl Test {
if let Err(e) = self.set_up_env(harness, context, async_result.clone()) { if let Err(e) = self.set_up_env(harness, context, async_result.clone()) {
return (false, e); return (false, e);
} }
context.strict(strict);
// TODO: timeout // TODO: timeout
let value = match context.eval(&*test_content) { let value = match context.eval(source) {
Ok(v) => v, Ok(v) => v,
Err(e) => return (false, format!("Uncaught {e}")), Err(e) => return (false, format!("Uncaught {e}")),
}; };
@ -193,11 +205,12 @@ impl Test {
error_type, error_type,
ErrorType::SyntaxError, ErrorType::SyntaxError,
"non-SyntaxError parsing/early error found in {}", "non-SyntaxError parsing/early error found in {}",
self.name self.path.display()
); );
let mut context = Context::default(); let mut context = Context::default();
match context.parse(&*test_content) { context.strict(strict);
match context.parse(source) {
Ok(statement_list) => match context.compile(&statement_list) { Ok(statement_list) => match context.compile(&statement_list) {
Ok(_) => (false, "StatementList compilation should fail".to_owned()), Ok(_) => (false, "StatementList compilation should fail".to_owned()),
Err(e) => (true, format!("Uncaught {e:?}")), Err(e) => (true, format!("Uncaught {e:?}")),
@ -217,8 +230,9 @@ impl Test {
if let Err(e) = self.set_up_env(harness, context, AsyncResult::default()) { if let Err(e) = self.set_up_env(harness, context, AsyncResult::default()) {
return (false, e); return (false, e);
} }
let code = match Parser::new(test_content.as_bytes()) context.strict(strict);
.parse_all(context.interner_mut()) let code = match context
.parse(source)
.map_err(Into::into) .map_err(Into::into)
.and_then(|stmts| context.compile(&stmts)) .and_then(|stmts| context.compile(&stmts))
{ {
@ -260,7 +274,7 @@ impl Test {
let (result, result_text) = result.map_or_else( let (result, result_text) = result.map_or_else(
|_| { |_| {
eprintln!("last panic was on test \"{}\"", self.name); eprintln!("last panic was on test \"{}\"", self.path.display());
(TestOutcomeResult::Panic, String::new()) (TestOutcomeResult::Panic, String::new())
}, },
|(res, text)| { |(res, text)| {
@ -275,7 +289,7 @@ impl Test {
if verbose > 1 { if verbose > 1 {
println!( println!(
"`{}`{}: {}", "`{}`{}: {}",
self.name, self.path.display(),
if strict { " (strict mode)" } else { "" }, if strict { " (strict mode)" } else { "" },
if result == TestOutcomeResult::Passed { if result == TestOutcomeResult::Passed {
"Passed".green() "Passed".green()
@ -299,7 +313,7 @@ impl Test {
if verbose > 2 { if verbose > 2 {
println!( println!(
"`{}`{}: result text", "`{}`{}: result text",
self.name, self.path.display(),
if strict { " (strict mode)" } else { "" }, if strict { " (strict mode)" } else { "" },
); );
println!("{result_text}"); println!("{result_text}");
@ -331,29 +345,38 @@ impl Test {
return Ok(()); return Ok(());
} }
let assert = Source::from_reader(
harness.assert.content.as_bytes(),
Some(&harness.assert.path),
);
let sta = Source::from_reader(harness.sta.content.as_bytes(), Some(&harness.sta.path));
context context
.eval(harness.assert.as_ref()) .eval(assert)
.map_err(|e| format!("could not run assert.js:\n{e}"))?; .map_err(|e| format!("could not run assert.js:\n{e}"))?;
context context
.eval(harness.sta.as_ref()) .eval(sta)
.map_err(|e| format!("could not run sta.js:\n{e}"))?; .map_err(|e| format!("could not run sta.js:\n{e}"))?;
if self.flags.contains(TestFlags::ASYNC) { if self.flags.contains(TestFlags::ASYNC) {
let dph = Source::from_reader(
harness.doneprint_handle.content.as_bytes(),
Some(&harness.doneprint_handle.path),
);
context context
.eval(harness.doneprint_handle.as_ref()) .eval(dph)
.map_err(|e| format!("could not run doneprintHandle.js:\n{e}"))?; .map_err(|e| format!("could not run doneprintHandle.js:\n{e}"))?;
} }
for include in self.includes.iter() { for include_name in self.includes.iter() {
context let include = harness
.eval(
harness
.includes .includes
.get(include) .get(include_name)
.ok_or_else(|| format!("could not find the {include} include file."))? .ok_or_else(|| format!("could not find the {include_name} include file."))?;
.as_ref(), let source = Source::from_reader(include.content.as_bytes(), Some(&include.path));
) context.eval(source).map_err(|e| {
.map_err(|e| format!("could not run the {include} include file:\nUncaught {e}"))?; format!("could not run the harness `{include_name}`:\nUncaught {e}",)
})?;
} }
Ok(()) Ok(())

25
boa_tester/src/main.rs

@ -297,16 +297,23 @@ fn run_test_suite(
/// All the harness include files. /// All the harness include files.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct Harness { struct Harness {
assert: Box<str>, assert: HarnessFile,
sta: Box<str>, sta: HarnessFile,
doneprint_handle: Box<str>, doneprint_handle: HarnessFile,
includes: FxHashMap<Box<str>, Box<str>>, includes: FxHashMap<Box<str>, HarnessFile>,
}
#[derive(Debug, Clone)]
struct HarnessFile {
content: Box<str>,
path: Box<Path>,
} }
/// Represents a test suite. /// Represents a test suite.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct TestSuite { struct TestSuite {
name: Box<str>, name: Box<str>,
path: Box<Path>,
suites: Box<[TestSuite]>, suites: Box<[TestSuite]>,
tests: Box<[Test]>, tests: Box<[Test]>,
} }
@ -362,7 +369,7 @@ enum TestOutcomeResult {
} }
/// Represents a test. /// Represents a test.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone)]
#[allow(dead_code)] #[allow(dead_code)]
struct Test { struct Test {
name: Box<str>, name: Box<str>,
@ -374,16 +381,16 @@ struct Test {
expected_outcome: Outcome, expected_outcome: Outcome,
includes: Box<[Box<str>]>, includes: Box<[Box<str>]>,
locale: Locale, locale: Locale,
content: Box<str>, path: Box<Path>,
ignored: bool, ignored: bool,
} }
impl Test { impl Test {
/// Creates a new test. /// Creates a new test.
fn new<N, C>(name: N, content: C, metadata: MetaData) -> Self fn new<N, C>(name: N, path: C, metadata: MetaData) -> Self
where where
N: Into<Box<str>>, N: Into<Box<str>>,
C: Into<Box<str>>, C: Into<Box<Path>>,
{ {
Self { Self {
name: name.into(), name: name.into(),
@ -395,7 +402,7 @@ impl Test {
expected_outcome: Outcome::from(metadata.negative), expected_outcome: Outcome::from(metadata.negative),
includes: metadata.includes, includes: metadata.includes,
locale: metadata.locale, locale: metadata.locale,
content: content.into(), path: path.into(),
ignored: false, ignored: false,
} }
} }

49
boa_tester/src/read.rs

@ -1,6 +1,6 @@
//! Module to read the list of test suites from disk. //! Module to read the list of test suites from disk.
use crate::Ignored; use crate::{HarnessFile, Ignored};
use super::{Harness, Locale, Phase, Test, TestSuite}; use super::{Harness, Locale, Phase, Test, TestSuite};
use color_eyre::{ use color_eyre::{
@ -9,7 +9,10 @@ use color_eyre::{
}; };
use fxhash::FxHashMap; use fxhash::FxHashMap;
use serde::Deserialize; use serde::Deserialize;
use std::{fs, io, path::Path}; use std::{
fs, io,
path::{Path, PathBuf},
};
/// Representation of the YAML metadata in Test262 tests. /// Representation of the YAML metadata in Test262 tests.
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
@ -84,6 +87,15 @@ pub(super) enum TestFlag {
/// Reads the Test262 defined bindings. /// Reads the Test262 defined bindings.
pub(super) fn read_harness(test262_path: &Path) -> Result<Harness> { pub(super) fn read_harness(test262_path: &Path) -> Result<Harness> {
fn read_harness_file(path: PathBuf) -> Result<HarnessFile> {
let content = fs::read_to_string(path.as_path())
.wrap_err_with(|| format!("error reading the harness file `{}`", path.display()))?;
Ok(HarnessFile {
content: content.into_boxed_str(),
path: path.into_boxed_path(),
})
}
let mut includes = FxHashMap::default(); let mut includes = FxHashMap::default();
for entry in fs::read_dir(test262_path.join("harness")) for entry in fs::read_dir(test262_path.join("harness"))
@ -97,23 +109,14 @@ pub(super) fn read_harness(test262_path: &Path) -> Result<Harness> {
continue; continue;
} }
let content = fs::read_to_string(entry.path())
.wrap_err_with(|| format!("error reading the harnes/{file_name}"))?;
includes.insert( includes.insert(
file_name.into_owned().into_boxed_str(), file_name.into_owned().into_boxed_str(),
content.into_boxed_str(), read_harness_file(entry.path())?,
); );
} }
let assert = fs::read_to_string(test262_path.join("harness/assert.js")) let assert = read_harness_file(test262_path.join("harness/assert.js"))?;
.wrap_err("error reading harnes/assert.js")? let sta = read_harness_file(test262_path.join("harness/sta.js"))?;
.into_boxed_str(); let doneprint_handle = read_harness_file(test262_path.join("harness/doneprintHandle.js"))?;
let sta = fs::read_to_string(test262_path.join("harness/sta.js"))
.wrap_err("error reading harnes/sta.js")?
.into_boxed_str();
let doneprint_handle = fs::read_to_string(test262_path.join("harness/doneprintHandle.js"))
.wrap_err("error reading harnes/doneprintHandle.js")?
.into_boxed_str();
Ok(Harness { Ok(Harness {
assert, assert,
@ -177,6 +180,7 @@ pub(super) fn read_suite(
Ok(TestSuite { Ok(TestSuite {
name: name.into(), name: name.into(),
path: Box::from(path),
suites: suites.into_boxed_slice(), suites: suites.into_boxed_slice(),
tests: tests.into_boxed_slice(), tests: tests.into_boxed_slice(),
}) })
@ -200,16 +204,15 @@ pub(super) fn read_test(path: &Path) -> io::Result<Test> {
) )
})?; })?;
let content = fs::read_to_string(path)?; let metadata = read_metadata(path)?;
let metadata = read_metadata(&content, path)?;
Ok(Test::new(name, content, metadata)) Ok(Test::new(name, path, metadata))
} }
/// Reads the metadata from the input test code. /// Reads the metadata from the input test code.
fn read_metadata(code: &str, test: &Path) -> io::Result<MetaData> { fn read_metadata(test: &Path) -> io::Result<MetaData> {
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::bytes::Regex;
/// Regular expression to retrieve the metadata of a test. /// Regular expression to retrieve the metadata of a test.
static META_REGEX: Lazy<Regex> = Lazy::new(|| { static META_REGEX: Lazy<Regex> = Lazy::new(|| {
@ -217,8 +220,10 @@ fn read_metadata(code: &str, test: &Path) -> io::Result<MetaData> {
.expect("could not compile metadata regular expression") .expect("could not compile metadata regular expression")
}); });
let code = fs::read(test)?;
let yaml = META_REGEX let yaml = META_REGEX
.captures(code) .captures(&code)
.ok_or_else(|| { .ok_or_else(|| {
io::Error::new( io::Error::new(
io::ErrorKind::InvalidData, io::ErrorKind::InvalidData,
@ -226,13 +231,13 @@ fn read_metadata(code: &str, test: &Path) -> io::Result<MetaData> {
) )
})? })?
.get(1) .get(1)
.map(|m| String::from_utf8_lossy(m.as_bytes()))
.ok_or_else(|| { .ok_or_else(|| {
io::Error::new( io::Error::new(
io::ErrorKind::InvalidData, io::ErrorKind::InvalidData,
format!("no metadata found for test {}", test.display()), format!("no metadata found for test {}", test.display()),
) )
})? })?
.as_str()
.replace('\r', "\n"); .replace('\r', "\n");
serde_yaml::from_str(&yaml).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) serde_yaml::from_str(&yaml).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))

4
boa_wasm/src/lib.rs

@ -57,7 +57,7 @@
clippy::nursery, clippy::nursery,
)] )]
use boa_engine::Context; use boa_engine::{Context, Source};
use getrandom as _; use getrandom as _;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -66,7 +66,7 @@ use wasm_bindgen::prelude::*;
pub fn evaluate(src: &str) -> Result<String, JsValue> { pub fn evaluate(src: &str) -> Result<String, JsValue> {
// Setup executor // Setup executor
Context::default() Context::default()
.eval(src) .eval(Source::from_bytes(src))
.map_err(|e| JsValue::from(format!("Uncaught {e}"))) .map_err(|e| JsValue::from(format!("Uncaught {e}")))
.map(|v| v.display().to_string()) .map(|v| v.display().to_string())
} }

Loading…
Cancel
Save