#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NameMarker {
High,
Low,
NoMarker,
Ambiguous,
}
pub fn token_spans(chars: &[char]) -> Vec<(usize, usize)> {
let mut spans = Vec::new();
let mut start: Option<usize> = None;
for (i, &c) in chars.iter().enumerate() {
if !c.is_ascii_alphanumeric() {
if let Some(s) = start.take() {
spans.push((s, i));
}
continue;
}
if let Some(s) = start {
let p = chars[i - 1];
let boundary = p.is_ascii_digit() != c.is_ascii_digit()
|| (p.is_ascii_lowercase() && c.is_ascii_uppercase())
|| (p.is_ascii_uppercase()
&& c.is_ascii_uppercase()
&& chars.get(i + 1).is_some_and(|n| n.is_ascii_lowercase()));
if boundary {
spans.push((s, i));
start = Some(i);
}
} else {
start = Some(i);
}
}
if let Some(s) = start {
spans.push((s, chars.len()));
}
spans
}
pub fn token_marker_suffix(chars: &[char], span: (usize, usize)) -> Option<(usize, NameMarker)> {
let token: String = chars[span.0..span.1]
.iter()
.map(|c| c.to_ascii_lowercase())
.collect();
let marker = |s: &str| match s {
"high" => Some(NameMarker::High),
"low" => Some(NameMarker::Low),
_ => None,
};
if let Some(m) = marker(&token) {
return Some((0, m));
}
let digit_preceded = span.0 > 0 && chars[span.0 - 1].is_ascii_digit();
if !digit_preceded {
return None;
}
for suffix in ["high", "low"] {
if let Some(prefix) = token.strip_suffix(suffix) {
if prefix.len() <= 2 {
return Some((prefix.len(), marker(suffix).expect("marker suffix")));
}
}
}
None
}
pub fn token_is_marker(chars: &[char], span: (usize, usize)) -> Option<NameMarker> {
token_marker_suffix(chars, span).map(|(_, marker)| marker)
}
pub fn classify_name(name: &str) -> NameMarker {
let chars: Vec<char> = name.chars().collect();
let mut has_high = false;
let mut has_low = false;
for span in token_spans(&chars) {
match token_is_marker(&chars, span) {
Some(NameMarker::High) => has_high = true,
Some(NameMarker::Low) => has_low = true,
_ => {}
}
}
match (has_high, has_low) {
(true, false) => NameMarker::High,
(false, true) => NameMarker::Low,
(false, false) => NameMarker::NoMarker,
(true, true) => NameMarker::Ambiguous,
}
}