use crate::consts;
use crate::instruction::Instruction;
use crate::typedef::*;
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use semver::Version;
use serde::{Deserialize, Serialize};
use std::{
fs,
io::{Read, Write},
path::Path,
};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Program {
target_version: Version,
system_id: String,
instructions: Vec<Instruction>,
mem_pages: Option<u8>,
entry_point: Address,
}
impl Program {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Program> {
let gz_buf = fs::read(path)?;
let res = Self::from_slice(&gz_buf)?;
Ok(res)
}
pub fn from_slice(vec: &[u8]) -> Result<Program> {
let mut decoder = GzDecoder::new(vec);
let mut msgpack_buf = Vec::new();
decoder.read_to_end(&mut msgpack_buf)?;
rmp_serde::from_slice(msgpack_buf.as_slice()).map_err(|e| format_err!("Error: {}", e))
}
pub fn to_vec(&self) -> Result<Vec<u8>> {
let msgpack_buf = rmp_serde::to_vec(&self)?;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(msgpack_buf.as_slice())?;
let gz_buf = encoder.finish()?;
Ok(gz_buf)
}
pub fn save_as<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let gz_buf = self.to_vec()?;
fs::write(path, gz_buf)?;
Ok(())
}
pub fn target_version(&self) -> &Version {
&self.target_version
}
pub fn system_id(&self) -> &String {
&self.system_id
}
pub fn instructions(&self) -> &Vec<Instruction> {
&self.instructions
}
pub fn mem_pages(&self) -> Option<u8> {
self.mem_pages
}
pub fn entry_point(&self) -> Address {
self.entry_point
}
}
pub struct ProgramBuilder {
system_id: String,
instructions: Vec<Instruction>,
mem_pages: Option<u8>,
entry_point: Address,
}
impl ProgramBuilder {
pub fn new(system_id: &str) -> ProgramBuilder {
ProgramBuilder {
system_id: system_id.into(),
entry_point: 0,
instructions: Vec::new(),
mem_pages: None,
}
}
pub fn instructions(mut self, instructions: Vec<Instruction>) -> ProgramBuilder {
self.instructions = instructions;
self
}
pub fn entry_point(mut self, entry_point: Address) -> ProgramBuilder {
self.entry_point = entry_point;
self
}
pub fn mem_pages(mut self, mem_pages: u8) -> ProgramBuilder {
self.mem_pages = Some(mem_pages);
self
}
#[cfg(test)]
pub(crate) fn gen_with_version(&self, version: &str) -> Program {
Program {
target_version: Version::parse(version).expect("unable to parse version"),
system_id: self.system_id.clone(),
instructions: self.instructions.clone(),
mem_pages: self.mem_pages,
entry_point: self.entry_point,
}
}
pub fn gen(&self) -> Program {
Program {
target_version: (*consts::VERSION).clone(),
system_id: self.system_id.clone(),
instructions: self.instructions.clone(),
mem_pages: self.mem_pages,
entry_point: self.entry_point,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{distributions::Standard, thread_rng, Rng};
use std::path::PathBuf;
use tempfile::TempDir;
fn gen_dir() -> TempDir {
tempfile::tempdir().expect("unable to create temporary directory")
}
#[test]
fn save_and_load() {
let rng = thread_rng();
let tmp_dir = gen_dir();
let file_name = tmp_dir
.path()
.join(PathBuf::from("test").with_extension(ROM_FILE_EXTENSION));
let program = ProgramBuilder::new("bogus_system")
.mem_pages(1)
.instructions(rng.sample_iter(&Standard).take(100).collect())
.gen_with_version("0.0.0");
program.save_as(&file_name).unwrap();
let loaded_program = Program::from_file(&file_name).unwrap();
assert_eq!(program.target_version, loaded_program.target_version);
assert_eq!(program.system_id, loaded_program.system_id);
assert_eq!(program.mem_pages, loaded_program.mem_pages);
}
}