#![deny(missing_docs)]
pub use indexmap::IndexMap;
#[cfg(feature = "rhai")]
use rhai::{Array, Scope};
#[cfg(feature = "rhai")]
pub use rhai::{Engine, AST};
use std::cmp::Ordering;
#[cfg(feature = "strsim")]
use strsim::{self};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Predicate {
pub code: String,
pub remaining_code: String,
pub texts: Vec<String>,
pub can_commit: bool,
}
pub struct Translator {
dictionary: IndexMap<String, Vec<String>>,
#[cfg(feature = "rhai")]
translators: IndexMap<String, AST>,
#[cfg(feature = "rhai")]
engine: Engine,
auto_commit: bool,
min_confidence: f64,
}
impl Translator {
pub fn new(
dictionary: IndexMap<String, Vec<String>>,
auto_commit: bool,
min_confidence: f64,
) -> Self {
Self {
dictionary,
auto_commit,
#[cfg(feature = "rhai")]
translators: IndexMap::default(),
#[cfg(feature = "rhai")]
engine: Engine::new(),
min_confidence,
}
}
#[cfg(feature = "rhai")]
pub fn register(&mut self, name: String, ast: AST) {
self.translators.insert(name, ast);
}
#[cfg(feature = "rhai")]
pub fn unregister(&mut self, name: &str) {
self.translators.shift_remove(name);
}
pub fn translate(&self, input: &str) -> Vec<Predicate> {
{
let mut ch = input.chars();
if ch.next().is_none() || ch.next().is_none() {
return vec![];
}
}
let input_len = input.len();
let input_first_char = input.chars().next().unwrap();
#[cfg(feature = "rhai")]
let mut scope = Scope::new();
let predicates = self.dictionary.iter().filter_map(|(key, values)| {
let key_len = key.len();
if input_len > key_len || !key.starts_with(input_first_char) {
return None;
}
if key == input {
return Some((
1.0_f64,
Predicate {
code: key.to_owned(),
remaining_code: String::new(),
texts: values.to_owned(),
can_commit: self.auto_commit,
},
));
}
#[cfg(feature = "strsim")]
if key_len == input_len {
let confidence = strsim::hamming(key.as_ref(), input)
.map(|n| 1.0 - (n as f64 / key_len as f64))
.unwrap_or(0.0);
if confidence > self.min_confidence {
return Some((
confidence,
Predicate {
code: key.to_owned(),
remaining_code: String::new(),
texts: values.to_owned(),
can_commit: false,
},
));
}
}
if key.starts_with(input) {
return Some((
0.5,
Predicate {
code: key.to_owned(),
remaining_code: key[input_len..].to_owned(),
texts: values.to_owned(),
can_commit: false,
},
));
}
None
});
#[cfg(feature = "rhai")]
let predicates =
predicates.chain(self.translators.iter().filter_map(|(_name, translator)| {
let mut data = self
.engine
.call_fn::<Array>(&mut scope, translator, "translate", (input.to_owned(),))
.unwrap_or_default();
(data.len() == 4).then(|| {
let code = data.remove(0).into_string().unwrap();
let remaining_code = data.remove(0).into_string().unwrap();
let value = data.remove(0);
let values = if value.is_array() {
value.into_array().unwrap()
} else {
vec![value]
};
let values = values
.into_iter()
.map(|e| e.into_string().unwrap())
.collect();
let translated = data.remove(0).as_bool().unwrap();
(
1.0_f64,
Predicate {
code,
remaining_code,
texts: values,
can_commit: translated,
},
)
})
}));
let mut predicates: Vec<(f64, Predicate)> = predicates.collect();
predicates.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
predicates
.into_iter()
.map(|(_, predicate)| predicate)
.collect()
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_translate() {
#[cfg(feature = "rhai")]
use crate::Engine;
use crate::{IndexMap, Predicate, Translator};
let mut dictionary = IndexMap::new();
dictionary.insert("halo".to_string(), ["hello".to_string()].to_vec());
#[cfg(not(feature = "rhai"))]
let translator = Translator::new(dictionary, true, 0.7);
#[cfg(feature = "rhai")]
let mut translator = Translator::new(dictionary, true, 0.7);
translator.translate("รน");
#[cfg(feature = "rhai")]
{
let engine = Engine::new();
let ast1 = engine.compile("fn translate(input) {}").unwrap();
let ast2 = engine
.compile(
r#"
fn translate(input) {
if input == "hi" {
["hi", "", "hello", true]
}
}
"#,
)
.unwrap();
translator.register("none".to_string(), ast1);
translator.unregister("none");
translator.register("some".to_string(), ast2);
}
assert_eq!(translator.translate("h"), vec![]);
#[cfg(feature = "rhai")]
assert_eq!(
translator.translate("hi"),
vec![Predicate {
code: "hi".to_owned(),
remaining_code: "".to_owned(),
texts: vec!["hello".to_owned()],
can_commit: true
}]
);
assert_eq!(
translator.translate("ha"),
vec![Predicate {
code: "halo".to_owned(),
remaining_code: "lo".to_owned(),
texts: vec!["hello".to_owned()],
can_commit: false
}]
);
#[cfg(feature = "strsim")]
assert_eq!(
translator.translate("helo"),
vec![Predicate {
code: "halo".to_owned(),
remaining_code: "".to_owned(),
texts: vec!["hello".to_owned()],
can_commit: false
}]
);
}
}