use crate::compiler::cursor::FunctionCursor;
use crate::compiler::value::parameters::{parameter_vectors_match, Parameter};
use crate::compiler::value::values::{FunctionDefinitionObject, TypeDefinition, Value};
use crate::store::{arguments_match_value, Store};
use serde::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Error, ErrorKind, Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
mod serialization;
pub const RECORD_SIZE: u64 = 1024;
pub const HEADER_SIZE: usize = 2;
const FLAG_IN_USE: u8 = 0b10000000;
pub struct FilePerFunctionStore {
base_dir: PathBuf,
}
impl FilePerFunctionStore {
pub fn new(base_dir: PathBuf) -> FilePerFunctionStore {
FilePerFunctionStore { base_dir }
}
pub fn get_path(&self) -> PathBuf {
self.base_dir.clone()
}
}
fn is_record_in_use(header: &[u8; HEADER_SIZE]) -> bool {
header[0] & FLAG_IN_USE != 0
}
fn set_record_in_use(header: &mut [u8; HEADER_SIZE]) {
header[0] |= FLAG_IN_USE;
}
fn set_record_not_in_use(header: &mut [u8; HEADER_SIZE]) {
header[0] &= !FLAG_IN_USE;
}
fn set_length_in_header(header: &mut [u8; HEADER_SIZE], length: usize) {
if length > (RECORD_SIZE as usize - HEADER_SIZE) {
panic!("Invalid record length: {}", length);
}
let low_byte: u8 = length as u8;
let high_bits: u8 = (length >> 8) as u8;
header[1] = low_byte;
header[0] &= FLAG_IN_USE; header[0] |= high_bits & !FLAG_IN_USE;
}
fn get_length_from_header(header: &[u8; HEADER_SIZE]) -> usize {
let mut result = 0_usize;
result |= (header[0] & (!FLAG_IN_USE)) as usize;
result <<= 8;
result |= header[1] as usize;
result
}
fn read_value_from_record<'a, T: Deserialize<'a>>(
file: &mut File,
buffer: &'a mut [u8],
) -> Result<(T, bool), Error> {
let read_bytes = file.read(buffer)?;
let header: [u8; 2] = buffer[0..HEADER_SIZE].try_into().unwrap();
let in_use = is_record_in_use(&header);
let length = get_length_from_header(&header);
if read_bytes < length + HEADER_SIZE {
panic!(
"Buffer underflow, read {} bytes, expected at least {}",
read_bytes,
length + HEADER_SIZE
);
}
Ok((
serde_json::from_slice(&buffer[HEADER_SIZE..(length + HEADER_SIZE)]).unwrap(),
in_use,
))
}
fn write_value_to_record<T: Serialize>(
file: &mut File,
buffer: &mut [u8],
object: &T,
in_use: bool,
) -> Result<(), Error> {
let header = unsafe { &mut *(buffer.as_mut_ptr() as *mut [u8; 2]) };
if in_use {
set_record_in_use(header);
} else {
set_record_not_in_use(header);
}
let serialized_object_len = match serde_json_core::to_slice(&object, &mut buffer[HEADER_SIZE..])
{
Ok(length) => length,
Err(_) => return Err(Error::from(ErrorKind::Other)),
};
set_length_in_header(header, serialized_object_len);
if file.write(buffer)? != buffer.len() {
panic!("Failed to write buffer");
}
Ok(())
}
impl Store for FilePerFunctionStore {
fn is_function_name_defined(&self, name: &str) -> Result<bool, Error> {
return match OpenOptions::new()
.read(true)
.open(self.base_dir.join(format!("{}.def", name)))
{
Ok(_) => Ok(true),
Err(e) => match e.kind() {
ErrorKind::NotFound => Ok(false),
_ => Err(e),
},
};
}
fn get_function_value(&self, name: &str, argument: &[Value]) -> Result<Option<Value>, Error> {
let mut val_file = match OpenOptions::new()
.read(true)
.open(self.base_dir.join(format!("{}.val", name)))
{
Ok(file) => file,
Err(e) => {
return match e.kind() {
ErrorKind::NotFound => Ok(None),
_ => Err(e),
}
}
};
let val_file_size = val_file.metadata()?.len();
let mut offset = 0;
let mut buffer = [0_u8; RECORD_SIZE as usize];
while offset < val_file_size {
val_file.seek(SeekFrom::Start(offset))?;
let (key, in_use): (Vec<Value>, bool) =
read_value_from_record(&mut val_file, &mut buffer)?;
if in_use {
offset += RECORD_SIZE;
val_file.seek(SeekFrom::Start(offset))?;
if key.eq(argument) {
let (value, _): (Value, bool) =
read_value_from_record(&mut val_file, &mut buffer)?;
return Ok(Some(value));
}
}
offset += RECORD_SIZE;
}
Ok(None)
}
fn set_function_value(
&mut self,
name: &str,
argument: &[Value],
value: Value,
) -> Result<(), Error> {
let mut val_file = OpenOptions::new()
.create(true)
.write(true)
.read(true)
.open(self.base_dir.join(format!("{}.val", name)))?;
let val_file_size = val_file.metadata()?.len();
let mut offset = 0;
let mut buffer = [0_u8; RECORD_SIZE as usize];
while offset < val_file_size {
let (key, in_use): (Vec<Value>, bool) =
read_value_from_record(&mut val_file, &mut buffer)?;
if in_use {
offset += RECORD_SIZE;
val_file.seek(SeekFrom::Start(offset))?;
if key.eq(argument) {
write_value_to_record(&mut val_file, &mut buffer, &value, true)?;
return Ok(());
}
}
offset += RECORD_SIZE; val_file.seek(SeekFrom::Start(offset))?;
}
write_value_to_record(&mut val_file, &mut buffer, &argument, true)?;
offset += RECORD_SIZE;
val_file.seek(SeekFrom::Start(offset))?;
write_value_to_record(&mut val_file, &mut buffer, &value, true)?;
Ok(())
}
fn get_function_definition(
&self,
name: &str,
argument: &[Value],
) -> Result<Option<FunctionDefinitionObject>, Error> {
let path = self.base_dir.join(format!("{}.def", name));
if Path::new(&path).exists() {
let mut def_file = OpenOptions::new().read(true).open(path)?;
let def_file_len = def_file.metadata()?.len();
let mut offset = 0;
let mut buffer = [0_u8; RECORD_SIZE as usize];
while offset < def_file_len {
def_file.seek(SeekFrom::Start(offset))?;
let (parameters, in_use): (Vec<Parameter>, bool) =
read_value_from_record(&mut def_file, &mut buffer)?;
if in_use {
offset += RECORD_SIZE;
def_file.seek(SeekFrom::Start(offset))?;
if arguments_match_value(¶meters, argument) {
let (value, in_use): (FunctionDefinitionObject, bool) =
read_value_from_record(&mut def_file, &mut buffer)?;
if !in_use {
panic!(
"Key was in use but not definition. This isn't supposed to happen"
);
}
return Ok(Some(value));
}
}
offset += RECORD_SIZE;
}
}
Ok(None)
}
fn set_function_definition(
&mut self,
name: &str,
parameters: &[Parameter],
function: FunctionDefinitionObject,
) -> Result<(), Error> {
let mut def_file = OpenOptions::new()
.create(true)
.write(true)
.read(true)
.open(self.base_dir.join(format!("{}.def", name)))?;
let def_file_size = def_file.metadata()?.len();
let mut offset = 0;
let mut buffer = [0_u8; RECORD_SIZE as usize];
while offset < def_file_size {
let (on_disk_params, in_use): (Vec<Parameter>, bool) =
read_value_from_record(&mut def_file, &mut buffer)?;
if in_use {
offset += RECORD_SIZE;
def_file.seek(SeekFrom::Start(offset))?;
if parameter_vectors_match(&on_disk_params, parameters) {
write_value_to_record(&mut def_file, &mut buffer, &function, true)?;
return Ok(());
}
}
offset += RECORD_SIZE; def_file.seek(SeekFrom::Start(offset))?;
}
write_value_to_record(&mut def_file, &mut buffer, ¶meters, true)?;
offset += RECORD_SIZE;
def_file.seek(SeekFrom::Start(offset))?;
write_value_to_record(&mut def_file, &mut buffer, &function, true)?;
Ok(())
}
fn function_cursor(&self, _name: &str) -> FunctionCursor {
todo!()
}
fn has_values(&self, _name: &str) -> bool {
todo!()
}
fn domain_arity(&self, _name: &str) -> Option<u8> {
todo!()
}
fn codomain_arity(&self, _name: &str) -> Option<u8> {
todo!()
}
fn set_type_definition(&mut self, _name: &str, _the_type: TypeDefinition) -> Result<(), Error> {
todo!()
}
fn type_definition_by_name(&self, _name: &str) -> Result<Option<TypeDefinition>, Error> {
todo!()
}
}
impl FilePerFunctionStore {
pub fn init_store(&mut self) -> Result<(), Error> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::compiler::value::parameters::Parameter;
use crate::compiler::value::parameters::Parameter::Constant;
use crate::compiler::value::primitives::Primitive::{Boolean, Integer};
use crate::compiler::value::values::Value::PrimitiveValue;
use crate::compiler::value::values::{FunctionDefinitionObject, FunctionValuesObject};
use crate::runtime::compiled_script::Bytecode;
use crate::store::serde::{
get_length_from_header, is_record_in_use, set_length_in_header, set_record_in_use,
set_record_not_in_use, FilePerFunctionStore, FLAG_IN_USE, HEADER_SIZE, RECORD_SIZE,
};
use crate::store::Store;
use crate::{string_to_value, vector_to_value};
use std::fs;
use std::fs::{create_dir_all, remove_dir, remove_file, OpenOptions};
use std::io::Error;
use std::path::PathBuf;
fn get_base_dir(for_test: &str) -> PathBuf {
let mut base_test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
base_test_dir.push("target/tmp");
base_test_dir.push(for_test);
create_dir_all(base_test_dir.clone()).unwrap();
println!("working in dir {}", base_test_dir.to_str().unwrap());
return base_test_dir;
}
#[test]
fn function_is_defined_if_def_file_exists() -> Result<(), Error> {
let base_test_dir = get_base_dir("function_is_defined_if_def_file_exists");
let file_path = base_test_dir.join("books.def");
println!("creating file {:?}", file_path);
let _ = OpenOptions::new()
.write(true)
.create(true)
.open(file_path.clone())?;
let store = FilePerFunctionStore::new(base_test_dir.clone());
assert!(store.is_function_name_defined("books")?);
remove_file(file_path)?;
remove_dir(base_test_dir)?;
Ok(())
}
#[test]
fn function_is_not_defined_if_only_val_file_exists() -> Result<(), Error> {
let base_test_dir = get_base_dir("function_is_not_defined_if_only_val_file_exists");
let file_path = base_test_dir.join("books.val");
println!("creating file {:?}", file_path);
let _ = OpenOptions::new()
.write(true)
.create(true)
.open(file_path.clone())?;
let store = FilePerFunctionStore::new(base_test_dir.clone());
assert!(!store.is_function_name_defined("books")?);
remove_file(file_path)?;
remove_dir(base_test_dir)?;
Ok(())
}
#[test]
fn function_is_not_defined_if_file_with_no_extension_exists() -> Result<(), Error> {
let base_test_dir =
get_base_dir("function_is_not_defined_if_file_with_no_extension_exists");
let file_path = base_test_dir.join("books");
println!("creating file {:?}", file_path);
let _ = OpenOptions::new()
.write(true)
.create(true)
.open(file_path.clone())?;
let store = FilePerFunctionStore::new(base_test_dir.clone());
assert!(!store.is_function_name_defined("books")?);
remove_file(file_path)?;
remove_dir(base_test_dir)?;
Ok(())
}
#[test]
fn function_is_defined_if_file_with_def_and_val_extension_exists() -> Result<(), Error> {
let base_test_dir =
get_base_dir("function_is_defined_if_file_with_def_and_val_extension_exists");
let file_path1 = base_test_dir.join("books.def");
let file_path2 = base_test_dir.join("books.val");
let _ = OpenOptions::new()
.write(true)
.create(true)
.open(file_path1.clone())?;
let _ = OpenOptions::new()
.write(true)
.create(true)
.open(file_path2.clone())?;
let store = FilePerFunctionStore::new(base_test_dir.clone());
assert!(store.is_function_name_defined("books")?);
remove_file(file_path1)?;
remove_file(file_path2)?;
remove_dir(base_test_dir)?;
Ok(())
}
#[test]
fn write_and_read_a_function() -> Result<(), Error> {
let base_test_dir = get_base_dir("write_a_function");
let mut store = FilePerFunctionStore::new(base_test_dir);
let mut bytecode = Bytecode::new();
bytecode.bytes = vec![1, 2, 3, 4, 5];
bytecode.lines = vec![0, 0, 0, 0, 0];
let fdo = FunctionDefinitionObject {
formal_arguments: vec![
Parameter::Variable("val".to_string()),
Constant(PrimitiveValue(Integer(12))),
],
constants: vec![],
variables: vec![],
bytecode,
name: "the fdo".to_string(),
};
let mut fo = FunctionValuesObject::new();
let input = vec![
PrimitiveValue(Integer(-42)),
string_to_value!("forty-two".to_string()),
PrimitiveValue(Boolean(false)),
];
let out_val = vec![
PrimitiveValue(Integer(1)),
PrimitiveValue(Boolean(false)),
string_to_value!("".to_string()),
];
let output = vector_to_value!(out_val);
fo.set_value(input.clone(), output.clone());
store.set_function_definition("books", &fdo.formal_arguments, fdo.clone())?;
store.set_function_value("books", &input, output.clone())?;
let read_back_value = store.get_function_value("books", &input)?.unwrap();
println!("{:?}", read_back_value);
assert_eq!(output, read_back_value.clone());
fs::remove_dir_all(store.get_path())?;
Ok(())
}
#[test]
fn test_header_length_encoding() {
let mut header_in_use = [FLAG_IN_USE, 0];
let mut header_not_in_use = [0_u8, 0];
for i in 0..RECORD_SIZE as usize - HEADER_SIZE {
set_length_in_header(&mut header_in_use, i);
assert_eq!(get_length_from_header(&header_in_use), i);
assert!(is_record_in_use(&header_in_use));
set_length_in_header(&mut header_not_in_use, i);
assert_eq!(get_length_from_header(&header_not_in_use), i);
assert!(!is_record_in_use(&header_not_in_use));
}
}
#[test]
fn test_header_set_unset_in_use_flag() {
let mut header = [0_u8, 0];
for i in 0..RECORD_SIZE as usize - HEADER_SIZE {
set_length_in_header(&mut header, i);
set_record_in_use(&mut header);
assert!(is_record_in_use(&header));
set_record_not_in_use(&mut header);
assert!(!is_record_in_use(&header));
assert_eq!(i, get_length_from_header(&header));
}
}
}