use core::mem;
#[cfg(feature = "std")]
use std::{
fs::File,
io::{self, Write},
path::PathBuf,
};
use crate::computer::Memory;
use super::MAX_FILE_SIZE;
#[allow(clippy::module_name_repetitions)]
pub fn save_to_buffer(buffer: &mut [u8; MAX_FILE_SIZE], memory: Memory) -> &[u8] {
let memory: [u16; 100] = unsafe { mem::transmute(memory) };
let mut index = 0;
let mut offset: u8 = 2;
for number in memory {
#[allow(clippy::cast_possible_truncation)]
let lower = (number >> offset) as u8;
#[allow(clippy::cast_possible_truncation)]
let upper = (number << (8 - offset)) as u8;
buffer[index] |= lower;
index += 1;
buffer[index] |= upper;
offset += 2;
if offset == 10 {
index += 1;
offset = 2;
}
}
let last_index = buffer
.iter()
.enumerate()
.rev()
.find_map(|(last_index, number)| if *number == 0 { None } else { Some(last_index) });
last_index.map_or_else(|| &buffer[..0], |last_index| &buffer[..=last_index])
}
#[cfg(feature = "std")]
#[allow(clippy::module_name_repetitions)]
pub fn save_to_file(file: &mut File, memory: Memory) -> io::Result<()> {
let mut buffer = [0; MAX_FILE_SIZE];
let buffer_trimmed = save_to_buffer(&mut buffer, memory);
file.write_all(buffer_trimmed)
}
#[cfg(feature = "std")]
pub fn save(path: &str, memory: Memory) -> io::Result<()> {
save_to_file(&mut File::create(path)?, memory)
}
#[cfg(feature = "std")]
#[allow(clippy::module_name_repetitions)]
pub fn save_to_path(path: PathBuf, memory: Memory) -> io::Result<()> {
save_to_file(&mut File::create(path)?, memory)
}
#[cfg(test)]
mod test {
use core::assert_eq;
use std::{
env::temp_dir,
fs::{self, File},
io::Read,
};
use uuid::Uuid;
use crate::{
file::{save, MAX_FILE_SIZE},
num3::ThreeDigitNumber,
};
use super::save_to_buffer;
#[test]
fn empty_buffer() {
let memory = [ThreeDigitNumber::ZERO; 100];
let mut buffer = [0; MAX_FILE_SIZE];
let buffer_trimmed = save_to_buffer(&mut buffer, memory);
assert_eq!(
buffer_trimmed.len(),
0,
"Zeroed memory did not save an empty buffer!",
);
}
#[test]
fn full_buffer() {
let memory = [unsafe { ThreeDigitNumber::from_unchecked(1) }; 100];
let mut buffer = [0; MAX_FILE_SIZE];
let buffer_trimmed = save_to_buffer(&mut buffer, memory);
assert_eq!(
buffer_trimmed.len(),
MAX_FILE_SIZE,
"Full memory did not fill up buffer!"
);
assert!(
buffer_trimmed.iter().enumerate().all(|(index, byte)| {
*byte == {
let shift = 8 - 2 * (index % 5);
if shift < 8 {
1 << shift
} else {
0
}
}
}),
"Full memory did not save all 1s!"
);
}
#[test]
fn empty() {
let mut path = temp_dir();
path.push(format!("lminc-test-{}", Uuid::new_v4()));
let file = File::create(path.clone()).expect("failed to create file");
file.sync_all().expect("failed to sync file data");
drop(file);
let path_str = path.to_str().expect("failed to convert path to str");
let memory = [ThreeDigitNumber::ZERO; 100];
save(path_str, memory).expect("failed to write to file");
let file = File::open(path.clone()).expect("failed to open file");
assert_eq!(
file.metadata().expect("failed to get file metadata").len(),
0,
"Zeroed memory did not save an empty file!",
);
if let Err(error) = fs::remove_file(path.clone()) {
eprintln!("Warning: Failed to remove file ({path_str})!\nError: {error}");
}
}
#[test]
fn full() {
let mut path = temp_dir();
path.push(format!("lminc-test-{}", Uuid::new_v4()));
let file = File::create(path.clone()).expect("failed to create file");
file.sync_all().expect("failed to sync file data");
drop(file);
let path_str = path.to_str().expect("failed to convert path to str");
let memory = [unsafe { ThreeDigitNumber::from_unchecked(1) }; 100];
save(path_str, memory).expect("failed to write to file");
let mut file = File::open(path.clone()).expect("failed to open file");
let mut buffer = Vec::new();
assert_eq!(
file.metadata().expect("failed to get file metadata").len(),
MAX_FILE_SIZE as u64,
"Full memory did not fill up file!"
);
file.read_to_end(&mut buffer).expect("failed to read file");
assert!(
buffer.iter().enumerate().all(|(index, byte)| {
*byte == {
let shift = 8 - 2 * (index % 5);
if shift < 8 {
1 << shift
} else {
0
}
}
}),
"Full memory did not save all 1s!"
);
}
}