use std::sync::OnceLock;
use regex::Regex;
use crate::detail::container::{find_next_token, mark};
use crate::detail::element::element_from_token;
use crate::detail::keyword::KeywordKind;
use crate::detail::token::{
is_delimiter_token, is_free_token, is_not_delimiter_token, is_numeric_token, Token,
};
use crate::detail::util::from_ordinal_number;
use crate::element::{Element, ElementKind};
fn is_part_keyword(token: &Token) -> bool {
token
.keyword
.is_some_and(|k| k.kind == KeywordKind::Part && (!k.ambiguous || token.is_enclosed))
}
fn p_prefixed_pattern() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| crate::detail::regex_util::compile(r"^P([0-9]{1,2})$"))
}
pub(super) fn parse_part(tokens: &mut [Token]) -> Option<Element> {
let keyword_indices: Vec<usize> = tokens
.iter()
.enumerate()
.filter(|(_, t)| is_part_keyword(t))
.map(|(i, _)| i)
.collect();
for keyword_idx in keyword_indices {
if keyword_idx >= 2
&& tokens.get(keyword_idx - 1).is_some_and(is_delimiter_token)
&& tokens.get(keyword_idx - 2).is_some_and(is_free_token)
{
if let Some((value, position)) = tokens
.get(keyword_idx - 2)
.map(|t| (t.value.clone(), t.position))
{
if let Some(number) = from_ordinal_number(&value) {
mark(tokens, keyword_idx - 2, ElementKind::Part);
mark(tokens, keyword_idx, ElementKind::Part);
return Some(Element {
kind: ElementKind::Part,
value: number.to_string(),
position,
});
}
}
}
let Some(next_idx) = find_next_token(tokens, keyword_idx, is_not_delimiter_token) else {
continue;
};
let Some(next) = tokens.get(next_idx) else {
continue;
};
if !is_numeric_token(next) {
continue;
}
mark(tokens, keyword_idx, ElementKind::Part);
mark(tokens, next_idx, ElementKind::Part);
let token = tokens.get(next_idx)?;
return Some(element_from_token(ElementKind::Part, token, None, None));
}
for idx in 0..tokens.len() {
if !tokens.get(idx).is_some_and(is_free_token) {
continue;
}
let Some((value, position)) = tokens.get(idx).map(|t| (t.value.clone(), t.position)) else {
continue;
};
let Some(caps) = p_prefixed_pattern().captures(&value) else {
continue;
};
let Some(group1) = caps.get(1) else {
continue;
};
let group1 = group1.as_str().to_string();
mark(tokens, idx, ElementKind::Part);
return Some(Element {
kind: ElementKind::Part,
value: group1,
position,
});
}
None
}