feat: 实现 CLI 程序并支持 AST 解析

This commit is contained in:
2026-06-05 01:41:00 +08:00
parent 49ec2c7224
commit cea52a6d8d
3 changed files with 180 additions and 2 deletions
+115 -2
View File
@@ -1,3 +1,116 @@
fn main() {
println!("OxideJS CLI - not yet implemented");
use std::fs;
use std::process::ExitCode;
use ansi_term::Colour::Red;
use clap::{Parser, Subcommand};
use oxide_parser::Allocator;
#[derive(Parser)]
#[command(name = "oxide", version, about = "OxideJS - Rust JavaScript engine")]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
#[arg(short, long, global = true)]
verbose: bool,
#[arg(short, long, global = true)]
quiet: bool,
}
#[derive(Subcommand)]
enum Commands {
Eval { code: String },
Run { file: String },
Bench { suite: Option<String> },
Test { suite: Option<String> },
}
fn main() -> ExitCode {
let cli = Cli::parse();
match cli.command {
Some(Commands::Eval { code }) => eval(&code),
Some(Commands::Run { file }) => run(&file),
Some(Commands::Bench { .. }) => not_implemented("bench"),
Some(Commands::Test { .. }) => not_implemented("test"),
None => repl(),
}
}
fn eval(code: &str) -> ExitCode {
let allocator = Allocator::default();
match oxide_parser::parse(&allocator, code) {
Ok(program) => {
println!("{program:#?}");
ExitCode::SUCCESS
}
Err(errors) => {
for err in &errors {
eprintln!("{}", Red.paint(err.to_string()));
}
ExitCode::FAILURE
}
}
}
fn run(file: &str) -> ExitCode {
match fs::read_to_string(file) {
Ok(source) => eval(&source),
Err(err) => {
eprintln!("{}", Red.paint(format!("Cannot read {file}: {err}")));
ExitCode::FAILURE
}
}
}
fn repl() -> ExitCode {
use rustyline::error::ReadlineError;
use rustyline::DefaultEditor;
let mut rl = match DefaultEditor::new() {
Ok(editor) => editor,
Err(err) => {
eprintln!("{}", Red.paint(format!("Failed to start REPL: {err}")));
return ExitCode::FAILURE;
}
};
loop {
match rl.readline("oxide> ") {
Ok(line) => {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if trimmed == ".exit" || trimmed == ".quit" {
println!("exit");
return ExitCode::SUCCESS;
}
rl.add_history_entry(trimmed).ok();
eval(trimmed);
}
Err(ReadlineError::Interrupted) => {
println!("^C");
return ExitCode::SUCCESS;
}
Err(ReadlineError::Eof) => {
println!("exit");
return ExitCode::SUCCESS;
}
Err(err) => {
eprintln!("{}", Red.paint(format!("REPL error: {err}")));
return ExitCode::FAILURE;
}
}
}
}
fn not_implemented(command: &str) -> ExitCode {
use ansi_term::Colour::Yellow;
eprintln!(
"{}",
Yellow.paint(format!("'{command}' is not yet implemented"))
);
ExitCode::SUCCESS
}
+64
View File
@@ -0,0 +1,64 @@
use std::process::Command;
#[test]
fn eval_simple_expression() {
let output = Command::new("cargo")
.args(["run", "--", "eval", "1 + 2"])
.output()
.expect("failed to run oxide eval");
assert!(output.status.success(), "eval 1+2 should exit 0");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("Program"),
"output should contain AST node: {stdout}"
);
}
#[test]
fn eval_syntax_error() {
let output = Command::new("cargo")
.args(["run", "--", "eval", "function("])
.output()
.expect("failed to run oxide eval");
assert!(
!output.status.success(),
"syntax error should exit non-zero"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.is_empty(),
"syntax error should produce stderr output"
);
}
#[test]
fn run_file() {
let fixture = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/hello.js");
let output = Command::new("cargo")
.args(["run", "--", "run", fixture])
.output()
.expect("failed to run oxide run");
assert!(output.status.success(), "run hello.js should exit 0");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("Program"),
"run output should contain AST node"
);
}
#[test]
fn bench_not_implemented() {
let output = Command::new("cargo")
.args(["run", "--", "bench"])
.output()
.expect("failed to run oxide bench");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("not yet implemented"),
"bench should print not-implemented message"
);
}
+1
View File
@@ -0,0 +1 @@
1 + 2