use crate::domain::{
enclosure::EnclosureType,
language::LanguageRules,
types::{AbbreviationState, Boundary, BoundaryCandidate, BoundaryFlags, PartialState},
};
use std::collections::HashMap;
fn is_sentence_starter(word: &str) -> bool {
let first_char = word.chars().next().unwrap_or(' ');
first_char.is_uppercase() && word.len() > 1
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnhancedAbbreviationState {
pub basic: AbbreviationState,
pub abbreviation_text: Option<String>,
pub abbreviation_start: Option<usize>,
pub confidence: f32,
pub is_continuation: bool,
}
impl Default for EnhancedAbbreviationState {
fn default() -> Self {
Self {
basic: AbbreviationState::default(),
abbreviation_text: None,
abbreviation_start: None,
confidence: 0.0,
is_continuation: false,
}
}
}
impl EnhancedAbbreviationState {
pub fn from_basic(basic: AbbreviationState) -> Self {
Self {
basic,
abbreviation_text: None,
abbreviation_start: None,
confidence: 1.0,
is_continuation: false,
}
}
pub fn is_cross_chunk_abbreviation(&self, next: &Self) -> bool {
if self.basic.dangling_dot && next.basic.head_alpha {
return true;
}
if let Some(text) = &self.abbreviation_text {
if text.ends_with('.') && next.basic.head_alpha {
return true;
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct EnclosureStateTracker {
pub open_enclosures: Vec<(EnclosureType, usize)>,
pub depth_map: HashMap<usize, i32>,
pub unclosed_positions: Vec<usize>,
pub unopened_closures: Vec<usize>,
}
impl EnclosureStateTracker {
pub fn new() -> Self {
Self {
open_enclosures: Vec::new(),
depth_map: HashMap::new(),
unclosed_positions: Vec::new(),
unopened_closures: Vec::new(),
}
}
pub fn update(
&mut self,
enclosure_type: EnclosureType,
type_id: usize,
is_opening: bool,
position: usize,
) {
if is_opening {
self.open_enclosures.push((enclosure_type, position));
*self.depth_map.entry(type_id).or_insert(0) += 1;
self.unclosed_positions.push(position);
} else {
if let Some(pos) = self
.open_enclosures
.iter()
.rposition(|(t, _)| *t == enclosure_type)
{
let removed_position = self.open_enclosures[pos].1;
self.open_enclosures.remove(pos);
self.unclosed_positions.retain(|&p| p != removed_position);
} else {
self.unopened_closures.push(position);
}
*self.depth_map.entry(type_id).or_insert(0) -= 1;
}
}
pub fn merge_with_next(&self, next: &Self) -> Self {
let mut merged = self.clone();
for &_pos in &next.unopened_closures {
if !merged.open_enclosures.is_empty() {
merged.open_enclosures.pop();
}
if let Some(idx) = merged.unclosed_positions.iter().position(|_| true) {
merged.unclosed_positions.remove(idx);
}
}
merged
.open_enclosures
.extend(next.open_enclosures.iter().cloned());
for (type_id, &depth) in &next.depth_map {
*merged.depth_map.entry(*type_id).or_insert(0) += depth;
}
if !next.unopened_closures.is_empty() && !self.open_enclosures.is_empty() {
if let Some(_type_id) = self.open_enclosures.first().and_then(|(enc_type, _)| {
match enc_type {
EnclosureType::DoubleQuote => Some(0),
EnclosureType::SingleQuote => Some(1),
EnclosureType::Parenthesis => Some(2),
_ => None,
}
}) {
}
}
merged
}
}
impl Default for EnclosureStateTracker {
fn default() -> Self {
Self::new()
}
}
pub struct CrossChunkValidator {
overlap_size: usize,
#[allow(dead_code)]
min_context: usize,
}
impl CrossChunkValidator {
pub fn new(overlap_size: usize) -> Self {
Self {
overlap_size,
min_context: 20, }
}
pub fn validate_chunk_boundary(
&self,
boundary: &BoundaryCandidate,
state: &PartialState,
next_state: Option<&PartialState>,
_language_rules: &dyn LanguageRules,
) -> ValidationResult {
if !self.validate_enclosure_balance(boundary, state) {
return ValidationResult::Weakened(BoundaryFlags::WEAK);
}
let near_end = state.chunk_length.saturating_sub(boundary.local_offset) < self.overlap_size;
let near_start = boundary.local_offset < self.overlap_size;
if !near_end && !near_start {
return ValidationResult::Valid;
}
if near_end && next_state.is_none() {
return ValidationResult::NeedsMoreContext;
}
if near_end {
if let Some(next) = next_state {
if state.abbreviation.is_cross_chunk_abbr(&next.abbreviation) {
if let Some(ref first_word) = next.abbreviation.first_word {
if is_sentence_starter(first_word) {
return ValidationResult::Weakened(BoundaryFlags::STRONG);
}
}
return ValidationResult::Invalid(
"Cross-chunk abbreviation detected".to_string(),
);
}
}
}
ValidationResult::Valid
}
fn validate_enclosure_balance(
&self,
boundary: &BoundaryCandidate,
_state: &PartialState,
) -> bool {
boundary.local_depths.iter().all(|&depth| depth == 0)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ValidationResult {
Valid,
Weakened(BoundaryFlags),
Invalid(String),
NeedsMoreContext,
}
#[derive(Debug, Clone)]
pub struct EnhancedPartialState {
pub base: PartialState,
pub enhanced_abbreviation: EnhancedAbbreviationState,
pub enclosure_tracker: EnclosureStateTracker,
pub chunk_metadata: ChunkMetadata,
}
#[derive(Debug, Clone)]
pub struct ChunkMetadata {
pub index: usize,
pub total_chunks: usize,
pub has_prefix_overlap: bool,
pub has_suffix_overlap: bool,
pub prefix_overlap: Option<String>,
pub suffix_overlap: Option<String>,
}
impl ChunkMetadata {
pub fn is_first(&self) -> bool {
self.index == 0
}
pub fn is_last(&self) -> bool {
self.index == self.total_chunks - 1
}
}
pub struct CrossChunkResolver {
validator: CrossChunkValidator,
}
impl CrossChunkResolver {
pub fn new(overlap_size: usize) -> Self {
Self {
validator: CrossChunkValidator::new(overlap_size),
}
}
pub fn resolve_boundaries(
&self,
states: &[EnhancedPartialState],
language_rules: &dyn LanguageRules,
) -> Vec<Boundary> {
let mut all_boundaries = Vec::new();
for (i, state) in states.iter().enumerate() {
let next_state = states.get(i + 1);
for candidate in &state.base.boundary_candidates {
let validation = self.validator.validate_chunk_boundary(
candidate,
&state.base,
next_state.map(|s| &s.base),
language_rules,
);
match validation {
ValidationResult::Valid => {
all_boundaries.push(Boundary {
offset: candidate.local_offset, flags: candidate.flags,
});
}
ValidationResult::Weakened(flags) => {
all_boundaries.push(Boundary {
offset: candidate.local_offset,
flags,
});
}
ValidationResult::Invalid(_) | ValidationResult::NeedsMoreContext => {
}
}
}
}
all_boundaries
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::types::DepthVec;
#[test]
fn test_enhanced_abbreviation_state() {
let basic = AbbreviationState::with_first_word(
true, false, None, );
let enhanced = EnhancedAbbreviationState {
basic,
abbreviation_text: Some("Dr.".to_string()),
abbreviation_start: Some(10),
confidence: 0.95,
is_continuation: false,
};
let next = EnhancedAbbreviationState {
basic: AbbreviationState::with_first_word(
false, true, None, ),
..Default::default()
};
assert!(enhanced.is_cross_chunk_abbreviation(&next));
}
#[test]
fn test_enclosure_state_tracker() {
let mut tracker = EnclosureStateTracker::new();
tracker.update(EnclosureType::DoubleQuote, 0, true, 10);
assert_eq!(tracker.open_enclosures.len(), 1);
assert_eq!(tracker.depth_map[&0], 1);
tracker.update(EnclosureType::DoubleQuote, 0, false, 20);
assert_eq!(tracker.open_enclosures.len(), 0);
assert_eq!(tracker.depth_map[&0], 0);
}
#[test]
fn test_cross_chunk_validator() {
let validator = CrossChunkValidator::new(10);
let mut state = PartialState::new(5);
state.chunk_length = 100;
let boundary = BoundaryCandidate {
local_offset: 50, local_depths: DepthVec::from_vec(vec![0, 0, 0, 0, 0]),
flags: BoundaryFlags::STRONG,
};
let result = validator.validate_chunk_boundary(
&boundary,
&state,
None,
&crate::domain::language::MockLanguageRules::english(),
);
assert_eq!(result, ValidationResult::Valid);
}
#[test]
fn test_chunk_metadata() {
let metadata = ChunkMetadata {
index: 0,
total_chunks: 3,
has_prefix_overlap: false,
has_suffix_overlap: true,
prefix_overlap: None,
suffix_overlap: Some(" overlap".to_string()),
};
assert!(metadata.is_first());
assert!(!metadata.is_last());
}
}