use super::{
language::{
BoundaryContext as OldBoundaryContext, BoundaryDecision as OldBoundaryDecision,
LanguageRules,
},
traits::{
BoundaryAnalyzer, BoundaryCandidateInfo, BoundaryContext as NewBoundaryContext,
BoundaryDecision as NewBoundaryDecision, BoundaryMarkerType, CharacterClass,
CharacterClassifier, LanguageSpecificRules, QuoteBehavior, QuoteType, RejectionReason,
},
types::PartialState,
BoundaryFlags,
};
pub struct LanguageRulesAdapter<T: LanguageRules> {
inner: T,
}
impl<T: LanguageRules> LanguageRulesAdapter<T> {
pub fn new(rules: T) -> Self {
Self { inner: rules }
}
}
impl<T: LanguageRules> CharacterClassifier for LanguageRulesAdapter<T> {
fn classify(&self, ch: char) -> CharacterClass {
match ch {
'.' | '?' | '!' | '。' | '?' | '!' | '…' | '‥' => {
CharacterClass::SentenceTerminal
}
ch if ch.is_whitespace() => CharacterClass::Whitespace,
ch if ch.is_alphabetic() => CharacterClass::Alphabetic,
ch if ch.is_numeric() => CharacterClass::Numeric,
ch if self.inner.get_enclosure_char(ch).is_some() => {
if matches!(
ch,
'(' | '['
| '{'
| '"'
| '\''
| '「'
| '『'
| '('
| '['
| '{'
| '〔'
| '【'
| '〈'
| '《'
) {
CharacterClass::DelimiterOpen
} else {
CharacterClass::DelimiterClose
}
}
ch if ch.is_ascii_punctuation() => CharacterClass::OtherPunctuation,
_ => CharacterClass::Other,
}
}
}
impl<T: LanguageRules> LanguageSpecificRules for LanguageRulesAdapter<T> {
fn is_abbreviation(&self, word: &str) -> bool {
let result = self.inner.process_abbreviation(word, word.len());
result.is_abbreviation
}
fn quote_behavior(&self, _quote_type: QuoteType) -> QuoteBehavior {
QuoteBehavior::Contextual
}
fn language_code(&self) -> &str {
self.inner.language_code()
}
}
impl<T: LanguageRules> BoundaryAnalyzer for LanguageRulesAdapter<T> {
fn analyze_candidate(&self, context: &NewBoundaryContext) -> BoundaryCandidateInfo {
let marker_type = match context.boundary_char {
'.' | '。' => BoundaryMarkerType::Period,
'?' | '?' => BoundaryMarkerType::Question,
'!' | '!' => BoundaryMarkerType::Exclamation,
ch => BoundaryMarkerType::Other(ch),
};
let base_confidence = match marker_type {
BoundaryMarkerType::Question | BoundaryMarkerType::Exclamation => 0.9,
BoundaryMarkerType::Period => 0.7,
BoundaryMarkerType::Other(_) => 0.5,
};
BoundaryCandidateInfo {
position: context.position,
confidence: base_confidence,
context: context.clone(),
marker_type,
}
}
fn evaluate_boundary(
&self,
candidate: &BoundaryCandidateInfo,
_state: &PartialState,
) -> NewBoundaryDecision {
let old_context = OldBoundaryContext {
text: format!(
"{}{}",
candidate.context.text_before, candidate.context.text_after
),
position: candidate.context.text_before.len(),
boundary_char: candidate.context.boundary_char,
preceding_context: candidate.context.text_before.clone(),
following_context: candidate.context.text_after.clone(),
};
match self.inner.detect_sentence_boundary(&old_context) {
OldBoundaryDecision::Boundary(flags) => {
let confidence = if flags.contains(BoundaryFlags::STRONG) {
1.0
} else {
0.75
};
NewBoundaryDecision::Confirmed { confidence }
}
OldBoundaryDecision::NotBoundary => {
let reason = if self.is_abbreviation(&candidate.context.text_before) {
RejectionReason::Abbreviation
} else if candidate.context.enclosure_depth > 0 {
RejectionReason::InsideEnclosure
} else {
RejectionReason::InvalidFollowing
};
NewBoundaryDecision::Rejected { reason }
}
OldBoundaryDecision::NeedsMoreContext => NewBoundaryDecision::Pending,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::language::ConfigurableLanguageRules;
#[test]
fn test_language_rules_adapter_character_classification() {
let rules = ConfigurableLanguageRules::from_code("en").unwrap();
let adapter = LanguageRulesAdapter::new(rules);
assert_eq!(adapter.classify('.'), CharacterClass::SentenceTerminal);
assert_eq!(adapter.classify('?'), CharacterClass::SentenceTerminal);
assert_eq!(adapter.classify('!'), CharacterClass::SentenceTerminal);
assert_eq!(adapter.classify(' '), CharacterClass::Whitespace);
assert_eq!(adapter.classify('\t'), CharacterClass::Whitespace);
assert_eq!(adapter.classify('\n'), CharacterClass::Whitespace);
assert_eq!(adapter.classify('a'), CharacterClass::Alphabetic);
assert_eq!(adapter.classify('Z'), CharacterClass::Alphabetic);
assert_eq!(adapter.classify('0'), CharacterClass::Numeric);
assert_eq!(adapter.classify('9'), CharacterClass::Numeric);
assert_eq!(adapter.classify('('), CharacterClass::DelimiterOpen);
assert_eq!(adapter.classify(')'), CharacterClass::DelimiterClose);
assert_eq!(adapter.classify('"'), CharacterClass::DelimiterOpen);
assert_eq!(adapter.classify(','), CharacterClass::OtherPunctuation);
assert_eq!(adapter.classify(';'), CharacterClass::OtherPunctuation);
}
#[test]
fn test_language_specific_rules_adapter() {
let rules = ConfigurableLanguageRules::from_code("en").unwrap();
let adapter = LanguageRulesAdapter::new(rules);
assert!(adapter.is_abbreviation("Dr"));
assert!(adapter.is_abbreviation("Mr"));
assert!(!adapter.is_abbreviation("Hello"));
assert_eq!(adapter.language_code(), "en");
assert!(matches!(
adapter.quote_behavior(QuoteType::Double),
QuoteBehavior::Contextual
));
}
#[test]
fn test_boundary_analyzer_adapter() {
let rules = ConfigurableLanguageRules::from_code("en").unwrap();
let adapter = LanguageRulesAdapter::new(rules);
let context = NewBoundaryContext {
text_before: "Hello".to_string(),
text_after: " World".to_string(),
position: 5,
boundary_char: '.',
enclosure_depth: 0,
};
let candidate = adapter.analyze_candidate(&context);
assert_eq!(candidate.position, 5);
assert_eq!(candidate.confidence, 0.7); assert!(matches!(candidate.marker_type, BoundaryMarkerType::Period));
let context_question = NewBoundaryContext {
boundary_char: '?',
..context.clone()
};
let candidate_question = adapter.analyze_candidate(&context_question);
assert_eq!(candidate_question.confidence, 0.9);
let state = PartialState::default();
let decision = adapter.evaluate_boundary(&candidate, &state);
assert!(matches!(decision, NewBoundaryDecision::Confirmed { .. }));
}
#[test]
fn test_japanese_character_classification() {
let rules = ConfigurableLanguageRules::from_code("ja").unwrap();
let adapter = LanguageRulesAdapter::new(rules);
assert_eq!(adapter.classify('。'), CharacterClass::SentenceTerminal);
assert_eq!(adapter.classify('?'), CharacterClass::SentenceTerminal);
assert_eq!(adapter.classify('!'), CharacterClass::SentenceTerminal);
assert_eq!(adapter.classify('「'), CharacterClass::DelimiterOpen);
assert_eq!(adapter.classify('」'), CharacterClass::DelimiterClose);
assert_eq!(adapter.classify('('), CharacterClass::DelimiterOpen);
assert_eq!(adapter.classify(')'), CharacterClass::DelimiterClose);
}
}