Skip to content

Commit db8c086

Browse files
committed
Refactor CLI: Move binaries to examples and use clap for command-line interface
- Move qjs and rust_qjs from bin/ to examples/ - Add clap library for generating CLI in examples - Update Cargo.toml to include examples and clap dependency - Remove unused functions in core.rs and js_regexp.rs - Fix imports in test files to use public API
1 parent 7562d77 commit db8c086

33 files changed

+154
-165
lines changed

Cargo.toml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,8 @@ name = "javascript"
33
version = "0.1.0"
44
edition = "2021"
55

6-
[[bin]]
7-
name = "qjs"
8-
path = "bin/qjs.rs"
9-
10-
[[bin]]
11-
name = "rust_qjs"
12-
path = "bin/rust_qjs.rs"
13-
146
[dependencies]
157
chrono = { version = "0.4", features = ["serde"] }
16-
env_logger = { version = "0.11", optional = true }
178
libc = "0.2.177"
189
log = "0.4"
1910
regex = "1.12.2"
@@ -24,5 +15,14 @@ thiserror = "2.0.17"
2415
windows-sys = { version = "0.61.2", features = ["Win32_System_Threading", "Win32_System_Diagnostics_ToolHelp", "Win32_Foundation"] }
2516

2617
[dev-dependencies]
18+
clap = { version = "4.5.53", features = ["derive"] }
2719
ctor = "0.6"
2820
env_logger = { version = "0.11" }
21+
22+
[[example]]
23+
name = "js"
24+
path = "examples/js.rs"
25+
26+
[[example]]
27+
name = "rust_js"
28+
path = "examples/rust_js.rs"

bin/qjs.rs renamed to examples/js.rs

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
use javascript::core::*;
2-
use std::env;
3-
use std::fs;
1+
use javascript::*;
42
use std::process;
53

