use crate::types::*;
use crate::vocab::*;
pub const TRANSITION_SMOOTHING_LOGPROB: f32 = -13.815510557964274;
#[derive(PartialEq,Clone,Debug)]
pub struct Offset {
pub begin: usize,
pub end: usize,
}
impl Offset {
pub fn convert(&mut self, map: &Vec<Option<usize>>) {
self.begin = map.get(self.begin).expect(format!("Bytes to unicode: Begin offset {} must exist in map",self.begin).as_str()).expect("Offset in map may not be None");
self.end = map.get(self.end).expect(format!("Bytes to unicode: End offset {} must exist in map",self.end).as_str()).expect("Offset in map may not be None");
}
}
#[derive(Clone,Debug)]
pub struct Match<'a> {
pub text: &'a str,
pub offset: Offset,
pub variants: Option<Vec<VariantResult>>,
pub selected: Option<usize>,
pub tag: Vec<u16>,
pub seqnr: Vec<u8>,
pub prevboundary: Option<usize>,
pub nextboundary: Option<usize>,
pub n: usize
}
impl<'a> Match<'a> {
pub fn new_empty(text: &'a str, offset: Offset) -> Self {
Match {
text,
offset,
variants: None,
selected: None,
prevboundary: None,
nextboundary: None,
tag: vec!(),
seqnr: vec!(),
n: 0
}
}
pub fn is_empty(&self) -> bool {
self.variants.is_none() || self.variants.as_ref().unwrap().is_empty()
}
pub fn solution(&self) -> Option<&VariantResult> {
if let Some(selected) = self.selected {
self.variants.as_ref().expect("match must have variants when 'selected' is set").get(selected)
} else {
None
}
}
pub fn internal_boundaries(&self, boundaries: &'a [Match<'_>]) -> &'a [Match<'_>] {
let mut begin = None;
let mut end = 0;
for (i, boundary) in boundaries.iter().enumerate() {
if boundary.offset.begin > self.offset.begin && boundary.offset.end < self.offset.end {
if begin.is_none() {
begin = Some(i);
} else {
end = i+1;
}
}
}
if begin.is_none() || begin.unwrap() >= end {
&[]
} else {
&boundaries[begin.unwrap()..end]
}
}
}
#[derive(Clone,Debug)]
pub struct Context<'a> {
pub left: Option<&'a str>,
pub right: Option<&'a str>
}
#[derive(PartialEq,PartialOrd,Clone,Debug)]
pub struct OutputSymbol {
pub vocab_id: VocabId,
pub match_index: usize,
pub variant_index: Option<usize>,
pub boundary_index: usize,
pub symbol: u32,
}
#[derive(Clone,Debug)]
pub struct Sequence {
pub output_symbols: Vec<OutputSymbol>,
pub variant_cost: f32,
pub lm_logprob: f32,
pub perplexity: f64,
pub context_score: f64,
pub tags: Vec<Vec<(u16,u8)>> }
impl Sequence {
pub fn new(variant_cost: f32) -> Self {
Self {
output_symbols: Vec::new(),
variant_cost,
lm_logprob: 0.0,
perplexity: 0.0,
context_score: 1.0,
tags: Vec::new()
}
}
}
#[derive(PartialEq,PartialOrd,Copy,Clone,Debug)]
pub enum BoundaryStrength {
None,
Weak,
Normal,
Hard
}
pub fn find_boundaries<'a>(text: &'a str) -> Vec<Match<'a>> {
let mut boundaries = Vec::new();
let mut begin: Option<usize> = None;
for (i,c) in text.char_indices() {
if let Some(b) = begin {
if c.is_alphabetic() {
boundaries.push(Match::new_empty(&text[b..i], Offset {
begin: b,
end: i
}));
begin = None;
}
} else {
if !c.is_alphabetic() {
begin = Some(i);
}
}
}
if let Some(b) = begin {
boundaries.push(Match::new_empty(&text[b..], Offset {
begin: b,
end: text.len()
}));
} else {
boundaries.push(Match::new_empty("", Offset {
begin: text.len(),
end: text.len()
}));
}
boundaries
}
pub fn classify_boundaries(boundaries: &Vec<Match<'_>>) -> Vec<BoundaryStrength> {
let mut strengths = Vec::new();
for (i, boundary) in boundaries.iter().enumerate() {
let strength = if i == boundaries.len() - 1 {
BoundaryStrength::Hard
} else if boundary.text.len() > 1 {
BoundaryStrength::Hard
} else {
match boundary.text {
"'" | "-" | "_" => BoundaryStrength::Weak,
_ => BoundaryStrength::Normal
}
};
strengths.push(strength)
}
strengths
}
pub fn find_match_ngrams<'a>(text: &'a str, boundaries: &[Match<'a>], order: u8, begin: usize, end: Option<usize>) -> Vec<Match<'a>> {
let mut ngrams = Vec::new();
let mut begin = begin;
let end = end.unwrap_or(text.len());
let mut i = 0;
while let Some(boundary) = boundaries.get(i + order as usize - 1) {
if boundary.offset.begin > end {
break;
}
let matchtext = &text[begin..boundary.offset.begin];
if !matchtext.is_empty() && matchtext != " " {
let mut ngram = Match::new_empty(matchtext, Offset {
begin: begin,
end: boundary.offset.begin,
});
ngram.n = order as usize;
ngrams.push(ngram);
}
begin = boundaries.get(i).expect("boundary").offset.end;
i += 1;
}
if begin < end {
let matchtext = &text[begin..end];
if !matchtext.is_empty() && matchtext != " " {
let mut ngram = Match::new_empty(matchtext, Offset {
begin: begin,
end: end,
});
ngram.n = order as usize;
if ngram.internal_boundaries(boundaries).iter().count() == order as usize {
ngrams.push(ngram);
}
}
}
ngrams
}
pub fn redundant_match<'a>(candidate: &Match<'a>, matches: &[Match<'a>]) -> bool {
for refmatch in matches.iter() {
if refmatch.n == 1 {
if refmatch.offset.begin >= candidate.offset.begin && refmatch.offset.end <= candidate.offset.end {
if let Some(variants) = &refmatch.variants {
if variants.is_empty() || variants.get(0).expect("variant").dist_score < 1.0 {
return false; }
} else {
return false; }
}
} else {
break; }
}
true
}
#[derive(Clone,Debug)]
pub enum PatternMatch {
Vocab(VocabId),
Any,
NoLexicon,
FromLexicon(u8),
Not(Box<PatternMatch>),
Disjunction(Box<Vec<PatternMatch>>)
}
#[derive(Clone,Debug)]
pub struct ContextRule {
pub pattern: Vec<PatternMatch>,
pub score: f32,
pub tag: Vec<u16>,
pub tagoffset: Vec<(u8,u8)> }
#[derive(Clone,Debug)]
pub struct PatternMatchResult {
pub score: f32,
pub tag: Option<u16>,
pub seqnr: u8,
}
impl PatternMatch {
pub fn matches(&self, sequence: &[(VocabId,u32)], index: usize) -> bool {
match self {
PatternMatch::Any => {
return true;
},
PatternMatch::NoLexicon => {
if let Some((vocabid, lexindex)) = sequence.get(index) {
if *lexindex == 0 || *vocabid == 0 {
return true;
}
}
},
PatternMatch::Vocab(testvocabid) => {
if let Some((vocabid, _lexindex)) = sequence.get(index) {
if testvocabid == vocabid {
return true;
}
}
},
PatternMatch::FromLexicon(lextest) => {
if let Some((_vocabid, lexindex)) = sequence.get(index) {
if lexindex & (1 << lextest) == 1 << lextest {
return true;
}
}
},
PatternMatch::Not(pm) => {
return !pm.matches(sequence, index);
},
PatternMatch::Disjunction(pms) => {
for pm in pms.iter() {
if pm.matches(sequence, index) {
return true;
}
}
},
};
false
}
pub fn parse(s: &str, lexicons: &Vec<String>, encoder: &VocabEncoder) -> Result<Self,std::io::Error> {
let s = s.trim();
if s == "?" {
Ok(Self::Any)
} else if s == "^" {
Ok(Self::NoLexicon)
} else if s.starts_with("!(") && s.ends_with(")") {
let s = &s[2..s.len() - 1];
let pm = Self::parse(s, lexicons, encoder)?;
Ok(Self::Not(Box::new(pm)))
} else if s.find("|").is_some() {
let items_in: Vec<&str> = s.split("|").collect();
let mut items_out: Vec<Self> = Vec::new();
for item in items_in {
match Self::parse(item, lexicons, encoder) {
Ok(pm) => items_out.push(pm),
Err(err) => return Err(err)
};
}
Ok(Self::Disjunction(Box::new(items_out)))
} else if s.starts_with("!") {
let s = &s[1..];
let pm = Self::parse(s, lexicons, encoder)?;
Ok(Self::Not(Box::new(pm)))
} else if s.starts_with("@") {
let source = &s[1..];
let relsource = format!("/{}", source);
for (i, lexicon) in lexicons.iter().enumerate() {
if source == lexicon || lexicon.ends_with(&relsource) {
return Ok(Self::FromLexicon(i as u8));
}
}
Err(std::io::Error::new(std::io::ErrorKind::Other, format!("WARNING: Context rule references lexicon or variant list '{}' but this source was not loaded", source)))
} else {
if let Some(vocab_id) = encoder.get(s) {
return Ok(Self::Vocab(*vocab_id));
}
Err(std::io::Error::new(std::io::ErrorKind::Other, format!("WARNING: Context rule references word '{}' but this word does not occur in any lexicon", s)))
}
}
}
impl ContextRule {
pub fn invert_score(&self) -> f32 {
return 1.0 / self.score;
}
pub fn len(&self) -> usize {
self.pattern.len()
}
pub fn matches(&self, sequence: &[(VocabId,u32)], begin: usize, sequence_result: &mut Vec<Vec<PatternMatchResult>>) -> bool {
assert_eq!(sequence.len(), sequence_result.len());
if begin + self.pattern.len() > sequence.len() {
return false;
}
let mut found = true;
for (cursor, contextmatch) in self.pattern.iter().enumerate() {
if !sequence_result[begin+cursor].is_empty() || !contextmatch.matches(sequence, begin+cursor) {
found = false;
break;
}
}
if found {
for cursor in 0..self.pattern.len() {
sequence_result[begin+cursor] = if self.tag.is_empty() {
vec!(PatternMatchResult {
score: self.score,
tag: None,
seqnr: cursor as u8
})
} else {
self.tag.iter().zip(self.tagoffset.iter()).filter_map(|(tag,tagoffset)| {
if cursor as u8 >= tagoffset.0 && (cursor as u8) < tagoffset.0 + tagoffset.1 {
Some(PatternMatchResult {
score: self.score,
tag: Some(*tag),
seqnr: cursor as u8 - tagoffset.0
})
} else {
None
}
}).collect()
};
}
true
} else {
false
}
}
}
pub(crate) fn remap_offsets_to_unicodepoints<'a>(text: &'a str, mut matches: Vec<Match<'a>>) -> Vec<Match<'a>> {
let mut bytes2unicodepoints: Vec<Option<usize>> = Vec::new();
let mut end = 0;
for (unicodeoffset, c) in text.chars().enumerate() {
bytes2unicodepoints.push(Some(unicodeoffset));
for _ in 0..c.len_utf8()-1 {
bytes2unicodepoints.push(None);
}
end = unicodeoffset+1;
}
bytes2unicodepoints.push(Some(end));
for m in matches.iter_mut() {
m.offset.convert(&bytes2unicodepoints);
}
matches
}