Skip to content

Commit 5bcfde9

Browse files
committed
refactor(cli): Librarify nova_cli to avoid code duplication
This already factored out some logic that was duplicated between the `eval` and `repl` subcommands. However, the real goal is to provide a library the benchmark runner can use, to avoid code duplication there.
1 parent 48c43f2 commit 5bcfde9

File tree

8 files changed

+728
-558
lines changed

8 files changed

+728
-558
lines changed

nova_cli/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ readme.workspace = true
1212
keywords.workspace = true
1313
categories = ["development-tools", "command-line-utilities"]
1414

15+
[lib]
16+
name = "nova_cli"
17+
path = "src/lib/lib.rs"
18+
19+
[[bin]]
20+
name = "nova_cli"
21+
path = "src/main.rs"
22+
1523
[dependencies]
1624
clap = { workspace = true }
1725
cliclack = { workspace = true }

nova_cli/src/lib/child_hooks.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! The [`HostHooks`] implementation for macrotasks and promise jobs, i.e.
6+
//! everything but the main thread.
7+
8+
use std::{
9+
cell::RefCell,
10+
collections::VecDeque,
11+
sync::{atomic::AtomicBool, mpsc},
12+
thread,
13+
time::Duration,
14+
};
15+
16+
use nova_vm::ecmascript::execution::agent::{HostHooks, Job};
17+
18+
use crate::{ChildToHostMessage, HostToChildMessage};
19+
20+
pub struct CliChildHooks {
21+
promise_job_queue: RefCell<VecDeque<Job>>,
22+
macrotask_queue: RefCell<Vec<Job>>,
23+
pub(crate) receiver: mpsc::Receiver<HostToChildMessage>,
24+
pub(crate) host_sender: mpsc::SyncSender<ChildToHostMessage>,
25+
ready_to_leave: AtomicBool,
26+
}
27+
28+
// RefCell doesn't implement Debug
29+
impl std::fmt::Debug for CliChildHooks {
30+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31+
f.debug_struct("CliHostHooks")
32+
//.field("promise_job_queue", &*self.promise_job_queue.borrow())
33+
.finish()
34+
}
35+
}
36+
37+
impl CliChildHooks {
38+
pub fn new(
39+
host_sender: mpsc::SyncSender<ChildToHostMessage>,
40+
) -> (Self, mpsc::SyncSender<HostToChildMessage>) {
41+
let (sender, receiver) = mpsc::sync_channel(1);
42+
(
43+
Self {
44+
promise_job_queue: Default::default(),
45+
macrotask_queue: Default::default(),
46+
receiver,
47+
host_sender,
48+
ready_to_leave: Default::default(),
49+
},
50+
sender,
51+
)
52+
}
53+
54+
pub fn is_ready_to_leave(&self) -> bool {
55+
self.ready_to_leave
56+
.load(std::sync::atomic::Ordering::Relaxed)
57+
}
58+
59+
pub fn mark_ready_to_leave(&self) {
60+
self.ready_to_leave
61+
.store(true, std::sync::atomic::Ordering::Relaxed)
62+
}
63+
64+
pub fn has_promise_jobs(&self) -> bool {
65+
!self.promise_job_queue.borrow().is_empty()
66+
}
67+
68+
pub fn pop_promise_job(&self) -> Option<Job> {
69+
self.promise_job_queue.borrow_mut().pop_front()
70+
}
71+
72+
pub fn has_macrotasks(&self) -> bool {
73+
!self.macrotask_queue.borrow().is_empty()
74+
}
75+
76+
pub fn pop_macrotask(&self) -> Option<Job> {
77+
let mut off_thread_job_queue = self.macrotask_queue.borrow_mut();
78+
let mut counter = 0u8;
79+
while !off_thread_job_queue.is_empty() {
80+
counter = counter.wrapping_add(1);
81+
for (i, job) in off_thread_job_queue.iter().enumerate() {
82+
if job.is_finished() {
83+
let job = off_thread_job_queue.swap_remove(i);
84+
return Some(job);
85+
}
86+
}
87+
if counter == 0 {
88+
thread::sleep(Duration::from_millis(5));
89+
} else {
90+
core::hint::spin_loop();
91+
}
92+
}
93+
None
94+
}
95+
}
96+
97+
impl HostHooks for CliChildHooks {
98+
fn enqueue_generic_job(&self, job: Job) {
99+
self.macrotask_queue.borrow_mut().push(job);
100+
}
101+
102+
fn enqueue_promise_job(&self, job: Job) {
103+
self.promise_job_queue.borrow_mut().push_back(job);
104+
}
105+
106+
fn enqueue_timeout_job(&self, _timeout_job: Job, _milliseconds: u64) {}
107+
108+
fn get_host_data(&self) -> &dyn std::any::Any {
109+
self
110+
}
111+
}

