use crate::store::{Store, TokenId};
use std::collections::HashSet;
pub struct StreamingRecognizer<'a> {
store: &'a Store,
stack: Vec<TokenId>,
focused: Option<&'a HashSet<TokenId>>,
}
impl<'a> StreamingRecognizer<'a> {
pub fn new(store: &'a Store) -> Self {
Self {
store,
stack: Vec::new(),
focused: None,
}
}
pub fn with_focus(store: &'a Store, focused: &'a HashSet<TokenId>) -> Self {
Self {
store,
stack: Vec::new(),
focused: Some(focused),
}
}
pub fn feed_token(&mut self, token: TokenId) {
self.stack.push(token);
self.reduce();
}
pub fn feed_tokens(&mut self, tokens: &[TokenId]) {
for &t in tokens {
self.feed_token(t);
}
}
pub fn feed_bytes(&mut self, bytes: &[u8]) {
for &b in bytes {
let token_id = b as TokenId;
self.feed_token(token_id);
}
}
fn reduce(&mut self) {
loop {
let len = self.stack.len();
if len < 2 {
break;
}
let a = self.stack[len - 2];
let b = self.stack[len - 1];
let level = self.store.level_of(a).max(self.store.level_of(b)) + 1;
if let Some(composite_id) = self.store.lookup(level, &[a, b]) {
let usable = self.focused.is_none_or(|f| f.contains(&composite_id));
if usable {
self.stack.pop();
self.stack.pop();
self.stack.push(composite_id);
continue;
}
}
break;
}
}
pub fn as_slice(&self) -> &[TokenId] {
&self.stack
}
pub fn len(&self) -> usize {
self.stack.len()
}
pub fn is_empty(&self) -> bool {
self.stack.is_empty()
}
pub fn clear(&mut self) {
self.stack.clear();
}
pub fn drain(&mut self) -> Vec<TokenId> {
std::mem::take(&mut self.stack)
}
pub fn finish(self) -> Vec<TokenId> {
self.stack
}
pub fn recognize(store: &'a Store, bytes: &[u8]) -> Vec<TokenId> {
Self::recognize_focused(store, bytes, None)
}
pub fn recognize_focused(
store: &'a Store,
bytes: &[u8],
focused: Option<&'a HashSet<TokenId>>,
) -> Vec<TokenId> {
let mut recognizer = match focused {
Some(f) => Self::with_focus(store, f),
None => Self::new(store),
};
recognizer.feed_bytes(bytes);
recognizer.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::merge::TieBreak;
use crate::wheel::WheelState;
fn build_test_store() -> (Store, TokenId, TokenId, TokenId) {
let mut store = Store::new();
store.init_factory();
let a = b'a' as TokenId;
let b = b'b' as TokenId;
let c = b'c' as TokenId;
let ab = store.intern(1, &[a, b]);
let bc = store.intern(1, &[b, c]);
let abc = store.intern(2, &[ab, c]);
(store, ab, bc, abc)
}
#[test]
fn test_streaming_matches_one_shot() {
let (store, _ab, _bc, abc) = build_test_store();
let tokens_one_shot = StreamingRecognizer::recognize(&store, b"abc");
assert_eq!(tokens_one_shot, vec![abc]);
let mut recognizer = StreamingRecognizer::new(&store);
recognizer.feed_bytes(b"a");
recognizer.feed_bytes(b"b");
recognizer.feed_bytes(b"c");
let tokens_stream = recognizer.finish();
assert_eq!(tokens_stream, vec![abc]);
}
#[test]
fn test_arbitrary_chunk_sizes_equivalence() {
let mut wheel = WheelState::new();
wheel.turn(b"the cat sat on the mat. the cat sat on the mat.", 2, TieBreak::BirthOrder);
let store = &wheel.store;
let input = b"the cat sat on the mat. the cat sat on the mat.";
let canonical = StreamingRecognizer::recognize(store, input);
let mut rec1 = StreamingRecognizer::new(store);
for &b in input {
rec1.feed_bytes(&[b]);
}
assert_eq!(rec1.finish(), canonical, "не совпадает при подаче по 1 байту");
let mut rec3 = StreamingRecognizer::new(store);
for chunk in input.chunks(3) {
rec3.feed_bytes(chunk);
}
assert_eq!(rec3.finish(), canonical, "не совпадает при чанках по 3 байта");
let mut rec7 = StreamingRecognizer::new(store);
for chunk in input.chunks(7) {
rec7.feed_bytes(chunk);
}
assert_eq!(rec7.finish(), canonical, "не совпадает при чанках по 7 байт");
}
#[test]
fn test_focused_filter_respects_allowed_set() {
let (store, ab, _bc, abc) = build_test_store();
let mut focused = HashSet::new();
focused.insert(ab);
let tokens = StreamingRecognizer::recognize_focused(&store, b"abc", Some(&focused));
assert_eq!(tokens, vec![ab, b'c' as TokenId]);
focused.insert(abc);
let tokens_full = StreamingRecognizer::recognize_focused(&store, b"abc", Some(&focused));
assert_eq!(tokens_full, vec![abc]);
}
#[test]
fn test_drain_and_continue() {
let (store, ab, _bc, _abc) = build_test_store();
let mut rec = StreamingRecognizer::new(&store);
rec.feed_bytes(b"ab");
let out1 = rec.drain();
assert_eq!(out1, vec![ab]);
assert!(rec.is_empty());
rec.feed_bytes(b"ab");
let out2 = rec.finish();
assert_eq!(out2, vec![ab]);
}
}