use javascript::*;
#[derive(clap::Parser)]
#[command(name = "js", version, about = "JavaScript Rust Interpreter")]
struct Cli {
#[arg(short, long)]
eval: Option<String>,
file: Option<std::path::PathBuf>,
}
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
env_logger::init();
#[cfg(windows)]
{
let builder = std::thread::Builder::new().stack_size(8 * 1024 * 1024);
let handler = builder.spawn(run_main)?;
handler.join().unwrap()
}
#[cfg(unix)]
run_main()
}
fn run_main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let cli = <Cli as clap::Parser>::parse();
let script_content = if let Some(script) = cli.eval {
script
} else if let Some(ref file) = cli.file {
match read_script_file(file) {
Ok(content) => content,
Err(e) => {
eprintln!("Error reading file {}: {}", file.display(), e.user_message());
std::process::exit(1);
}
}
} else {
run_persistent_repl()?;
return Ok(());
};
match evaluate_script(script_content, cli.file.as_ref()) {
Ok(result) => println!("{result}"),
Err(err) => {
if let Some(file_path) = cli.file.as_ref() {
let msg = err.message();
if let (Some(line), Some(col)) = (err.js_line(), err.js_column()) {
eprintln!("{} at file: {}:{}:{}", msg, file_path.display(), line, col);
} else {
eprintln!("{} at file: {}", msg, file_path.display());
}
} else {
eprintln!("{}", err.user_message());
}
let stack = err.stack();
if !stack.is_empty() {
eprintln!("Stack trace:");
for frame in stack {
eprintln!(" at {}", frame);
}
}
std::process::exit(1);
}
}
Ok(())
}
#[allow(clippy::println_empty_string)]
fn run_persistent_repl() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
use rustyline::Editor;
use rustyline::error::ReadlineError;
use std::path::PathBuf;
let ver = clap::crate_version!();
println!("JavaScript Interpreter REPL (persistent environment) v{ver}. Type 'exit' or Ctrl-D to quit.");
let mut rl = match Editor::<(), rustyline::history::FileHistory>::new() {
Ok(e) => e,
Err(err) => {
eprintln!("Failed to initialize line editor: {err}");
std::process::exit(1);
}
};
let history_path: Option<PathBuf> = std::env::var("HOME").ok().map(|h| PathBuf::from(h).join(".js_repl_history"));
if let Some(ref p) = history_path {
rl.load_history(p)?;
}
let repl = Repl::new();
let mut buffer = String::new();
loop {
let prompt = if buffer.is_empty() {
"\x1b[1;32mjs> \x1b[0m"
} else {
"\x1b[1;33m... \x1b[0m"
};
match rl.readline(prompt) {
Ok(line) => {
let trimmed = line.trim();
if trimmed == "exit" || trimmed == ".exit" {
break;
}
if buffer.is_empty() {
buffer = line.clone();
} else {
buffer.push('\n');
buffer.push_str(&line);
}
if !Repl::is_complete_input(&buffer) {
continue;
}
if buffer.trim().is_empty() {
buffer.clear();
continue;
}
rl.add_history_entry(buffer.clone())?;
match repl.eval(&buffer) {
Ok(val) => println!("{val}"),
Err(e) => {
eprintln!("{}", e.user_message());
let stack = e.stack();
if !stack.is_empty() {
eprintln!("Stack trace:");
for frame in stack {
eprintln!(" at {}", frame);
}
}
}
}
buffer.clear();
}
Err(ReadlineError::Interrupted) => {
println!("");
buffer.clear();
continue;
}
Err(ReadlineError::Eof) => {
println!("Goodbye");
break;
}
Err(err) => {
eprintln!("Readline error: {err}");
break;
}
}
}
if let Some(ref p) = history_path {
rl.save_history(p)?;
}
Ok(())
}