#![allow(clippy::needless_return)]
extern crate yaml_rust;
use yaml_rust::{Yaml};
use crate::errors::*;
use crate::prefs::*;
use std::{cell::RefCell, cell::Ref, cell::RefMut, collections::HashSet, rc::Rc};
use std::{collections::HashMap, path::Path};
use crate::shim_filesystem::read_to_string_shim;
#[derive(Debug, Clone)]
pub enum Contains {
Vec(Rc<RefCell<Vec<String>>>),
Set(Rc<RefCell<HashSet<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);
return match names {
None => None,
Some(contains) => match contains {
Contains::Vec(_) => None,
Contains::Set(hashset) => Some(hashset.borrow()),
}
}
}
pub fn get_vec(&self, name: &str) -> Option<Ref<Vec<String>>> {
let names = self.name_to_var_mapping.get(name);
return match names {
None => None,
Some(contains) => match contains {
Contains::Vec(v) => Some(v.borrow()),
Contains::Set(_) => None,
}
}
}
}
thread_local!{
pub static DEFINITIONS: RefCell<Definitions> = RefCell::new( Definitions::new() );
}
pub fn read_definitions_file(locations: &Locations) -> Result<()> {
thread_local!{
static LOCATION_CACHE: RefCell<Locations> =
RefCell::new( Locations::default() );
}
if LOCATION_CACHE.with(|cache| are_locations_same(&cache.borrow(), locations)) {
return Ok( () );
} else {
LOCATION_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
cache[0]= locations[0].clone();
cache[1]= locations[1].clone();
cache[2]= locations[2].clone();
})
}
let result = locations.iter().try_for_each(|path|
match path {
None => Ok(()),
Some(path) => read_one_definitions_file(path)
});
verify_definitions()?;
DEFINITIONS.with(|defs| {
let mut defs = defs.borrow_mut();
let all_functions = build_all_functions_set(&defs);
let name_to_mapping = &mut defs.name_to_var_mapping;
name_to_mapping.insert("FunctionNames".to_string(), Contains::Set( Rc::new( RefCell::new( all_functions ) ) ));
});
return result;
fn build_all_functions_set(defs: &RefMut<Definitions>) -> HashSet<String> {
let trig_functions = defs.get_hashset("TrigFunctionNames").unwrap();
let mut all_functions = defs.get_hashset("AdditionalFunctionNames").unwrap().clone();
for trig_function in trig_functions.iter() {
all_functions.insert(trig_function.clone());
}
return all_functions;
}
}
fn verify_definitions() -> Result<()> {
lazy_static! {
static ref USED_SETS: Vec<&'static str> = vec!["TrigFunctionNames", "AdditionalFunctionNames", "LikelyFunctionNames",
"LargeOperators"];
static ref USED_VECTORS: Vec<&'static str> = vec![
"NumbersHundreds", "NumbersTens", "NumbersOnes",
"NumbersOrdinalPluralLarge", "NumbersOrdinalLarge", "NumbersLarge",
"NumbersOrdinalPluralHundreds", "NumbersOrdinalPluralTens", "NumbersOrdinalPluralOnes",
"NumbersOrdinalHundreds", "NumbersOrdinalTens", "NumbersOrdinalOnes",
"NumbersOrdinalFractionalPluralOnes", "NumbersOrdinalFractionalOnes"
];
}
return DEFINITIONS.with(|definitions| {
let definitions = definitions.borrow();
let name_definition_map = &definitions.name_to_var_mapping;
for name in USED_SETS.iter() {
if !name_definition_map.contains_key(*name) {
bail!("Required (set) name '{}' is missing from 'definitions.yaml'", *name);
}
}
for name in USED_VECTORS.iter() {
if !name_definition_map.contains_key(*name) {
bail!("Required (array) name '{}' is missing from 'definitions.yaml'", *name);
}
}
for (name,collection) in name_definition_map.iter() {
if name.contains("number") && !name.contains("fraction") {
match collection {
Contains::Vec(v) => {
let v = v.borrow();
if v.is_empty() || v.len() % 10 != 0 {
bail!("{} has wrong number of values: {}", name, v.len());
}
},
_ => bail!("{} is not a vector!", name),
}
}
};
return Ok( () )
});
}
use crate::speech::*;
fn read_one_definitions_file(path: &Path) -> Result<()> {
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 vec = crate::speech::as_vec_checked(variable_def_list)
.chain_err(||format!("in file {:?}", path.to_str()))?;
for variable_def in vec {
build_values(variable_def).chain_err(||format!("in file {:?}", path.to_str()))?;
}
return Ok(());
};
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) -> Result<()> {
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 name = key.as_str().ok_or_else(|| format!("definition list name '{}' is not a string", yaml_to_type(key)))?;
let values = value.as_vec().ok_or_else(|| format!("definition list value '{}' is not an array", yaml_to_type(value)))?;
return DEFINITIONS.with(|definitions| {
let name_definition_map = &mut definitions.borrow_mut().name_to_var_mapping;
let collection = name_definition_map.entry(name.to_string()).or_insert_with_key(|key| {
if key.starts_with("Numbers") || key.ends_with("_vec") {
Contains::Vec( Rc::new( RefCell::new( vec![] ) ) )
} else {
Contains::Set( Rc::new( RefCell::new( HashSet::new() ) ) )
}
});
match collection {
Contains::Vec(v) => v.borrow_mut().clear(),
Contains::Set(s) => s.borrow_mut().clear(),
};
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();
match collection {
Contains::Vec(v) => { v.borrow_mut().push(value); },
Contains::Set(s) => { s.borrow_mut().insert(value); },
}
}
return Ok( () );
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_def() {
let str = 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) {
bail!("{}", crate::interface::errors_to_string(&e.chain_err(||format!("in file {:?}", str))));
}
}
return Ok(());
};
compile_rule(&str, defs_build_fn).unwrap();
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"));
});
}
}