nova_cli/src/lib/fmt.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! Formatting values and errors.
6+
7+
use nova_vm::{
8+
ecmascript::{
9+
execution::{Agent, JsResult},
10+
types::Value,
11+
},
12+
engine::context::{Bindable, GcScope},
13+
};
14+
use oxc_diagnostics::OxcDiagnostic;
15+
16+
pub fn print_result(agent: &mut Agent, result: JsResult<Value>, verbose: bool, gc: GcScope) {
17+
match result {
18+
Ok(result) => {
19+
if verbose {
20+
println!("{result:?}");
21+
}
22+
}
23+
Err(error) => {
24+
eprintln!(
25+
"Uncaught exception: {}",
26+
error
27+
.value()
28+
.unbind()
29+
.string_repr(agent, gc)
30+
.as_wtf8(agent)
31+
.to_string_lossy()
32+
);
33+
std::process::exit(1);
34+
}
35+
}
36+
}
37+
38+
/// Exit the program with parse errors.
39+
pub fn exit_with_parse_errors(errors: Vec<OxcDiagnostic>, source_path: &str, source: &str) -> ! {
40+
assert!(!errors.is_empty());
41+
42+
// This seems to be needed for color and Unicode output.
43+
miette::set_hook(Box::new(|_| {
44+
Box::new(oxc_diagnostics::GraphicalReportHandler::new())
45+
}))
46+
.unwrap();
47+
48+
eprintln!("Parse errors:");
49+
50+
// SAFETY: This function never returns, so `source`'s lifetime must last for
51+
// the duration of the program.
52+
let source: &'static str = unsafe { std::mem::transmute(source) };
53+
let named_source = miette::NamedSource::new(source_path, source);
54+
55+
for error in errors {
56+
let report = error.with_source_code(named_source.clone());
57+
eprint!("{report:?}");
58+
}
59+
eprintln!();
60+
61+
std::process::exit(1);
62+
}
Lines changed: 6 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,9 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use std::{
6-
cell::RefCell,
7-
collections::VecDeque,
8-
ops::Deref,
9-
sync::{LazyLock, atomic::AtomicBool, mpsc},
10-
thread,
11-
time::Duration,
12-
};
5+
//! Initializing the global object with builtin functions.
6+
7+
use std::{ops::Deref, sync::LazyLock, thread, time::Duration};
138

