use super::inner::is_escaped_literal;
use super::leading_literal_run;
use crate::types::MIN_LITERAL_PREFIX_CHARS;
pub(crate) const MAX_CHARCLASS_PREFIX_EXPANSION: usize = 8;
pub(crate) fn expand_leading_literal_alternation_with_tail(pattern: &str) -> Option<Vec<String>> {
let after_paren = pattern.strip_prefix('(')?;
let (inner, tail_src) = leading_group_parts(after_paren)?;
let inner = strip_group_prefix(inner);
if !has_top_level_alternation(inner) {
return None;
}
let tail = leading_literal_run(tail_src);
if tail.is_empty() {
return None;
}
let parts = split_top_level_alternatives(inner);
let mut out = Vec::with_capacity(parts.len());
for part in &parts {
let head = leading_literal_run(part);
if head.len() != part.len() {
return None;
}
let mut full = head;
full.push_str(&tail);
if full.len() < MIN_LITERAL_PREFIX_CHARS {
return None;
}
out.push(full);
}
(!out.is_empty()).then_some(out)
}
pub(crate) fn expand_leading_charclass_prefixes(pattern: &str) -> Option<Vec<String>> {
let bytes = pattern.as_bytes();
let mut head = String::new();
let mut i = 0;
loop {
let &b = bytes.get(i)?;
match b {
b'[' => break,
b'\\' => {
let next = *bytes.get(i + 1)? as char;
if is_escaped_literal(next) {
head.push(next);
i += 2;
} else {
return None;
}
}
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-' => {
head.push(b as char);
i += 1;
}
_ => return None,
}
}
let class_start = i + 1;
if bytes.get(class_start) == Some(&b'^') {
return None; }
let mut members = Vec::new();
let mut j = class_start;
while let Some(&b) = bytes.get(j) {
match b {
b']' => break,
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' => {
members.push(b as char);
j += 1;
}
b'\\' => {
let next = *bytes.get(j + 1)? as char;
if is_escaped_literal(next) {
members.push(next);
j += 2;
} else {
return None;
}
}
_ => return None,
}
if members.len() > MAX_CHARCLASS_PREFIX_EXPANSION {
return None;
}
}
if bytes.get(j) != Some(&b']') || members.is_empty() {
return None; }
let tail = leading_literal_run(&pattern[j + 1..]);
let mut out = Vec::with_capacity(members.len());
for m in members {
let mut prefix = head.clone();
prefix.push(m);
prefix.push_str(&tail);
if prefix.len() < MIN_LITERAL_PREFIX_CHARS {
return None;
}
out.push(prefix);
}
Some(out)
}
pub(super) fn extract_group_alternatives(s: &str) -> Option<Vec<String>> {
let (inner, _tail) = leading_group_parts(s)?;
let inner = strip_group_prefix(inner);
if !has_top_level_alternation(inner) {
return None;
}
let parts = split_top_level_alternatives(inner);
let literals: Vec<String> = parts.iter().filter_map(|part| literal_head(part)).collect();
if literals.len() == parts.len() && !literals.is_empty() {
Some(literals)
} else {
None
}
}
pub(super) fn extract_plain_group_inner(s: &str) -> Option<&str> {
let (inner, _tail) = leading_group_parts(s)?;
let inner = strip_group_prefix(inner);
if has_top_level_alternation(inner) {
return None;
}
Some(inner)
}
pub(super) fn strip_group_prefix(s: &str) -> &str {
s.strip_prefix("?:")
.or_else(|| s.strip_prefix("?i:"))
.or_else(|| s.strip_prefix("?m:"))
.or_else(|| s.strip_prefix("?s:"))
.or_else(|| s.strip_prefix("?im:"))
.or_else(|| s.strip_prefix("?is:"))
.or_else(|| s.strip_prefix("?ms:"))
.unwrap_or(s) }
pub(super) fn leading_group_parts(s: &str) -> Option<(&str, &str)> {
let mut depth = 0i32;
let mut end = None;
let mut in_class = false;
let mut escaped = false;
for (i, ch) in s.char_indices() {
if escaped {
escaped = false;
continue;
}
if ch == '\\' {
escaped = true;
continue;
}
if in_class {
if ch == ']' {
in_class = false;
}
continue;
}
if ch == '[' {
in_class = true;
continue;
}
match ch {
'(' => depth += 1,
')' => {
if depth == 0 {
end = Some(i);
break;
}
depth -= 1;
}
_ => {}
}
}
let end = end?;
Some((&s[..end], &s[end + 1..]))
}
pub(super) fn has_top_level_alternation(s: &str) -> bool {
let mut depth = 0i32;
let mut in_class = false;
let mut escaped = false;
for ch in s.chars() {
if escaped {
escaped = false;
continue;
}
if ch == '\\' {
escaped = true;
continue;
}
if in_class {
if ch == ']' {
in_class = false;
}
continue;
}
if ch == '[' {
in_class = true;
continue;
}
match ch {
'(' => depth += 1,
')' => depth -= 1,
'|' if depth == 0 => return true,
_ => {}
}
}
false
}
pub(super) fn split_top_level_alternatives(group_content: &str) -> Vec<&str> {
let mut parts = Vec::new();
let mut start = 0;
let mut d = 0i32;
let mut in_class = false;
let mut escaped = false;
for (i, ch) in group_content.char_indices() {
if escaped {
escaped = false;
continue;
}
if ch == '\\' {
escaped = true;
continue;
}
if in_class {
if ch == ']' {
in_class = false;
}
continue;
}
if ch == '[' {
in_class = true;
continue;
}
match ch {
'(' => d += 1,
')' => d -= 1,
'|' if d == 0 => {
parts.push(&group_content[start..i]);
start = i + 1;
}
_ => {}
}
}
parts.push(&group_content[start..]);
parts
}
pub(super) fn literal_head(part: &str) -> Option<String> {
let mut lit = String::new();
for ch in part.chars() {
match ch {
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '.' | ':' | '=' | ' ' => {
lit.push(ch);
}
'\\' => break,
_ => break,
}
}
if lit.is_empty() {
None
} else {
Some(lit)
}
}