#![allow(clippy::needless_return)]
extern crate yaml_rust;
use yaml_rust::yaml::Hash;
use yaml_rust::Yaml;
use crate::errors::*;
use crate::prefs::*;
use std::{cell::RefCell, cell::Ref, cell::RefMut, rc::Rc};
use std::path::{Path, PathBuf};
use std::collections::{HashMap, HashSet};
use crate::shim_filesystem::read_to_string_shim;
#[derive(Debug, Clone)]
pub enum Contains {
Vec(Rc<RefCell<Vec<String>>>),
Set(Rc<RefCell<HashSet<String>>>),
Map(Rc<RefCell<HashMap<String, String>>>),
}
impl Contains {
}
pub type CollectionFromFile = Contains;
type VariableDefHashMap = HashMap<String, CollectionFromFile>;
pub struct Definitions {
pub name_to_var_mapping: VariableDefHashMap,
}
impl Default for Definitions {
fn default() -> Self {
Definitions {
name_to_var_mapping: HashMap::with_capacity(30),
}
}
}
impl Definitions {
fn new() -> Self {
Definitions {
name_to_var_mapping: HashMap::with_capacity(30),
}
}
pub fn get_hashset(&self, name: &str) -> Option<Ref<'_, HashSet<String>>> {
let names = self.name_to_var_mapping.get(name);
if let Some(Contains::Set(set)) = names {
return Some(set.borrow());
}
return None;
}
pub fn get_hashmap(&self, name: &str) -> Option<Ref<'_, HashMap<String, String>>> {
let names = self.name_to_var_mapping.get(name);
if let Some(Contains::Map(map)) = names {
return Some(map.borrow());
}
return None;
}
pub fn get_vec(&self, name: &str) -> Option<Ref<'_, Vec<String>>> {
let names = self.name_to_var_mapping.get(name);
if let Some(Contains::Vec(vec)) = names {
return Some(vec.borrow());
}
return None;
}
}
thread_local!{
pub static SPEECH_DEFINITIONS: RefCell<Definitions> = RefCell::new( Definitions::new() );
pub static BRAILLE_DEFINITIONS: RefCell<Definitions> = RefCell::new( Definitions::new() );
pub static DEFINITIONS: &'static std::thread::LocalKey<RefCell<Definitions>> = const { &SPEECH_DEFINITIONS };
}
pub fn read_definitions_file(use_speech_defs: bool) -> Result<Vec<PathBuf>> {
let pref_manager = PreferenceManager::get();
let pref_manager = pref_manager.borrow();
let file_path = pref_manager.get_definitions_file(use_speech_defs);
let definitions = if use_speech_defs {&SPEECH_DEFINITIONS} else {&BRAILLE_DEFINITIONS};
definitions.with( |defs| defs.borrow_mut().name_to_var_mapping.clear() );
let mut new_files = vec![file_path.to_path_buf()];
let mut files_read = read_one_definitions_file(use_speech_defs, file_path).chain_err(|| format!("in file '{}", file_path.to_string_lossy()))?;
new_files.append(&mut files_read);
return definitions.with(|defs| {
let mut defs = defs.borrow_mut();
make_all_set_references_valid(&mut defs);
return Ok(new_files);
});
fn make_all_set_references_valid(defs: &mut RefMut<Definitions>) {
let used_set_names = ["GeometryPrefixOperators", "LikelyFunctionNames", "TrigFunctionNames", "AdditionalFunctionNames", "Arrows", "GeometryShapes"];
for set_name in used_set_names {
if defs.get_hashset(set_name).is_none() {
defs.name_to_var_mapping.insert(set_name.to_string(), Contains::Set( Rc::new( RefCell::new( HashSet::with_capacity(0) ) ) ));
}
}
if defs.get_hashset("FunctionNames").is_none() {
let all_functions = build_all_functions_set(defs);
defs.name_to_var_mapping.insert("FunctionNames".to_string(), Contains::Set( Rc::new( RefCell::new( all_functions ) ) ));
}
}
fn build_all_functions_set(defs: &mut RefMut<Definitions>) -> HashSet<String> {
let trig_functions = defs.get_hashset("TrigFunctionNames").unwrap();
let mut all_functions = defs.get_hashset("AdditionalFunctionNames").unwrap().clone();
for trig_name in trig_functions.iter() {
all_functions.insert(trig_name.clone());
}
return all_functions;
}
}
use crate::speech::*;
fn read_one_definitions_file(use_speech_defs: bool, path: &Path) -> Result<Vec<PathBuf>> {
let definition_file_contents = read_to_string_shim(path)
.chain_err(|| format!("trying to read {}", path.to_str().unwrap()))?;
let defs_build_fn = |variable_def_list: &Yaml| {
let mut files_read = vec![path.to_path_buf()];
let vec = crate::speech::as_vec_checked(variable_def_list)
.chain_err(||format!("in file {:?}", path.to_str()))?;
for variable_def in vec {
if let Some(mut added_files) = build_values(variable_def, use_speech_defs, path).chain_err(||format!("in file {:?}", path.to_str()))? {
files_read.append(&mut added_files);
}
}
return Ok(files_read);
};
return crate::speech::compile_rule(&definition_file_contents, defs_build_fn)
.chain_err(|| format!("In file '{}'", path.to_str().unwrap()));
}
fn build_values(definition: &Yaml, use_speech_defs: bool, path: &Path) -> Result<Option<Vec<PathBuf>>> {
let dictionary = crate::speech::as_hash_checked(definition)?;
if dictionary.len()!=1 {
bail!("Should only be one definition rule: {}", yaml_to_type(definition));
}
let (key, value) = dictionary.iter().next().unwrap();
let def_name = key.as_str().ok_or_else(|| format!("definition list name '{}' is not a string", yaml_to_type(key)))?;
if def_name == "include" {
let do_include_fn = |new_file: &Path| {
read_one_definitions_file(use_speech_defs, new_file)
};
let include_file_name = value.as_str().ok_or_else(|| format!("definition list include name '{}' is not a string", yaml_to_type(value)))?;
return Ok( Some(crate::speech::process_include(path, include_file_name, do_include_fn)?) );
}
let result;
if def_name.starts_with("Numbers") || def_name.ends_with("_vec") {
result = Contains::Vec( Rc::new( RefCell::new( get_vec_values(value.as_vec().unwrap())? ) ) );
} else {
let dict = value.as_hash().ok_or_else(|| format!("definition list value '{}' is not an array or dictionary", yaml_to_type(value)))?;
if dict.is_empty() {
result = Contains::Set( Rc::new( RefCell::new( HashSet::with_capacity(0) ) ) );
} else {
let (_, entry_value) = dict.iter().next().unwrap();
if entry_value.is_null() {
result = Contains::Set( Rc::new( RefCell::new( get_set_values(dict)
.chain_err(||format!("while reading value '{def_name}'"))? ) ) );
} else {
let (_, entry_value) = dict.iter().next().unwrap();
if entry_value.is_null() {
result = Contains::Set( Rc::new( RefCell::new( get_set_values(dict)
.chain_err(||format!("while reading value '{def_name}'"))? ) ) );
} else {
result = Contains::Map( Rc::new( RefCell::new( get_map_values(dict)
.chain_err(||format!("while reading value '{def_name}'"))? ) ) );
}
}
}
};
let definitions = if use_speech_defs {&SPEECH_DEFINITIONS} else {&BRAILLE_DEFINITIONS};
return definitions.with(|definitions| {
let name_definition_map = &mut definitions.borrow_mut().name_to_var_mapping;
name_definition_map.insert(def_name.to_string(), result);
return Ok(None);
});
fn get_vec_values(values: &Vec<Yaml>) -> Result<Vec<String>> {
let mut result = Vec::with_capacity(values.len());
for yaml_value in values {
let value = yaml_value.as_str()
.ok_or_else(|| format!("list entry '{}' is not a string", yaml_to_type(yaml_value)))?
.to_string();
result.push(value);
}
return Ok(result);
}
fn get_set_values(values: &Hash) -> Result<HashSet<String>> {
let mut result = HashSet::with_capacity(2*values.len());
for (key, value) in values {
let key = key.as_str()
.ok_or_else(|| format!("list entry '{}' is not a string", yaml_to_type(key)))?
.to_string();
if let Yaml::Null = value {
} else {
bail!("list entry '{}' is not a string", yaml_to_type(value));
}
result.insert(key);
}
return Ok(result);
}
fn get_map_values(values: &Hash) -> Result<HashMap<String, String>> {
let mut result = HashMap::with_capacity(2*values.len());
for (key, value) in values {
let key = key.as_str()
.ok_or_else(|| format!("list entry '{}' is not a string", yaml_to_type(key)))?
.to_string();
let value = value.as_str()
.ok_or_else(|| format!("list entry '{}' is not a string", yaml_to_type(value)))?
.to_string();
result.insert(key, value);
}
return Ok(result);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vec() {
let numbers = r#"[NumbersTens: ["", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]]"#;
let defs_build_fn = |variable_def_list: &Yaml| {
for variable_def in variable_def_list.as_vec().unwrap() {
if let Err(e) = build_values(variable_def, true, &Path::new("")) {
bail!("{}", crate::interface::errors_to_string(&e.chain_err(||format!("in file {:?}", numbers))));
}
}
return Ok(vec![]);
};
compile_rule(&numbers, defs_build_fn).unwrap();
SPEECH_DEFINITIONS.with(|defs| {
let defs = defs.borrow();
let names = defs.get_vec("NumbersTens");
assert!(names.is_some());
let names = names.unwrap();
assert_eq!(names.len(), 10);
assert_eq!(names[0], "");
assert_eq!(names[9], "ninety");
});
}
#[test]
fn test_set() {
let likely_function_names = r#"[LikelyFunctionNames: {"f", "g", "h", "F", "G", "H", "[A-Za-z]+"}]"#;
let defs_build_fn = |variable_def_list: &Yaml| {
for variable_def in variable_def_list.as_vec().unwrap() {
if let Err(e) = build_values(variable_def, true, &Path::new("")) {
bail!("{}", crate::interface::errors_to_string(&e.chain_err(||format!("in file {:?}", likely_function_names))));
}
}
return Ok(vec![]);
};
compile_rule(&likely_function_names, defs_build_fn).unwrap();
SPEECH_DEFINITIONS.with(|defs| {
let defs = defs.borrow();
let names = defs.get_hashset("LikelyFunctionNames");
assert!(names.is_some());
let names = names.unwrap();
assert_eq!(names.len(), 7);
assert!(names.contains("f"));
assert!(!names.contains("a"));
});
}
#[test]
fn test_hashmap() {
let units = r#"[Units: {"A": "amp", "g": "gram", "m": "meter", "sec": "second"}]"#;
let defs_build_fn = |variable_def_list: &Yaml| {
for variable_def in variable_def_list.as_vec().unwrap() {
if let Err(e) = build_values(variable_def, true, &Path::new("")) {
bail!("{}", crate::interface::errors_to_string(&e.chain_err(||format!("in file {:?}", units))));
}
}
return Ok(vec![]);
};
compile_rule(&units, defs_build_fn).unwrap();
SPEECH_DEFINITIONS.with(|defs| {
let defs = defs.borrow();
let names = defs.get_hashmap("Units");
assert!(names.is_some());
let names = names.unwrap();
assert_eq!(names.len(), 4);
assert_eq!(names.get("A").unwrap(), "amp");
assert_eq!(names.get("sec").unwrap(), "second");
assert_eq!(names.get("xxx"), None);
});
}
}