use std::fs;
use std::path::PathBuf;
use std::process::ExitCode;
use kz80_prolog::{parse, Program};
fn print_usage() {
eprintln!("kz80_prolog - Prolog compiler for Z80");
eprintln!();
eprintln!("Usage: kz80_prolog [options] [input.pl]");
eprintln!();
eprintln!("Options:");
eprintln!(" -o <file> Output binary file (default: out.bin)");
eprintln!(" --repl Generate on-target REPL binary");
eprintln!(" --ast Print AST and exit");
eprintln!(" --tokens Print tokens and exit");
eprintln!(" -h, --help Show this help");
eprintln!();
eprintln!("Examples:");
eprintln!(" kz80_prolog program.pl Compile to out.bin");
eprintln!(" kz80_prolog -o test.bin test.pl Compile to test.bin");
eprintln!(" kz80_prolog --repl -o repl.bin Generate REPL binary");
eprintln!(" kz80_prolog --ast program.pl Print AST");
}
fn print_ast(program: &Program) {
println!("=== Clauses ===");
for (i, clause) in program.clauses.iter().enumerate() {
println!("Clause {}:", i + 1);
println!(" Head: {:?}", clause.head);
if !clause.body.is_empty() {
println!(" Body:");
for goal in &clause.body {
println!(" {:?}", goal);
}
}
}
if let Some(query) = &program.query {
println!("\n=== Query ===");
for goal in query {
println!(" {:?}", goal);
}
}
}
fn print_tokens(input: &str) {
let mut lexer = kz80_prolog::Lexer::new(input);
println!("=== Tokens ===");
loop {
match lexer.next_token() {
Ok(token) => {
println!("{:?}", token);
if token == kz80_prolog::Token::Eof {
break;
}
}
Err(e) => {
eprintln!("Lexer error: {}", e);
break;
}
}
}
}
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
print_usage();
return ExitCode::from(1);
}
let mut input_file: Option<PathBuf> = None;
let mut output_file = PathBuf::from("out.bin");
let mut print_ast_flag = false;
let mut print_tokens_flag = false;
let mut repl_mode = false;
let mut i = 1;
while i < args.len() {
match args[i].as_str() {
"-h" | "--help" => {
print_usage();
return ExitCode::SUCCESS;
}
"-o" => {
i += 1;
if i >= args.len() {
eprintln!("Error: -o requires an argument");
return ExitCode::from(1);
}
output_file = PathBuf::from(&args[i]);
}
"--ast" => {
print_ast_flag = true;
}
"--tokens" => {
print_tokens_flag = true;
}
"--repl" => {
repl_mode = true;
}
arg if arg.starts_with('-') => {
eprintln!("Unknown option: {}", arg);
return ExitCode::from(1);
}
_ => {
input_file = Some(PathBuf::from(&args[i]));
}
}
i += 1;
}
if repl_mode {
let code = kz80_prolog::repl::generate_repl();
match fs::write(&output_file, &code) {
Ok(()) => {
println!("Generated REPL binary: {}", output_file.display());
println!(" {} bytes", code.len());
}
Err(e) => {
eprintln!("Error writing REPL: {}", e);
return ExitCode::from(1);
}
}
return ExitCode::SUCCESS;
}
let input_file = match input_file {
Some(f) => f,
None => {
eprintln!("Error: No input file specified");
print_usage();
return ExitCode::from(1);
}
};
let input = match fs::read_to_string(&input_file) {
Ok(s) => s,
Err(e) => {
eprintln!("Error reading {}: {}", input_file.display(), e);
return ExitCode::from(1);
}
};
if print_tokens_flag {
print_tokens(&input);
return ExitCode::SUCCESS;
}
let program = match parse(&input) {
Ok(p) => p,
Err(e) => {
eprintln!("Parse error: {}", e);
return ExitCode::from(1);
}
};
if print_ast_flag {
print_ast(&program);
return ExitCode::SUCCESS;
}
let compiled = kz80_prolog::compile::compile_program(&program);
println!("Compiled {} clauses", program.clauses.len());
println!(" {} predicates", compiled.predicates.len());
println!(" {} atoms interned", compiled.atoms.len());
println!(" {} bytes of bytecode", compiled.code.len());
if compiled.query_offset.is_some() {
println!(" Query present at offset 0x{:04X}", compiled.query_offset.unwrap());
}
match kz80_prolog::codegen::generate_rom_file(&compiled, &output_file) {
Ok(()) => {
println!("\nWrote ROM to: {}", output_file.display());
}
Err(e) => {
eprintln!("Error writing ROM: {}", e);
return ExitCode::from(1);
}
}
ExitCode::SUCCESS
}