use std::collections::BTreeSet;
use crate::text::tokenize;
pub type Lexicon = Vec<Vec<String>>;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(super) struct Edits {
pub replace: Option<Vec<String>>,
pub extend: Vec<String>,
pub remove: Vec<String>,
}
pub(super) fn effective(defaults: &[&str], edits: Option<&Edits>) -> Result<Lexicon, String> {
let Some(edits) = edits else {
return compile(defaults.iter().copied()).map(|set| set.into_iter().collect());
};
if let Some(replace) = &edits.replace {
if !edits.extend.is_empty() || !edits.remove.is_empty() {
return Err("`replace` cannot be combined with `extend` or `remove`".to_owned());
}
return compile(replace.iter().map(String::as_str)).map(|set| set.into_iter().collect());
}
let mut lexicon = compile(defaults.iter().copied())?;
let removals = compile(edits.remove.iter().map(String::as_str))?;
let extensions = compile(edits.extend.iter().map(String::as_str))?;
if let Some(both) = removals.intersection(&extensions).next() {
return Err(format!("`{}` is both extended and removed", both.join(" ")));
}
for removal in removals {
if !lexicon.remove(&removal) {
return Err(format!("`{}` is not in the lexicon", removal.join(" ")));
}
}
for extension in extensions {
let text = extension.join(" ");
if !lexicon.insert(extension) {
return Err(format!("`{text}` is already in the lexicon"));
}
}
Ok(lexicon.into_iter().collect())
}
fn compile<'a>(
phrases: impl IntoIterator<Item = &'a str>,
) -> Result<BTreeSet<Vec<String>>, String> {
let mut compiled = BTreeSet::new();
for phrase in phrases {
let tokens = tokenize(phrase);
if tokens.is_empty() {
return Err(format!("`{phrase}` has no word"));
}
if !compiled.insert(tokens) {
return Err(format!("`{phrase}` is listed twice"));
}
}
Ok(compiled)
}