Skip to content

Commit

Permalink
Implement host hooks and job queues APIs (#2529)
Browse files Browse the repository at this point in the history
Follows from #2528, and should complement #2411 to implement the module import hooks.

~~Similarly to the Intl/ICU4X PR (#2478), this has a lot of trivial changes caused by the new lifetimes. I thought about passing the queue and the hooks by value, but it was very painful having to wrap everything with `Rc` in order to be accessible by the host.
In contrast, `&dyn` can be easily provided by the host and has the advantage of not requiring additional allocations, with the downside of adding two more lifetimes to our `Context`, but I think it's worth.~~ I was able to unify all lifetimes into the shortest one of the three, making our API just like before!

Changes:
- Added a new `HostHooks` trait and a `&dyn HostHooks` field to `Context`. This allows hosts to implement the trait for their custom type, then pass it to the context.
- Added a new `JobQueue` trait and a `&dyn JobQueue` field to our `Context`, allowing custom event loops and other fun things.
- Added two simple implementations of `JobQueue`: `IdleJobQueue` which does nothing and `SimpleJobQueue` which runs all jobs until all successfully complete or until any of them throws an error.
- Modified `boa_cli` to run all jobs until the queue is empty, even if a job returns `Err`. This also prints all errors to the user.
  • Loading branch information
jedel1043 committed Jan 19, 2023
1 parent 08e5e46 commit 5ab0aa2
Show file tree
Hide file tree
Showing 15 changed files with 654 additions and 472 deletions.
36 changes: 33 additions & 3 deletions boa_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ mod helper;

use boa_ast::StatementList;
use boa_engine::{
context::ContextBuilder,
job::{JobQueue, NativeJob},
vm::flowgraph::{Direction, Graph},
Context, JsResult,
};
use clap::{Parser, ValueEnum, ValueHint};
use colored::{Color, Colorize};
use rustyline::{config::Config, error::ReadlineError, EditMode, Editor};
use std::{fs::read, fs::OpenOptions, io, path::PathBuf};
use std::{cell::RefCell, collections::VecDeque, fs::read, fs::OpenOptions, io, path::PathBuf};

#[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))]
#[cfg_attr(
Expand Down Expand Up @@ -253,7 +255,8 @@ fn generate_flowgraph(
fn main() -> Result<(), io::Error> {
let args = Opt::parse();

let mut context = Context::default();
let queue = Jobs::default();
let mut context = ContextBuilder::new().job_queue(&queue).build();

// Trace Output
context.set_trace(args.trace);
Expand All @@ -280,6 +283,7 @@ fn main() -> Result<(), io::Error> {
Ok(v) => println!("{}", v.display()),
Err(v) => eprintln!("Uncaught {v}"),
}
context.run_jobs();
}
}

Expand Down Expand Up @@ -333,11 +337,14 @@ fn main() -> Result<(), io::Error> {
}
} else {
match context.eval(line.trim_end()) {
Ok(v) => println!("{}", v.display()),
Ok(v) => {
println!("{}", v.display());
}
Err(v) => {
eprintln!("{}: {}", "Uncaught".red(), v.to_string().red());
}
}
context.run_jobs();
}
}

Expand All @@ -355,3 +362,26 @@ fn main() -> Result<(), io::Error> {

Ok(())
}

#[derive(Default)]
struct Jobs(RefCell<VecDeque<NativeJob>>);

impl JobQueue for Jobs {
fn enqueue_promise_job(&self, job: NativeJob, _: &mut Context<'_>) {
self.0.borrow_mut().push_front(job);
}

fn run_jobs(&self, context: &mut Context<'_>) {
loop {
let jobs = std::mem::take(&mut *self.0.borrow_mut());
if jobs.is_empty() {
return;
}
for job in jobs {
if let Err(e) = job.call(context) {
eprintln!("Uncaught {e}");
}
}
}
}
}
18 changes: 10 additions & 8 deletions boa_engine/src/builtins/async_generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,20 +632,22 @@ impl AsyncGenerator {
context,
NativeFunction::from_copy_closure_with_captures(
|_this, args, generator, context| {
let mut generator_borrow_mut = generator.borrow_mut();
let gen = generator_borrow_mut
.as_async_generator_mut()
.expect("already checked before");
let next = {
let mut generator_borrow_mut = generator.borrow_mut();
let gen = generator_borrow_mut
.as_async_generator_mut()
.expect("already checked before");

// a. Set generator.[[AsyncGeneratorState]] to completed.
gen.state = AsyncGeneratorState::Completed;
// a. Set generator.[[AsyncGeneratorState]] to completed.
gen.state = AsyncGeneratorState::Completed;

gen.queue.pop_front().expect("must have one entry")
};

// b. Let result be NormalCompletion(value).
let result = Ok(args.get_or_undefined(0).clone());

// c. Perform AsyncGeneratorCompleteStep(generator, result, true).
let next = gen.queue.pop_front().expect("must have one entry");
drop(generator_borrow_mut);
Self::complete_step(&next, result, true, context);

// d. Perform AsyncGeneratorDrainQueue(generator).
Expand Down
10 changes: 6 additions & 4 deletions boa_engine/src/builtins/eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,17 @@ impl Eval {
// Because of implementation details the following code differs from the spec.

// 5. Perform ? HostEnsureCanCompileStrings(evalRealm).
let mut parser = Parser::new(x.as_bytes());
if strict {
parser.set_strict();
}
context.host_hooks().ensure_can_compile_strings(context)?;

// 11. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection:
// a. Let script be ParseText(StringToCodePoints(x), Script).
// b. If script is a List of errors, throw a SyntaxError exception.
// c. If script Contains ScriptBody is false, return undefined.
// d. Let body be the ScriptBody of script.
let mut parser = Parser::new(x.as_bytes());
if strict {
parser.set_strict();
}
let body = parser.parse_eval(direct, context.interner_mut())?;

// 6. Let inFunction be false.
Expand Down
Loading

0 comments on commit 5ab0aa2

Please sign in to comment.