use super::{BacktrackingRewrite, BacktrackingRewriteRefusal};
use crate::oniguruma::*;
use crate::regint::*;
use crate::regparse_types::*;
pub(crate) fn apply(
root: &mut Node,
options: OnigOptionType,
capture_history: MemStatusType,
) -> Vec<BacktrackingRewrite> {
let refusal = if capture_history != 0
|| options.intersects(
ONIG_OPTION_FIND_LONGEST | ONIG_OPTION_FIND_NOT_EMPTY | ONIG_OPTION_CALLBACK_EACH_MATCH,
) {
Some(BacktrackingRewriteRefusal::UnsupportedMode)
} else if unsupported(root) {
Some(BacktrackingRewriteRefusal::UnsupportedConstruct)
} else {
None
};
let mut reports = Vec::new();
visit(root, None, refusal, &mut reports);
reports
}
fn without_captures(mut node: &Node) -> &Node {
while let NodeInner::Bag(bag) = &node.inner {
if bag.bag_type != BagType::Memory {
break;
}
let Some(body) = bag.body.as_deref() else {
break;
};
node = body;
}
node
}
fn decimal_loop(node: &Node) -> bool {
decimal_loop_with_lower(node, 1)
}
fn decimal_loop_with_lower(node: &Node, lower: i32) -> bool {
let NodeInner::Quant(outer) = &node.inner else {
return false;
};
if !outer.greedy || outer.lower != lower || !is_infinite_repeat(outer.upper) {
return false;
}
let Some(body) = outer.body.as_deref() else {
return false;
};
let NodeInner::List(first) = &without_captures(body).inner else {
return false;
};
let Some(tail) = first.cdr.as_deref() else {
return false;
};
let NodeInner::List(second) = &tail.inner else {
return false;
};
if second.cdr.is_some() {
return false;
}
let NodeInner::Quant(digits) = &without_captures(&first.car).inner else {
return false;
};
let NodeInner::Quant(separator) = &without_captures(&second.car).inner else {
return false;
};
digits.greedy
&& digits.lower == 1
&& is_infinite_repeat(digits.upper)
&& digits.body.as_deref().is_some_and(decimal_class)
&& separator.greedy
&& separator.lower == 0
&& separator.upper == 1
&& separator.body.as_deref().is_some_and(|body| {
matches!(&without_captures(body).inner, NodeInner::String(s) if s.s == b"_" && !s.is_crude())
})
}
fn decimal_tail(node: &Node) -> bool {
let NodeInner::List(first) = &node.inner else {
return false;
};
let Some(tail) = first.cdr.as_deref() else {
return false;
};
let NodeInner::List(second) = &tail.inner else {
return false;
};
decimal_loop_with_lower(without_captures(&first.car), 0)
&& matches!(&without_captures(&second.car).inner, NodeInner::Quant(q)
if q.greedy && q.lower == 1 && is_infinite_repeat(q.upper)
&& q.body.as_deref().is_some_and(decimal_class))
}
fn decimal_class(node: &Node) -> bool {
let NodeInner::CClass(class) = &without_captures(node).inner else {
return false;
};
!class.is_not()
&& class.mbuf.is_none()
&& (0..SINGLE_BYTE_SIZE).all(|byte| {
bitset_at(&class.bs, byte) == (b'0' as usize..=b'9' as usize).contains(&byte)
})
}
pub(super) fn deterministic_tail_parts(node: &Node) -> Option<(&Node, &Node)> {
if !decimal_tail(node) {
return None;
}
let NodeInner::List(first) = &node.inner else {
return None;
};
let NodeInner::Quant(prefix) = &first.car.inner else {
return None;
};
let body = prefix.body.as_deref()?;
let NodeInner::List(digits) = &without_captures(body).inner else {
return None;
};
let NodeInner::Quant(q) = &digits.car.inner else {
return None;
};
if !matches!(&q.body.as_deref()?.inner, NodeInner::CClass(_)) {
return None;
}
let NodeInner::List(separator) = &digits.cdr.as_deref()?.inner else {
return None;
};
let NodeInner::Quant(q) = &separator.car.inner else {
return None;
};
if !matches!(&q.body.as_deref()?.inner, NodeInner::String(_)) {
return None;
}
let NodeInner::List(tail) = &first.cdr.as_deref()?.inner else {
return None;
};
Some((body, &tail.car))
}
fn tail_followed_by_boundary(node: &Node, next: Option<&Node>) -> bool {
let NodeInner::List(first) = &node.inner else {
return false;
};
let Some(tail) = first.cdr.as_deref() else {
return false;
};
let NodeInner::List(second) = &tail.inner else {
return false;
};
let following = second.cdr.as_deref().and_then(|rest| match &rest.inner {
NodeInner::List(list) => Some(list.car.as_ref()),
_ => None,
});
matches!(following.or(next).map(without_captures),
Some(Node { inner: NodeInner::Anchor(anchor), .. })
if anchor.anchor_type == ANCR_WORD_BOUNDARY && anchor.body.is_none())
}
fn make_tail_atomic(node: &mut Node) {
let deterministic = deterministic_tail_parts(node).is_some();
let mut pair = std::mem::replace(node, *node_new_bag(BagType::StopBacktrack));
let NodeInner::List(first) = &mut pair.inner else {
unreachable!("decimal tail must be a list");
};
let NodeInner::List(second) = &mut first.cdr.as_deref_mut().unwrap().inner else {
unreachable!("decimal tail must have a second element");
};
let rest = second.cdr.take();
node.set_body(Some(Box::new(pair)));
if deterministic {
node.status |= ND_ST_DECIMAL_TAIL_PREFIX;
}
if rest.is_some() {
let atomic = std::mem::replace(node, *node_new_bag(BagType::StopBacktrack));
*node = *node_new_list(Box::new(atomic), rest);
}
}
fn without_captures_mut(mut node: &mut Node) -> &mut Node {
while matches!(&node.inner, NodeInner::Bag(bag) if bag.bag_type == BagType::Memory && bag.body.is_some())
{
let NodeInner::Bag(bag) = &mut node.inner else {
unreachable!("capture bag was checked above");
};
node = bag
.body
.as_deref_mut()
.expect("capture body was checked above");
}
node
}
fn possessify_digits(node: &mut Node) -> bool {
let NodeInner::Quant(outer) = &mut node.inner else {
return false;
};
let Some(body) = outer.body.as_deref_mut() else {
return false;
};
let NodeInner::List(first) = &mut without_captures_mut(body).inner else {
return false;
};
let digits = without_captures_mut(&mut first.car);
if !matches!(&digits.inner, NodeInner::Quant(q) if q.body.as_deref().is_some_and(|body| matches!(body.inner, NodeInner::CClass(_))))
{
return false;
}
let original = std::mem::replace(digits, *node_new_bag(BagType::StopBacktrack));
digits.set_body(Some(Box::new(original)));
digits.status |= ND_ST_POSSESSIVE_CLASS_REPEAT;
true
}
fn mandatory_dot(node: &Node) -> bool {
matches!(&without_captures(node).inner, NodeInner::String(s) if s.s.first() == Some(&b'.') && !s.is_crude())
}
fn unsupported(node: &Node) -> bool {
match &node.inner {
NodeInner::BackRef(_) | NodeInner::Call(_) | NodeInner::Gimmick(_) => true,
NodeInner::Anchor(anchor) => {
anchor.body.is_some() || anchor.anchor_type == ANCR_BEGIN_POSITION
}
NodeInner::Bag(bag) => {
bag.bag_type != BagType::Memory || bag.body.as_deref().is_some_and(unsupported)
}
NodeInner::Quant(q) => q.body.as_deref().is_some_and(unsupported),
NodeInner::List(list) | NodeInner::Alt(list) => {
unsupported(&list.car) || list.cdr.as_deref().is_some_and(unsupported)
}
NodeInner::String(_) | NodeInner::CClass(_) | NodeInner::CType(_) => false,
}
}
fn visit(
node: &mut Node,
next: Option<&Node>,
refusal: Option<BacktrackingRewriteRefusal>,
reports: &mut Vec<BacktrackingRewrite>,
) {
if decimal_tail(node) {
if let Some(reason) = refusal {
reports.push(BacktrackingRewrite::Refused(reason));
} else if !tail_followed_by_boundary(node, next) {
reports.push(BacktrackingRewrite::Refused(
BacktrackingRewriteRefusal::NoWordBoundary,
));
} else {
let deterministic = deterministic_tail_parts(node).is_some();
make_tail_atomic(node);
reports.push(if deterministic {
BacktrackingRewrite::DeterministicDecimalTail
} else {
BacktrackingRewrite::AtomicDecimalTail
});
if let NodeInner::List(list) = &mut node.inner {
if let Some(rest) = list.cdr.as_deref_mut() {
visit(rest, next, refusal, reports);
}
}
return;
}
}
if decimal_loop(node) {
if let Some(reason) = refusal {
reports.push(BacktrackingRewrite::Refused(reason));
} else if !next.is_some_and(mandatory_dot) {
reports.push(BacktrackingRewrite::Refused(
BacktrackingRewriteRefusal::NoMandatoryDot,
));
} else {
if possessify_digits(node) {
reports.push(BacktrackingRewrite::PossessiveDecimalDigits);
} else {
let original = std::mem::replace(node, *node_new_bag(BagType::StopBacktrack));
node.set_body(Some(Box::new(original)));
reports.push(BacktrackingRewrite::AtomicDecimalLoop);
}
return;
}
}
match &mut node.inner {
NodeInner::List(list) => {
let following = list.cdr.as_deref().and_then(|tail| match &tail.inner {
NodeInner::List(rest) => Some(rest.car.as_ref()),
_ => None,
});
visit(&mut list.car, following.or(next), refusal, reports);
if let Some(rest) = list.cdr.as_deref_mut() {
visit(rest, next, refusal, reports);
}
}
NodeInner::Alt(list) => {
visit(&mut list.car, next, refusal, reports);
if let Some(rest) = list.cdr.as_deref_mut() {
visit(rest, next, refusal, reports);
}
}
NodeInner::Bag(bag) => {
if let Some(body) = bag.body.as_deref_mut() {
visit(body, next, refusal, reports);
}
}
NodeInner::Quant(q) => {
if let Some(body) = q.body.as_deref_mut() {
visit(body, None, refusal, reports);
}
}
NodeInner::Anchor(anchor) => {
if let Some(body) = anchor.body.as_deref_mut() {
visit(body, None, refusal, reports);
}
}
_ => {}
}
}