use anyhow::{Context, Result};
use ruchy::frontend::ast::Expr;
use ruchy::{Parser as RuchyParser, Transpiler};
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum VmMode {
Ast,
Bytecode,
}
pub fn handle_run_command(file: &Path, verbose: bool, vm_mode: VmMode) -> Result<()> {
log_run_start(file, verbose);
if verbose {
println!("Execution mode: {:?}", vm_mode);
}
let source = if file.to_str() == Some("-") {
use std::io::Read;
let mut input = String::new();
std::io::stdin().read_to_string(&mut input)?;
input
} else {
super::read_file_with_context(file)?
};
let mut parser = RuchyParser::new(&source);
let ast = match parser.parse() {
Ok(ast) => ast,
Err(e) => {
eprintln!("✗ Syntax error: {e}");
eprintln!("Error: Syntax error: {e}");
std::process::exit(1);
}
};
match vm_mode {
VmMode::Ast => {
let mut repl = super::create_repl()?;
match repl.eval(&source) {
Ok(_result) => {
match repl.eval("main()") {
Ok(_) => Ok(()),
Err(e) => {
eprintln!("Error: {e}");
std::process::exit(1);
}
}
}
Err(e) => {
eprintln!("Error: {e}");
std::process::exit(1);
}
}
}
VmMode::Bytecode => {
use ruchy::runtime::bytecode::{Compiler, VM};
let mut compiler = Compiler::new("main".to_string());
if let Err(e) = compiler.compile_expr(&ast) {
eprintln!("✗ Compilation error: {}", e);
eprintln!("Error: Compilation error: {}", e);
std::process::exit(1);
}
let chunk = compiler.finalize();
let mut vm = VM::new();
match vm.execute(&chunk) {
Ok(_result) => {
Ok(())
}
Err(e) => {
eprintln!("✗ VM execution error: {}", e);
eprintln!("Error: VM execution error: {}", e);
std::process::exit(1);
}
}
}
}
}
fn log_run_start(file: &Path, verbose: bool) {
if verbose {
eprintln!("Running file: {}", file.display());
}
}
#[allow(dead_code)]
pub fn transpile_for_execution(ast: &Expr, file: &Path) -> Result<String> {
let mut transpiler = Transpiler::new();
transpiler
.transpile_to_program_with_context(ast, Some(file))
.map(|tokens| tokens.to_string())
.with_context(|| "Failed to transpile to Rust")
}
#[allow(dead_code)]
pub fn prepare_compilation(
rust_code: &str,
verbose: bool,
) -> Result<(tempfile::NamedTempFile, PathBuf)> {
let temp_source =
tempfile::NamedTempFile::new().with_context(|| "Failed to create temporary file")?;
fs::write(temp_source.path(), rust_code).with_context(|| "Failed to write temporary file")?;
if verbose {
eprintln!("Temporary Rust file: {}", temp_source.path().display());
eprintln!("Compiling and running...");
}
let unique_id = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let binary_path = std::env::temp_dir().join(format!(
"ruchy_temp_bin_{}_{}",
std::process::id(),
unique_id
));
Ok((temp_source, binary_path))
}
#[allow(dead_code)]
pub fn compile_rust_code(source_path: &Path, binary_path: &Path) -> Result<()> {
let output = std::process::Command::new("rustc")
.arg("--edition=2018")
.arg("--crate-name=ruchy_temp")
.arg("-o")
.arg(binary_path)
.arg(source_path)
.output()
.with_context(|| "Failed to run rustc")?;
if !output.status.success() {
eprintln!("Compilation failed:");
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
return Err(anyhow::anyhow!("Compilation failed"));
}
Ok(())
}
#[allow(dead_code)]
pub fn execute_binary(binary_path: &Path) -> Result<()> {
let run_output = std::process::Command::new(binary_path)
.output()
.with_context(|| "Failed to run compiled binary")?;
print!("{}", String::from_utf8_lossy(&run_output.stdout));
if !run_output.stderr.is_empty() {
eprint!("{}", String::from_utf8_lossy(&run_output.stderr));
}
if !run_output.status.success() {
return Err(anyhow::anyhow!(
"Program exited with code {}",
run_output.status.code().unwrap_or(1)
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_vm_mode_values() {
assert_eq!(VmMode::Ast, VmMode::Ast);
assert_eq!(VmMode::Bytecode, VmMode::Bytecode);
assert_ne!(VmMode::Ast, VmMode::Bytecode);
}
#[test]
fn test_vm_mode_debug() {
let ast_debug = format!("{:?}", VmMode::Ast);
let bytecode_debug = format!("{:?}", VmMode::Bytecode);
assert!(ast_debug.contains("Ast"));
assert!(bytecode_debug.contains("Bytecode"));
}
#[test]
fn test_vm_mode_clone() {
let mode = VmMode::Ast;
let cloned = mode;
assert_eq!(mode, cloned);
}
#[test]
fn test_log_run_start_verbose() {
let file = Path::new("test.ruchy");
log_run_start(file, true);
log_run_start(file, false);
}
#[test]
fn test_vm_mode_copy() {
let mode = VmMode::Bytecode;
let copied = mode;
assert_eq!(mode, copied);
}
#[test]
fn test_vm_mode_all_variants() {
let modes = [VmMode::Ast, VmMode::Bytecode];
assert_eq!(modes.len(), 2);
}
#[test]
fn test_log_run_start_various_paths() {
let paths = [
Path::new("simple.ruchy"),
Path::new("path/to/file.ruchy"),
Path::new("/absolute/path.ruchy"),
];
for path in paths {
log_run_start(path, true);
log_run_start(path, false);
}
}
#[test]
fn test_transpile_for_execution_nonexistent() {
use ruchy::frontend::ast::{Expr, ExprKind, Literal, Span};
let ast = Expr {
kind: ExprKind::Literal(Literal::Integer(42, None)),
span: Span::new(0, 0),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
let result = transpile_for_execution(&ast, Path::new("/test.ruchy"));
let _ = result;
}
#[test]
fn test_prepare_compilation_basic() {
let result = prepare_compilation("fn main() {}", false);
if let Ok((temp_source, binary_path)) = result {
assert!(temp_source.path().exists());
let _ = binary_path;
}
}
#[test]
fn test_prepare_compilation_verbose() {
let result = prepare_compilation("fn main() { println!(\"test\"); }", true);
let _ = result;
}
#[test]
fn test_compile_rust_code_invalid() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("invalid.rs");
std::fs::write(&source_path, "invalid rust code !!!").unwrap();
let binary_path = temp_dir.path().join("output");
let result = compile_rust_code(&source_path, &binary_path);
assert!(result.is_err());
}
#[test]
fn test_execute_binary_nonexistent() {
let result = execute_binary(Path::new("/nonexistent/binary"));
assert!(result.is_err());
}
#[test]
fn test_vm_mode_pattern_matching() {
let mode = VmMode::Ast;
let msg = match mode {
VmMode::Ast => "ast",
VmMode::Bytecode => "bytecode",
};
assert_eq!(msg, "ast");
}
#[test]
fn test_vm_mode_bytecode_pattern() {
let mode = VmMode::Bytecode;
let msg = match mode {
VmMode::Ast => "ast",
VmMode::Bytecode => "bytecode",
};
assert_eq!(msg, "bytecode");
}
#[test]
fn test_log_run_start_unicode_path() {
let path = Path::new("日本語/test.ruchy");
log_run_start(path, true);
log_run_start(path, false);
}
#[test]
fn test_log_run_start_empty_path() {
let path = Path::new("");
log_run_start(path, true);
log_run_start(path, false);
}
}