149
// Record the start time of the program.
1510
// To be used for the `now` function for time measurement.
@@ -23,7 +18,7 @@ use nova_vm::{
2318
},
2419
execution::{
2520
Agent, JsResult,
26-
agent::{ExceptionType, GcAgent, HostHooks, Job, Options, unwrap_try},
21+
agent::{ExceptionType, GcAgent, Options, unwrap_try},
2722
},
2823
scripts_and_modules::script::{parse_script, script_evaluation},
2924
types::{
@@ -36,97 +31,8 @@ use nova_vm::{
3631
rootable::Scopable,
3732
},
3833
};
39-
use oxc_diagnostics::OxcDiagnostic;
40-
41-
use crate::{ChildToHostMessage, CliHostHooks, HostToChildMessage};
42-
43-
struct CliChildHooks {
44-
promise_job_queue: RefCell<VecDeque<Job>>,
45-
macrotask_queue: RefCell<Vec<Job>>,
46-
receiver: mpsc::Receiver<HostToChildMessage>,
47-
host_sender: mpsc::SyncSender<ChildToHostMessage>,
48-
ready_to_leave: AtomicBool,
49-
}
50-
51-
// RefCell doesn't implement Debug
52-
impl std::fmt::Debug for CliChildHooks {
53-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54-
f.debug_struct("CliHostHooks")
55-
//.field("promise_job_queue", &*self.promise_job_queue.borrow())
56-
.finish()
57-
}
58-
}
59-
60-
impl CliChildHooks {
61-
fn new(
62-
host_sender: mpsc::SyncSender<ChildToHostMessage>,
63-
) -> (Self, mpsc::SyncSender<HostToChildMessage>) {
64-
let (sender, receiver) = mpsc::sync_channel(1);
65-
(
66-
Self {
67-
promise_job_queue: Default::default(),
68-
macrotask_queue: Default::default(),
69-
receiver,
70-
host_sender,
71-
ready_to_leave: Default::default(),
72-
},
73-
sender,
74-
)
75-
}
76-
77-
fn is_ready_to_leave(&self) -> bool {
78-
self.ready_to_leave
79-
.load(std::sync::atomic::Ordering::Relaxed)
80-
}
81-
82-
fn has_promise_jobs(&self) -> bool {
83-
!self.promise_job_queue.borrow().is_empty()
84-
}
85-
86-
fn pop_promise_job(&self) -> Option<Job> {
87-
self.promise_job_queue.borrow_mut().pop_front()
88-
}
89-
90-
fn has_macrotasks(&self) -> bool {
91-
!self.macrotask_queue.borrow().is_empty()
92-
}
93-
94-
fn pop_macrotask(&self) -> Option<Job> {
95-
let mut off_thread_job_queue = self.macrotask_queue.borrow_mut();
96-
let mut counter = 0u8;
97-
while !off_thread_job_queue.is_empty() {
98-
counter = counter.wrapping_add(1);
99-
for (i, job) in off_thread_job_queue.iter().enumerate() {
100-
if job.is_finished() {
101-
let job = off_thread_job_queue.swap_remove(i);
102-
return Some(job);
103-
}
104-
}
105-
if counter == 0 {
106-
thread::sleep(Duration::from_millis(5));
107-
} else {
108-
core::hint::spin_loop();
109-
}
110-
}
111-
None
112-
}
113-
}
114-
115-
impl HostHooks for CliChildHooks {
116-
fn enqueue_generic_job(&self, job: Job) {
117-
self.macrotask_queue.borrow_mut().push(job);
118-
}
119-
120-
fn enqueue_promise_job(&self, job: Job) {
121-
self.promise_job_queue.borrow_mut().push_back(job);
122-
}
123-
124-
fn enqueue_timeout_job(&self, _timeout_job: Job, _milliseconds: u64) {}
12534

126-
fn get_host_data(&self) -> &dyn std::any::Any {
127-
self
128-
}
129-
}
35+
use crate::{ChildToHostMessage, CliChildHooks, CliHostHooks, HostToChildMessage};
13036

13137
/// Initialize the global object with the built-in functions.
13238
pub fn initialize_global_object(agent: &mut Agent, global: Object, gc: GcScope) {
@@ -714,9 +620,7 @@ fn initialize_child_global_object(agent: &mut Agent, global: Object, mut gc: GcS
714620
.get_host_data()
715621
.downcast_ref::<CliChildHooks>()
716622
.unwrap();
717-
hooks
718-
.ready_to_leave
719-
.store(true, std::sync::atomic::Ordering::Relaxed);
623+
hooks.mark_ready_to_leave();
720624
Ok(Value::Undefined)
721625
}
722626

@@ -765,29 +669,3 @@ fn initialize_child_global_object(agent: &mut Agent, global: Object, mut gc: GcS
765669
create_obj_func(agent, agent_obj, "sleep", sleep, 1, gc);
766670
create_obj_func(agent, agent_obj, "monotonicNow", monotonic_now, 0, gc);
767671
}
768-
769-
/// Exit the program with parse errors.
770-
pub fn exit_with_parse_errors(errors: Vec<OxcDiagnostic>, source_path: &str, source: &str) -> ! {
771-
assert!(!errors.is_empty());
772-
773-
// This seems to be needed for color and Unicode output.
774-
miette::set_hook(Box::new(|_| {
775-
Box::new(oxc_diagnostics::GraphicalReportHandler::new())
776-
}))
777-
.unwrap();
778-
779-
eprintln!("Parse errors:");
780-
781-
// SAFETY: This function never returns, so `source`'s lifetime must last for
782-
// the duration of the program.
783-
let source: &'static str = unsafe { std::mem::transmute(source) };
784-
let named_source = miette::NamedSource::new(source_path, source);
785-
786-
for error in errors {
787-
let report = error.with_source_code(named_source.clone());
788-
eprint!("{report:?}");
789-
}
790-
eprintln!();
791-
792-
std::process::exit(1);
793-
}

0 commit comments

Comments
 (0)