use crate::persistence::ImportError;
use crate::persistence::export::ExportModel;
use calamine::{Data, Reader, open_workbook_auto};
use k580_core::Cpu8080State;
use std::fs;
use std::path::Path;
pub struct Importers;
impl Importers {
pub fn read_txt(path: impl AsRef<Path>) -> Result<ExportModel, ImportError> {
let raw = fs::read_to_string(path)?;
Self::parse_txt(&raw)
}
pub fn read_xlsx(path: impl AsRef<Path>) -> Result<ExportModel, ImportError> {
let mut workbook = open_workbook_auto(path.as_ref())
.map_err(|e| ImportError::Spreadsheet(e.to_string()))?;
let sheet_name = workbook
.sheet_names()
.first()
.cloned()
.ok_or_else(|| ImportError::Spreadsheet("workbook has no sheets".to_owned()))?;
Self::read_xlsx_sheet_from_workbook(&mut workbook, &sheet_name)
}
pub fn read_xlsx_sheet(
path: impl AsRef<Path>,
sheet_name: &str,
) -> Result<ExportModel, ImportError> {
let mut workbook = open_workbook_auto(path.as_ref())
.map_err(|e| ImportError::Spreadsheet(e.to_string()))?;
Self::read_xlsx_sheet_from_workbook(&mut workbook, sheet_name)
}
pub fn xlsx_sheet_names(path: impl AsRef<Path>) -> Result<Vec<String>, ImportError> {
let workbook = open_workbook_auto(path.as_ref())
.map_err(|e| ImportError::Spreadsheet(e.to_string()))?;
Ok(workbook.sheet_names().to_vec())
}
pub fn txt_section_names(path: impl AsRef<Path>) -> Result<Vec<String>, ImportError> {
let raw = fs::read_to_string(path)?;
Ok(text_sections(&raw)
.into_iter()
.map(|section| section.name)
.collect())
}
pub fn read_txt_section(
path: impl AsRef<Path>,
section_name: &str,
) -> Result<ExportModel, ImportError> {
let raw = fs::read_to_string(path)?;
let section = text_sections(&raw)
.into_iter()
.find(|section| section.name == section_name)
.ok_or_else(|| {
ImportError::Malformed(format!("unknown text section `{section_name}`"))
})?;
Self::parse_txt(§ion.body)
}
fn read_xlsx_sheet_from_workbook<R: std::io::Read + std::io::Seek>(
workbook: &mut calamine::Sheets<R>,
sheet_name: &str,
) -> Result<ExportModel, ImportError> {
let sheet = workbook
.worksheet_range(sheet_name)
.map_err(|e| ImportError::Spreadsheet(e.to_string()))?;
let mut registers: Vec<(String, String)> = Vec::new();
let mut flags: Vec<(String, bool)> = Vec::new();
let mut memory: Vec<(u16, u8)> = Vec::new();
let mut section: Option<Section> = None;
for row in sheet.rows() {
let key = cell_string(row.first());
let value = cell_string(row.get(1));
if key.is_empty() && value.is_empty() {
continue;
}
if key.eq_ignore_ascii_case("Field") && value.eq_ignore_ascii_case("Value") {
section = Some(Section::Registers);
continue;
}
if key.eq_ignore_ascii_case("Flag") && value.eq_ignore_ascii_case("Set") {
section = Some(Section::Flags);
continue;
}
if key.eq_ignore_ascii_case("Address") && value.eq_ignore_ascii_case("Value") {
section = Some(Section::Memory);
continue;
}
match section {
Some(Section::Registers) => registers.push((key, value)),
Some(Section::Flags) => {
let set = parse_bool(&value).ok_or_else(|| {
ImportError::Malformed(format!("invalid flag value `{value}` for `{key}`"))
})?;
flags.push((key, set));
}
Some(Section::Memory) => {
let address = parse_u16_hex(&key).ok_or_else(|| {
ImportError::Malformed(format!("invalid memory address `{key}`"))
})?;
let cell = parse_u8_hex(&value).ok_or_else(|| {
ImportError::Malformed(format!("invalid memory value `{value}`"))
})?;
memory.push((address, cell));
}
None => {
return Err(ImportError::Malformed(format!(
"data row `{key}`/`{value}` outside of any section"
)));
}
}
}
Ok(ExportModel {
registers,
flags,
memory,
})
}
fn parse_txt(text: &str) -> Result<ExportModel, ImportError> {
let mut registers: Vec<(String, String)> = Vec::new();
let mut flags: Vec<(String, bool)> = Vec::new();
let mut memory: Vec<(u16, u8)> = Vec::new();
let mut section: Option<Section> = None;
for raw_line in text.lines() {
let line = raw_line.trim();
if line.is_empty() {
continue;
}
match line {
"[Registers]" => {
section = Some(Section::Registers);
continue;
}
"[Flags]" => {
section = Some(Section::Flags);
continue;
}
"[Memory]" => {
section = Some(Section::Memory);
continue;
}
_ => {}
}
if line.starts_with('[') && line.ends_with(']') {
section = None;
continue;
}
let Some((key, value)) = line.split_once('=') else {
return Err(ImportError::Malformed(format!(
"expected `key=value` line, got `{line}`"
)));
};
let key = key.trim().to_owned();
let value = value.trim().to_owned();
match section {
Some(Section::Registers) => registers.push((key, value)),
Some(Section::Flags) => {
let set = parse_bool(&value).ok_or_else(|| {
ImportError::Malformed(format!("invalid flag value `{value}` for `{key}`"))
})?;
flags.push((key, set));
}
Some(Section::Memory) => {
let address = parse_u16_hex(&key).ok_or_else(|| {
ImportError::Malformed(format!("invalid memory address `{key}`"))
})?;
let cell = parse_u8_hex(&value).ok_or_else(|| {
ImportError::Malformed(format!("invalid memory value `{value}`"))
})?;
memory.push((address, cell));
}
None => {
return Err(ImportError::Malformed(format!(
"data line `{line}` outside of any section"
)));
}
}
}
Ok(ExportModel {
registers,
flags,
memory,
})
}
}
struct TextSection {
name: String,
body: String,
}
fn text_sections(text: &str) -> Vec<TextSection> {
let mut sections = Vec::new();
let mut current: Option<TextSection> = None;
for raw_line in text.lines() {
let line = raw_line.trim();
if let Some(name) = named_text_section(line) {
if let Some(section) = current.take() {
sections.push(section);
}
current = Some(TextSection {
name: name.to_owned(),
body: String::new(),
});
continue;
}
if let Some(section) = current.as_mut() {
section.body.push_str(raw_line);
section.body.push('\n');
}
}
if let Some(section) = current {
sections.push(section);
}
sections
}
fn named_text_section(line: &str) -> Option<&str> {
if !line.starts_with('[') || !line.ends_with(']') {
return None;
}
match line {
"[Registers]" | "[Flags]" | "[Memory]" => None,
_ => Some(&line[1..line.len() - 1]),
}
}
impl ExportModel {
pub fn apply_to(&self, state: &mut Cpu8080State) -> Result<(), ImportError> {
for (name, value) in &self.registers {
match name.as_str() {
"A" => {
state.registers.a = parse_u8_hex(value).ok_or_else(|| reg_err(name, value))?
}
"W" => {
state.registers.w = parse_u8_hex(value).ok_or_else(|| reg_err(name, value))?
}
"Z" => {
state.registers.z = parse_u8_hex(value).ok_or_else(|| reg_err(name, value))?
}
"B" => {
state.registers.b = parse_u8_hex(value).ok_or_else(|| reg_err(name, value))?
}
"C" => {
state.registers.c = parse_u8_hex(value).ok_or_else(|| reg_err(name, value))?
}
"D" => {
state.registers.d = parse_u8_hex(value).ok_or_else(|| reg_err(name, value))?
}
"E" => {
state.registers.e = parse_u8_hex(value).ok_or_else(|| reg_err(name, value))?
}
"H" => {
state.registers.h = parse_u8_hex(value).ok_or_else(|| reg_err(name, value))?
}
"L" => {
state.registers.l = parse_u8_hex(value).ok_or_else(|| reg_err(name, value))?
}
"PC" => state.pc = parse_u16_hex(value).ok_or_else(|| reg_err(name, value))?,
"SP" => state.sp = parse_u16_hex(value).ok_or_else(|| reg_err(name, value))?,
"cycles" => {
state.cycle_count = value.parse::<u64>().map_err(|_| reg_err(name, value))?;
}
_ => {
return Err(ImportError::Malformed(format!("unknown register `{name}`")));
}
}
}
for (name, set) in &self.flags {
match name.as_str() {
"S" => state.flags.sign = *set,
"Z" => state.flags.zero = *set,
"AC" => state.flags.auxiliary_carry = *set,
"P" => state.flags.parity = *set,
"C" | "CY" => state.flags.carry = *set,
_ => {
return Err(ImportError::Malformed(format!("unknown flag `{name}`")));
}
}
}
for (address, value) in &self.memory {
state.memory.write(*address, *value);
}
Ok(())
}
}
#[derive(Clone, Copy)]
enum Section {
Registers,
Flags,
Memory,
}
fn cell_string(cell: Option<&Data>) -> String {
match cell {
Some(Data::String(s)) => s.trim().to_owned(),
Some(Data::Float(f)) => {
if f.fract() == 0.0 {
format!("{}", *f as i64)
} else {
f.to_string()
}
}
Some(Data::Int(i)) => i.to_string(),
Some(Data::Bool(b)) => b.to_string(),
Some(Data::DateTime(d)) => d.to_string(),
Some(Data::DateTimeIso(s)) => s.trim().to_owned(),
Some(Data::DurationIso(s)) => s.trim().to_owned(),
Some(Data::Error(e)) => format!("{e:?}"),
Some(Data::Empty) | None => String::new(),
}
}
fn parse_bool(value: &str) -> Option<bool> {
match value.trim().to_ascii_lowercase().as_str() {
"true" | "1" | "yes" | "set" => Some(true),
"false" | "0" | "no" | "unset" => Some(false),
_ => None,
}
}
fn parse_u8_hex(value: &str) -> Option<u8> {
let v = value
.trim()
.trim_start_matches("0x")
.trim_start_matches("0X");
u8::from_str_radix(v, 16).ok()
}
fn parse_u16_hex(value: &str) -> Option<u16> {
let v = value
.trim()
.trim_start_matches("0x")
.trim_start_matches("0X");
u16::from_str_radix(v, 16).ok()
}
fn reg_err(name: &str, value: &str) -> ImportError {
ImportError::Malformed(format!("invalid value `{value}` for register `{name}`"))
}