use crate::types::Lang;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum OovKind {
Symbol(char),
BoundaryPunct,
InternalPunct(char),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OovEvent {
kind: OovKind,
char_index: usize,
word_index: usize,
language: Lang,
}
impl OovEvent {
#[must_use]
pub const fn new(kind: OovKind, char_index: usize, word_index: usize, language: Lang) -> Self {
Self {
kind,
char_index,
word_index,
language,
}
}
#[must_use]
pub const fn kind(&self) -> &OovKind {
&self.kind
}
#[must_use]
pub const fn char_index(&self) -> usize {
self.char_index
}
#[must_use]
pub const fn word_index(&self) -> usize {
self.word_index
}
#[must_use]
pub const fn language(&self) -> &Lang {
&self.language
}
pub fn set_language(&mut self, language: Lang) {
self.language = language;
}
#[must_use]
pub fn char(&self) -> Option<char> {
match self.kind {
OovKind::Symbol(c) | OovKind::InternalPunct(c) => Some(c),
OovKind::BoundaryPunct => None,
}
}
#[must_use]
pub fn matches_position(&self, other: &OovEvent) -> bool {
self.kind == other.kind
&& self.char_index == other.char_index
&& self.word_index == other.word_index
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum OovDecision {
Wildcard,
FailClosed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedOov {
event: OovEvent,
decision: OovDecision,
}
impl ResolvedOov {
#[must_use]
pub const fn new(event: OovEvent, decision: OovDecision) -> Self {
Self { event, decision }
}
#[must_use]
pub const fn event(&self) -> &OovEvent {
&self.event
}
#[must_use]
pub const fn decision(&self) -> OovDecision {
self.decision
}
}
#[must_use]
pub fn default_oov_decisions(events: &[OovEvent]) -> Vec<ResolvedOov> {
events
.iter()
.map(|ev| {
let decision = match &ev.kind {
OovKind::Symbol(c) => {
if c.is_alphanumeric() || *c == '\'' || *c == '\u{2019}' {
OovDecision::Wildcard
} else {
OovDecision::FailClosed
}
}
OovKind::BoundaryPunct | OovKind::InternalPunct(_) => OovDecision::Wildcard,
};
ResolvedOov {
event: ev.clone(),
decision,
}
})
.collect()
}
#[must_use]
pub fn wildcard_all_decisions(events: &[OovEvent]) -> Vec<ResolvedOov> {
events
.iter()
.map(|ev| ResolvedOov {
event: ev.clone(),
decision: OovDecision::Wildcard,
})
.collect()
}
#[must_use]
pub fn fail_closed_all_decisions(events: &[OovEvent]) -> Vec<ResolvedOov> {
events
.iter()
.map(|ev| ResolvedOov {
event: ev.clone(),
decision: OovDecision::FailClosed,
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn ev(c: char) -> OovEvent {
OovEvent {
kind: OovKind::Symbol(c),
char_index: 0,
word_index: 0,
language: Lang::En,
}
}
fn boundary_ev() -> OovEvent {
OovEvent {
kind: OovKind::BoundaryPunct,
char_index: 0,
word_index: 0,
language: Lang::En,
}
}
fn internal_ev(c: char) -> OovEvent {
OovEvent {
kind: OovKind::InternalPunct(c),
char_index: 0,
word_index: 0,
language: Lang::En,
}
}
fn decisions_only(resolved: &[ResolvedOov]) -> Vec<OovDecision> {
resolved.iter().map(|r| r.decision).collect()
}
#[test]
fn default_wildcards_alphanumeric() {
let events = vec![ev('4'), ev('a'), ev('Z')];
let resolved = default_oov_decisions(&events);
assert_eq!(
decisions_only(&resolved),
vec![
OovDecision::Wildcard,
OovDecision::Wildcard,
OovDecision::Wildcard,
]
);
for (r, e) in resolved.iter().zip(events.iter()) {
assert_eq!(&r.event, e);
}
}
#[test]
fn default_wildcards_apostrophes() {
let events = vec![ev('\''), ev('\u{2019}')];
assert_eq!(
decisions_only(&default_oov_decisions(&events)),
vec![OovDecision::Wildcard, OovDecision::Wildcard]
);
}
#[test]
fn default_fails_closed_on_pronounced_symbols() {
let events = vec![ev('&'), ev('@'), ev('%'), ev(',')];
assert_eq!(
decisions_only(&default_oov_decisions(&events)),
vec![
OovDecision::FailClosed,
OovDecision::FailClosed,
OovDecision::FailClosed,
OovDecision::FailClosed,
]
);
}
#[test]
fn wildcard_all_does_what_it_says() {
let events = vec![ev('a'), ev('&'), ev(',')];
assert_eq!(
decisions_only(&wildcard_all_decisions(&events)),
vec![
OovDecision::Wildcard,
OovDecision::Wildcard,
OovDecision::Wildcard,
]
);
}
#[test]
fn fail_closed_all_does_what_it_says() {
let events = vec![ev('a'), ev('&'), ev(',')];
assert_eq!(
decisions_only(&fail_closed_all_decisions(&events)),
vec![
OovDecision::FailClosed,
OovDecision::FailClosed,
OovDecision::FailClosed,
]
);
}
#[test]
fn default_wildcards_structural_kinds() {
let events = vec![boundary_ev(), internal_ev('.')];
assert_eq!(
decisions_only(&default_oov_decisions(&events)),
vec![OovDecision::Wildcard, OovDecision::Wildcard],
);
}
#[test]
fn fail_closed_all_includes_structural_wildcards() {
let events = vec![ev('a'), boundary_ev(), internal_ev('.')];
assert_eq!(
decisions_only(&fail_closed_all_decisions(&events)),
vec![
OovDecision::FailClosed,
OovDecision::FailClosed,
OovDecision::FailClosed,
],
);
}
#[test]
fn empty_events_returns_empty_decisions() {
assert!(default_oov_decisions(&[]).is_empty());
assert!(wildcard_all_decisions(&[]).is_empty());
assert!(fail_closed_all_decisions(&[]).is_empty());
}
}