use std::ops::Range;
mod dictionaries;
pub use dictionaries::DictionaryComposer;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Language {
Chinese,
English,
Japanese,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lexeme {
pub input: Range<usize>,
pub text: String,
pub language: Language,
pub rank: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Segment {
pub input: Range<usize>,
pub text: String,
pub language: Language,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Conversion {
pub segments: Vec<Segment>,
pub chinese_complete: bool,
}
pub const MAX_INPUT_BYTES: usize = 96;
pub const MAX_LEXEMES: usize = 4096;
pub fn decode(input: &str, lexemes: &[Lexeme], enabled: &[Language]) -> Option<Conversion> {
if input.is_empty() || input.len() > MAX_INPUT_BYTES || lexemes.len() > MAX_LEXEMES {
return None;
}
let mut outgoing = vec![Vec::new(); input.len() + 1];
for (index, lexeme) in lexemes.iter().enumerate() {
if lexeme.input.start < lexeme.input.end
&& lexeme.input.end <= input.len()
&& input.is_char_boundary(lexeme.input.start)
&& input.is_char_boundary(lexeme.input.end)
&& !lexeme.text.is_empty()
&& enabled.contains(&lexeme.language)
{
outgoing[lexeme.input.start].push(index);
}
}
let mut states: Vec<[Option<Path>; 24]> = vec![std::array::from_fn(|_| None); input.len() + 1];
states[0][0] = Some(Path::default());
for position in 0..input.len() {
for slot in 0..24 {
let Some(previous) = states[position][slot].clone() else {
continue;
};
let used = slot / 3;
let last = slot % 3;
for &index in &outgoing[position] {
let lexeme = &lexemes[index];
let language = lexeme.language as usize;
let target_slot = (used | (1 << language)) * 3 + language;
let switches = previous.switches + usize::from(used != 0 && last != language);
let rank = previous.rank + usize::from(lexeme.rank);
let target = &mut states[lexeme.input.end][target_slot];
if target.as_ref().is_some_and(|old| {
(previous.lexemes.len() + 1, switches, rank)
> (old.lexemes.len(), old.switches, old.rank)
}) {
continue;
}
let mut path = previous.clone();
path.lexemes.push(index);
path.switches = switches;
path.rank = rank;
if target
.as_ref()
.is_none_or(|old| path.precedes(old, lexemes))
{
*target = Some(path);
}
}
}
}
let mut best: Option<&Path> = None;
for (slot, path) in states[input.len()].iter().enumerate() {
if (slot / 3).count_ones() < 2 {
continue;
}
if let Some(path) = path {
if best.is_none_or(|old| path.precedes(old, lexemes)) {
best = Some(path);
}
}
}
let best = best?;
if states[input.len()].iter().enumerate().any(|(slot, path)| {
(slot / 3).count_ones() == 1
&& path
.as_ref()
.is_some_and(|single| single.lexemes.len() < best.lexemes.len())
}) {
return None;
}
let chinese_complete = states[input.len()]
.iter()
.enumerate()
.any(|(slot, path)| slot / 3 == (1 << Language::Chinese as usize) && path.is_some());
let mut segments: Vec<Segment> = Vec::new();
for &index in &best.lexemes {
let lexeme = &lexemes[index];
if let Some(last) = segments
.last_mut()
.filter(|segment| segment.language == lexeme.language)
{
last.input.end = lexeme.input.end;
last.text.push_str(&lexeme.text);
} else {
segments.push(Segment {
input: lexeme.input.clone(),
text: lexeme.text.clone(),
language: lexeme.language,
});
}
}
Some(Conversion {
segments,
chinese_complete,
})
}
#[derive(Clone, Default)]
struct Path {
lexemes: Vec<usize>,
switches: usize,
rank: usize,
}
impl Path {
fn precedes(&self, other: &Self, lexemes: &[Lexeme]) -> bool {
let cost = |path: &Self| (path.lexemes.len(), path.switches, path.rank);
let order = cost(self).cmp(&cost(other));
if !order.is_eq() {
return order.is_lt();
}
let key = |index: &usize| {
let lexeme = &lexemes[*index];
(
lexeme.language,
lexeme.text.as_str(),
lexeme.input.start,
lexeme.input.end,
)
};
self.lexemes
.iter()
.map(key)
.cmp(other.lexemes.iter().map(key))
.is_lt()
}
}
#[cfg(test)]
#[path = "multilingual/tests.rs"]
mod tests;
#[cfg(test)]
#[path = "multilingual/reference_tests.rs"]
mod reference_tests;