use std::error::Error;
use std::fmt;
use std::fmt::Write;
use crate::checksum::checksum;
use crate::record::Record;
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
pub enum WriterError {
DataExceedsMaximumLength(usize),
MissingEndOfFileRecord,
MultipleEndOfFileRecords(usize),
SynthesisFailed,
}
impl Error for WriterError {}
impl fmt::Display for WriterError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
WriterError::DataExceedsMaximumLength(bytes) => {
write!(f, "record has {} bytes (max 255)", bytes)
}
WriterError::MissingEndOfFileRecord => {
write!(f, "object is missing end of file record")
}
WriterError::MultipleEndOfFileRecords(eofs) => {
write!(f, "object contains {} end of file records", eofs)
}
WriterError::SynthesisFailed => {
write!(f, "unable to write string representation of record")
}
}
}
}
impl Record {
pub fn to_record_string(&self) -> Result<String, WriterError> {
match self {
Record::Data { offset, value } => format_record(self.record_type(), *offset, value),
Record::EndOfFile => format_record(self.record_type(), 0x0000, &[]),
Record::ExtendedSegmentAddress(segment_address) => format_record(
self.record_type(),
0x0000,
&[
((segment_address & 0xFF00) >> 8) as u8,
(segment_address & 0x00FF) as u8,
],
),
Record::StartSegmentAddress { cs, ip } => format_record(
self.record_type(),
0x0000,
&[
((cs & 0xFF00) >> 8) as u8,
(cs & 0x00FF) as u8,
((ip & 0xFF00) >> 8) as u8,
(ip & 0x00FF) as u8,
],
),
Record::ExtendedLinearAddress(linear_address) => format_record(
self.record_type(),
0x0000,
&[
((linear_address & 0xFF00) >> 8) as u8,
(linear_address & 0x00FF) as u8,
],
),
Record::StartLinearAddress(address) => format_record(
self.record_type(),
0x0000,
&[
((address & 0xFF00_0000) >> 24) as u8,
((address & 0x00FF_0000) >> 16) as u8,
((address & 0x0000_FF00) >> 8) as u8,
(address & 0x0000_00FF) as u8,
],
),
}
}
}
fn format_record<T>(record_type: u8, address: u16, input: T) -> Result<String, WriterError>
where
T: AsRef<[u8]>,
{
let data = input.as_ref();
if data.len() > 0xFF {
return Err(WriterError::DataExceedsMaximumLength(data.len()));
}
let data_length = 1 + 2 + 1 + data.len() + 1;
let mut data_region = Vec::<u8>::with_capacity(data_length);
data_region.push(data.len() as u8);
data_region.push(((address & 0xFF00) >> 8) as u8);
data_region.push((address & 0x00FF) as u8);
data_region.push(record_type);
data_region.extend_from_slice(data);
let checksum = checksum(data_region.as_slice());
data_region.push(checksum);
let result_length = 1 + (2 * data_length);
let mut result = String::with_capacity(result_length);
result.push(':');
data_region.iter().try_fold(result, |mut acc, byte| {
write!(&mut acc, "{:02X}", byte)
.map_err(|_| WriterError::SynthesisFailed)
.map(|_| acc)
})
}
pub fn create_object_file_representation(records: &[Record]) -> Result<String, WriterError> {
if let Some(Record::EndOfFile) = records.last() {
} else {
return Err(WriterError::MissingEndOfFileRecord);
}
let eof_record_count = records
.iter()
.filter(|x| {
if let Record::EndOfFile = x {
true
} else {
false
}
})
.count();
if eof_record_count > 1 {
return Err(WriterError::MultipleEndOfFileRecords(eof_record_count));
}
records.iter().try_fold(String::new(), |mut acc, record| {
acc.push_str(&record.to_record_string()?);
acc.push_str("\n");
Ok(acc)
})
}