use super::exceptions::{HyphenationExceptionsCollection, HyphenationOverride};
use super::pattern::{Pattern, PatternsCollection};
pub struct LoadedPatterns {
pub patterns: PatternsCollection,
pub exceptions: HyphenationExceptionsCollection,
pub max_pattern_length: usize,
}
fn match_command(rest: &str) -> Option<(usize, String)> {
let bytes = rest.as_bytes();
debug_assert_eq!(bytes.first(), Some(&b'\\'));
let mut i = 1;
while i < bytes.len() && bytes[i].is_ascii_alphabetic() {
i += 1;
}
if i == 1 {
None
} else {
Some((i, rest[1..i].to_string()))
}
}
fn match_token_len(rest: &str) -> usize {
let mut end = 0;
for ch in rest.chars() {
if matches!(ch, ' ' | '\t' | '\n' | '\r' | '\u{0B}' | '\u{0C}') {
break;
}
end += ch.len_utf8();
}
end
}
pub fn parse_tex(contents: &str) -> LoadedPatterns {
let mut patterns = PatternsCollection::new();
let mut exceptions = HyphenationExceptionsCollection::new();
let mut command: Option<String> = None;
let mut in_braces = false;
for line in contents.lines() {
let bytes = line.as_bytes();
let mut offset = 0usize;
while offset < bytes.len() {
let b = bytes[offset];
if b == b'%' && !in_braces {
break;
}
if b == b'\\' && !in_braces {
if let Some((end, cmd)) = match_command(&line[offset..]) {
command = Some(cmd);
offset += end;
continue;
}
offset += 1;
continue;
}
if b == b'{' {
if command.is_some() {
in_braces = true;
}
offset += 1;
continue;
}
if b == b'}' && in_braces {
in_braces = false;
command = None;
offset += 1;
continue;
}
if in_braces {
match command.as_deref() {
Some("patterns") => {
let rest = &line[offset..];
let end = match_token_len(rest);
if end > 0 {
let token = &rest[..end];
if let Some(pattern) = parse_pattern_token(token) {
patterns.add(pattern);
}
offset += end;
continue;
}
}
Some("hyphenation") => {
let rest = &line[offset..];
let end = match_token_len(rest);
if end > 0 {
let token = &rest[..end];
let word = token.replace('-', "").to_lowercase();
let hyphenated = token.to_lowercase();
exceptions.add(HyphenationOverride::new(word, hyphenated));
offset += end;
continue;
}
}
_ => {}
}
}
offset += 1;
}
}
let max_pattern_length = patterns.max_length();
LoadedPatterns {
patterns,
exceptions,
max_pattern_length,
}
}
pub fn parse_pattern_token(token: &str) -> Option<Pattern> {
let mut chars: Vec<String> = Vec::new();
let mut numbers = String::new();
let mut expect_number = true;
let mut has_digit = false;
for ch in token.chars() {
if ch.is_ascii_digit() {
numbers.push(ch);
has_digit = true;
expect_number = false;
} else {
if expect_number {
numbers.push('0');
}
chars.push(ch.to_string());
expect_number = true;
}
}
if expect_number {
numbers.push('0');
}
if chars.is_empty() || !has_digit {
return None;
}
let weights: Vec<i32> = numbers
.chars()
.map(|d| d.to_digit(10).unwrap() as i32)
.collect();
Some(Pattern::new(chars, weights))
}