mod codegen;
mod lexer;
mod parser;
mod runtime;
use clap::Parser;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::Command;
#[derive(Parser)]
#[command(name = "xbasic64")]
#[command(about = "Compiles 1980s-era BASIC programs to x86-64 executables")]
struct Args {
input: String,
#[arg(short, long)]
output: Option<String>,
#[arg(short = 'S')]
asm_only: bool,
}
fn main() {
let args = Args::parse();
let input_file = &args.input;
let source = match fs::read_to_string(input_file) {
Ok(s) => s,
Err(e) => {
eprintln!("Error reading {}: {}", input_file, e);
std::process::exit(1);
}
};
let mut lexer = lexer::Lexer::new(&source);
let tokens = match lexer.tokenize() {
Ok(t) => t,
Err(e) => {
eprintln!("Lexer error: {}", e);
std::process::exit(1);
}
};
let mut parser = parser::Parser::new(tokens);
let program = match parser.parse() {
Ok(p) => p,
Err(e) => {
eprintln!("Parse error: {}", e);
std::process::exit(1);
}
};
let mut codegen = codegen::CodeGen::default();
let asm = codegen.generate(&program);
let runtime_asm = runtime::generate_runtime();
let full_asm = format!("{}\n{}", asm, runtime_asm);
let input_path = Path::new(&input_file);
let stem = input_path.file_stem().unwrap().to_str().unwrap();
let input_dir = input_path.parent().unwrap_or(Path::new("."));
let exe_file = args.output.unwrap_or_else(|| {
if cfg!(windows) {
input_dir
.join(format!("{}.exe", stem))
.to_string_lossy()
.to_string()
} else {
input_dir.join(stem).to_string_lossy().to_string()
}
});
let exe_path = Path::new(&exe_file);
let exe_dir = exe_path.parent().unwrap_or(Path::new("."));
let exe_stem = exe_path.file_stem().unwrap().to_str().unwrap();
let asm_file = exe_dir
.join(format!("{}.s", exe_stem))
.to_string_lossy()
.to_string();
let obj_file = exe_dir
.join(format!("{}.o", exe_stem))
.to_string_lossy()
.to_string();
match fs::File::create(&asm_file) {
Ok(mut f) => {
if let Err(e) = f.write_all(full_asm.as_bytes()) {
eprintln!("Error writing assembly: {}", e);
std::process::exit(1);
}
}
Err(e) => {
eprintln!("Error creating assembly file: {}", e);
std::process::exit(1);
}
}
if args.asm_only {
println!("Assembly written to {}", asm_file);
return;
}
let as_status = Command::new("as")
.args(["-o", &obj_file, &asm_file])
.status();
match as_status {
Ok(status) if status.success() => {}
Ok(status) => {
eprintln!("Assembler failed with status: {}", status);
std::process::exit(1);
}
Err(e) => {
eprintln!("Failed to run assembler: {}", e);
std::process::exit(1);
}
}
#[allow(unused_mut)] let mut cc_args = vec!["-o", &exe_file, &obj_file, "-lm"];
#[cfg(target_os = "linux")]
cc_args.push("-no-pie");
let cc_status = Command::new("cc").args(&cc_args).status();
match cc_status {
Ok(status) if status.success() => {}
Ok(status) => {
eprintln!("Linker failed with status: {}", status);
std::process::exit(1);
}
Err(e) => {
eprintln!("Failed to run linker: {}", e);
std::process::exit(1);
}
}
let _ = fs::remove_file(&asm_file);
let _ = fs::remove_file(&obj_file);
println!("Compiled {} -> {}", input_file, exe_file);
}