#![allow(
missing_docs,
non_snake_case,
non_camel_case_types,
non_upper_case_globals
)]
use core::ffi::c_void;
use core::ptr;
use std::os::raw::{c_char, c_int};
use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl};
use crate::abi::types::xmlChar;
#[allow(dead_code)]
const MAX_NFA_STATES: usize = 1024;
#[allow(dead_code)]
const MAX_PARSE_DEPTH: usize = 256;
const REGEXP_MATCH: c_int = 1;
const REGEXP_NOMATCH: c_int = 0;
const REGEXP_ERROR: c_int = -1;
#[derive(Debug, Clone, Copy, PartialEq)]
enum AnchorType {
Start,
End,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum PredefinedClass {
Digit,
NotDigit,
Space,
NotSpace,
Word,
NotWord,
}
#[derive(Debug, Clone, PartialEq)]
enum Transition {
Char(u8),
Range(u8, u8),
#[allow(dead_code)]
Set(Vec<u8>),
NotRange(u8, u8),
NotSet(Vec<u8>),
Wildcard,
Predefined(PredefinedClass),
Epsilon,
Anchor(AnchorType),
}
#[derive(Debug, Clone)]
struct NfaState {
transitions: Vec<(Transition, usize)>,
is_accept: bool,
}
impl NfaState {
const fn new() -> Self {
NfaState {
transitions: Vec::new(),
is_accept: false,
}
}
}
#[derive(Debug, Clone)]
struct Nfa {
states: Vec<NfaState>,
start: usize,
}
impl Nfa {
fn new() -> Self {
let start = 0;
Nfa {
states: vec![NfaState::new()],
start,
}
}
fn add_state(&mut self) -> usize {
let index = self.states.len();
self.states.push(NfaState::new());
index
}
fn add_transition(&mut self, from: usize, to: usize, trans: Transition) {
if from < self.states.len() && to < self.states.len() {
self.states[from].transitions.push((trans, to));
}
}
fn set_accept(&mut self, state: usize) {
if state < self.states.len() {
self.states[state].is_accept = true;
}
}
}
struct NfaFragment {
nfa: Nfa,
start: usize,
out: Vec<usize>,
}
impl NfaFragment {
const fn new(nfa: Nfa, start: usize, out: Vec<usize>) -> Self {
NfaFragment { nfa, start, out }
}
}
impl Clone for NfaFragment {
fn clone(&self) -> Self {
NfaFragment {
nfa: self.nfa.clone(),
start: self.start,
out: self.out.clone(),
}
}
}
fn nfa_char(c: u8) -> NfaFragment {
let mut nfa = Nfa::new();
let start = nfa.start;
let accept = nfa.add_state();
nfa.set_accept(accept);
nfa.add_transition(start, accept, Transition::Char(c));
NfaFragment::new(nfa, start, vec![accept])
}
fn nfa_epsilon() -> NfaFragment {
let mut nfa = Nfa::new();
let start = nfa.start;
nfa.set_accept(start);
NfaFragment::new(nfa, start, vec![start])
}
fn nfa_range(lo: u8, hi: u8) -> NfaFragment {
let mut nfa = Nfa::new();
let start = nfa.start;
let accept = nfa.add_state();
nfa.set_accept(accept);
nfa.add_transition(start, accept, Transition::Range(lo, hi));
NfaFragment::new(nfa, start, vec![accept])
}
#[allow(dead_code)]
fn nfa_set(chars: Vec<u8>) -> NfaFragment {
let mut nfa = Nfa::new();
let start = nfa.start;
let accept = nfa.add_state();
nfa.set_accept(accept);
nfa.add_transition(start, accept, Transition::Set(chars));
NfaFragment::new(nfa, start, vec![accept])
}
fn nfa_not_range(lo: u8, hi: u8) -> NfaFragment {
let mut nfa = Nfa::new();
let start = nfa.start;
let accept = nfa.add_state();
nfa.set_accept(accept);
nfa.add_transition(start, accept, Transition::NotRange(lo, hi));
NfaFragment::new(nfa, start, vec![accept])
}
#[allow(dead_code)]
fn nfa_not_set(chars: Vec<u8>) -> NfaFragment {
let mut nfa = Nfa::new();
let start = nfa.start;
let accept = nfa.add_state();
nfa.set_accept(accept);
nfa.add_transition(start, accept, Transition::NotSet(chars));
NfaFragment::new(nfa, start, vec![accept])
}
fn nfa_wildcard() -> NfaFragment {
let mut nfa = Nfa::new();
let start = nfa.start;
let accept = nfa.add_state();
nfa.set_accept(accept);
nfa.add_transition(start, accept, Transition::Wildcard);
NfaFragment::new(nfa, start, vec![accept])
}
fn nfa_predefined(class: PredefinedClass) -> NfaFragment {
let mut nfa = Nfa::new();
let start = nfa.start;
let accept = nfa.add_state();
nfa.set_accept(accept);
nfa.add_transition(start, accept, Transition::Predefined(class));
NfaFragment::new(nfa, start, vec![accept])
}
fn nfa_start_anchor() -> NfaFragment {
let mut nfa = Nfa::new();
let start = nfa.start;
let accept = nfa.add_state();
nfa.set_accept(accept);
nfa.add_transition(start, accept, Transition::Anchor(AnchorType::Start));
NfaFragment::new(nfa, start, vec![accept])
}
fn nfa_end_anchor() -> NfaFragment {
let mut nfa = Nfa::new();
let start = nfa.start;
let accept = nfa.add_state();
nfa.set_accept(accept);
nfa.add_transition(start, accept, Transition::Anchor(AnchorType::End));
NfaFragment::new(nfa, start, vec![accept])
}
fn concat(a: NfaFragment, b: NfaFragment) -> NfaFragment {
let a_out = a.out.clone();
let a_start = a.start;
let a_size = a.nfa.states.len();
let mut nfa = a.nfa;
let b_start = b.nfa.start + a_size;
for mut state in b.nfa.states {
for (_, target) in &mut state.transitions {
*target += a_size;
}
nfa.states.push(state);
}
for &out_state in &a_out {
nfa.add_transition(out_state, b_start, Transition::Epsilon);
}
let b_out: Vec<usize> = b.out.iter().map(|&s| s + a_size).collect();
NfaFragment::new(nfa, a_start, b_out)
}
fn union(a: NfaFragment, b: NfaFragment) -> NfaFragment {
let mut nfa = Nfa::new();
let new_start = nfa.start;
let a_start = nfa.states.len();
let _a_size = a.nfa.states.len();
for mut state in a.nfa.states {
for (_, target) in &mut state.transitions {
*target += a_start;
}
nfa.states.push(state);
}
let b_start = nfa.states.len();
for mut state in b.nfa.states {
for (_, target) in &mut state.transitions {
*target += b_start;
}
nfa.states.push(state);
}
nfa.add_transition(new_start, a_start, Transition::Epsilon);
nfa.add_transition(new_start, b_start, Transition::Epsilon);
let mut out = Vec::new();
for &s in &a.out {
out.push(s + a_start);
}
for &s in &b.out {
out.push(s + b_start);
}
NfaFragment::new(nfa, new_start, out)
}
fn kleene_star(frag: NfaFragment) -> NfaFragment {
let mut nfa = Nfa::new();
let new_start = nfa.start;
let new_accept = nfa.add_state();
let frag_start = nfa.states.len();
let _frag_size = frag.nfa.states.len();
for mut state in frag.nfa.states {
for (_, target) in &mut state.transitions {
*target += frag_start;
}
nfa.states.push(state);
}
nfa.add_transition(new_start, new_accept, Transition::Epsilon);
nfa.add_transition(new_start, frag_start, Transition::Epsilon);
for &s in &frag.out {
nfa.add_transition(s + frag_start, frag_start, Transition::Epsilon);
nfa.add_transition(s + frag_start, new_accept, Transition::Epsilon);
}
nfa.set_accept(new_accept);
NfaFragment::new(nfa, new_start, vec![new_accept])
}
fn plus(frag: NfaFragment) -> NfaFragment {
let out_orig = frag.out.clone();
let _frag_start_orig = frag.start;
let mut nfa = Nfa::new();
let new_start = nfa.start;
let frag_start = nfa.states.len();
for mut state in frag.nfa.states {
for (_, target) in &mut state.transitions {
*target += frag_start;
}
nfa.states.push(state);
}
nfa.add_transition(new_start, frag_start, Transition::Epsilon);
for &s in &out_orig {
let adjusted = s + frag_start;
nfa.add_transition(adjusted, frag_start, Transition::Epsilon);
}
let out: Vec<usize> = out_orig.iter().map(|&s| s + frag_start).collect();
NfaFragment::new(nfa, new_start, out)
}
fn optional(frag: NfaFragment) -> NfaFragment {
let mut nfa = Nfa::new();
let new_start = nfa.start;
let new_accept = nfa.add_state();
let frag_start = nfa.states.len();
for mut state in frag.nfa.states {
for (_, target) in &mut state.transitions {
*target += frag_start;
}
nfa.states.push(state);
}
nfa.add_transition(new_start, new_accept, Transition::Epsilon);
nfa.add_transition(new_start, frag_start, Transition::Epsilon);
for &s in &frag.out {
nfa.add_transition(s + frag_start, new_accept, Transition::Epsilon);
}
nfa.set_accept(new_accept);
NfaFragment::new(nfa, new_start, vec![new_accept])
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
enum RegexToken {
Char(u8),
Dot,
AnchorStart,
AnchorEnd,
LParen,
RParen,
Pipe,
Star,
Plus,
Question,
LBrace,
RBrace,
Comma,
#[allow(dead_code)]
Number(u32),
Escape(u8),
ClassStart,
ClassEnd,
ClassNegate,
ClassRange,
}
struct RegexParser<'a> {
input: &'a [u8],
pos: usize,
lookahead: Option<RegexToken>,
}
impl<'a> RegexParser<'a> {
const fn new(input: &'a [u8]) -> Self {
RegexParser {
input,
pos: 0,
lookahead: None,
}
}
fn peek(&self) -> Option<u8> {
self.input.get(self.pos).copied()
}
fn advance(&mut self) -> Option<u8> {
let ch = self.input.get(self.pos).copied();
if ch.is_some() {
self.pos += 1;
}
ch
}
#[allow(dead_code)]
fn skip_whitespace(&mut self) {
while let Some(ch) = self.peek() {
if ch == b' ' || ch == b'\t' || ch == b'\n' || ch == b'\r' {
self.advance();
} else {
break;
}
}
}
fn scan_token(&mut self) -> Option<RegexToken> {
let ch = self.advance()?;
match ch {
b'.' => Some(RegexToken::Dot),
b'^' => Some(RegexToken::AnchorStart),
b'$' => Some(RegexToken::AnchorEnd),
b'(' => Some(RegexToken::LParen),
b')' => Some(RegexToken::RParen),
b'|' => Some(RegexToken::Pipe),
b'*' => Some(RegexToken::Star),
b'+' => Some(RegexToken::Plus),
b'?' => Some(RegexToken::Question),
b'{' => Some(RegexToken::LBrace),
b'}' => Some(RegexToken::RBrace),
b',' => Some(RegexToken::Comma),
b'[' => Some(RegexToken::ClassStart),
b']' => Some(RegexToken::ClassEnd),
b'\\' => {
let next = self.advance()?;
Some(RegexToken::Escape(next))
}
_ => Some(RegexToken::Char(ch)),
}
}
fn next_token(&mut self) -> Option<RegexToken> {
if let Some(token) = self.lookahead.take() {
Some(token)
} else {
self.scan_token()
}
}
const fn unscan(&mut self, token: RegexToken) {
self.lookahead = Some(token);
}
fn parse(&mut self) -> Result<NfaFragment, String> {
let frag = self.parse_alternation()?;
Ok(frag)
}
fn parse_alternation(&mut self) -> Result<NfaFragment, String> {
let mut frag = self.parse_sequence()?;
loop {
match self.next_token() {
Some(RegexToken::Pipe) => {
let rhs = self.parse_sequence()?;
frag = union(frag, rhs);
}
Some(other) => {
self.unscan(other);
break;
}
None => break,
}
}
Ok(frag)
}
fn parse_sequence(&mut self) -> Result<NfaFragment, String> {
let mut fragments: Vec<NfaFragment> = Vec::new();
loop {
match self.peek() {
None => break,
Some(b'|') | Some(b')') => break,
_ => {}
}
let atom = self.parse_atom()?;
fragments.push(atom);
}
if fragments.is_empty() {
return Ok(nfa_epsilon());
}
let mut result = fragments.remove(0);
for frag in fragments {
result = concat(result, frag);
}
Ok(result)
}
fn parse_atom(&mut self) -> Result<NfaFragment, String> {
let token = self
.next_token()
.ok_or_else(|| "Unexpected end of pattern".to_string())?;
let base = match token {
RegexToken::Char(c) => nfa_char(c),
RegexToken::Dot => nfa_wildcard(),
RegexToken::AnchorStart => nfa_start_anchor(),
RegexToken::AnchorEnd => nfa_end_anchor(),
RegexToken::LParen => {
let inner = self.parse_alternation()?;
match self.next_token() {
Some(RegexToken::RParen) => inner,
Some(t) => return Err(format!("Expected ')', got {:?}", t)),
None => return Err("Unterminated group".to_string()),
}
}
RegexToken::Escape(c) => {
match c {
b'd' => nfa_predefined(PredefinedClass::Digit),
b'D' => nfa_predefined(PredefinedClass::NotDigit),
b's' => nfa_predefined(PredefinedClass::Space),
b'S' => nfa_predefined(PredefinedClass::NotSpace),
b'w' => nfa_predefined(PredefinedClass::Word),
b'W' => nfa_predefined(PredefinedClass::NotWord),
b'n' => nfa_char(b'\n'),
b'r' => nfa_char(b'\r'),
b't' => nfa_char(b'\t'),
b'\\' => nfa_char(b'\\'),
b'.' => nfa_char(b'.'),
b'^' => nfa_char(b'^'),
b'$' => nfa_char(b'$'),
b'|' => nfa_char(b'|'),
b'*' => nfa_char(b'*'),
b'+' => nfa_char(b'+'),
b'?' => nfa_char(b'?'),
b'(' => nfa_char(b'('),
b')' => nfa_char(b')'),
b'[' => nfa_char(b'['),
b']' => nfa_char(b']'),
b'{' => nfa_char(b'{'),
b'}' => nfa_char(b'}'),
b'-' => nfa_char(b'-'),
b'0'..=b'9' => {
nfa_char(c)
}
_ => nfa_char(c),
}
}
RegexToken::ClassStart => self.parse_char_class()?,
_ => return Err(format!("Unexpected token: {:?}", token)),
};
self.parse_quantifier(base)
}
fn parse_quantifier(&mut self, frag: NfaFragment) -> Result<NfaFragment, String> {
match self.peek() {
Some(b'*') => {
self.advance();
Ok(kleene_star(frag))
}
Some(b'+') => {
self.advance();
Ok(plus(frag))
}
Some(b'?') => {
self.advance();
Ok(optional(frag))
}
Some(b'{') => {
self.advance();
self.parse_brace_quantifier(frag)
}
_ => Ok(frag),
}
}
fn parse_brace_quantifier(&mut self, frag: NfaFragment) -> Result<NfaFragment, String> {
let mut min: u32 = 0;
while let Some(b'0'..=b'9') = self.peek() {
let d = self.advance().unwrap() - b'0';
min = min * 10 + d as u32;
}
let mut max: Option<u32> = None;
match self.peek() {
Some(b',') => {
self.advance();
let mut max_val: u32 = 0;
let mut has_max = false;
while let Some(b'0'..=b'9') = self.peek() {
let d = self.advance().unwrap() - b'0';
max_val = max_val * 10 + d as u32;
has_max = true;
}
if has_max {
max = Some(max_val);
}
}
Some(b'}') => {
max = Some(min);
}
_ => return Err("Expected '}' in quantifier".to_string()),
}
match self.peek() {
Some(b'}') => {
self.advance();
}
_ => return Err("Expected '}' in quantifier".to_string()),
}
if min == 0 && max.is_none() {
Ok(kleene_star(frag))
} else if min == 0 && max == Some(0) {
Ok(nfa_epsilon())
} else if min == 1 && max.is_none() {
Ok(plus(frag))
} else if min == 0 && max == Some(1) {
Ok(optional(frag))
} else {
let mut result = nfa_epsilon();
for _ in 0..min {
result = concat(result, frag.clone());
}
if let Some(max_val) = max {
for _ in min..max_val {
result = concat(result, optional(frag.clone()));
}
} else {
result = concat(result, kleene_star(frag.clone()));
}
Ok(result)
}
}
fn parse_char_class(&mut self) -> Result<NfaFragment, String> {
let mut chars: Vec<u8> = Vec::new();
let mut ranges: Vec<(u8, u8)> = Vec::new();
let mut negated = false;
if let Some(b'^') = self.peek() {
negated = true;
self.advance();
}
let mut prev: Option<u8> = None;
loop {
match self.peek() {
None => return Err("Unterminated character class".to_string()),
Some(b']') => {
if prev.is_some() {
if let Some(c) = prev {
chars.push(c);
}
prev = None;
}
self.advance();
break;
}
Some(b'-') if prev.is_some() => {
self.advance();
let lo = prev.take().unwrap();
match self.peek() {
Some(b']') => {
chars.push(lo);
chars.push(b'-');
prev = None;
}
Some(ch) => {
self.advance();
if lo <= ch {
ranges.push((lo, ch));
}
prev = None;
}
None => {
chars.push(lo);
chars.push(b'-');
prev = None;
}
}
}
Some(b'\\') => {
self.advance();
if let Some(ch) = self.advance() {
match ch {
b'd' | b'D' | b's' | b'S' | b'w' | b'W' => {
if let Some(p) = prev.take() {
chars.push(p);
}
let class = match ch {
b'd' => PredefinedClass::Digit,
b'D' => PredefinedClass::NotDigit,
b's' => PredefinedClass::Space,
b'S' => PredefinedClass::NotSpace,
b'w' => PredefinedClass::Word,
b'W' => PredefinedClass::NotWord,
_ => unreachable!(),
};
match class {
PredefinedClass::Digit => {
ranges.push((b'0', b'9'));
}
PredefinedClass::NotDigit => {
ranges.push((0x00, b'/' - 1));
ranges.push((b':', 0xFF));
}
PredefinedClass::Space => {
chars.push(b' ');
chars.push(b'\t');
chars.push(b'\n');
chars.push(b'\r');
}
PredefinedClass::NotSpace => {
ranges.push((0x00, b' ' - 1));
ranges.push((b'!' + 1, b'\t' - 1));
ranges.push((b'\t' + 1, b'\n' - 1));
ranges.push((b'\n' + 1, b'\r' - 1));
ranges.push((b'\r' + 1, 0xFF));
}
PredefinedClass::Word => {
ranges.push((b'0', b'9'));
ranges.push((b'A', b'Z'));
ranges.push((b'a', b'z'));
chars.push(b'_');
}
PredefinedClass::NotWord => {
ranges.push((0x00, b'0' - 1));
ranges.push((b'9' + 1, b'A' - 1));
ranges.push((b'Z' + 1, b'_' - 1));
ranges.push((b'_' + 1, b'a' - 1));
ranges.push((b'z' + 1, 0xFF));
}
}
}
_ => {
let c = match ch {
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
b'\\' => b'\\',
b'0'..=b'9' => ch, _ => ch,
};
if let Some(p) = prev.take() {
chars.push(p);
}
prev = Some(c);
}
}
}
}
Some(ch) => {
self.advance();
if let Some(p) = prev.take() {
chars.push(p);
}
prev = Some(ch);
}
}
}
if let Some(c) = prev {
chars.push(c);
}
let mut combined_ranges: Vec<(u8, u8)> = ranges;
for &c in &chars {
combined_ranges.push((c, c));
}
if combined_ranges.is_empty() {
return if negated {
Ok(nfa_epsilon())
} else {
Ok(nfa_epsilon())
};
}
combined_ranges.sort_by_key(|a| a.0);
let mut merged: Vec<(u8, u8)> = Vec::new();
for (lo, hi) in combined_ranges {
if let Some(last) = merged.last_mut() {
if lo <= last.1 + 1 {
last.1 = last.1.max(hi);
continue;
}
}
merged.push((lo, hi));
}
if negated {
if merged.len() == 1 && merged[0].0 == 0 && merged[0].1 == 255 {
Ok(nfa_epsilon())
} else {
Ok(nfa_not_range(0, 255))
}
} else if merged.len() == 1 {
let (lo, hi) = merged[0];
if lo == hi {
Ok(nfa_char(lo))
} else {
Ok(nfa_range(lo, hi))
}
} else {
let mut result = nfa_range(merged[0].0, merged[0].1);
for &(lo, hi) in &merged[1..] {
result = union(result, nfa_range(lo, hi));
}
Ok(result)
}
}
}
fn matches_transition(c: u8, trans: &Transition) -> bool {
match trans {
Transition::Char(ch) => c == *ch,
Transition::Range(lo, hi) => c >= *lo && c <= *hi,
Transition::Set(chars) => chars.contains(&c),
Transition::NotRange(lo, hi) => c < *lo || c > *hi,
Transition::NotSet(chars) => !chars.contains(&c),
Transition::Wildcard => true,
Transition::Predefined(class) => matches_predefined(c, *class),
Transition::Epsilon => false, Transition::Anchor(_) => false, }
}
const fn matches_predefined(c: u8, class: PredefinedClass) -> bool {
match class {
PredefinedClass::Digit => c >= b'0' && c <= b'9',
PredefinedClass::NotDigit => c < b'0' || c > b'9',
PredefinedClass::Space => {
c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' || c == 0x0b || c == 0x0c
}
PredefinedClass::NotSpace => {
!(c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' || c == 0x0b || c == 0x0c)
}
PredefinedClass::Word => {
(c >= b'0' && c <= b'9')
|| (c >= b'A' && c <= b'Z')
|| (c >= b'a' && c <= b'z')
|| c == b'_'
}
PredefinedClass::NotWord => {
!((c >= b'0' && c <= b'9')
|| (c >= b'A' && c <= b'Z')
|| (c >= b'a' && c <= b'z')
|| c == b'_')
}
}
}
fn epsilon_closure(nfa: &Nfa, states: &[usize]) -> Vec<usize> {
let mut visited = vec![false; nfa.states.len()];
let mut result = Vec::new();
let mut stack: Vec<usize> = states.to_vec();
while let Some(s) = stack.pop() {
if s >= nfa.states.len() || visited[s] {
continue;
}
visited[s] = true;
result.push(s);
for (cond, target) in &nfa.states[s].transitions {
if matches!(cond, Transition::Epsilon)
&& *target < nfa.states.len()
&& !visited[*target]
{
stack.push(*target);
}
}
}
result
}
fn move_on_char(nfa: &Nfa, states: &[usize], c: u8, is_start: bool, is_end: bool) -> Vec<usize> {
let mut expanded = states.to_vec();
let mut more = true;
while more {
more = false;
let mut new_states = Vec::new();
for &s in &expanded {
if s >= nfa.states.len() {
continue;
}
for (cond, target) in &nfa.states[s].transitions {
if *target >= nfa.states.len() {
continue;
}
let should_follow = match cond {
Transition::Anchor(AnchorType::Start) => {
is_start && !expanded.contains(target) && !new_states.contains(target)
}
Transition::Anchor(AnchorType::End) => {
is_end && !expanded.contains(target) && !new_states.contains(target)
}
_ => false,
};
if should_follow {
new_states.push(*target);
more = true;
}
}
}
expanded.extend(new_states);
}
let mut eps_expanded = expanded.clone();
let mut more_eps = true;
while more_eps {
more_eps = false;
let mut new_eps = Vec::new();
for &s in &eps_expanded {
if s >= nfa.states.len() {
continue;
}
for (cond, target) in &nfa.states[s].transitions {
if let Transition::Epsilon = cond {
if *target < nfa.states.len()
&& !eps_expanded.contains(target)
&& !new_eps.contains(target)
{
new_eps.push(*target);
more_eps = true;
}
}
}
}
eps_expanded.extend(new_eps);
}
let mut next = Vec::new();
for &s in &eps_expanded {
if s >= nfa.states.len() {
continue;
}
for (cond, target) in &nfa.states[s].transitions {
if *target >= nfa.states.len() {
continue;
}
match cond {
Transition::Epsilon => continue,
Transition::Anchor(_) => continue, _ => {
if matches_transition(c, cond) && !next.contains(target) {
next.push(*target);
}
}
}
}
}
next
}
fn has_accept_state(nfa: &Nfa, states: &[usize]) -> bool {
states
.iter()
.any(|&s| s < nfa.states.len() && nfa.states[s].is_accept)
}
fn nfa_exec(nfa: &Nfa, input: &[u8]) -> c_int {
if nfa.states.is_empty() {
return REGEXP_ERROR;
}
let mut current = epsilon_closure(nfa, &[nfa.start]);
if input.is_empty() {
let mut final_states = current.clone();
for &s in ¤t {
for (cond, target) in &nfa.states[s].transitions {
if let Transition::Anchor(AnchorType::End) = cond {
if *target < nfa.states.len() {
let ec = epsilon_closure(nfa, &[*target]);
final_states.extend(ec);
}
}
}
}
final_states = epsilon_closure(nfa, &final_states);
return if has_accept_state(nfa, &final_states) {
REGEXP_MATCH
} else {
REGEXP_NOMATCH
};
}
for (i, &c) in input.iter().enumerate() {
let is_start = i == 0;
let next_states = move_on_char(nfa, ¤t, c, is_start, false);
if next_states.is_empty() {
return REGEXP_NOMATCH;
}
current = epsilon_closure(nfa, &next_states);
if current.is_empty() {
return REGEXP_NOMATCH;
}
}
let mut final_states = current.clone();
for &s in ¤t {
for (cond, target) in &nfa.states[s].transitions {
if let Transition::Anchor(AnchorType::End) = cond {
if *target < nfa.states.len() {
let ec = epsilon_closure(nfa, &[*target]);
final_states.extend(ec);
}
}
}
}
final_states = epsilon_closure(nfa, &final_states);
if has_accept_state(nfa, &final_states) {
REGEXP_MATCH
} else {
REGEXP_NOMATCH
}
}
#[derive(Debug)]
#[repr(C)]
pub struct XmlRegexp {
pattern: *mut xmlChar,
nfa: Option<Box<Nfa>>,
is_deterministic: c_int,
}
fn compile_pattern(pattern: &[u8]) -> Option<Box<Nfa>> {
if pattern.is_empty() {
let mut nfa = Nfa::new();
nfa.set_accept(nfa.start);
return Some(Box::new(nfa));
}
let mut parser = RegexParser::new(pattern);
match parser.parse() {
Ok(fragment) => {
let mut nfa = fragment.nfa;
for state in &mut nfa.states {
state.is_accept = false;
}
for &s in &fragment.out {
if s < nfa.states.len() {
nfa.set_accept(s);
}
}
Some(Box::new(nfa))
}
Err(_) => None,
}
}
fn is_deterministic(nfa: &Nfa) -> bool {
for (state_idx, state) in nfa.states.iter().enumerate() {
let epsilon_count = state
.transitions
.iter()
.filter(|(cond, _)| matches!(cond, Transition::Epsilon))
.count();
let consuming_count = state
.transitions
.iter()
.filter(|(cond, _)| !matches!(cond, Transition::Epsilon | Transition::Anchor(_)))
.count();
if epsilon_count > 1 {
return false;
}
if epsilon_count > 0 && consuming_count > 0 {
return false;
}
let closure = if epsilon_count == 0 {
epsilon_closure(nfa, &[state_idx])
} else {
continue;
};
let mut has_wildcard = false;
let mut chars = Vec::new();
let mut has_range = false;
let mut has_not = false;
let mut has_predefined = false;
for &s_idx in &closure {
if s_idx >= nfa.states.len() {
continue;
}
for (cond, _) in &nfa.states[s_idx].transitions {
match cond {
Transition::Epsilon | Transition::Anchor(_) => {}
Transition::Wildcard => {
if has_wildcard {
return false;
}
has_wildcard = true;
}
Transition::Char(c) => {
chars.push(*c);
}
Transition::Range(_, _) => {
has_range = true;
}
Transition::Set(_) => {}
Transition::NotRange(_, _) | Transition::NotSet(_) => {
has_not = true;
}
Transition::Predefined(_) => {
has_predefined = true;
}
}
}
}
if has_wildcard && (!chars.is_empty() || has_range || has_not || has_predefined) {
return false;
}
if has_range && (has_wildcard || has_not || has_predefined) {
return false;
}
if has_not && (has_wildcard || has_range || has_predefined) {
return false;
}
if has_predefined && (has_wildcard || has_range || has_not) {
return false;
}
chars.sort();
let dedup_len = {
chars.dedup();
chars.len()
};
if dedup_len > 1 && (has_range || has_wildcard || has_not || has_predefined) {
return false;
}
}
true
}
const unsafe fn xml_strlen(s: *const xmlChar) -> usize {
if s.is_null() {
return 0;
}
let mut len: usize = 0;
while *s.add(len) != 0 {
len += 1;
}
len
}
unsafe fn xml_strdup(s: *const xmlChar) -> *mut xmlChar {
if s.is_null() {
return ptr::null_mut();
}
let len = xml_strlen(s);
let new_ptr = xmlMallocImpl((len + 1) * core::mem::size_of::<xmlChar>()) as *mut xmlChar;
if new_ptr.is_null() {
return ptr::null_mut();
}
for i in 0..=len {
unsafe { *new_ptr.add(i) = *s.add(i) };
}
new_ptr
}
#[no_mangle]
pub unsafe extern "C" fn xmlRegexpCompile(pattern: *const xmlChar) -> *mut XmlRegexp {
if pattern.is_null() {
return ptr::null_mut();
}
let len = xml_strlen(pattern);
let pattern_bytes = unsafe { core::slice::from_raw_parts(pattern, len) };
let nfa = compile_pattern(pattern_bytes);
let pattern_copy = xml_strdup(pattern);
let compiled = xmlMallocImpl(core::mem::size_of::<XmlRegexp>()) as *mut XmlRegexp;
if compiled.is_null() {
if !pattern_copy.is_null() {
xmlFreeImpl(pattern_copy as *mut c_void);
}
return ptr::null_mut();
}
let det = nfa
.as_ref()
.map_or(0, |n| if is_deterministic(n) { 1 } else { 0 });
unsafe {
(*compiled).pattern = pattern_copy;
core::ptr::write(&mut (*compiled).nfa, nfa);
(*compiled).is_deterministic = det;
}
compiled
}
#[no_mangle]
pub unsafe extern "C" fn xmlRegexpExec(compiled: *const XmlRegexp, value: *const xmlChar) -> c_int {
if compiled.is_null() || value.is_null() {
return REGEXP_ERROR;
}
let regex = unsafe { &*compiled };
let nfa = match ®ex.nfa {
Some(nfa) => nfa,
None => return REGEXP_ERROR,
};
let len = xml_strlen(value);
let input = unsafe { core::slice::from_raw_parts(value, len) };
nfa_exec(nfa, input)
}
#[no_mangle]
pub const unsafe extern "C" fn xmlRegexpIsDeterministic(compiled: *const XmlRegexp) -> c_int {
if compiled.is_null() {
return 0;
}
unsafe { (*compiled).is_deterministic }
}
#[no_mangle]
pub unsafe extern "C" fn xmlRegexpPrint(output: *mut c_void, compiled: *const XmlRegexp) {
let mut msg = String::new();
if compiled.is_null() {
msg.push_str("(null regex)\n");
} else {
let regex = unsafe { &*compiled };
let pattern_str = if regex.pattern.is_null() {
"(null)".to_string()
} else {
let len = xml_strlen(regex.pattern);
let slice = unsafe { core::slice::from_raw_parts(regex.pattern, len) };
core::str::from_utf8(slice)
.unwrap_or("(invalid utf-8)")
.to_string()
};
msg.push_str(&format!("Regex: /{}/\n", pattern_str));
msg.push_str(&format!(" deterministic: {}\n", regex.is_deterministic));
if let Some(ref nfa) = regex.nfa {
msg.push_str(&format!(" states: {}\n", nfa.states.len()));
msg.push_str(&format!(" start state: {}\n", nfa.start));
for (i, state) in nfa.states.iter().enumerate() {
msg.push_str(&format!(" state[{}]: ", i));
if state.is_accept {
msg.push_str("(accept) ");
}
for (j, (cond, target)) in state.transitions.iter().enumerate() {
if j > 0 {
msg.push_str(", ");
}
match cond {
Transition::Epsilon => msg.push_str(&format!("ε->{}", target)),
Transition::Char(c) => {
if *c >= 0x20 && *c <= 0x7e {
msg.push_str(&format!("'{}'->{}", *c as char, target));
} else {
msg.push_str(&format!("0x{:02x}->{}", c, target));
}
}
Transition::Range(lo, hi) => {
msg.push_str(&format!("[{:02x}-{:02x}]->{}", lo, hi, target));
}
Transition::Set(chars) => {
msg.push('{');
for (k, c) in chars.iter().enumerate() {
if k > 0 {
msg.push(',');
}
msg.push_str(&format!("0x{:02x}", c));
}
msg.push_str("}->");
msg.push_str(&target.to_string());
}
Transition::NotRange(lo, hi) => {
msg.push_str(&format!("[^{:02x}-{:02x}]->{}", lo, hi, target));
}
Transition::NotSet(chars) => {
msg.push_str("^{{");
for (k, c) in chars.iter().enumerate() {
if k > 0 {
msg.push(',');
}
msg.push_str(&format!("0x{:02x}", c));
}
msg.push_str("}}->");
msg.push_str(&target.to_string());
}
Transition::Wildcard => msg.push_str(&format!(".*->{}", target)),
Transition::Predefined(class) => {
let name = match class {
PredefinedClass::Digit => "\\d",
PredefinedClass::NotDigit => "\\D",
PredefinedClass::Space => "\\s",
PredefinedClass::NotSpace => "\\S",
PredefinedClass::Word => "\\w",
PredefinedClass::NotWord => "\\W",
};
msg.push_str(&format!("{}->{}", name, target));
}
Transition::Anchor(at) => match at {
AnchorType::Start => msg.push_str(&format!("^->{}", target)),
AnchorType::End => msg.push_str(&format!("$->{}", target)),
},
}
}
msg.push('\n');
}
}
}
unsafe {
let out = if output.is_null() {
libc::fdopen(2, b"w\0" as *const u8 as *const c_char) as *mut c_void
} else {
output
};
if out.is_null() {
return;
}
libc::fwrite(
msg.as_ptr() as *const c_void,
1,
msg.len(),
out as *mut libc::FILE,
);
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlRegFreeRegexp(regexp: *mut XmlRegexp) {
if regexp.is_null() {
return;
}
unsafe {
if !(*regexp).pattern.is_null() {
xmlFreeImpl((*regexp).pattern as *mut c_void);
}
let _ = (*regexp).nfa.take();
xmlFreeImpl(regexp as *mut c_void);
}
}
#[derive(Debug)]
#[repr(C)]
pub struct RegExecCtxt {
compiled: *mut XmlRegexp,
current_states: Vec<usize>,
started: bool,
callback: Option<crate::abi::callbacks::xmlRegExecCallbacks>,
data: *mut c_void,
}
#[no_mangle]
pub unsafe extern "C" fn xmlRegNewExecCtxt(
compiled: *mut XmlRegexp,
callback: Option<crate::abi::callbacks::xmlRegExecCallbacks>,
data: *mut c_void,
) -> *mut RegExecCtxt {
if compiled.is_null() {
return ptr::null_mut();
}
let ctxt = xmlMallocImpl(core::mem::size_of::<RegExecCtxt>()) as *mut RegExecCtxt;
if ctxt.is_null() {
return ptr::null_mut();
}
unsafe {
(*ctxt).compiled = compiled;
core::ptr::write(&mut (*ctxt).current_states, Vec::new());
(*ctxt).started = false;
(*ctxt).callback = callback;
(*ctxt).data = data;
}
ctxt
}
#[no_mangle]
pub unsafe extern "C" fn xmlRegExecPushString(
ctxt: *mut RegExecCtxt,
value: *const xmlChar,
_data: *mut c_void,
) -> c_int {
if ctxt.is_null() {
return REGEXP_ERROR;
}
let exec_ctxt = unsafe { &mut *ctxt };
let regex = match unsafe { exec_ctxt.compiled.as_mut() } {
Some(r) => r,
None => return REGEXP_ERROR,
};
let nfa = match ®ex.nfa {
Some(nfa) => nfa,
None => return REGEXP_ERROR,
};
if value.is_null() {
if !exec_ctxt.started {
exec_ctxt.current_states = epsilon_closure(nfa, &[nfa.start]);
exec_ctxt.started = true;
}
return if has_accept_state(nfa, &exec_ctxt.current_states) {
REGEXP_MATCH
} else {
REGEXP_NOMATCH
};
}
let len = xml_strlen(value);
let input = unsafe { core::slice::from_raw_parts(value, len) };
if input.is_empty() {
return REGEXP_NOMATCH;
}
if !exec_ctxt.started {
exec_ctxt.current_states = epsilon_closure(nfa, &[nfa.start]);
exec_ctxt.started = true;
}
for (i, &c) in input.iter().enumerate() {
let is_end = i == input.len() - 1 && true; let next_states = move_on_char(nfa, &exec_ctxt.current_states, c, i == 0, is_end);
if next_states.is_empty() {
exec_ctxt.current_states = Vec::new();
return REGEXP_NOMATCH;
}
exec_ctxt.current_states = epsilon_closure(nfa, &next_states);
}
if has_accept_state(nfa, &exec_ctxt.current_states) {
REGEXP_MATCH
} else {
REGEXP_NOMATCH
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlRegFreeExecCtxt(ctxt: *mut RegExecCtxt) {
if ctxt.is_null() {
return;
}
unsafe {
core::ptr::drop_in_place(&mut (*ctxt).current_states);
xmlFreeImpl(ctxt as *mut c_void);
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::ptr;
fn to_xml_str(s: &[u8]) -> *mut xmlChar {
let len = s.len();
let ptr =
unsafe { xmlMallocImpl((len + 1) * core::mem::size_of::<xmlChar>()) } as *mut xmlChar;
if ptr.is_null() {
return ptr::null_mut();
}
unsafe {
core::ptr::copy_nonoverlapping(s.as_ptr(), ptr, len);
*ptr.add(len) = 0;
}
ptr
}
fn match_regex(pattern: &[u8], input: &[u8]) -> c_int {
let pat = to_xml_str(pattern);
let val = to_xml_str(input);
unsafe {
let compiled = xmlRegexpCompile(pat);
if compiled.is_null() {
return REGEXP_ERROR;
}
let ret = xmlRegexpExec(compiled, val);
xmlRegFreeRegexp(compiled);
ret
}
}
fn compile(pattern: &[u8]) -> *mut XmlRegexp {
let pat = to_xml_str(pattern);
unsafe { xmlRegexpCompile(pat) }
}
#[test]
fn test_literal_exact() {
assert_eq!(match_regex(b"hello", b"hello"), REGEXP_MATCH);
}
#[test]
fn test_literal_no_match() {
assert_eq!(match_regex(b"hello", b"world"), REGEXP_NOMATCH);
}
#[test]
fn test_literal_partial_prefix() {
assert_eq!(match_regex(b"hello", b"hel"), REGEXP_NOMATCH);
}
#[test]
fn test_literal_empty_pattern() {
assert_eq!(match_regex(b"", b""), REGEXP_MATCH);
}
#[test]
fn test_literal_empty_pattern_nonempty() {
assert_eq!(match_regex(b"", b"a"), REGEXP_NOMATCH);
}
#[test]
fn test_literal_single_char() {
assert_eq!(match_regex(b"a", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"a", b"b"), REGEXP_NOMATCH);
}
#[test]
fn test_alternation_simple() {
assert_eq!(match_regex(b"a|b", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"a|b", b"b"), REGEXP_MATCH);
assert_eq!(match_regex(b"a|b", b"c"), REGEXP_NOMATCH);
}
#[test]
fn test_alternation_three() {
assert_eq!(match_regex(b"a|b|c", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"a|b|c", b"b"), REGEXP_MATCH);
assert_eq!(match_regex(b"a|b|c", b"c"), REGEXP_MATCH);
assert_eq!(match_regex(b"a|b|c", b"d"), REGEXP_NOMATCH);
}
#[test]
fn test_zero_or_more() {
assert_eq!(match_regex(b"a*", b""), REGEXP_MATCH);
assert_eq!(match_regex(b"a*", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"a*", b"aaa"), REGEXP_MATCH);
}
#[test]
fn test_one_or_more() {
assert_eq!(match_regex(b"a+", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"a+", b"aaa"), REGEXP_MATCH);
assert_eq!(match_regex(b"a+", b""), REGEXP_NOMATCH);
}
#[test]
fn test_zero_or_one() {
assert_eq!(match_regex(b"a?", b""), REGEXP_MATCH);
assert_eq!(match_regex(b"a?", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"a?", b"aa"), REGEXP_NOMATCH);
}
#[test]
fn test_zero_or_more_middle() {
assert_eq!(match_regex(b"a*b", b"b"), REGEXP_MATCH);
assert_eq!(match_regex(b"a*b", b"ab"), REGEXP_MATCH);
assert_eq!(match_regex(b"a*b", b"aaab"), REGEXP_MATCH);
assert_eq!(match_regex(b"a*b", b"a"), REGEXP_NOMATCH);
}
#[test]
fn test_grouping() {
assert_eq!(match_regex(b"(a)", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"(a)", b"b"), REGEXP_NOMATCH);
}
#[test]
fn test_grouping_with_quantifier() {
assert_eq!(match_regex(b"(ab)+", b"ab"), REGEXP_MATCH);
assert_eq!(match_regex(b"(ab)+", b"abab"), REGEXP_MATCH);
assert_eq!(match_regex(b"(ab)+", b"a"), REGEXP_NOMATCH);
}
#[test]
fn test_start_anchor() {
assert_eq!(match_regex(b"^a", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"^a", b"ba"), REGEXP_NOMATCH);
}
#[test]
fn test_end_anchor() {
assert_eq!(match_regex(b"a$", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"a$", b"ba"), REGEXP_NOMATCH);
}
#[test]
fn test_both_anchors() {
assert_eq!(match_regex(b"^a$", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"^a$", b"ab"), REGEXP_NOMATCH);
assert_eq!(match_regex(b"^a$", b"ba"), REGEXP_NOMATCH);
}
#[test]
fn test_wildcard() {
assert_eq!(match_regex(b".", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b".", b"1"), REGEXP_MATCH);
assert_eq!(match_regex(b"...", b"abc"), REGEXP_MATCH);
assert_eq!(match_regex(b"...", b"ab"), REGEXP_NOMATCH);
}
#[test]
fn test_wildcard_with_literal() {
assert_eq!(match_regex(b"a.b", b"axb"), REGEXP_MATCH);
assert_eq!(match_regex(b"a.b", b"azb"), REGEXP_MATCH);
assert_eq!(match_regex(b"a.b", b"ab"), REGEXP_NOMATCH);
}
#[test]
fn test_escaped_newline() {
assert_eq!(match_regex(b"a\\nb", b"a\nb"), REGEXP_MATCH);
assert_eq!(match_regex(b"a\\nb", b"ab"), REGEXP_NOMATCH);
}
#[test]
fn test_escaped_tab() {
assert_eq!(match_regex(b"a\\tb", b"a\tb"), REGEXP_MATCH);
}
#[test]
fn test_escaped_metachar() {
assert_eq!(match_regex(b"\\.", b"."), REGEXP_MATCH);
assert_eq!(match_regex(b"\\.", b"a"), REGEXP_NOMATCH);
}
#[test]
fn test_char_class_single() {
assert_eq!(match_regex(b"[a]", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"[a]", b"b"), REGEXP_NOMATCH);
}
#[test]
fn test_char_class_multiple_chars() {
assert_eq!(match_regex(b"[abc]", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"[abc]", b"b"), REGEXP_MATCH);
assert_eq!(match_regex(b"[abc]", b"c"), REGEXP_MATCH);
assert_eq!(match_regex(b"[abc]", b"d"), REGEXP_NOMATCH);
}
#[test]
fn test_char_class_range() {
assert_eq!(match_regex(b"[a-z]", b"a"), REGEXP_MATCH);
assert_eq!(match_regex(b"[a-z]", b"m"), REGEXP_MATCH);
assert_eq!(match_regex(b"[a-z]", b"z"), REGEXP_MATCH);
assert_eq!(match_regex(b"[a-z]", b"1"), REGEXP_NOMATCH);
}
#[test]
fn test_char_class_range_with_escape() {
assert_eq!(match_regex(b"[\\d]", b"5"), REGEXP_MATCH);
assert_eq!(match_regex(b"[\\d]", b"a"), REGEXP_NOMATCH);
}
#[test]
fn test_multiple_char_classes() {
assert_eq!(match_regex(b"[a-z0-9]+", b"abc123"), REGEXP_MATCH);
assert_eq!(match_regex(b"[a-z0-9]+", b"ABC"), REGEXP_NOMATCH);
}
#[test]
fn test_digit_class() {
assert_eq!(match_regex(b"\\d", b"5"), REGEXP_MATCH);
assert_eq!(match_regex(b"\\d", b"a"), REGEXP_NOMATCH);
}
#[test]
fn test_word_class() {
assert_eq!(match_regex(b"\\w+", b"hello"), REGEXP_MATCH);
assert_eq!(match_regex(b"\\w+", b"hello123"), REGEXP_MATCH);
assert_eq!(match_regex(b"\\w+", b""), REGEXP_NOMATCH);
}
#[test]
fn test_space_class() {
assert_eq!(match_regex(b"\\s", b" "), REGEXP_MATCH);
assert_eq!(match_regex(b"\\s", b"\t"), REGEXP_MATCH);
assert_eq!(match_regex(b"\\s", b"a"), REGEXP_NOMATCH);
}
#[test]
fn test_complex_email_like() {
assert_eq!(
match_regex(b"\\w+@\\w+\\.\\w+", b"user@example.com"),
REGEXP_MATCH
);
assert_eq!(match_regex(b"\\w+@\\w+\\.\\w+", b"invalid"), REGEXP_NOMATCH);
}
#[test]
fn test_complex_phone_like() {
assert_eq!(
match_regex(b"\\d{3}-\\d{3}-\\d{4}", b"555-123-4567"),
REGEXP_MATCH
);
assert_eq!(
match_regex(b"\\d{3}-\\d{3}-\\d{4}", b"555-123-456"),
REGEXP_NOMATCH
);
}
#[test]
fn test_pattern_with_all_features() {
assert_eq!(match_regex(b"^(a|b)+c$", b"ac"), REGEXP_MATCH);
assert_eq!(match_regex(b"^(a|b)+c$", b"bc"), REGEXP_MATCH);
assert_eq!(match_regex(b"^(a|b)+c$", b"ababc"), REGEXP_MATCH);
assert_eq!(match_regex(b"^(a|b)+c$", b"abd"), REGEXP_NOMATCH);
}
#[test]
fn test_exact_quantifier() {
assert_eq!(match_regex(b"a{3}", b"aaa"), REGEXP_MATCH);
assert_eq!(match_regex(b"a{3}", b"aa"), REGEXP_NOMATCH);
assert_eq!(match_regex(b"a{3}", b"aaaa"), REGEXP_NOMATCH);
}
#[test]
fn test_between_quantifier() {
assert_eq!(match_regex(b"a{2,4}", b"aa"), REGEXP_MATCH);
assert_eq!(match_regex(b"a{2,4}", b"aaa"), REGEXP_MATCH);
assert_eq!(match_regex(b"a{2,4}", b"aaaa"), REGEXP_MATCH);
assert_eq!(match_regex(b"a{2,4}", b"a"), REGEXP_NOMATCH);
assert_eq!(match_regex(b"a{2,4}", b"aaaaa"), REGEXP_NOMATCH);
}
#[test]
fn test_at_least_quantifier() {
assert_eq!(match_regex(b"a{2,}", b"aa"), REGEXP_MATCH);
assert_eq!(match_regex(b"a{2,}", b"aaaa"), REGEXP_MATCH);
assert_eq!(match_regex(b"a{2,}", b"a"), REGEXP_NOMATCH);
}
#[test]
fn test_deterministic_literal() {
let compiled = compile(b"hello");
assert!(!compiled.is_null());
assert_eq!(unsafe { xmlRegexpIsDeterministic(compiled) }, 1);
unsafe { xmlRegFreeRegexp(compiled) };
}
#[test]
fn test_non_deterministic() {
let compiled = compile(b"a|b");
assert!(!compiled.is_null());
assert_eq!(unsafe { xmlRegexpIsDeterministic(compiled) }, 0);
unsafe { xmlRegFreeRegexp(compiled) };
}
#[test]
fn test_incremental_empty_input() {
let compiled = compile(b"a*");
assert!(!compiled.is_null());
let ctxt = unsafe { xmlRegNewExecCtxt(compiled, None, ptr::null_mut()) };
assert!(!ctxt.is_null());
let ret = unsafe { xmlRegExecPushString(ctxt, ptr::null_mut(), ptr::null_mut()) };
assert_eq!(ret, REGEXP_MATCH);
unsafe { xmlRegFreeExecCtxt(ctxt) };
unsafe { xmlRegFreeRegexp(compiled) };
}
#[test]
fn test_incremental_simple_match() {
let compiled = compile(b"abc");
assert!(!compiled.is_null());
let ctxt = unsafe { xmlRegNewExecCtxt(compiled, None, ptr::null_mut()) };
assert!(!ctxt.is_null());
let val = to_xml_str(b"abc");
let ret = unsafe { xmlRegExecPushString(ctxt, val, ptr::null_mut()) };
assert_eq!(ret, REGEXP_MATCH);
unsafe { xmlRegFreeExecCtxt(ctxt) };
unsafe { xmlRegFreeRegexp(compiled) };
}
#[test]
fn test_null_pattern() {
let compiled = unsafe { xmlRegexpCompile(ptr::null()) };
assert!(compiled.is_null());
}
#[test]
fn test_null_input() {
let compiled = compile(b"a");
assert!(!compiled.is_null());
let ret = unsafe { xmlRegexpExec(compiled, ptr::null()) };
assert_eq!(ret, REGEXP_ERROR);
unsafe { xmlRegFreeRegexp(compiled) };
}
#[test]
fn test_double_free() {
let compiled = compile(b"test");
assert!(!compiled.is_null());
unsafe { xmlRegFreeRegexp(compiled) };
}
#[test]
fn test_print() {
let compiled = compile(b"hello");
assert!(!compiled.is_null());
unsafe { xmlRegexpPrint(ptr::null_mut(), compiled) };
unsafe { xmlRegFreeRegexp(compiled) };
}
}