use std::rc::Rc;
use crate::ast::{Node, Predef};
use crate::state::State;
use crate::unicode;
pub type Cont<'p> = Rc<dyn Fn(&mut State) -> bool + 'p>;
fn k_true<'p>() -> Cont<'p> {
Rc::new(|_| true)
}
pub fn m_node<'p>(node: &'p Node, st: &mut State, k: &Cont<'p>) -> bool {
match node {
Node::Empty => k(st),
Node::Lit { ch, ign } => m_lit(*ch, *ign, st, k),
Node::LitStr { chars, ign } => m_litstr(chars, *ign, st, k),
Node::Any { dotall } => m_any(*dotall, st, k),
Node::Class { cc } => m_class(cc, st, k),
Node::Predef {
kind,
negated,
ascii,
} => m_predef(*kind, *negated, *ascii, st, k),
Node::Prop(p) => m_prop(p.pred, p.negated, st, k),
Node::StartLine { multiline } => m_startline(*multiline, st, k),
Node::EndLine { multiline } => m_endline(*multiline, st, k),
Node::StartText => {
if st.pos == 0 {
k(st)
} else {
false
}
}
Node::EndText => {
if st.pos == st.len() {
k(st)
} else {
false
}
}
Node::WordBoundary { negated, ascii } => m_word_boundary(*negated, *ascii, st, k),
Node::WordEdge { end, ascii } => m_word_edge(*end, *ascii, st, k),
Node::Grapheme => m_grapheme(st, k),
Node::Group { index, node } => m_group(*index, node, st, k),
Node::NonCap(node) => m_node(node, st, k),
Node::Atomic(node) => m_atomic(node, st, k),
Node::Branch { alts } => m_branch(alts, st, k),
Node::Sequence { items } => m_seq(items, 0, st, k),
Node::Repeat {
node,
min,
max,
greedy,
} => m_repeat(node, *min, *max, *greedy, st, k),
Node::BackRef { group, ign } => m_backref(*group, *ign, st, k),
Node::Look {
behind,
positive,
node,
} => m_look(*behind, *positive, node, st, k),
}
}
fn m_seq<'p>(items: &'p [Node], idx: usize, st: &mut State, k: &Cont<'p>) -> bool {
if idx >= items.len() {
return k(st);
}
let k = k.clone();
let next: Cont<'p> = Rc::new(move |st: &mut State| m_seq(items, idx + 1, st, &k));
m_node(&items[idx], st, &next)
}
fn m_lit<'p>(ch: char, ign: bool, st: &mut State, k: &Cont<'p>) -> bool {
let matches = match st.cur() {
Some(c) if !ign => c == ch,
Some(c) if ign => unicode::case_eq(c, ch),
_ => false,
};
if !matches {
if st.partial_mode && st.cur().is_none() {
st.record_partial_block();
}
return false;
}
st.pos += 1;
if k(st) {
true
} else {
st.pos -= 1;
false
}
}
fn m_litstr<'p>(chars: &[char], ign: bool, st: &mut State, k: &Cont<'p>) -> bool {
let start = st.pos;
for &c in chars {
let ok = match st.cur() {
Some(cur) if !ign => cur == c,
Some(cur) if ign => unicode::case_eq(cur, c),
_ => false,
};
if !ok {
if st.partial_mode && st.cur().is_none() {
st.record_partial_block();
}
st.pos = start;
return false;
}
st.pos += 1;
}
if k(st) {
true
} else {
st.pos = start;
false
}
}
fn m_any<'p>(dotall: bool, st: &mut State, k: &Cont<'p>) -> bool {
let ok = match st.cur() {
Some(c) => dotall || c != '\n',
None => false,
};
if !ok {
if st.partial_mode && st.cur().is_none() {
st.record_partial_block();
}
return false;
}
st.pos += 1;
if k(st) {
true
} else {
st.pos -= 1;
false
}
}
fn m_class<'p>(cc: &crate::ast::CharClass, st: &mut State, k: &Cont<'p>) -> bool {
let Some(c) = st.cur() else {
if st.partial_mode {
st.record_partial_block();
}
return false;
};
if !cc.matches(c) {
return false;
}
st.pos += 1;
if k(st) {
true
} else {
st.pos -= 1;
false
}
}
fn m_predef<'p>(kind: Predef, negated: bool, ascii: bool, st: &mut State, k: &Cont<'p>) -> bool {
let Some(c) = st.cur() else {
if st.partial_mode {
st.record_partial_block();
}
return false;
};
let pred = match kind {
Predef::Digit => unicode::is_digit(c, ascii),
Predef::Word => unicode::is_word(c, ascii),
Predef::Space => unicode::is_space(c, ascii),
};
if pred == negated {
return false;
}
st.pos += 1;
if k(st) {
true
} else {
st.pos -= 1;
false
}
}
fn m_prop<'p>(pred: unicode::PropFn, negated: bool, st: &mut State, k: &Cont<'p>) -> bool {
let Some(c) = st.cur() else {
if st.partial_mode {
st.record_partial_block();
}
return false;
};
if pred(c) == negated {
return false;
}
st.pos += 1;
if k(st) {
true
} else {
st.pos -= 1;
false
}
}
fn m_startline<'p>(multiline: bool, st: &mut State, k: &Cont<'p>) -> bool {
let at_start_of_text = st.pos == 0;
let after_newline = multiline && matches!(st.prev(), Some('\n'));
if at_start_of_text || after_newline {
k(st)
} else {
false
}
}
fn m_endline<'p>(multiline: bool, st: &mut State, k: &Cont<'p>) -> bool {
let at_end = st.pos == st.len();
let before_trailing_newline = matches!(st.cur(), Some('\n')) && st.pos + 1 == st.len();
let before_any_newline = multiline && matches!(st.cur(), Some('\n'));
if at_end || before_trailing_newline || before_any_newline {
k(st)
} else {
false
}
}
fn m_word_boundary<'p>(negated: bool, ascii: bool, st: &mut State, k: &Cont<'p>) -> bool {
let before = st
.prev()
.map(|c| unicode::is_word(c, ascii))
.unwrap_or(false);
let after = st
.cur()
.map(|c| unicode::is_word(c, ascii))
.unwrap_or(false);
let boundary = before != after;
if boundary != negated { k(st) } else { false }
}
fn m_word_edge<'p>(end: bool, ascii: bool, st: &mut State, k: &Cont<'p>) -> bool {
let before = st
.prev()
.map(|c| unicode::is_word(c, ascii))
.unwrap_or(false);
let after = st
.cur()
.map(|c| unicode::is_word(c, ascii))
.unwrap_or(false);
let ok = if end {
before && !after
} else {
!before && after
};
if ok { k(st) } else { false }
}
fn m_grapheme<'p>(st: &mut State, k: &Cont<'p>) -> bool {
if st.cur().is_none() {
if st.partial_mode {
st.record_partial_block();
}
return false;
}
st.pos += 1;
if k(st) {
true
} else {
st.pos -= 1;
false
}
}
fn m_group<'p>(index: usize, node: &'p Node, st: &mut State, k: &Cont<'p>) -> bool {
let start = st.pos;
st.open_groups.push((index, start));
let idx = index;
let kc = k.clone();
let close: Cont<'p> = Rc::new(move |st: &mut State| {
if st
.open_groups
.last()
.map(|&(i, _)| i == idx)
.unwrap_or(false)
{
st.open_groups.pop();
}
st.close_group(idx, start);
kc(st)
});
let r = m_node(node, st, &close);
if !r {
if st
.open_groups
.last()
.map(|&(i, _)| i == index)
.unwrap_or(false)
{
st.open_groups.pop();
}
}
r
}
fn m_atomic<'p>(node: &'p Node, st: &mut State, k: &Cont<'p>) -> bool {
let snap = st.snapshot();
if !m_node(node, st, &k_true()) {
st.restore(snap);
return false;
}
if k(st) {
true
} else {
st.restore(snap);
false
}
}
fn m_branch<'p>(alts: &'p [Node], st: &mut State, k: &Cont<'p>) -> bool {
let snap = st.snapshot();
for alt in alts {
st.restore(snap.clone());
if m_node(alt, st, k) {
return true;
}
}
st.restore(snap);
false
}
fn m_repeat<'p>(
node: &'p Node,
min: usize,
max: Option<usize>,
greedy: bool,
st: &mut State,
k: &Cont<'p>,
) -> bool {
m_rep(node, min, max, greedy, 0, st, k)
}
#[allow(clippy::too_many_arguments)]
fn m_rep<'p>(
node: &'p Node,
min: usize,
max: Option<usize>,
greedy: bool,
count: usize,
st: &mut State,
k: &Cont<'p>,
) -> bool {
let iter_start = st.pos;
let can_more = match max {
Some(m) => count < m,
None => true,
};
let kc = k.clone();
let after_body: Cont<'p> = Rc::new(move |st: &mut State| {
if st.pos == iter_start {
kc(st)
} else {
m_rep(node, min, max, greedy, count + 1, st, &kc)
}
});
if greedy {
if can_more {
let snap = st.snapshot();
if m_node(node, st, &after_body) {
return true;
}
st.restore(snap);
}
if count >= min {
return k(st);
}
false
} else {
if count >= min {
let snap = st.snapshot();
if k(st) {
return true;
}
st.restore(snap);
}
if can_more {
let snap = st.snapshot();
if m_node(node, st, &after_body) {
return true;
}
st.restore(snap);
}
false
}
}
fn m_backref<'p>(group: usize, ign: bool, st: &mut State, k: &Cont<'p>) -> bool {
let Some(Some((s, e))) = st.caps.get(group).copied() else {
return k(st);
};
let start = st.pos;
for i in s..e {
let want = st.chars[i];
let ok = match st.cur() {
Some(cur) if !ign => cur == want,
Some(cur) if ign => unicode::case_eq(cur, want),
_ => false,
};
if !ok {
if st.partial_mode && st.cur().is_none() {
st.record_partial_block();
}
st.pos = start;
return false;
}
st.pos += 1;
}
if k(st) {
true
} else {
st.pos = start;
false
}
}
fn m_look<'p>(behind: bool, positive: bool, node: &'p Node, st: &mut State, k: &Cont<'p>) -> bool {
let snap = st.snapshot();
let matched = if behind {
lookbehind_matches(node, st)
} else {
let saved = st.pos;
let ok = m_node(node, st, &k_true());
st.pos = saved; ok
};
if matched == positive {
if k(st) {
true
} else {
st.restore(snap);
false
}
} else {
st.restore(snap);
false
}
}
fn lookbehind_matches<'p>(node: &'p Node, st: &mut State) -> bool {
let target = st.pos;
let snap = st.snapshot();
let mut start = target;
loop {
st.restore(snap.clone());
st.pos = start;
let end_check: Cont<'p> = Rc::new(move |st: &mut State| st.pos == target);
if m_node(node, st, &end_check) {
st.restore(snap);
return true;
}
if start == 0 {
break;
}
start -= 1;
}
st.restore(snap);
false
}
pub fn try_match(node: &Node, st: &mut State) -> bool {
m_node(node, st, &k_true())
}
pub fn try_match_to(node: &Node, st: &mut State, end_pos: usize) -> bool {
let end_check: Cont<'_> = Rc::new(move |s: &mut State| s.pos == end_pos);
m_node(node, st, &end_check)
}