use crate::assembler::AssemblyOutput;
use clap::ValueEnum;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Read, Write};
use std::path::Path;
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum OutputFormat {
Binary,
Hex,
Text,
}
pub fn read_output(path: &Path, format: OutputFormat) -> io::Result<AssemblyOutput> {
match format {
OutputFormat::Binary => read_binary_output(path),
OutputFormat::Hex => read_hex_output(path),
OutputFormat::Text => read_text_output(path),
}
}
pub fn read_binary_output(path: &Path) -> io::Result<AssemblyOutput> {
let mut file = File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
if buffer.len() % 4 != 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Binary file size is not a multiple of 4 bytes",
));
}
let mut code = Vec::with_capacity(buffer.len() / 4);
for chunk in buffer.chunks(4) {
let word = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
code.push(word);
}
Ok(AssemblyOutput {
code,
size: buffer.len(),
start_address: 0, })
}
pub fn read_hex_output(path: &Path) -> io::Result<AssemblyOutput> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut bytes = Vec::new();
let mut start_address = None;
for line_result in reader.lines() {
let line = line_result?;
if !line.starts_with(':') {
continue; }
let hex_data = &line[1..];
if hex_data == "00000001FF" {
break;
}
let count = usize::from_str_radix(&hex_data[0..2], 16)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let address = u32::from_str_radix(&hex_data[2..6], 16)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
if start_address.is_none() {
start_address = Some(address);
}
let data_start = 8;
for i in 0..count {
let pos = data_start + i * 2;
if pos + 2 > hex_data.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Record too short for declared byte count",
));
}
let byte = u8::from_str_radix(&hex_data[pos..pos + 2], 16)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
bytes.push(byte);
}
}
let mut code = Vec::with_capacity((bytes.len() + 3) / 4);
for chunk in bytes.chunks(4) {
let mut word_bytes = [0u8; 4];
for (i, &byte) in chunk.iter().enumerate() {
word_bytes[i] = byte;
}
code.push(u32::from_le_bytes(word_bytes));
}
Ok(AssemblyOutput {
code,
size: bytes.len(),
start_address: start_address.unwrap_or(0),
})
}
pub fn read_text_output(path: &Path) -> io::Result<AssemblyOutput> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut code = Vec::new();
let mut start_address = None;
for line_result in reader.lines() {
let line = line_result?;
let parts: Vec<&str> = line.split(':').collect();
if parts.len() != 2 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid text format line: {}", line),
));
}
let address_str = parts[0].trim();
let word_str = parts[1].trim();
let address = u32::from_str_radix(address_str, 16)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let word = u32::from_str_radix(word_str, 16)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
if start_address.is_none() {
start_address = Some(address);
}
code.push(word);
}
Ok(AssemblyOutput {
code: code.clone(),
size: code.len() * 4, start_address: start_address.unwrap_or(0),
})
}
pub fn read_file(path: &Path) -> io::Result<String> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
pub fn write_output(
assembled: &AssemblyOutput,
path: &Path,
format: OutputFormat,
) -> io::Result<()> {
match format {
OutputFormat::Binary => write_binary_output(assembled, path),
OutputFormat::Hex => write_hex_output(assembled, path),
OutputFormat::Text => write_text_output(assembled, path),
}
}
pub fn write_binary_output(assembled: &AssemblyOutput, path: &Path) -> io::Result<()> {
let mut file = File::create(path)?;
for word in &assembled.code {
file.write_all(&word.to_le_bytes())?;
}
Ok(())
}
pub fn write_hex_output(assembled: &AssemblyOutput, path: &Path) -> io::Result<()> {
let mut file = File::create(path)?;
let mut address = assembled.start_address;
for chunk in assembled.code.chunks(4) {
let mut line = Vec::new();
for word in chunk {
line.extend_from_slice(&word.to_le_bytes());
}
if !line.is_empty() {
let mut checksum = line.len() as u8; checksum = checksum.wrapping_add((address >> 8) as u8); checksum = checksum.wrapping_add(address as u8);
write!(file, ":{:02X}{:04X}00", line.len(), address)?;
for byte in &line {
write!(file, "{:02X}", byte)?;
checksum = checksum.wrapping_add(*byte);
}
write!(file, "{:02X}\n", (0u8).wrapping_sub(checksum))?;
}
address += (chunk.len() * 4) as u32;
}
writeln!(file, ":00000001FF")?;
Ok(())
}
pub fn write_text_output(assembled: &AssemblyOutput, path: &Path) -> io::Result<()> {
let mut file = File::create(path)?;
let mut address = assembled.start_address;
for word in &assembled.code {
writeln!(file, "{:08X}: {:08X}", address, word)?;
address += 4; }
Ok(())
}