use crate::abi::allocator::{xmlFree, xmlMalloc};
use crate::xml::regex::{
xmlRegExecPushString, xmlRegFreeExecCtxt, xmlRegFreeRegexp, xmlRegNewExecCtxt,
xmlRegexpCompile, xmlRegexpIsDeterministic, XmlRegexp,
};
use core::ffi::c_int;
use core::ptr;
pub type XmlAutomataStatePtr = *mut XmlAutomataState;
pub type XmlAutomataPtr = *mut XmlAutomata;
#[repr(C)]
pub struct XmlAutomata {
regexp: Option<Box<XmlRegexp>>,
states: Vec<*mut XmlAutomataState>,
init_state: Option<*mut XmlAutomataState>,
error: c_int,
}
#[repr(C)]
pub struct XmlAutomataState {
transitions: Vec<AutomataTransition>,
}
#[repr(C)]
pub struct AutomataTransition {
token: Option<u8>,
min: c_int,
max: c_int,
to: Option<*mut XmlAutomataState>,
once: bool,
all: bool,
epsilon: bool,
counter: c_int,
data: *mut core::ffi::c_void,
}
unsafe impl Send for XmlAutomata {}
unsafe impl Sync for XmlAutomata {}
unsafe impl Send for XmlAutomataState {}
unsafe impl Sync for XmlAutomataState {}
#[no_mangle]
pub unsafe extern "C" fn xmlNewAutomata() -> XmlAutomataPtr {
let am = xmlMalloc(core::mem::size_of::<XmlAutomata>()) as XmlAutomataPtr;
if am.is_null() {
return ptr::null_mut();
}
unsafe {
core::ptr::write(&mut (*am).regexp, None as Option<Box<XmlRegexp>>);
core::ptr::write(&mut (*am).states, Vec::new());
(*am).init_state = None;
(*am).error = 0;
}
am
}
#[no_mangle]
pub unsafe extern "C" fn xmlFreeAutomata(am: XmlAutomataPtr) {
if am.is_null() {
return;
}
unsafe {
for &state in &(*am).states {
if !state.is_null() {
core::ptr::drop_in_place(&mut (*state).transitions);
xmlFree(state as *mut core::ffi::c_void);
}
}
core::ptr::drop_in_place(&mut (*am).states);
let _ = (*am).regexp.take();
xmlFree(am as *mut core::ffi::c_void);
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewState(am: XmlAutomataPtr) -> XmlAutomataStatePtr {
if am.is_null() {
return ptr::null_mut();
}
let state = xmlMalloc(core::mem::size_of::<XmlAutomataState>()) as XmlAutomataStatePtr;
if state.is_null() {
return ptr::null_mut();
}
unsafe {
core::ptr::write(&mut (*state).transitions, Vec::new());
(*am).states.push(state);
if (*am).init_state.is_none() {
(*am).init_state = Some(state);
}
}
state
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataSetFinalState(
_am: XmlAutomataPtr,
_state: XmlAutomataStatePtr,
) -> c_int {
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataGetInitState(am: XmlAutomataPtr) -> XmlAutomataStatePtr {
if am.is_null() {
return ptr::null_mut();
}
unsafe { (*am).init_state.unwrap_or(ptr::null_mut()) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewEpsilon(
am: XmlAutomataPtr,
from: XmlAutomataStatePtr,
to: XmlAutomataStatePtr,
) -> XmlAutomataStatePtr {
if am.is_null() || from.is_null() || to.is_null() {
return ptr::null_mut();
}
unsafe {
(*from).transitions.push(AutomataTransition {
token: None,
min: 0,
max: 0,
to: Some(to),
once: false,
all: false,
epsilon: true,
counter: -1,
data: ptr::null_mut(),
});
}
from
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewTransition(
am: XmlAutomataPtr,
from: XmlAutomataStatePtr,
to: XmlAutomataStatePtr,
token: *const core::ffi::c_char,
_data: *mut core::ffi::c_void,
) -> XmlAutomataStatePtr {
if am.is_null() || from.is_null() || to.is_null() {
return ptr::null_mut();
}
let tok = if token.is_null() {
None
} else {
unsafe { Some(*token as u8) }
};
unsafe {
(*from).transitions.push(AutomataTransition {
token: tok,
min: 0,
max: 0,
to: Some(to),
once: false,
all: false,
epsilon: false,
counter: -1,
data: ptr::null_mut(),
});
}
from
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewCountTrans(
am: XmlAutomataPtr,
from: XmlAutomataStatePtr,
to: XmlAutomataStatePtr,
token: *const core::ffi::c_char,
_data: *mut core::ffi::c_void,
min: c_int,
max: c_int,
) -> XmlAutomataStatePtr {
if am.is_null() || from.is_null() || to.is_null() {
return ptr::null_mut();
}
let tok = if token.is_null() {
None
} else {
unsafe { Some(*token as u8) }
};
unsafe {
(*from).transitions.push(AutomataTransition {
token: tok,
min,
max,
to: Some(to),
once: false,
all: false,
epsilon: false,
counter: -1,
data: ptr::null_mut(),
});
}
from
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewOnceTrans(
am: XmlAutomataPtr,
from: XmlAutomataStatePtr,
to: XmlAutomataStatePtr,
token: *const core::ffi::c_char,
_data: *mut core::ffi::c_void,
min: c_int,
max: c_int,
) -> XmlAutomataStatePtr {
if am.is_null() || from.is_null() || to.is_null() {
return ptr::null_mut();
}
let tok = if token.is_null() {
None
} else {
unsafe { Some(*token as u8) }
};
unsafe {
(*from).transitions.push(AutomataTransition {
token: tok,
min,
max,
to: Some(to),
once: true,
all: false,
epsilon: false,
counter: -1,
data: ptr::null_mut(),
});
}
from
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewAllTrans(
am: XmlAutomataPtr,
from: XmlAutomataStatePtr,
to: XmlAutomataStatePtr,
_lax: c_int,
) -> XmlAutomataStatePtr {
if am.is_null() || from.is_null() || to.is_null() {
return ptr::null_mut();
}
unsafe {
(*from).transitions.push(AutomataTransition {
token: None,
min: 0,
max: 0,
to: Some(to),
once: false,
all: true,
epsilon: false,
counter: -1,
data: ptr::null_mut(),
});
}
from
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewCountedTrans(
am: XmlAutomataPtr,
from: XmlAutomataStatePtr,
to: XmlAutomataStatePtr,
counter: c_int,
) -> XmlAutomataStatePtr {
if am.is_null() || from.is_null() || to.is_null() {
return ptr::null_mut();
}
unsafe {
(*from).transitions.push(AutomataTransition {
token: None,
min: 0,
max: 0,
to: Some(to),
once: false,
all: false,
epsilon: false,
counter,
data: ptr::null_mut(),
});
}
from
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewCounterTrans(
am: XmlAutomataPtr,
from: XmlAutomataStatePtr,
to: XmlAutomataStatePtr,
counter: c_int,
) -> XmlAutomataStatePtr {
if am.is_null() || from.is_null() || to.is_null() {
return ptr::null_mut();
}
unsafe {
(*from).transitions.push(AutomataTransition {
token: None,
min: 0,
max: 0,
to: Some(to),
once: false,
all: false,
epsilon: false,
counter,
data: ptr::null_mut(),
});
}
from
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewCounter(
_am: XmlAutomataPtr,
_min: c_int,
_max: c_int,
) -> c_int {
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataCompile(am: XmlAutomataPtr) -> c_int {
if am.is_null() {
return -1;
}
unsafe {
let mut pattern = Vec::new();
let init = match (*am).init_state {
Some(s) => s,
None => return 0, };
if build_pattern_from_automata(&*am, init, &mut pattern).is_err() {
(*am).error = -1;
return -1;
}
if pattern.is_empty() {
return 0;
}
pattern.push(0); let compiled = xmlRegexpCompile(pattern.as_ptr());
if compiled.is_null() {
(*am).error = -1;
return -1;
}
(*am).regexp = Some(Box::from_raw(compiled));
0
}
}
unsafe fn build_pattern_from_automata(
am: &XmlAutomata,
state: XmlAutomataStatePtr,
pattern: &mut Vec<u8>,
) -> Result<(), ()> {
if state.is_null() {
return Ok(());
}
let transitions = &(*state).transitions;
if transitions.is_empty() {
return Ok(());
}
if transitions.len() == 1 {
let t = &transitions[0];
if t.epsilon {
if let Some(to) = t.to {
return build_pattern_from_automata(am, to, pattern);
}
} else if t.all {
pattern.push(b'.');
if let Some(to) = t.to {
return build_pattern_from_automata(am, to, pattern);
}
} else if let Some(tok) = t.token {
pattern.push(tok);
if let Some(to) = t.to {
return build_pattern_from_automata(am, to, pattern);
}
}
} else {
pattern.push(b'(');
for (i, t) in transitions.iter().enumerate() {
if i > 0 {
pattern.push(b'|');
}
if let Some(tok) = t.token {
pattern.push(tok);
} else if t.all {
pattern.push(b'.');
}
if let Some(to) = t.to {
if !(*to).transitions.is_empty() {
let mut sub = Vec::new();
let _ = build_pattern_from_automata(am, to, &mut sub);
pattern.extend(sub);
}
}
}
pattern.push(b')');
}
Ok(())
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataIsDeterministic(am: XmlAutomataPtr) -> c_int {
if am.is_null() {
return 0;
}
unsafe {
match &(*am).regexp {
Some(regexp) => xmlRegexpIsDeterministic(&**regexp as *const XmlRegexp),
None => 1, }
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::ptr;
#[test]
fn test_new_automata() {
unsafe {
let am = xmlNewAutomata();
assert!(!am.is_null());
xmlFreeAutomata(am);
}
}
#[test]
fn test_new_automata_null_safety() {
unsafe {
xmlFreeAutomata(ptr::null_mut());
assert!(xmlAutomataGetInitState(ptr::null_mut()).is_null());
assert_eq!(xmlAutomataCompile(ptr::null_mut()), -1);
}
}
#[test]
fn test_new_state() {
unsafe {
let am = xmlNewAutomata();
let state = xmlAutomataNewState(am);
assert!(!state.is_null());
let init = xmlAutomataGetInitState(am);
assert_eq!(init, state);
xmlFreeAutomata(am);
}
}
#[test]
fn test_epsilon_transition() {
unsafe {
let am = xmlNewAutomata();
let s1 = xmlAutomataNewState(am);
let s2 = xmlAutomataNewState(am);
let result = xmlAutomataNewEpsilon(am, s1, s2);
assert!(!result.is_null());
assert_eq!(result, s1);
xmlFreeAutomata(am);
}
}
#[test]
fn test_char_transition() {
unsafe {
let am = xmlNewAutomata();
let s1 = xmlAutomataNewState(am);
let s2 = xmlAutomataNewState(am);
let token = b"a\0".as_ptr() as *const core::ffi::c_char;
let result = xmlAutomataNewTransition(am, s1, s2, token, ptr::null_mut());
assert!(!result.is_null());
assert_eq!(result, s1);
xmlFreeAutomata(am);
}
}
#[test]
fn test_count_transition() {
unsafe {
let am = xmlNewAutomata();
let s1 = xmlAutomataNewState(am);
let s2 = xmlAutomataNewState(am);
let token = b"a\0".as_ptr() as *const core::ffi::c_char;
let result = xmlAutomataNewCountTrans(am, s1, s2, token, ptr::null_mut(), 1, 5);
assert!(!result.is_null());
xmlFreeAutomata(am);
}
}
#[test]
fn test_all_transition() {
unsafe {
let am = xmlNewAutomata();
let s1 = xmlAutomataNewState(am);
let s2 = xmlAutomataNewState(am);
let result = xmlAutomataNewAllTrans(am, s1, s2, 0);
assert!(!result.is_null());
xmlFreeAutomata(am);
}
}
#[test]
fn test_once_transition() {
unsafe {
let am = xmlNewAutomata();
let s1 = xmlAutomataNewState(am);
let s2 = xmlAutomataNewState(am);
let token = b"x\0".as_ptr() as *const core::ffi::c_char;
let result = xmlAutomataNewOnceTrans(am, s1, s2, token, ptr::null_mut(), 0, 1);
assert!(!result.is_null());
xmlFreeAutomata(am);
}
}
#[test]
fn test_counter_transition() {
unsafe {
let am = xmlNewAutomata();
let s1 = xmlAutomataNewState(am);
let s2 = xmlAutomataNewState(am);
let cid = xmlAutomataNewCounter(am, 0, 10);
let r1 = xmlAutomataNewCountedTrans(am, s1, s2, cid);
assert!(!r1.is_null());
let r2 = xmlAutomataNewCounterTrans(am, s2, s1, cid);
assert!(!r2.is_null());
xmlFreeAutomata(am);
}
}
#[test]
fn test_compile_empty() {
unsafe {
let am = xmlNewAutomata();
let result = xmlAutomataCompile(am);
assert_eq!(result, 0);
xmlFreeAutomata(am);
}
}
#[test]
fn test_set_final_state() {
unsafe {
let am = xmlNewAutomata();
let state = xmlAutomataNewState(am);
let result = xmlAutomataSetFinalState(am, state);
assert_eq!(result, 0);
xmlFreeAutomata(am);
}
}
#[test]
fn test_is_deterministic_not_compiled() {
unsafe {
let am = xmlNewAutomata();
assert_eq!(xmlAutomataIsDeterministic(am), 1);
xmlFreeAutomata(am);
}
}
#[test]
fn test_null_automata_returns_null_state() {
unsafe {
let state = xmlAutomataNewState(ptr::null_mut());
assert!(state.is_null());
}
}
#[test]
fn test_null_automata_returns_null_init() {
unsafe {
assert!(xmlAutomataGetInitState(ptr::null_mut()).is_null());
}
}
#[test]
fn test_new_state_adds_to_list() {
unsafe {
let am = xmlNewAutomata();
let s1 = xmlAutomataNewState(am);
let s2 = xmlAutomataNewState(am);
assert!(!s1.is_null());
assert!(!s2.is_null());
assert_ne!(s1, s2);
assert_eq!((*am).states.len(), 2);
xmlFreeAutomata(am);
}
}
#[test]
fn test_compile_simple_chain() {
unsafe {
let am = xmlNewAutomata();
let s1 = xmlAutomataNewState(am);
let s2 = xmlAutomataNewState(am);
let s3 = xmlAutomataNewState(am);
let token_a = b"a\0".as_ptr() as *const core::ffi::c_char;
let token_b = b"b\0".as_ptr() as *const core::ffi::c_char;
xmlAutomataNewTransition(am, s1, s2, token_a, ptr::null_mut());
xmlAutomataNewTransition(am, s2, s3, token_b, ptr::null_mut());
let result = xmlAutomataCompile(am);
assert_eq!(result, 0);
xmlFreeAutomata(am);
}
}
}