1use crate::consts;
2use crate::instruction::Instruction;
3use crate::typedef::*;
4use flate2::{read::GzDecoder, write::GzEncoder, Compression};
5use semver::Version;
6use serde::{Deserialize, Serialize};
7use std::{
8    fs,
9    io::{Read, Write},
10    path::Path,
11};
12
13#[derive(Serialize, Deserialize, Clone, Debug)]
19pub struct Program {
20    target_version: Version,
22    system_id: String,
24    instructions: Vec<Instruction>,
26    mem_pages: Option<u8>,
28    entry_point: Address,
30}
31
32impl Program {
33    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Program> {
35        let gz_buf = fs::read(path)?;
36
37        let res = Self::from_slice(&gz_buf)?;
38
39        Ok(res)
40    }
41
42    pub fn from_slice(vec: &[u8]) -> Result<Program> {
44        let mut decoder = GzDecoder::new(vec);
45        let mut msgpack_buf = Vec::new();
46        decoder.read_to_end(&mut msgpack_buf)?;
47
48        rmp_serde::from_slice(msgpack_buf.as_slice()).map_err(|e| format_err!("Error: {}", e))
49    }
50
51    pub fn to_vec(&self) -> Result<Vec<u8>> {
53        let msgpack_buf = rmp_serde::to_vec(&self)?;
54
55        let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
56        encoder.write_all(msgpack_buf.as_slice())?;
57        let gz_buf = encoder.finish()?;
58
59        Ok(gz_buf)
60    }
61
62    pub fn save_as<P: AsRef<Path>>(&self, path: P) -> Result<()> {
64        let gz_buf = self.to_vec()?;
65
66        fs::write(path, gz_buf)?;
67
68        Ok(())
69    }
70
71    pub fn target_version(&self) -> &Version {
73        &self.target_version
74    }
75    pub fn system_id(&self) -> &String {
77        &self.system_id
78    }
79    pub fn instructions(&self) -> &Vec<Instruction> {
81        &self.instructions
82    }
83    pub fn mem_pages(&self) -> Option<u8> {
85        self.mem_pages
86    }
87    pub fn entry_point(&self) -> Address {
89        self.entry_point
90    }
91}
92
93pub struct ProgramBuilder {
95    system_id: String,
97    instructions: Vec<Instruction>,
99    mem_pages: Option<u8>,
101    entry_point: Address,
103}
104
105impl ProgramBuilder {
106    pub fn new(system_id: &str) -> ProgramBuilder {
108        ProgramBuilder {
109            system_id: system_id.into(),
110            entry_point: 0,
111            instructions: Vec::new(),
112            mem_pages: None,
113        }
114    }
115
116    pub fn instructions(mut self, instructions: Vec<Instruction>) -> ProgramBuilder {
118        self.instructions = instructions;
119        self
120    }
121
122    pub fn entry_point(mut self, entry_point: Address) -> ProgramBuilder {
124        self.entry_point = entry_point;
125        self
126    }
127
128    pub fn mem_pages(mut self, mem_pages: u8) -> ProgramBuilder {
130        self.mem_pages = Some(mem_pages);
131        self
132    }
133
134    #[cfg(test)]
136    pub(crate) fn gen_with_version(&self, version: &str) -> Program {
137        Program {
138            target_version: Version::parse(version).expect("unable to parse version"),
139            system_id: self.system_id.clone(),
140            instructions: self.instructions.clone(),
141            mem_pages: self.mem_pages,
142            entry_point: self.entry_point,
143        }
144    }
145
146    pub fn gen(&self) -> Program {
148        Program {
149            target_version: (*consts::VERSION).clone(),
150            system_id: self.system_id.clone(),
151            instructions: self.instructions.clone(),
152            mem_pages: self.mem_pages,
153            entry_point: self.entry_point,
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use rand::{distributions::Standard, thread_rng, Rng};
162    use std::path::PathBuf;
163    use tempfile::TempDir;
164
165    fn gen_dir() -> TempDir {
166        tempfile::tempdir().expect("unable to create temporary directory")
167    }
168
169    #[test]
170    fn save_and_load() {
171        let rng = thread_rng();
172        let tmp_dir = gen_dir();
173
174        let file_name = tmp_dir
175            .path()
176            .join(PathBuf::from("test").with_extension(ROM_FILE_EXTENSION));
177
178        let program = ProgramBuilder::new("bogus_system")
179            .mem_pages(1)
180            .instructions(rng.sample_iter(&Standard).take(100).collect())
181            .gen_with_version("0.0.0");
182
183        program.save_as(&file_name).unwrap();
184
185        let loaded_program = Program::from_file(&file_name).unwrap();
186
187        assert_eq!(program.target_version, loaded_program.target_version);
188        assert_eq!(program.system_id, loaded_program.system_id);
189        assert_eq!(program.mem_pages, loaded_program.mem_pages);
190    }
191}