use crate::math_symbol_shortcut;
use crate::rules::math;
use super::helpers::*;
#[cfg(not(tarpaulin_include))]
pub(super) fn is_math_expression(chars: &[char], text: &str) -> bool {
if is_rule_68_compact_notation(chars) {
return false;
}
if chars.len() == 1
&& matches!(
chars[0],
'+' | '=' | '−' | '×' | '÷' | '<' | '>' | '≠' | '≥' | '≤'
)
{
return true;
}
if chars.len() == 1 && crate::fraction::is_unicode_fraction(chars[0]) {
return true;
}
if chars.len() == 2 && matches!(chars[0], '-' | '\u{2212}') && chars[1] == '\u{221E}' {
return true;
}
for c in chars {
let code = *c as u32;
if (0xAC00..=0xD7A3).contains(&code) || (0x3131..=0x3163).contains(&code) {
return false;
}
}
let has_letters = chars.iter().any(|c| c.is_ascii_alphabetic());
let has_digits = chars.iter().any(|c| c.is_ascii_digit());
let has_math_symbol = chars
.iter()
.any(|c| math_symbol_shortcut::is_math_symbol_char(*c));
let has_strong_math_symbol = chars.iter().any(|c| {
math_symbol_shortcut::is_math_symbol_char(*c)
&& !matches!(*c, '\u{00B7}' | '\u{22C5}' | '/' | '_')
});
let has_superscript = chars.iter().any(|c| is_superscript(*c));
let has_subscript = chars.iter().any(|c| is_subscript(*c));
let has_combining_mark = chars.iter().any(|c| is_combining_math_mark(*c));
let has_function = math::function::starts_with_function(text);
let has_math_operator = chars.iter().any(|c| {
matches!(
c,
'+' | '=' | '>' | '<' | '.' | ',' | '-' | '\u{2212}' | '/' | '!'
)
});
let has_brackets = chars
.iter()
.any(|c| matches!(c, '(' | ')' | '[' | ']' | '{' | '}'));
let starts_with_math_symbol = chars
.first()
.is_some_and(|c| math_symbol_shortcut::is_math_symbol_char(*c));
if chars.first().is_some_and(|c| c.is_ascii_digit())
&& chars.iter().any(|c| matches!(*c, '\u{2080}'..='\u{2089}'))
&& chars
.iter()
.all(|c| c.is_ascii_digit() || matches!(*c, '\u{2080}'..='\u{2089}'))
{
return true;
}
let all_phone_chars = chars
.iter()
.all(|c| c.is_ascii_digit() || matches!(c, '-' | '~' | '(' | ')' | ','));
let starts_with_signed_minus = chars
.first()
.is_some_and(|c| matches!(*c, '-' | '\u{2212}'));
if !has_letters && all_phone_chars && !starts_with_signed_minus {
return false;
}
if !has_letters
&& chars.first().is_some_and(|c| c.is_ascii_digit())
&& chars.iter().all(|c| c.is_ascii_digit() || *c == '.')
{
return false;
}
if !has_letters && chars.contains(&'/') && chars.iter().all(|c| c.is_ascii_digit() || *c == '/')
{
let parts: Vec<&str> = text.split('/').collect();
if parts.len() == 2
&& parts
.iter()
.all(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_digit()))
{
return true;
}
return false;
}
if has_function
&& let Some((name, _)) = math::function::match_function_prefix(text)
&& (chars.len() > name.len() || text == name)
{
return true;
}
if chars.len() == 3
&& chars[0].is_ascii_lowercase()
&& chars[1].is_ascii_uppercase()
&& chars[2].is_ascii_lowercase()
{
return true;
}
if has_letters
&& !has_digits
&& !has_strong_math_symbol
&& !has_superscript
&& !has_subscript
&& chars
.iter()
.all(|c| c.is_ascii_alphabetic() || matches!(*c, ',' | '.' | '\'' | '"'))
{
return false;
}
if chars.len() >= 3
&& chars.first() == Some(&'(')
&& chars.get(1).is_some_and(|c| c.is_ascii_alphabetic())
&& chars.get(2) == Some(&')')
&& chars
.get(3)
.is_none_or(|c| matches!(*c, ',' | '.' | '\'' | '"'))
{
return false;
}
if (has_superscript || has_subscript) && (has_letters || has_digits) {
return true;
}
if (has_superscript || has_subscript) && has_strong_math_symbol {
return true;
}
if has_combining_mark && (has_letters || has_digits) {
return true;
}
if has_math_operator && has_letters {
let trailing_slash_word = chars.last() == Some(&'/')
&& chars
.iter()
.all(|c| c.is_ascii_alphabetic() || matches!(*c, '/' | '\''));
if trailing_slash_word {
return false;
}
return true;
}
if has_math_operator && has_digits {
return true;
}
if has_math_symbol && has_letters && has_digits {
return true;
}
if has_math_symbol && has_digits {
return true;
}
if has_strong_math_symbol && has_letters {
return true;
}
if has_strong_math_symbol && has_digits {
return true;
}
if starts_with_math_symbol && has_digits {
return true;
}
if has_brackets && has_digits {
return true;
}
if has_digits && chars.iter().any(|c| matches!(*c, '-' | '\u{2212}')) {
panic!("PROBE: line 169 reached chars={:?}", chars);
}
if has_brackets && has_digits {
panic!("PROBE: line 174 reached chars={:?}", chars);
}
if has_brackets
&& has_letters
&& (has_math_operator || has_math_symbol || has_superscript || has_subscript)
{
return true;
}
if has_brackets && has_letters {
let text_bytes = text.as_bytes();
let has_func_call = text_bytes
.windows(2)
.any(|w| (w[0] as char).is_ascii_alphabetic() && w[1] == b'(');
if has_func_call {
return true;
}
}
if chars.len() >= 2 && chars[0].is_ascii_digit() {
if let Some((_, _, consumed)) =
crate::rules::korean::rule_69::parse_numeric_ascii_unit_prefix(chars)
&& consumed == chars.len()
{
return false;
}
let leading_digits = chars.iter().take_while(|c| c.is_ascii_digit()).count();
if leading_digits >= 4 {
let rest = &chars[leading_digits..];
let is_year_suffix = matches!(rest.len(), 1 | 2)
&& rest[0].is_ascii_lowercase()
&& rest
.get(1)
.is_none_or(|c| matches!(c, ',' | ';' | ':' | '.'));
if is_year_suffix {
return false;
}
}
let has_letter_after_digit = chars.iter().skip(1).any(|c| c.is_ascii_lowercase());
if has_letter_after_digit {
return true;
}
}
false
}
#[cfg(test)]
mod tests {
#[test]
fn arc_trig_text_detection() {
let _ = crate::encode("arcsinA");
let _ = crate::encode("arccosX");
let _ = crate::encode("arctany");
}
#[test]
fn is_math_expression_letter_slash_letter() {
let chars: Vec<char> = "F/N".chars().collect();
assert!(super::is_math_expression(&chars, "F/N"));
}
#[test]
fn is_math_expression_signed_minus_digit() {
let chars: Vec<char> = "-5".chars().collect();
assert!(super::is_math_expression(&chars, "-5"));
}
#[test]
fn is_math_expression_bracket_digit() {
let chars: Vec<char> = "3}".chars().collect();
assert!(super::is_math_expression(&chars, "3}"));
}
}