use checksmix::MMixAssembler;
use clap::Parser;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process;
use tracing_subscriber::{EnvFilter, fmt};
const BROKEN_PIPE_STATUS: i32 = 141;
fn stdout_failed(err: &io::Error) -> ! {
if err.kind() == io::ErrorKind::BrokenPipe {
process::exit(BROKEN_PIPE_STATUS);
}
eprintln!("error writing to stdout: {err}");
process::exit(1);
}
macro_rules! outln {
($($arg:tt)*) => {
if let Err(err) = writeln!(io::stdout(), $($arg)*) {
stdout_failed(&err);
}
};
}
#[derive(Parser, Debug)]
#[command(
name = "mmixasm",
about = "Assemble MMIX .mms files into .mmo object code",
version,
author
)]
struct Cli {
#[arg(value_name = "INPUT.mms", required = true, num_args = 1..)]
inputs: Vec<PathBuf>,
#[arg(short = 'o', long = "output", value_name = "OUTPUT.mmo")]
output: Option<PathBuf>,
}
fn main() {
fmt().with_env_filter(EnvFilter::from_default_env()).init();
let cli = Cli::parse();
let inputs = cli.inputs;
let output_file = cli
.output
.unwrap_or_else(|| inputs[0].with_extension("mmo"));
let reader = |p: &Path| std::fs::read_to_string(p);
let mut sources: Vec<(String, String)> = Vec::new();
for path in &inputs {
let src = fs::read_to_string(path).unwrap_or_else(|err| {
eprintln!("Error reading '{}': {}", path.display(), err);
process::exit(1);
});
let name = path
.to_str()
.map(|s| s.to_string())
.unwrap_or_else(|| path.display().to_string());
let base = path.parent().unwrap_or_else(|| Path::new("."));
let units =
MMixAssembler::resolve_includes(&src, &name, base, &reader).unwrap_or_else(|err| {
eprintln!("{}", err);
process::exit(1);
});
if units.is_empty() {
eprintln!("Error: '{}' contributed no source", path.display());
process::exit(1);
}
sources.extend(units);
}
if sources.len() == 1 {
outln!("Assembling: {}", sources[0].0);
} else {
outln!("Assembling {} inputs:", sources.len());
for (n, _) in &sources {
outln!(" {}", n);
}
}
let (first_name, first_src) = &sources[0];
let mut assembler = MMixAssembler::new(first_src, first_name);
for (n, s) in sources.iter().skip(1) {
assembler.add_source(s, n);
}
if let Err(e) = assembler.parse() {
eprintln!("{}", e);
process::exit(1);
}
for warning in assembler.warnings() {
eprintln!("{}", warning);
}
if assembler.instructions.is_empty() {
eprintln!("Error: No instructions to assemble");
process::exit(1);
}
let object_code = assembler.generate_object_code();
outln!("Generated {} bytes of object code", object_code.len());
fs::write(&output_file, &object_code).unwrap_or_else(|err| {
eprintln!("Error writing '{}': {}", output_file.display(), err);
process::exit(1);
});
outln!("Output written to: {}", output_file.display());
}