1use core::{fmt, mem};
2#[cfg(feature = "std")]
3use std::{
4 fs::File,
5 io::{self, Read},
6 path::PathBuf,
7};
8
9use crate::{computer::Memory, file::MAX_FILE_SIZE};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Error {
14 BufferTooLarge(usize),
16 InvalidNumber(usize, u16),
18}
19
20impl fmt::Display for Error {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 Self::BufferTooLarge(size) => write!(
24 f,
25 "The buffer is too long bytes long ({size} bytes > {MAX_FILE_SIZE} bytes)"
26 ),
27 Self::InvalidNumber(index, number) => write!(
28 f,
29 "A number in the decoded buffer is too large (index {index}, {number} > 999)"
30 ),
31 }
32 }
33}
34
35#[cfg(feature = "std")]
36impl std::error::Error for Error {}
37
38#[allow(clippy::module_name_repetitions)]
39pub fn load_from_buffer(buffer: &[u8]) -> Result<Memory, Error> {
44 if buffer.len() > MAX_FILE_SIZE {
45 return Err(Error::BufferTooLarge(buffer.len()));
46 }
47
48 let mut memory = [0; 100];
50 let mut address = 0;
51 let mut offset: u8 = 2;
52
53 for (index, byte) in buffer.iter().enumerate() {
54 let lower = u16::from(*byte) >> (10 - offset);
56 let upper = (u16::from(*byte) << offset) % 0b100_0000_0000;
57
58 memory[address] |= lower;
60
61 if index != 0 {
62 if memory[address] > 999 {
64 return Err(Error::InvalidNumber(address, memory[address]));
65 }
66
67 address += 1;
69 }
70
71 memory[address] |= upper;
73
74 offset += 2;
76 if offset == 10 {
77 address -= 1;
78 offset = 0;
79 }
80 }
81
82 Ok(unsafe { mem::transmute(memory) })
85}
86
87#[cfg(feature = "std")]
88#[derive(Debug)]
89pub enum FromFileError {
91 IoError(io::Error),
93 FileTooLarge(u64),
95 LoadError(Error),
97}
98
99#[cfg(feature = "std")]
100impl fmt::Display for FromFileError {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 match self {
103 Self::IoError(error) => write!(
104 f,
105 "An OS error occurred while reading a file!\nError: {error}"
106 ),
107 Self::FileTooLarge(size) => write!(
108 f,
109 "The file is too large ({size} bytes > {MAX_FILE_SIZE} bytes)"
110 ),
111 Self::LoadError(error) => fmt::Display::fmt(error, f),
112 }
113 }
114}
115
116#[cfg(feature = "std")]
117impl std::error::Error for FromFileError {
118 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
119 match self {
120 Self::IoError(error) => Some(error),
121 Self::LoadError(error) => Some(error),
122 Self::FileTooLarge(_) => None,
123 }
124 }
125}
126
127#[cfg(feature = "std")]
128impl From<io::Error> for FromFileError {
129 fn from(value: io::Error) -> Self {
130 Self::IoError(value)
131 }
132}
133
134#[cfg(feature = "std")]
135impl From<Error> for FromFileError {
136 fn from(value: Error) -> Self {
137 Self::LoadError(value)
138 }
139}
140
141#[cfg(feature = "std")]
142#[allow(clippy::module_name_repetitions)]
143pub fn load_from_file(file: &mut File) -> Result<Memory, FromFileError> {
151 let file_size = file.metadata()?.len();
153 if file_size > MAX_FILE_SIZE as u64 {
154 return Err(FromFileError::FileTooLarge(file_size));
155 }
156
157 let mut buffer = [0; MAX_FILE_SIZE];
159 let bytes_read = file.read(&mut buffer)?;
160
161 load_from_buffer(&buffer[..bytes_read]).map_err(FromFileError::from)
163}
164
165#[cfg(feature = "std")]
166pub fn load(path: &str) -> Result<Memory, FromFileError> {
171 load_from_file(&mut File::open(path)?)
172}
173
174#[cfg(feature = "std")]
175#[allow(clippy::module_name_repetitions)]
176pub fn load_from_path(path: PathBuf) -> Result<Memory, FromFileError> {
181 load_from_file(&mut File::open(path)?)
182}
183
184#[cfg(test)]
185mod test {
186 use std::{
187 env::temp_dir,
188 fs::{self, File},
189 io::Write,
190 };
191
192 use uuid::Uuid;
193
194 use crate::file::{load, MAX_FILE_SIZE};
195
196 use super::load_from_buffer;
197
198 #[test]
199 fn empty_buffer() {
200 let buffer = [];
201
202 let memory = load_from_buffer(&buffer[..]).expect("failed to load from buffer");
204
205 assert!(
206 memory.iter().all(|number| u16::from(*number) == 0),
207 "Empty buffer did not load all zeros!"
208 );
209 }
210
211 #[test]
212 fn full_buffer() {
213 let mut buffer = [0; MAX_FILE_SIZE];
214 buffer.iter_mut().enumerate().for_each(|(index, byte)| {
216 let shift = (2 * index) % 10;
217 if shift < 8 {
218 *byte = 0b1000_0000_u8 >> shift;
219 }
220 });
221
222 let memory = load_from_buffer(&buffer[..]).expect("failed to load from buffer");
224
225 assert!(
226 memory.iter().all(|number| u16::from(*number) == 512),
227 "Full buffer did not load all 512s!"
228 );
229 }
230
231 #[test]
232 fn empty() {
233 let mut path = temp_dir();
235 path.push(format!("lminc-test-{}", Uuid::new_v4()));
236
237 let file = File::create(path.clone()).expect("failed to create file");
239 file.sync_all().expect("failed to sync file data");
240 drop(file);
241
242 let path_str = path.to_str().expect("failed to convert path to str");
243
244 let memory = load(path_str).expect("failed to load memory from file");
246
247 if let Err(error) = fs::remove_file(path.clone()) {
249 eprintln!("Warning: Failed to remove file ({path_str})!\nError: {error}");
250 }
251
252 assert!(
253 memory.iter().all(|number| u16::from(*number) == 0),
254 "Empty file did not load all zeros!"
255 );
256 }
257
258 #[test]
259 fn full() {
260 let mut buffer = [0; MAX_FILE_SIZE];
261 buffer.iter_mut().enumerate().for_each(|(index, byte)| {
263 let shift = 8 - 2 * ((index + 1) % 5);
264 if shift < 8 {
265 *byte = 1 << shift;
266 }
267 });
268
269 let mut path = temp_dir();
271 path.push(format!("lminc-test-{}", Uuid::new_v4()));
272
273 let mut file = File::create(path.clone()).expect("failed to create file");
275 file.write_all(&buffer[..])
276 .expect("failed to write buffer to file");
277 file.sync_all().expect("failed to sync file data");
278 drop(file);
279
280 let path_str = path.to_str().expect("failed to convert path to str");
281
282 let memory = load(path_str).expect("failed to load memory from file");
284
285 if let Err(error) = fs::remove_file(path.clone()) {
287 eprintln!("Warning: Failed to remove file ({path_str})!\nError: {error}");
288 }
289
290 assert!(
291 memory.iter().all(|number| u16::from(*number) == 256),
292 "Full file did not load all 256s!"
293 );
294 }
295}