4+
#[derive(clap::Parser)]
5+
#[command(name = "js", version, about = "JavaScript Rust Interpreter")]
6+
struct Cli {
7+
/// Execute script
8+
#[arg(short, long)]
9+
eval: Option<String>,
10+
11+
/// JavaScript file to execute
12+
file: Option<std::path::PathBuf>,
13+
}
14+
615
unsafe fn get_js_string(val: &JSValue) -> String {
716
if val.get_tag() != JS_TAG_STRING {
817
return String::new();
@@ -18,43 +27,28 @@ unsafe fn get_js_string(val: &JSValue) -> String {
1827
}
1928

2029
fn main() {
21-
let args: Vec<String> = env::args().collect();
30+
let cli = <Cli as clap::Parser>::parse();
2231

2332
// Initialize logger (controlled by RUST_LOG)
24-
#[cfg(feature = "env_logger")]
2533
env_logger::init();
2634

27-
if args.len() < 2 {
28-
eprintln!("Usage: {} [options] [file.js | -e script]", args[0]);
29-
eprintln!("Options:");
30-
eprintln!(" -e script Execute script");
31-
eprintln!(" -h Show this help");
32-
process::exit(1);
33-
}
34-
3535
let script_content: String;
3636
let mut filename = "<eval>".to_string();
3737

38-
if args[1] == "-h" {
39-
println!("QuickJS Rust Interpreter");
40-
println!("Usage: {} [options] [file.js | -e script]", args[0]);
41-
return;
42-
} else if args[1] == "-e" {
43-
if args.len() < 3 {
44-
eprintln!("Error: -e requires a script argument");
45-
process::exit(1);
46-
}
47-
script_content = args[2].clone();
48-
} else {
49-
// Read from file
50-
filename = args[1].clone();
51-
match fs::read_to_string(&filename) {
38+
if let Some(script) = cli.eval {
39+
script_content = script;
40+
} else if let Some(file) = cli.file {
41+
filename = file.to_string_lossy().to_string();
42+
match std::fs::read_to_string(&file) {
5243
Ok(content) => script_content = content,
5344
Err(e) => {
54-
eprintln!("Error reading file {}: {}", filename, e);
45+
eprintln!("Error reading file {}: {}", file.display(), e);
5546
process::exit(1);
5647
}
5748
}
49+
} else {
50+
eprintln!("Error: Must provide either --eval or a file");
51+
process::exit(1);
5852
}
5953

6054
unsafe {

bin/rust_qjs.rs renamed to examples/rust_js.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
1-
use std::env;
1+
use javascript::*;
22
use std::process;
33

4-
use javascript::core::*;
4+
#[derive(clap::Parser)]
5+
#[command(name = "rust_js", version, about = "JavaScript Rust Interpreter with imports")]
6+
struct Cli {
7+
/// Execute script
8+
#[arg(short, long)]
9+
eval: Option<String>,
10+
11+
/// JavaScript file to execute
12+
file: Option<std::path::PathBuf>,
13+
}
514

615
fn main() {
7-
let args: Vec<String> = env::args().collect();
16+
let cli = <Cli as clap::Parser>::parse();
817

918
// Initialize logger (controlled by RUST_LOG)
10-
#[cfg(feature = "env_logger")]
1119
env_logger::init();
1220

1321
let script: String;
14-
if args.len() >= 3 && args[1] == "-e" {
15-
script = args[2].clone();
16-
} else if args.len() >= 2 && args[1] != "-h" {
17-
// Read from file
18-
match std::fs::read_to_string(&args[1]) {
22+
if let Some(s) = cli.eval {
23+
script = s;
24+
} else if let Some(file) = cli.file {
25+
match std::fs::read_to_string(&file) {
1926
Ok(content) => script = content,
2027
Err(e) => {
21-
eprintln!("Error reading file {}: {}", args[1], e);
28+
eprintln!("Error reading file {}: {}", file.display(), e);
2229
process::exit(1);
2330
}
2431
}
2532
} else {
26-
eprintln!("Usage: {} [file.js | -e script]", args[0]);
33+
eprintln!("Error: Must provide either --eval or a file");
2734
process::exit(1);
2835
}
2936

src/core.rs

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ use std::rc::Rc;
2222
// Task queue for asynchronous operations
2323
#[derive(Clone)]
2424
pub enum Task {
25-
PromiseResolve {
25+
Resolve {
2626
promise: Rc<RefCell<JSPromise>>,
2727
value: Value,
2828
},
29-
PromiseReject {
29+
Reject {
3030
promise: Rc<RefCell<JSPromise>>,
3131
reason: Value,
3232
},
33-
PromiseResolution {
33+
Resolution {
3434
promise: Rc<RefCell<JSPromise>>,
3535
callbacks: Vec<(Value, Rc<RefCell<JSPromise>>)>,
3636
},
37-
PromiseRejection {
37+
Rejection {
3838
promise: Rc<RefCell<JSPromise>>,
3939
callbacks: Vec<(Value, Rc<RefCell<JSPromise>>)>,
4040
},
@@ -63,7 +63,7 @@ pub fn resolve_promise(promise: Rc<RefCell<JSPromise>>, value: Value) -> Result<
6363
let callbacks = p.on_fulfilled.clone();
6464
p.on_fulfilled.clear();
6565
if !callbacks.is_empty() {
66-
queue_task(Task::PromiseResolution {
66+
queue_task(Task::Resolution {
6767
promise: promise.clone(),
6868
callbacks,
6969
});
@@ -81,7 +81,7 @@ pub fn reject_promise(promise: Rc<RefCell<JSPromise>>, reason: Value) -> Result<
8181
let callbacks = p.on_rejected.clone();
8282
p.on_rejected.clear();
8383
if !callbacks.is_empty() {
84-
queue_task(Task::PromiseRejection {
84+
queue_task(Task::Rejection {
8585
promise: promise.clone(),
8686
callbacks,
8787
});
@@ -4139,10 +4139,6 @@ pub fn env_set_const(env: &JSObjectDataPtr, key: &str, val: Value) {
41394139
env_mut.set_const(key.to_string());
41404140
}
41414141

4142-
pub fn env_set_rc(env: &JSObjectDataPtr, key: &str, val_rc: Rc<RefCell<Value>>) {
4143-
env.borrow_mut().insert(key.to_string(), val_rc);
4144-
}
4145-
41464142
// Higher-level property API that operates on expressions + environment.
41474143
// `get_prop_env` evaluates `obj_expr` in `env` and returns the property's Rc if present.
41484144
pub fn get_prop_env(env: &JSObjectDataPtr, obj_expr: &Expr, prop: &str) -> Result<Option<Rc<RefCell<Value>>>, JSError> {
@@ -6398,7 +6394,7 @@ pub fn handle_promise_method(obj_map: &JSObjectDataPtr, method: &str, args: &[Ex
63986394
PromiseState::Fulfilled(_) => {
63996395
// Queue task to execute on_fulfilled callback asynchronously
64006396
if let Some(fulfilled_callback) = on_fulfilled {
6401-
queue_task(Task::PromiseResolution {
6397+
queue_task(Task::Resolution {
64026398
promise: promise.clone(),
64036399
callbacks: vec![(fulfilled_callback, new_promise.clone())],
64046400
});
@@ -6411,7 +6407,7 @@ pub fn handle_promise_method(obj_map: &JSObjectDataPtr, method: &str, args: &[Ex
64116407
PromiseState::Rejected(_) => {
64126408
// Queue task to execute on_rejected callback asynchronously
64136409
if let Some(rejected_callback) = on_rejected {
6414-
queue_task(Task::PromiseRejection {
6410+
queue_task(Task::Rejection {
64156411
promise: promise.clone(),
64166412
callbacks: vec![(rejected_callback, new_promise.clone())],
64176413
});
@@ -6452,7 +6448,7 @@ pub fn handle_promise_method(obj_map: &JSObjectDataPtr, method: &str, args: &[Ex
64526448
}
64536449
PromiseState::Rejected(_) => {
64546450
// Queue task to execute on_rejected callback asynchronously
6455-
queue_task(Task::PromiseRejection {
6451+
queue_task(Task::Rejection {
64566452
promise: promise.clone(),
64576453
callbacks: vec![(on_rejected, new_promise.clone())],
64586454
});
@@ -6473,7 +6469,7 @@ pub fn run_event_loop() -> Result<(), JSError> {
64736469
let task = GLOBAL_TASK_QUEUE.with(|queue| queue.borrow_mut().pop_front());
64746470

64756471
match task {
6476-
Some(Task::PromiseResolve { promise, value }) => {
6472+
Some(Task::Resolve { promise, value }) => {
64776473
let mut promise_mut = promise.borrow_mut();
64786474
if let PromiseState::Pending = promise_mut.state {
64796475
promise_mut.state = PromiseState::Fulfilled(value.clone());
@@ -6483,14 +6479,14 @@ pub fn run_event_loop() -> Result<(), JSError> {
64836479
let callbacks = promise_mut.on_fulfilled.clone();
64846480
promise_mut.on_fulfilled.clear();
64856481
if !callbacks.is_empty() {
6486-
queue_task(Task::PromiseResolution {
6482+
queue_task(Task::Resolution {
64876483
promise: promise.clone(),
64886484
callbacks,
64896485
});
64906486
}
64916487
}
64926488
}
6493-
Some(Task::PromiseReject { promise, reason }) => {
6489+
Some(Task::Reject { promise, reason }) => {
64946490
let mut promise_mut = promise.borrow_mut();
64956491
if let PromiseState::Pending = promise_mut.state {
64966492
promise_mut.state = PromiseState::Rejected(reason.clone());
@@ -6500,14 +6496,14 @@ pub fn run_event_loop() -> Result<(), JSError> {
65006496
let callbacks = promise_mut.on_rejected.clone();
65016497
promise_mut.on_rejected.clear();
65026498
if !callbacks.is_empty() {
6503-
queue_task(Task::PromiseRejection {
6499+
queue_task(Task::Rejection {
65046500
promise: promise.clone(),
65056501
callbacks,
65066502
});
65076503
}
65086504
}
65096505
}
6510-
Some(Task::PromiseResolution { promise, callbacks }) => {
6506+
Some(Task::Resolution { promise, callbacks }) => {
65116507
for (callback, new_promise) in callbacks {
65126508
// Call the callback and resolve the new promise with the result
65136509
match evaluate_expr(
@@ -6526,7 +6522,7 @@ pub fn run_event_loop() -> Result<(), JSError> {
65266522
}
65276523
}
65286524
}
6529-
Some(Task::PromiseRejection { promise, callbacks }) => {
6525+
Some(Task::Rejection { promise, callbacks }) => {
65306526
for (callback, new_promise) in callbacks {
65316527
// Call the callback and resolve the new promise with the result
65326528
match evaluate_expr(

src/js_function.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ pub fn handle_global_function(func_name: &str, args: &[Expr], env: &JSObjectData
374374
if let Some(promise_val) = crate::core::obj_get_value(env, &promise_key)? {
375375
if let Value::Promise(promise_rc) = &*promise_val.borrow() {
376376
// Queue asynchronous resolution task
377-
crate::core::queue_task(crate::core::Task::PromiseResolve {
377+
crate::core::queue_task(crate::core::Task::Resolve {
378378
promise: promise_rc.clone(),
379379
value,
380380
});
@@ -405,7 +405,7 @@ pub fn handle_global_function(func_name: &str, args: &[Expr], env: &JSObjectData
405405
if let Some(promise_val) = crate::core::obj_get_value(env, &promise_key)? {
406406
if let Value::Promise(promise_rc) = &*promise_val.borrow() {
407407
// Queue asynchronous rejection task
408-
crate::core::queue_task(crate::core::Task::PromiseReject {
408+
crate::core::queue_task(crate::core::Task::Reject {
409409
promise: promise_rc.clone(),
410410
reason,
411411
});

src/js_regexp.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -358,20 +358,3 @@ pub(crate) fn handle_regexp_method(
358358
}),
359359
}
360360
}
361-
362-
/// Create the RegExp constructor function
363-
pub fn make_regexp_constructor() -> Result<JSObjectDataPtr, JSError> {
364-
let regexp_ctor = Rc::new(RefCell::new(JSObjectData::new()));
365-
obj_set_value(&regexp_ctor, "prototype", Value::Object(make_regexp_prototype()?))?;
366-
Ok(regexp_ctor)
367-
}
368-
369-
/// Create the RegExp prototype object
370-
pub fn make_regexp_prototype() -> Result<JSObjectDataPtr, JSError> {
371-
let proto = Rc::new(RefCell::new(JSObjectData::new()));
372-
obj_set_value(&proto, "constructor", Value::Function("RegExp".to_string()))?;
373-
obj_set_value(&proto, "exec", Value::Function("RegExp.prototype.exec".to_string()))?;
374-
obj_set_value(&proto, "test", Value::Function("RegExp.prototype.test".to_string()))?;
375-
obj_set_value(&proto, "toString", Value::Function("RegExp.prototype.toString".to_string()))?;
376-
Ok(proto)
377-
}

src/lib.rs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,32 @@
22
// pub mod libregexp;
33
// pub mod libunicode;
44
// pub mod libunicode_table;
5-
pub mod core;
6-
pub mod error;
7-
pub mod js_array;
8-
pub mod js_class;
9-
pub mod js_console;
10-
pub mod js_date;
11-
pub mod js_function;
12-
pub mod js_json;
13-
pub mod js_math;
14-
pub mod js_number;
15-
pub mod js_object;
16-
pub mod js_os;
17-
pub mod js_regexp;
18-
pub mod js_std;
19-
pub mod js_string;
20-
pub mod sprintf;
21-
pub mod tmpfile;
5+
pub(crate) mod core;
6+
pub(crate) mod error;
7+
pub(crate) mod js_array;
8+
pub(crate) mod js_class;
9+
pub(crate) mod js_console;
10+
pub(crate) mod js_date;
11+
pub(crate) mod js_function;
12+
pub(crate) mod js_json;
13+
pub(crate) mod js_math;
14+
pub(crate) mod js_number;
15+
pub(crate) mod js_object;
16+
pub(crate) mod js_os;
17+
pub(crate) mod js_regexp;
18+
pub(crate) mod js_std;
19+
pub(crate) mod js_string;
20+
pub(crate) mod sprintf;
21+
pub(crate) mod tmpfile;
22+
23+
pub use core::{
24+
evaluate_script, evaluate_script_async, get_prop_env, obj_get_value, tokenize, JSClassDef, JSObject, JSStackFrame, JSString, JSValue,
25+
JS_DefinePropertyValue, JS_DupValue, JS_Eval, JS_FreeContext, JS_FreeRuntime, JS_FreeValue, JS_GetProperty, JS_NewContext,
26+
JS_NewObject, JS_NewRuntime, JS_NewString, JS_SetProperty, Value,
27+
};
28+
pub use core::{
29+
JS_FLOAT64_NAN, JS_GC_OBJ_TYPE_ASYNC_FUNCTION, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, JS_GC_OBJ_TYPE_JS_CONTEXT, JS_GC_OBJ_TYPE_JS_OBJECT,
30+
JS_GC_OBJ_TYPE_SHAPE, JS_GC_OBJ_TYPE_VAR_REF, JS_TAG_BOOL, JS_TAG_CATCH_OFFSET, JS_TAG_FLOAT64, JS_TAG_INT, JS_TAG_NULL, JS_TAG_OBJECT,
31+
JS_TAG_SHORT_BIG_INT, JS_TAG_STRING, JS_TAG_STRING_ROPE, JS_TAG_UNDEFINED, JS_UNINITIALIZED,
32+
};
33+
pub use error::JSError;

tests/array_literals.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use javascript::core::evaluate_script;
2-
use javascript::core::Value;
1+
use javascript::evaluate_script;
2+
use javascript::Value;
33

44
// Initialize logger for this integration test binary so `RUST_LOG` is honored.
55
// Using `ctor` ensures initialization runs before tests start.

tests/async_await_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use javascript::core::{evaluate_script, obj_get_value, Value};
1+
use javascript::{evaluate_script, obj_get_value, Value};
22

33
// Initialize logger for this integration test binary so `RUST_LOG` is honored.
44
// Using `ctor` ensures initialization runs before tests start.

tests/basic_arithmetic.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use javascript::core::evaluate_script;
2-
use javascript::core::Value;
1+
use javascript::evaluate_script;
2+
use javascript::Value;
33

44
// Initialize logger for this integration test binary so `RUST_LOG` is honored.
55
// Using `ctor` ensures initialization runs before tests start.

0 commit comments

Comments
 (0)