mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 添加编译器集成测试与 CLI 编译命令
This commit is contained in:
Generated
+1
@@ -755,6 +755,7 @@ dependencies = [
|
|||||||
"ansi_term",
|
"ansi_term",
|
||||||
"clap",
|
"clap",
|
||||||
"oxide_api",
|
"oxide_api",
|
||||||
|
"oxide_compiler",
|
||||||
"oxide_parser",
|
"oxide_parser",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
oxide_parser = { path = "../oxide_parser" }
|
oxide_parser = { path = "../oxide_parser" }
|
||||||
|
oxide_compiler = { path = "../oxide_compiler" }
|
||||||
oxide_api = { path = "../oxide_api" }
|
oxide_api = { path = "../oxide_api" }
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
rustyline.workspace = true
|
rustyline.workspace = true
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::process::ExitCode;
|
|||||||
|
|
||||||
use ansi_term::Colour::Red;
|
use ansi_term::Colour::Red;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
use oxide_parser::Allocator;
|
use oxide_parser::Allocator;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@@ -22,6 +23,7 @@ struct Cli {
|
|||||||
enum Commands {
|
enum Commands {
|
||||||
Eval { code: String },
|
Eval { code: String },
|
||||||
Run { file: String },
|
Run { file: String },
|
||||||
|
Compile { file: String },
|
||||||
Bench { suite: Option<String> },
|
Bench { suite: Option<String> },
|
||||||
Test { suite: Option<String> },
|
Test { suite: Option<String> },
|
||||||
}
|
}
|
||||||
@@ -32,6 +34,7 @@ fn main() -> ExitCode {
|
|||||||
match cli.command {
|
match cli.command {
|
||||||
Some(Commands::Eval { code }) => eval(&code),
|
Some(Commands::Eval { code }) => eval(&code),
|
||||||
Some(Commands::Run { file }) => run(&file),
|
Some(Commands::Run { file }) => run(&file),
|
||||||
|
Some(Commands::Compile { file }) => compile(&file),
|
||||||
Some(Commands::Bench { .. }) => not_implemented("bench"),
|
Some(Commands::Bench { .. }) => not_implemented("bench"),
|
||||||
Some(Commands::Test { .. }) => not_implemented("test"),
|
Some(Commands::Test { .. }) => not_implemented("test"),
|
||||||
None => repl(),
|
None => repl(),
|
||||||
@@ -64,6 +67,39 @@ fn run(file: &str) -> ExitCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compile(file: &str) -> ExitCode {
|
||||||
|
let source = match fs::read_to_string(file) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", Red.paint(format!("Cannot read {file}: {err}")));
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let program = match oxide_parser::parse(&allocator, &source) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(errors) => {
|
||||||
|
for err in &errors {
|
||||||
|
eprintln!("{}", Red.paint(err.to_string()));
|
||||||
|
}
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let compiler = Compiler::new();
|
||||||
|
match compiler.compile(&program) {
|
||||||
|
Ok(module) => {
|
||||||
|
print!("{module}");
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{}", Red.paint(err));
|
||||||
|
ExitCode::FAILURE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn repl() -> ExitCode {
|
fn repl() -> ExitCode {
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
use rustyline::DefaultEditor;
|
use rustyline::DefaultEditor;
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
use oxide_compiler::cache::CodeCache;
|
||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_compiler::opcode::{self, OpCode};
|
||||||
|
use oxide_parser::Allocator;
|
||||||
|
|
||||||
|
fn compile_source(source: &str) -> oxide_compiler::module::CompiledModule {
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
|
||||||
|
let compiler = Compiler::new();
|
||||||
|
compiler.compile(&program).expect("compile failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_to_hash(source: &str) -> u64 {
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
|
||||||
|
oxide_compiler::cache::structural_hash(&program)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_simple_expr() {
|
||||||
|
let module = compile_source("1 + 2");
|
||||||
|
|
||||||
|
assert!(!module.bytecode.is_empty());
|
||||||
|
assert!(module.bytecode.len() >= 4);
|
||||||
|
|
||||||
|
let last = opcode::opcode(*module.bytecode.last().unwrap());
|
||||||
|
assert_eq!(last, OpCode::HALT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_constants() {
|
||||||
|
let module = compile_source("42");
|
||||||
|
|
||||||
|
assert!(!module.constants.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
module.constants[0],
|
||||||
|
oxide_compiler::compiler::Constant::Number(42.0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_negation() {
|
||||||
|
let module = compile_source("-5");
|
||||||
|
|
||||||
|
let has_neg = module
|
||||||
|
.bytecode
|
||||||
|
.iter()
|
||||||
|
.any(|&i| opcode::opcode(i) == OpCode::NEG);
|
||||||
|
assert!(has_neg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_multiple_stmts() {
|
||||||
|
let module = compile_source("1; 2; 3;");
|
||||||
|
|
||||||
|
let load_count = module
|
||||||
|
.bytecode
|
||||||
|
.iter()
|
||||||
|
.filter(|&&i| opcode::opcode(i) == OpCode::LOAD_CONST)
|
||||||
|
.count();
|
||||||
|
assert!(load_count >= 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_binary_ops() {
|
||||||
|
let tests = [
|
||||||
|
("3 * 4", OpCode::MUL),
|
||||||
|
("10 / 2", OpCode::DIV),
|
||||||
|
("7 % 3", OpCode::MOD),
|
||||||
|
("5 - 2", OpCode::SUB),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (src, expected_op) in tests {
|
||||||
|
let module = compile_source(src);
|
||||||
|
let has_op = module
|
||||||
|
.bytecode
|
||||||
|
.iter()
|
||||||
|
.any(|&i| opcode::opcode(i) == expected_op);
|
||||||
|
assert!(has_op, "expected {:?} in '{src}'", expected_op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn structural_cache_hit() {
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, "1 + 2").expect("parse failed");
|
||||||
|
let compiler = Compiler::new();
|
||||||
|
let mut cache = CodeCache::new();
|
||||||
|
|
||||||
|
let m1 = cache
|
||||||
|
.get_or_compile(&program, &compiler)
|
||||||
|
.expect("compile failed");
|
||||||
|
let m2 = cache
|
||||||
|
.get_or_compile(&program, &compiler)
|
||||||
|
.expect("compile failed");
|
||||||
|
|
||||||
|
assert_eq!(m1.bytecode, m2.bytecode);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn structural_cache_same_shape() {
|
||||||
|
let hash_a = parse_to_hash("var x = 1 + 2; var y = x;");
|
||||||
|
let hash_b = parse_to_hash("var a = 3 + 4; var b = a;");
|
||||||
|
assert_eq!(hash_a, hash_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn structural_cache_different_shape() {
|
||||||
|
let hash_a = parse_to_hash("1 + 2");
|
||||||
|
let hash_b = parse_to_hash("1 + 2 * 3");
|
||||||
|
assert_ne!(hash_a, hash_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_var_declaration() {
|
||||||
|
let module = compile_source("var x = 42;");
|
||||||
|
assert!(!module.bytecode.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
module.constants[0],
|
||||||
|
oxide_compiler::compiler::Constant::Number(42.0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_return_nothing() {
|
||||||
|
let module = compile_source("function f() { return; }");
|
||||||
|
let last = opcode::opcode(*module.bytecode.last().unwrap());
|
||||||
|
assert_eq!(last, OpCode::HALT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_return_value() {
|
||||||
|
let module = compile_source("function f() { return 42; }");
|
||||||
|
assert!(
|
||||||
|
!module.bytecode.is_empty(),
|
||||||
|
"function declaration should produce bytecode"
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user