Skip to content

Commit 192a470

Browse files
committed
refactor(cli): Refactor execution & microtasks into CliRunner struct
In order to add a repl to the cli, trynova#323 moved the `initialize_global_object` and `exit_with_parse_errors` functions into a new `helper` module. However, this still left duplicate code between the `eval` and `repl` commands. This patch moves the isolate and realm initialization, as well as the script execution and microtask checkpoint, to a new `CliRunner` struct that lives in the `helper` module. Aside from just refactoring, this patch makes two visible changes related to promise jobs: - When passing multiple files to `eval`, now promise jobs run after each script execution, rather than at the end. - In `repl` mode it used to be that promise jobs were never run. Now, all promise jobs enqueued when running a line of JS are run before the result is printed.
1 parent ff05681 commit 192a470

File tree

4 files changed

+129
-106
lines changed

4 files changed

+129
-106
lines changed

nova_cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ cliclack = { workspace = true }
1010
console = { workspace = true }
1111
miette = { workspace = true }
1212
nova_vm = { path = "../nova_vm" }
13+
oxc_allocator = { workspace = true }
1314
oxc_ast = { workspace = true }
1415
oxc_parser = { workspace = true }
1516
oxc_semantic = { workspace = true }

nova_cli/src/helper.rs

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
use nova_vm::ecmascript::{
22
builtins::{create_builtin_function, ArgumentsList, Behaviour, BuiltinFunctionArgs},
3-
execution::{Agent, JsResult},
3+
execution::{
4+
agent::{HostHooks, Job, Options},
5+
initialize_host_defined_realm, Agent, JsResult, Realm, RealmIdentifier,
6+
},
7+
scripts_and_modules::script::{parse_script, script_evaluation},
48
types::{InternalMethods, IntoValue, Object, PropertyDescriptor, PropertyKey, Value},
59
};
610
use oxc_diagnostics::OxcDiagnostic;
11+
use std::{cell::RefCell, collections::VecDeque, fmt::Debug};
712

