use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use crate::transducer::{Algorithm, Transducer};
use libdictenstein::double_array_trie::DoubleArrayTrie;
use libdictenstein::dynamic_dawg::DynamicDawg;
use libdictenstein::Dictionary;
#[derive(Serialize, Deserialize)]
pub struct WasmCandidate {
pub term: String,
pub distance: usize,
}
#[wasm_bindgen]
pub struct WasmTransducer {
inner: Transducer<DoubleArrayTrie<()>>,
}
#[wasm_bindgen]
impl WasmTransducer {
#[wasm_bindgen(constructor)]
pub fn new(terms: Vec<JsValue>, algorithm: &str) -> Result<WasmTransducer, JsValue> {
let terms: Result<Vec<String>, _> = terms
.into_iter()
.map(|v| {
v.as_string()
.ok_or_else(|| JsValue::from_str("all terms must be strings"))
})
.collect();
let terms = terms?;
let term_refs: Vec<&str> = terms.iter().map(|s| s.as_str()).collect();
let algorithm = parse_algorithm(algorithm)?;
let dict = DoubleArrayTrie::from_terms(term_refs);
let inner = Transducer::new(dict, algorithm);
Ok(WasmTransducer { inner })
}
pub fn query(&self, query: &str, max_distance: usize) -> Result<JsValue, JsValue> {
let candidates: Vec<WasmCandidate> = self
.inner
.query_with_distance(query, max_distance)
.map(|c| WasmCandidate {
term: c.term.clone(),
distance: c.distance,
})
.collect();
serde_wasm_bindgen::to_value(&candidates)
.map_err(|e| JsValue::from_str(&format!("serialization error: {}", e)))
}
#[wasm_bindgen(js_name = queryBest)]
pub fn query_best(&self, query: &str, max_distance: usize) -> Result<JsValue, JsValue> {
let mut candidates: Vec<WasmCandidate> = self
.inner
.query_with_distance(query, max_distance)
.map(|c| WasmCandidate {
term: c.term.clone(),
distance: c.distance,
})
.collect();
if let Some(min_dist) = candidates.iter().map(|c| c.distance).min() {
candidates.retain(|c| c.distance == min_dist);
}
serde_wasm_bindgen::to_value(&candidates)
.map_err(|e| JsValue::from_str(&format!("serialization error: {}", e)))
}
#[wasm_bindgen(js_name = queryLimit)]
pub fn query_limit(
&self,
query: &str,
max_distance: usize,
limit: usize,
) -> Result<JsValue, JsValue> {
let candidates: Vec<WasmCandidate> = self
.inner
.query_with_distance(query, max_distance)
.take(limit)
.map(|c| WasmCandidate {
term: c.term.clone(),
distance: c.distance,
})
.collect();
serde_wasm_bindgen::to_value(&candidates)
.map_err(|e| JsValue::from_str(&format!("serialization error: {}", e)))
}
pub fn contains(&self, term: &str) -> bool {
self.inner.dictionary().contains(term)
}
pub fn len(&self) -> usize {
self.inner.dictionary().len().unwrap_or(0)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[wasm_bindgen]
pub struct WasmDynamicTransducer {
dict: DynamicDawg<()>,
algorithm: Algorithm,
}
#[wasm_bindgen]
impl WasmDynamicTransducer {
#[wasm_bindgen(constructor)]
pub fn new(terms: Vec<JsValue>, algorithm: &str) -> Result<WasmDynamicTransducer, JsValue> {
let terms: Result<Vec<String>, _> = terms
.into_iter()
.map(|v| {
v.as_string()
.ok_or_else(|| JsValue::from_str("all terms must be strings"))
})
.collect();
let terms = terms?;
let term_refs: Vec<&str> = terms.iter().map(|s| s.as_str()).collect();
let algorithm = parse_algorithm(algorithm)?;
let dict = DynamicDawg::from_terms(term_refs);
Ok(WasmDynamicTransducer { dict, algorithm })
}
#[wasm_bindgen(js_name = empty)]
pub fn empty(algorithm: &str) -> Result<WasmDynamicTransducer, JsValue> {
let algorithm = parse_algorithm(algorithm)?;
Ok(WasmDynamicTransducer {
dict: DynamicDawg::new(),
algorithm,
})
}
pub fn query(&self, query: &str, max_distance: usize) -> Result<JsValue, JsValue> {
let transducer = Transducer::new(self.dict.clone(), self.algorithm);
let candidates: Vec<WasmCandidate> = transducer
.query_with_distance(query, max_distance)
.map(|c| WasmCandidate {
term: c.term.clone(),
distance: c.distance,
})
.collect();
serde_wasm_bindgen::to_value(&candidates)
.map_err(|e| JsValue::from_str(&format!("serialization error: {}", e)))
}
pub fn insert(&self, term: &str) -> bool {
self.dict.insert(term)
}
pub fn remove(&self, term: &str) -> bool {
self.dict.remove(term)
}
pub fn contains(&self, term: &str) -> bool {
self.dict.contains(term)
}
pub fn len(&self) -> usize {
self.dict.len().unwrap_or(0)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
fn parse_algorithm(s: &str) -> Result<Algorithm, JsValue> {
match s.to_lowercase().as_str() {
"standard" | "levenshtein" => Ok(Algorithm::Standard),
"transposition" | "damerau" | "damerau_levenshtein" | "damerau-levenshtein" => {
Ok(Algorithm::Transposition)
}
"merge_and_split" | "merge-and-split" | "mergesplit" => Ok(Algorithm::MergeAndSplit),
_ => Err(JsValue::from_str(
"unknown algorithm; use 'standard', 'transposition', or 'merge_and_split'",
)),
}
}