///|
/// Identifies the language of a stream of text fed in chunks, such as the
/// messages of a chat or the lines of a long file, without reading what
/// came before again. It keeps the counts detection needs (characters per
/// script, trigrams and, in `Mode::Enhanced`, words), so a chunk costs
/// time in proportion to its own length, and asking for the language costs
/// time in proportion to the number of distinct trigrams seen.
///
/// The result is that of `detect` on the chunks joined with line breaks,
/// with two differences on long streams: franc looks only at the first
/// 2048 UTF-16 code units of a text, while an accumulator uses every
/// chunk, and the script of each chunk is judged on its first 2048 code
/// units. Markup is removed chunk by chunk, so a URL or tag split across
/// two chunks is not recognised.
pub struct Accumulator {
priv detector : Detector
priv stats : TextStats
/// The last code unit of the cleaned text so far, for the trigram that
/// spans the line break before the next chunk.
priv mut last_unit : Int32?
priv mut chunks : Int32
}
///|
/// An empty accumulator with this detector's options.
pub fn Detector::accumulator(self : Detector) -> Accumulator {
{
detector: self,
stats: {
length: 0,
sample_length: 0,
scripts: FixedArray::make(script_ranges.length(), 0),
grams: Map([]),
words: Map([]),
},
last_unit: None,
chunks: 0,
}
}
///|
/// An empty accumulator for `options` (by default those of
/// `Options::new()`).
pub fn Accumulator::new(options~ : Options = default_options) -> Accumulator {
detector_for(options).accumulator()
}
///|
/// Adds `chunk` to the stream, after a line break when it is not the
/// first.
pub fn Accumulator::feed(self : Accumulator, chunk : String) -> Unit {
let options = self.detector.options
let text = prepared(chunk, options)
let separator = if self.chunks > 0 { 1 } else { 0 }
self.chunks += 1
self.stats.length += separator + text.length()
self.stats.sample_length += separator + text.length()
let scripts = script_counts(text)
for s in 0.. Detection? {
self.detector.decide(self.stats)
}
///|
/// The candidate languages for everything fed so far; see `rank`.
pub fn Accumulator::rank(self : Accumulator) -> Array[(Lang, Double)] {
self.detector.ranked(self.stats)
}
///|
/// UTF-16 code units fed so far, counting a line break between chunks,
/// after markup removal when the options remove it.
pub fn Accumulator::length(self : Accumulator) -> Int32 {
self.stats.length
}
///|
/// Forgets everything fed so far.
pub fn Accumulator::reset(self : Accumulator) -> Unit {
self.stats.length = 0
self.stats.sample_length = 0
self.stats.scripts.fill(0)
self.stats.grams.clear()
self.stats.words.clear()
self.last_unit = None
self.chunks = 0
}