813
/// Initialize the global object with the built-in functions.
9-
pub fn initialize_global_object(agent: &mut Agent, global: Object) {
14+
fn initialize_global_object(agent: &mut Agent, global: Object) {
1015
// `print` function
1116
fn print(agent: &mut Agent, _this: Value, args: ArgumentsList) -> JsResult<Value> {
1217
if args.len() == 0 {
@@ -59,3 +64,106 @@ pub fn exit_with_parse_errors(errors: Vec<OxcDiagnostic>, source_path: &str, sou
5964

6065
std::process::exit(1);
6166
}
67+
68+
#[derive(Default)]
69+
struct CliHostHooks {
70+
promise_job_queue: RefCell<VecDeque<Job>>,
71+
}
72+
73+
// RefCell doesn't implement Debug
74+
impl Debug for CliHostHooks {
75+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76+
f.debug_struct("CliHostHooks")
77+
//.field("promise_job_queue", &*self.promise_job_queue.borrow())
78+
.finish()
79+
}
80+
}
81+
82+
impl CliHostHooks {
83+
fn pop_promise_job(&self) -> Option<Job> {
84+
self.promise_job_queue.borrow_mut().pop_front()
85+
}
86+
}
87+
88+
impl HostHooks for CliHostHooks {
89+
fn enqueue_promise_job(&self, job: Job) {
90+
self.promise_job_queue.borrow_mut().push_back(job);
91+
}
92+
}
93+
94+
pub struct CliRunner {
95+
allocator: oxc_allocator::Allocator,
96+
host_hooks: &'static CliHostHooks,
97+
agent: Option<Agent>,
98+
realm_id: RealmIdentifier,
99+
}
100+
101+
impl CliRunner {
102+
pub fn new(print_internals: bool) -> Self {
103+
let host_hooks: &CliHostHooks = &*Box::leak(Box::default());
104+
let mut agent = Agent::new(
105+
Options {
106+
disable_gc: false,
107+
print_internals,
108+
},
109+
host_hooks,
110+
);
111+
112+
{
113+
let create_global_object: Option<fn(&mut Realm) -> Object> = None;
114+
let create_global_this_value: Option<fn(&mut Realm) -> Object> = None;
115+
initialize_host_defined_realm(
116+
&mut agent,
117+
create_global_object,
118+
create_global_this_value,
119+
Some(initialize_global_object),
120+
);
121+
}
122+
123+
let realm_id = agent.current_realm_id();
124+
Self {
125+
allocator: Default::default(),
126+
host_hooks,
127+
agent: Some(agent),
128+
realm_id,
129+
}
130+
}
131+
132+
pub fn run_script_and_microtasks(
133+
&mut self,
134+
script: Box<str>,
135+
script_path: &str,
136+
allow_loose_mode: bool,
137+
) -> JsResult<Value> {
138+
let script = match parse_script(
139+
&self.allocator,
140+
script,
141+
self.realm_id,
142+
!allow_loose_mode,
143+
None,
144+
) {
145+
Ok(script) => script,
146+
Err((file, errors)) => exit_with_parse_errors(errors, script_path, &file),
147+
};
148+
149+
let result = script_evaluation(self.agent(), script)?;
150+
151+
while let Some(job) = self.host_hooks.pop_promise_job() {
152+
job.run(self.agent())?;
153+
}
154+
155+
Ok(result)
156+
}
157+
158+
pub fn agent(&mut self) -> &mut Agent {
159+
self.agent.as_mut().unwrap()
160+
}
161+
}
162+
163+
impl Drop for CliRunner {
164+
fn drop(&mut self) {
165+
// The agent unsafely borrows the allocator and the host hooks, so it
166+
// has to be dropped first.
167+
self.agent.take();
168+
}
169+
}

nova_cli/src/main.rs

Lines changed: 16 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,10 @@
44
mod helper;
55
mod theme;
66

7-
use std::{cell::RefCell, collections::VecDeque, fmt::Debug};
8-
97
use clap::{Parser as ClapParser, Subcommand};
108
use cliclack::{input, intro, set_theme};
11-
use helper::{exit_with_parse_errors, initialize_global_object};
12-
use nova_vm::ecmascript::{
13-
execution::{
14-
agent::{HostHooks, Job, Options},
15-
initialize_host_defined_realm, Agent, Realm,
16-
},
17-
scripts_and_modules::script::{parse_script, script_evaluation},
18-
types::{Object, Value},
19-
};
9+
use helper::{exit_with_parse_errors, CliRunner};
10+
use nova_vm::ecmascript::types::Value;
2011
use oxc_parser::Parser;
2112
use oxc_semantic::{SemanticBuilder, SemanticBuilderReturn};
2213
use oxc_span::SourceType;
@@ -56,32 +47,6 @@ enum Command {
5647
Repl {},
5748
}
5849

59-
#[derive(Default)]
60-
struct CliHostHooks {
61-
promise_job_queue: RefCell<VecDeque<Job>>,
62-
}
63-
64-
// RefCell doesn't implement Debug
65-
impl Debug for CliHostHooks {
66-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67-
f.debug_struct("CliHostHooks")
68-
//.field("promise_job_queue", &*self.promise_job_queue.borrow())
69-
.finish()
70-
}
71-
}
72-
73-
impl CliHostHooks {
74-
fn pop_promise_job(&self) -> Option<Job> {
75-
self.promise_job_queue.borrow_mut().pop_front()
76-
}
77-
}
78-
79-
impl HostHooks for CliHostHooks {
80-
fn enqueue_promise_job(&self, job: Job) {
81-
self.promise_job_queue.borrow_mut().push_back(job);
82-
}
83-
}
84-
8550
fn main() -> Result<(), Box<dyn std::error::Error>> {
8651
let args = Cli::parse();
8752

@@ -112,27 +77,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
11277
no_strict,
11378
paths,
11479
} => {
115-
let allocator = Default::default();
116-
117-
let host_hooks: &CliHostHooks = &*Box::leak(Box::default());
118-
let mut agent = Agent::new(
119-
Options {
120-
disable_gc: false,
121-
print_internals: verbose,
122-
},
123-
host_hooks,
124-
);
125-
{
126-
let create_global_object: Option<fn(&mut Realm) -> Object> = None;
127-
let create_global_this_value: Option<fn(&mut Realm) -> Object> = None;
128-
initialize_host_defined_realm(
129-
&mut agent,
130-
create_global_object,
131-
create_global_this_value,
132-
Some(initialize_global_object),
133-
);
134-
}
135-
let realm = agent.current_realm_id();
80+
let mut cli_runner = CliRunner::new(verbose);
13681

13782
// `final_result` will always be overwritten in the paths loop, but
13883
// we populate it with a dummy value here so rustc won't complain.
@@ -141,25 +86,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
14186
assert!(!paths.is_empty());
14287
for path in paths {
14388
let file = std::fs::read_to_string(&path)?;
144-
let script = match parse_script(&allocator, file.into(), realm, !no_strict, None) {
145-
Ok(script) => script,
146-
Err((file, errors)) => exit_with_parse_errors(errors, &path, &file),
147-
};
148-
final_result = script_evaluation(&mut agent, script);
89+
final_result = cli_runner.run_script_and_microtasks(file.into(), &path, no_strict);
14990
if final_result.is_err() {
15091
break;
15192
}
15293
}
15394

154-
if final_result.is_ok() {
155-
while let Some(job) = host_hooks.pop_promise_job() {
156-
if let Err(err) = job.run(&mut agent) {
157-
final_result = Err(err);
158-
break;
159-
}
160-
}
161-
}
162-
16395
match final_result {
16496
Ok(result) => {
16597
if verbose {
@@ -169,33 +101,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
169101
Err(error) => {
170102
eprintln!(
171103
"Uncaught exception: {}",
172-
error.value().string_repr(&mut agent).as_str(&agent)
104+
error
105+
.value()
106+
.string_repr(cli_runner.agent())
107+
.as_str(cli_runner.agent())
173108
);
174109
std::process::exit(1);
175110
}
176111
}
112+
std::process::exit(0);
177113
}
178114
Command::Repl {} => {
179-
let allocator = Default::default();
180-
let host_hooks: &CliHostHooks = &*Box::leak(Box::default());
181-
let mut agent = Agent::new(
182-
Options {
183-
disable_gc: false,
184-
print_internals: true,
185-
},
186-
host_hooks,
187-
);
188-
{
189-
let create_global_object: Option<fn(&mut Realm) -> Object> = None;
190-
let create_global_this_value: Option<fn(&mut Realm) -> Object> = None;
191-
initialize_host_defined_realm(
192-
&mut agent,
193-
create_global_object,
194-
create_global_this_value,
195-
Some(initialize_global_object),
196-
);
197-
}
198-
let realm = agent.current_realm_id();
115+
let mut cli_runner = CliRunner::new(false);
199116

200117
set_theme(DefaultTheme);
201118
println!("\n\n");
@@ -209,21 +126,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
209126
std::process::exit(0);
210127
}
211128
placeholder = input.to_string();
212-
let script = match parse_script(&allocator, input.into(), realm, true, None) {
213-
Ok(script) => script,
214-
Err((file, errors)) => {
215-
exit_with_parse_errors(errors, "<stdin>", &file);
216-
}
217-
};
218-
let result = script_evaluation(&mut agent, script);
219-
match result {
129+
130+
match cli_runner.run_script_and_microtasks(input.into(), "<stdin>", false) {
220131
Ok(result) => {
221132
println!("{:?}\n", result);
222133
}
223134
Err(error) => {
224135
eprintln!(
225136
"Uncaught exception: {}",
226-
error.value().string_repr(&mut agent).as_str(&agent)
137+
error
138+
.value()
139+
.string_repr(cli_runner.agent())
140+
.as_str(cli_runner.agent())
227141
);
228142
}
229143
}

nova_vm/src/ecmascript/execution.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ pub(crate) use environments::{
1717
PrivateEnvironmentIndex, ThisBindingStatus,
1818
};
1919
pub(crate) use execution_context::*;
20+
pub(crate) use realm::ProtoIntrinsics;
2021
pub use realm::{
2122
create_realm, initialize_default_realm, initialize_host_defined_realm, set_realm_global_object,
22-
Realm,
23+
Realm, RealmIdentifier,
2324
};
24-
pub(crate) use realm::{ProtoIntrinsics, RealmIdentifier};

0 commit comments

Comments
 (0)