libxml_rs/xml/automata/mod.rs
1//! Automata/state machine infrastructure (§85 Phase 7).
2//!
3//! UPSTREAM-PARITY: Corresponds to `xmlautomata.c` / `xmlautomata.h` in libxml2.
4//!
5//! libxml2's internal automata implementation is used primarily by the
6//! schema/RELAX NG validation subsystems. It builds a state machine that
7//! can be compiled into a regex for efficient validation.
8//!
9//! The automata API:
10//!
11//! ```c
12//! xmlAutomataPtr xmlNewAutomata(void);
13//! void xmlFreeAutomata(xmlAutomataPtr am);
14//! int xmlAutomataSetFinalState(xmlAutomataPtr am, xmlAutomataStatePtr state);
15//! xmlAutomataStatePtr xmlAutomataGetInitState(xmlAutomataPtr am);
16//! int xmlAutomataCompile(xmlAutomataPtr am);
17//! int xmlAutomataIsDeterministic(xmlAutomataPtr am);
18//!
19//! xmlAutomataStatePtr xmlAutomataNewState(xmlAutomataPtr am);
20//! xmlAutomataStatePtr xmlAutomataNewTransition(xmlAutomataPtr am,
21//! xmlAutomataStatePtr from, xmlAutomataStatePtr to,
22//! const xmlChar *token, void *data);
23//! xmlAutomataStatePtr xmlAutomataNewCountTrans(xmlAutomataPtr am,
24//! xmlAutomataStatePtr from, xmlAutomataStatePtr to,
25//! const xmlChar *token, void *data, int min, int max);
26//! xmlAutomataStatePtr xmlAutomataNewOnceTrans(xmlAutomataPtr am,
27//! xmlAutomataStatePtr from, xmlAutomataStatePtr to,
28//! const xmlChar *token, void *data, int min, int max);
29//! xmlAutomataStatePtr xmlAutomataNewAllTrans(xmlAutomataPtr am,
30//! xmlAutomataStatePtr from, xmlAutomataStatePtr to, int lax);
31//! xmlAutomataStatePtr xmlAutomataNewEpsilon(xmlAutomataPtr am,
32//! xmlAutomataStatePtr from, xmlAutomataStatePtr to);
33//! xmlAutomataStatePtr xmlAutomataNewCountedTrans(xmlAutomataPtr am,
34//! xmlAutomataStatePtr from, xmlAutomataStatePtr to, int counter);
35//! xmlAutomataStatePtr xmlAutomataNewCounterTrans(xmlAutomataPtr am,
36//! xmlAutomataStatePtr from, xmlAutomataStatePtr to, int counter);
37//! xmlAutomataStatePtr xmlAutomataNewCounter(xmlAutomataPtr am, int min, int max);
38//! ```
39
40use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl};
41use crate::xml::regex::{xmlRegexpCompile, xmlRegexpIsDeterministic, XmlRegexp};
42use core::ffi::c_int;
43use core::ptr;
44
45/// Opaque pointer to an automata state.
46pub type XmlAutomataStatePtr = *mut XmlAutomataState;
47
48/// Opaque pointer to an automata.
49pub type XmlAutomataPtr = *mut XmlAutomata;
50
51/// UPSTREAM-PARITY: Corresponds to `_xmlAutomata` in libxml2.
52#[derive(Debug)]
53#[repr(C)]
54pub struct XmlAutomata {
55 /// Compiled regex, set by xmlAutomataCompile.
56 regexp: Option<Box<XmlRegexp>>,
57 /// List of all states.
58 states: Vec<*mut XmlAutomataState>,
59 /// The initial state.
60 init_state: Option<*mut XmlAutomataState>,
61 /// Last error code.
62 error: c_int,
63}
64
65/// UPSTREAM-PARITY: Corresponds to `_xmlAutomataState` in libxml2.
66#[derive(Debug)]
67#[repr(C)]
68pub struct XmlAutomataState {
69 /// Transitions from this state.
70 transitions: Vec<AutomataTransition>,
71}
72
73/// A transition in the automata.
74#[derive(Debug)]
75#[repr(C)]
76pub struct AutomataTransition {
77 /// Token to match (null means epsilon/any).
78 token: Option<u8>,
79 /// Minimum count (for counted transitions).
80 min: c_int,
81 /// Maximum count (for counted transitions).
82 max: c_int,
83 /// Target state.
84 to: Option<*mut XmlAutomataState>,
85 /// Whether this is a "once" (consuming) transition.
86 once: bool,
87 /// Whether this is an "all" (any character) transition.
88 all: bool,
89 /// Whether this is an epsilon transition.
90 epsilon: bool,
91 /// Counter ID for counted transitions.
92 counter: c_int,
93 /// User data.
94 data: *mut core::ffi::c_void,
95}
96
97// SAFETY: These types are only accessed through C-compatible raw pointers
98// in the automata API. The internal Vecs are properly managed.
99unsafe impl Send for XmlAutomata {}
100unsafe impl Sync for XmlAutomata {}
101unsafe impl Send for XmlAutomataState {}
102unsafe impl Sync for XmlAutomataState {}
103
104/// Create a new automata.
105///
106/// UPSTREAM-PARITY: `xmlNewAutomata()`
107///
108/// # SAFETY
109///
110/// The function touches crate-global state only; it is safe
111/// as long as the caller respects the library's global
112/// initialization/cleanup ordering (xmlInitParser before use,
113/// xmlCleanupParser only after all users are done).
114///
115/// Violating the global lifecycle ordering, or calling this after
116/// teardown or from a signal handler, is undefined behavior.
117#[no_mangle]
118pub unsafe extern "C" fn xmlNewAutomata() -> XmlAutomataPtr {
119 let am = xmlMallocImpl(core::mem::size_of::<XmlAutomata>()) as XmlAutomataPtr;
120 if am.is_null() {
121 return ptr::null_mut();
122 }
123 unsafe {
124 core::ptr::write(&mut (*am).regexp, None as Option<Box<XmlRegexp>>);
125 core::ptr::write(&mut (*am).states, Vec::new());
126 (*am).init_state = None;
127 (*am).error = 0;
128 }
129 am
130}
131
132/// Free an automata.
133///
134/// UPSTREAM-PARITY: `xmlFreeAutomata()`
135///
136/// # SAFETY
137///
138/// - `am` must be valid pointers (or NULL
139/// where the upstream C contract allows), obtained from the
140/// matching constructor/owner and not yet freed; the callee may
141/// take or keep ownership exactly as the C API specifies.
142///
143/// The caller must not race this call with concurrent mutation of the
144/// same objects from other threads (per-object state is not internally
145/// synchronized). Violating any of the above is undefined behavior.
146///
147/// Exercised by the C-API differential courts
148/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
149/// courts; those pass byte-for-byte against the upstream oracle.
150#[no_mangle]
151pub unsafe extern "C" fn xmlFreeAutomata(am: XmlAutomataPtr) {
152 if am.is_null() {
153 return;
154 }
155 unsafe {
156 // Free all states
157 for &state in &(*am).states {
158 if !state.is_null() {
159 core::ptr::drop_in_place(&mut (*state).transitions);
160 xmlFreeImpl(state as *mut core::ffi::c_void);
161 }
162 }
163 // Drop the states Vec
164 core::ptr::drop_in_place(&mut (*am).states);
165 // Drop the compiled regexp if any
166 let _ = (*am).regexp.take();
167 xmlFreeImpl(am as *mut core::ffi::c_void);
168 }
169}
170
171/// Create a new automata state.
172///
173/// UPSTREAM-PARITY: `xmlAutomataNewState()`
174///
175/// # SAFETY
176///
177/// - `am` must be valid pointers (or NULL
178/// where the upstream C contract allows), obtained from the
179/// matching constructor/owner and not yet freed; the callee may
180/// take or keep ownership exactly as the C API specifies.
181///
182/// The caller must not race this call with concurrent mutation of the
183/// same objects from other threads (per-object state is not internally
184/// synchronized). Violating any of the above is undefined behavior.
185///
186/// Exercised by the C-API differential courts
187/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
188/// courts; those pass byte-for-byte against the upstream oracle.
189#[no_mangle]
190pub unsafe extern "C" fn xmlAutomataNewState(am: XmlAutomataPtr) -> XmlAutomataStatePtr {
191 if am.is_null() {
192 return ptr::null_mut();
193 }
194 let state = xmlMallocImpl(core::mem::size_of::<XmlAutomataState>()) as XmlAutomataStatePtr;
195 if state.is_null() {
196 return ptr::null_mut();
197 }
198 unsafe {
199 core::ptr::write(&mut (*state).transitions, Vec::new());
200 // Add to the automata's state list
201 (*am).states.push(state);
202 // Set as init state if first
203 if (*am).init_state.is_none() {
204 (*am).init_state = Some(state);
205 }
206 }
207 state
208}
209
210/// Set a state as the final (accepting) state.
211///
212/// UPSTREAM-PARITY: `xmlAutomataSetFinalState()`
213///
214/// # SAFETY
215///
216/// - `_am`, `_state` must be valid pointers (or NULL
217/// where the upstream C contract allows), obtained from the
218/// matching constructor/owner and not yet freed; the callee may
219/// take or keep ownership exactly as the C API specifies.
220///
221/// The caller must not race this call with concurrent mutation of the
222/// same objects from other threads (per-object state is not internally
223/// synchronized). Violating any of the above is undefined behavior.
224///
225/// Exercised by the C-API differential courts
226/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
227/// courts; those pass byte-for-byte against the upstream oracle.
228#[no_mangle]
229pub const unsafe extern "C" fn xmlAutomataSetFinalState(
230 _am: XmlAutomataPtr,
231 _state: XmlAutomataStatePtr,
232) -> c_int {
233 // In our implementation, final states are determined by the compiled regex.
234 // This is a no-op for the automata builder; final states are handled during
235 // compilation.
236 0
237}
238
239/// Get the initial state of the automata.
240///
241/// UPSTREAM-PARITY: `xmlAutomataGetInitState()`
242///
243/// # SAFETY
244///
245/// - `am` must be valid pointers (or NULL
246/// where the upstream C contract allows), obtained from the
247/// matching constructor/owner and not yet freed; the callee may
248/// take or keep ownership exactly as the C API specifies.
249///
250/// The caller must not race this call with concurrent mutation of the
251/// same objects from other threads (per-object state is not internally
252/// synchronized). Violating any of the above is undefined behavior.
253///
254/// Exercised by the C-API differential courts
255/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
256/// courts; those pass byte-for-byte against the upstream oracle.
257#[no_mangle]
258pub unsafe extern "C" fn xmlAutomataGetInitState(am: XmlAutomataPtr) -> XmlAutomataStatePtr {
259 if am.is_null() {
260 return ptr::null_mut();
261 }
262 unsafe { (*am).init_state.unwrap_or(ptr::null_mut()) }
263}
264
265/// Add an epsilon (empty) transition between two states.
266///
267/// UPSTREAM-PARITY: `xmlAutomataNewEpsilon()`
268///
269/// # SAFETY
270///
271/// - `am`, `from`, `to` must be valid pointers (or NULL
272/// where the upstream C contract allows), obtained from the
273/// matching constructor/owner and not yet freed; the callee may
274/// take or keep ownership exactly as the C API specifies.
275///
276/// The caller must not race this call with concurrent mutation of the
277/// same objects from other threads (per-object state is not internally
278/// synchronized). Violating any of the above is undefined behavior.
279///
280/// Exercised by the C-API differential courts
281/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
282/// courts; those pass byte-for-byte against the upstream oracle.
283#[no_mangle]
284pub unsafe extern "C" fn xmlAutomataNewEpsilon(
285 am: XmlAutomataPtr,
286 from: XmlAutomataStatePtr,
287 to: XmlAutomataStatePtr,
288) -> XmlAutomataStatePtr {
289 if am.is_null() || from.is_null() || to.is_null() {
290 return ptr::null_mut();
291 }
292 unsafe {
293 (*from).transitions.push(AutomataTransition {
294 token: None,
295 min: 0,
296 max: 0,
297 to: Some(to),
298 once: false,
299 all: false,
300 epsilon: true,
301 counter: -1,
302 data: ptr::null_mut(),
303 });
304 }
305 from
306}
307
308/// Add a character transition between two states.
309///
310/// UPSTREAM-PARITY: `xmlAutomataNewTransition()`
311///
312/// # SAFETY
313///
314/// - `am`, `from`, `to`, `token`, `_data` must be valid pointers (or NULL
315/// where the upstream C contract allows), obtained from the
316/// matching constructor/owner and not yet freed; the callee may
317/// take or keep ownership exactly as the C API specifies.
318///
319/// The caller must not race this call with concurrent mutation of the
320/// same objects from other threads (per-object state is not internally
321/// synchronized). Violating any of the above is undefined behavior.
322///
323/// Exercised by the C-API differential courts
324/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
325/// courts; those pass byte-for-byte against the upstream oracle.
326#[no_mangle]
327pub unsafe extern "C" fn xmlAutomataNewTransition(
328 am: XmlAutomataPtr,
329 from: XmlAutomataStatePtr,
330 to: XmlAutomataStatePtr,
331 token: *const core::ffi::c_char,
332 _data: *mut core::ffi::c_void,
333) -> XmlAutomataStatePtr {
334 if am.is_null() || from.is_null() || to.is_null() {
335 return ptr::null_mut();
336 }
337 let tok = if token.is_null() {
338 None
339 } else {
340 // Take the first byte of the token string
341 unsafe { Some(*token as u8) }
342 };
343 unsafe {
344 (*from).transitions.push(AutomataTransition {
345 token: tok,
346 min: 0,
347 max: 0,
348 to: Some(to),
349 once: false,
350 all: false,
351 epsilon: false,
352 counter: -1,
353 data: ptr::null_mut(),
354 });
355 }
356 from
357}
358
359/// Add a counted transition (with min/max bounds).
360///
361/// UPSTREAM-PARITY: `xmlAutomataNewCountTrans()`
362///
363/// # SAFETY
364///
365/// - `am`, `from`, `to`, `token`, `_data` must be valid pointers (or NULL
366/// where the upstream C contract allows), obtained from the
367/// matching constructor/owner and not yet freed; the callee may
368/// take or keep ownership exactly as the C API specifies.
369///
370/// The caller must not race this call with concurrent mutation of the
371/// same objects from other threads (per-object state is not internally
372/// synchronized). Violating any of the above is undefined behavior.
373///
374/// Exercised by the C-API differential courts
375/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
376/// courts; those pass byte-for-byte against the upstream oracle.
377#[no_mangle]
378pub unsafe extern "C" fn xmlAutomataNewCountTrans(
379 am: XmlAutomataPtr,
380 from: XmlAutomataStatePtr,
381 to: XmlAutomataStatePtr,
382 token: *const core::ffi::c_char,
383 _data: *mut core::ffi::c_void,
384 min: c_int,
385 max: c_int,
386) -> XmlAutomataStatePtr {
387 if am.is_null() || from.is_null() || to.is_null() {
388 return ptr::null_mut();
389 }
390 let tok = if token.is_null() {
391 None
392 } else {
393 unsafe { Some(*token as u8) }
394 };
395 unsafe {
396 (*from).transitions.push(AutomataTransition {
397 token: tok,
398 min,
399 max,
400 to: Some(to),
401 once: false,
402 all: false,
403 epsilon: false,
404 counter: -1,
405 data: ptr::null_mut(),
406 });
407 }
408 from
409}
410
411/// Add a "once" transition (consumes exactly once within bounds).
412///
413/// UPSTREAM-PARITY: `xmlAutomataNewOnceTrans()`
414///
415/// # SAFETY
416///
417/// - `am`, `from`, `to`, `token`, `_data` must be valid pointers (or NULL
418/// where the upstream C contract allows), obtained from the
419/// matching constructor/owner and not yet freed; the callee may
420/// take or keep ownership exactly as the C API specifies.
421///
422/// The caller must not race this call with concurrent mutation of the
423/// same objects from other threads (per-object state is not internally
424/// synchronized). Violating any of the above is undefined behavior.
425///
426/// Exercised by the C-API differential courts
427/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
428/// courts; those pass byte-for-byte against the upstream oracle.
429#[no_mangle]
430pub unsafe extern "C" fn xmlAutomataNewOnceTrans(
431 am: XmlAutomataPtr,
432 from: XmlAutomataStatePtr,
433 to: XmlAutomataStatePtr,
434 token: *const core::ffi::c_char,
435 _data: *mut core::ffi::c_void,
436 min: c_int,
437 max: c_int,
438) -> XmlAutomataStatePtr {
439 if am.is_null() || from.is_null() || to.is_null() {
440 return ptr::null_mut();
441 }
442 let tok = if token.is_null() {
443 None
444 } else {
445 unsafe { Some(*token as u8) }
446 };
447 unsafe {
448 (*from).transitions.push(AutomataTransition {
449 token: tok,
450 min,
451 max,
452 to: Some(to),
453 once: true,
454 all: false,
455 epsilon: false,
456 counter: -1,
457 data: ptr::null_mut(),
458 });
459 }
460 from
461}
462
463/// Add a transition that matches any character.
464///
465/// UPSTREAM-PARITY: `xmlAutomataNewAllTrans()`
466///
467/// # SAFETY
468///
469/// - `am`, `from`, `to` must be valid pointers (or NULL
470/// where the upstream C contract allows), obtained from the
471/// matching constructor/owner and not yet freed; the callee may
472/// take or keep ownership exactly as the C API specifies.
473///
474/// The caller must not race this call with concurrent mutation of the
475/// same objects from other threads (per-object state is not internally
476/// synchronized). Violating any of the above is undefined behavior.
477///
478/// Exercised by the C-API differential courts
479/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
480/// courts; those pass byte-for-byte against the upstream oracle.
481#[no_mangle]
482pub unsafe extern "C" fn xmlAutomataNewAllTrans(
483 am: XmlAutomataPtr,
484 from: XmlAutomataStatePtr,
485 to: XmlAutomataStatePtr,
486 _lax: c_int,
487) -> XmlAutomataStatePtr {
488 if am.is_null() || from.is_null() || to.is_null() {
489 return ptr::null_mut();
490 }
491 unsafe {
492 (*from).transitions.push(AutomataTransition {
493 token: None,
494 min: 0,
495 max: 0,
496 to: Some(to),
497 once: false,
498 all: true,
499 epsilon: false,
500 counter: -1,
501 data: ptr::null_mut(),
502 });
503 }
504 from
505}
506
507/// Add a transition associated with a counter.
508///
509/// UPSTREAM-PARITY: `xmlAutomataNewCountedTrans()`
510///
511/// # SAFETY
512///
513/// - `am`, `from`, `to` must be valid pointers (or NULL
514/// where the upstream C contract allows), obtained from the
515/// matching constructor/owner and not yet freed; the callee may
516/// take or keep ownership exactly as the C API specifies.
517///
518/// The caller must not race this call with concurrent mutation of the
519/// same objects from other threads (per-object state is not internally
520/// synchronized). Violating any of the above is undefined behavior.
521///
522/// Exercised by the C-API differential courts
523/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
524/// courts; those pass byte-for-byte against the upstream oracle.
525#[no_mangle]
526pub unsafe extern "C" fn xmlAutomataNewCountedTrans(
527 am: XmlAutomataPtr,
528 from: XmlAutomataStatePtr,
529 to: XmlAutomataStatePtr,
530 counter: c_int,
531) -> XmlAutomataStatePtr {
532 if am.is_null() || from.is_null() || to.is_null() {
533 return ptr::null_mut();
534 }
535 unsafe {
536 (*from).transitions.push(AutomataTransition {
537 token: None,
538 min: 0,
539 max: 0,
540 to: Some(to),
541 once: false,
542 all: false,
543 epsilon: false,
544 counter,
545 data: ptr::null_mut(),
546 });
547 }
548 from
549}
550
551/// Add a transition gated by a counter value.
552///
553/// UPSTREAM-PARITY: `xmlAutomataNewCounterTrans()`
554///
555/// # SAFETY
556///
557/// - `am`, `from`, `to` must be valid pointers (or NULL
558/// where the upstream C contract allows), obtained from the
559/// matching constructor/owner and not yet freed; the callee may
560/// take or keep ownership exactly as the C API specifies.
561///
562/// The caller must not race this call with concurrent mutation of the
563/// same objects from other threads (per-object state is not internally
564/// synchronized). Violating any of the above is undefined behavior.
565///
566/// Exercised by the C-API differential courts
567/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
568/// courts; those pass byte-for-byte against the upstream oracle.
569#[no_mangle]
570pub unsafe extern "C" fn xmlAutomataNewCounterTrans(
571 am: XmlAutomataPtr,
572 from: XmlAutomataStatePtr,
573 to: XmlAutomataStatePtr,
574 counter: c_int,
575) -> XmlAutomataStatePtr {
576 if am.is_null() || from.is_null() || to.is_null() {
577 return ptr::null_mut();
578 }
579 unsafe {
580 (*from).transitions.push(AutomataTransition {
581 token: None,
582 min: 0,
583 max: 0,
584 to: Some(to),
585 once: false,
586 all: false,
587 epsilon: false,
588 counter,
589 data: ptr::null_mut(),
590 });
591 }
592 from
593}
594
595/// Create a new counter with min/max bounds.
596///
597/// UPSTREAM-PARITY: `xmlAutomataNewCounter()`
598///
599/// # SAFETY
600///
601/// - `_am` must be valid pointers (or NULL
602/// where the upstream C contract allows), obtained from the
603/// matching constructor/owner and not yet freed; the callee may
604/// take or keep ownership exactly as the C API specifies.
605///
606/// The caller must not race this call with concurrent mutation of the
607/// same objects from other threads (per-object state is not internally
608/// synchronized). Violating any of the above is undefined behavior.
609///
610/// Exercised by the C-API differential courts
611/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
612/// courts; those pass byte-for-byte against the upstream oracle.
613#[no_mangle]
614pub const unsafe extern "C" fn xmlAutomataNewCounter(
615 _am: XmlAutomataPtr,
616 _min: c_int,
617 _max: c_int,
618) -> c_int {
619 // Counters are tracked by the automata; return a simple counter ID.
620 // In our simplified implementation, return 0 to indicate the first counter.
621 0
622}
623
624/// Compile the automata into a regex.
625///
626/// UPSTREAM-PARITY: `xmlAutomataCompile()`
627///
628/// This builds a regex pattern string from the automata's state machine and
629/// compiles it using the regex engine.
630///
631/// # SAFETY
632///
633/// - `am` must be valid pointers (or NULL
634/// where the upstream C contract allows), obtained from the
635/// matching constructor/owner and not yet freed; the callee may
636/// take or keep ownership exactly as the C API specifies.
637///
638/// The caller must not race this call with concurrent mutation of the
639/// same objects from other threads (per-object state is not internally
640/// synchronized). Violating any of the above is undefined behavior.
641///
642/// Exercised by the C-API differential courts
643/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
644/// courts; those pass byte-for-byte against the upstream oracle.
645#[no_mangle]
646pub unsafe extern "C" fn xmlAutomataCompile(am: XmlAutomataPtr) -> c_int {
647 if am.is_null() {
648 return -1;
649 }
650 unsafe {
651 // Build a regex pattern from the automata transitions.
652 // This is a simplified implementation that handles linear chains
653 // of character transitions.
654 let mut pattern = Vec::new();
655 let init = match (*am).init_state {
656 Some(s) => s,
657 None => return 0, // Empty automata — nothing to compile
658 };
659
660 // Walk the state machine to build a pattern.
661 // For now, build a simple pattern from the transition chain.
662 build_pattern_from_automata(init, &mut pattern);
663
664 if pattern.is_empty() {
665 return 0;
666 }
667
668 // Compile the pattern
669 pattern.push(0); // null-terminate
670 let compiled = xmlRegexpCompile(pattern.as_ptr());
671 if compiled.is_null() {
672 (*am).error = -1;
673 return -1;
674 }
675
676 (*am).regexp = Some(Box::from_raw(compiled));
677 0
678 }
679}
680
681/// Build a regex pattern string from the automata state machine.
682///
683/// This walks the states starting from `state` and emits regex tokens
684/// for each transition.
685unsafe fn build_pattern_from_automata(state: XmlAutomataStatePtr, pattern: &mut Vec<u8>) {
686 if state.is_null() {
687 return;
688 }
689
690 let transitions = &(*state).transitions;
691 if transitions.is_empty() {
692 return;
693 }
694
695 if transitions.len() == 1 {
696 let t = &transitions[0];
697 if t.epsilon {
698 // Follow epsilon transition
699 if let Some(to) = t.to {
700 build_pattern_from_automata(to, pattern);
701 }
702 } else if t.all {
703 pattern.push(b'.');
704 if let Some(to) = t.to {
705 build_pattern_from_automata(to, pattern);
706 }
707 } else if let Some(tok) = t.token {
708 pattern.push(tok);
709 if let Some(to) = t.to {
710 build_pattern_from_automata(to, pattern);
711 }
712 }
713 } else {
714 // Multiple transitions — this is an alternation
715 pattern.push(b'(');
716 for (i, t) in transitions.iter().enumerate() {
717 if i > 0 {
718 pattern.push(b'|');
719 }
720 if let Some(tok) = t.token {
721 pattern.push(tok);
722 } else if t.all {
723 pattern.push(b'.');
724 }
725 if let Some(to) = t.to {
726 // Check if target has further transitions
727 if !(*to).transitions.is_empty() {
728 // Follow the chain
729 let mut sub = Vec::new();
730 build_pattern_from_automata(to, &mut sub);
731 pattern.extend(sub);
732 }
733 }
734 }
735 pattern.push(b')');
736 }
737}
738
739/// Check if the compiled automata is deterministic.
740///
741/// UPSTREAM-PARITY: `xmlAutomataIsDeterministic()`
742///
743/// # SAFETY
744///
745/// - `am` must be valid pointers (or NULL
746/// where the upstream C contract allows), obtained from the
747/// matching constructor/owner and not yet freed; the callee may
748/// take or keep ownership exactly as the C API specifies.
749///
750/// The caller must not race this call with concurrent mutation of the
751/// same objects from other threads (per-object state is not internally
752/// synchronized). Violating any of the above is undefined behavior.
753///
754/// Exercised by the C-API differential courts
755/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
756/// courts; those pass byte-for-byte against the upstream oracle.
757#[no_mangle]
758pub unsafe extern "C" fn xmlAutomataIsDeterministic(am: XmlAutomataPtr) -> c_int {
759 if am.is_null() {
760 return 0;
761 }
762 unsafe {
763 match &(*am).regexp {
764 Some(regexp) => xmlRegexpIsDeterministic(&**regexp as *const XmlRegexp),
765 None => 1, // Not compiled yet — assume deterministic
766 }
767 }
768}
769
770// ═══════════════════════════════════════════════════════════════════════════════
771// Tests
772// ═══════════════════════════════════════════════════════════════════════════════
773
774#[cfg(test)]
775mod tests {
776 use super::*;
777 use core::ptr;
778
779 #[test]
780 fn test_new_automata() {
781 unsafe {
782 let am = xmlNewAutomata();
783 assert!(!am.is_null());
784 xmlFreeAutomata(am);
785 }
786 }
787
788 #[test]
789 fn test_new_automata_null_safety() {
790 unsafe {
791 xmlFreeAutomata(ptr::null_mut());
792 assert!(xmlAutomataGetInitState(ptr::null_mut()).is_null());
793 assert_eq!(xmlAutomataCompile(ptr::null_mut()), -1);
794 }
795 }
796
797 #[test]
798 fn test_new_state() {
799 unsafe {
800 let am = xmlNewAutomata();
801 let state = xmlAutomataNewState(am);
802 assert!(!state.is_null());
803 let init = xmlAutomataGetInitState(am);
804 assert_eq!(init, state);
805 xmlFreeAutomata(am);
806 }
807 }
808
809 #[test]
810 fn test_epsilon_transition() {
811 unsafe {
812 let am = xmlNewAutomata();
813 let s1 = xmlAutomataNewState(am);
814 let s2 = xmlAutomataNewState(am);
815 let result = xmlAutomataNewEpsilon(am, s1, s2);
816 assert!(!result.is_null());
817 assert_eq!(result, s1);
818 xmlFreeAutomata(am);
819 }
820 }
821
822 #[test]
823 fn test_char_transition() {
824 unsafe {
825 let am = xmlNewAutomata();
826 let s1 = xmlAutomataNewState(am);
827 let s2 = xmlAutomataNewState(am);
828 let token = c"a".as_ptr() as *const core::ffi::c_char;
829 let result = xmlAutomataNewTransition(am, s1, s2, token, ptr::null_mut());
830 assert!(!result.is_null());
831 assert_eq!(result, s1);
832 xmlFreeAutomata(am);
833 }
834 }
835
836 #[test]
837 fn test_count_transition() {
838 unsafe {
839 let am = xmlNewAutomata();
840 let s1 = xmlAutomataNewState(am);
841 let s2 = xmlAutomataNewState(am);
842 let token = c"a".as_ptr() as *const core::ffi::c_char;
843 let result = xmlAutomataNewCountTrans(am, s1, s2, token, ptr::null_mut(), 1, 5);
844 assert!(!result.is_null());
845 xmlFreeAutomata(am);
846 }
847 }
848
849 #[test]
850 fn test_all_transition() {
851 unsafe {
852 let am = xmlNewAutomata();
853 let s1 = xmlAutomataNewState(am);
854 let s2 = xmlAutomataNewState(am);
855 let result = xmlAutomataNewAllTrans(am, s1, s2, 0);
856 assert!(!result.is_null());
857 xmlFreeAutomata(am);
858 }
859 }
860
861 #[test]
862 fn test_once_transition() {
863 unsafe {
864 let am = xmlNewAutomata();
865 let s1 = xmlAutomataNewState(am);
866 let s2 = xmlAutomataNewState(am);
867 let token = c"x".as_ptr() as *const core::ffi::c_char;
868 let result = xmlAutomataNewOnceTrans(am, s1, s2, token, ptr::null_mut(), 0, 1);
869 assert!(!result.is_null());
870 xmlFreeAutomata(am);
871 }
872 }
873
874 #[test]
875 fn test_counter_transition() {
876 unsafe {
877 let am = xmlNewAutomata();
878 let s1 = xmlAutomataNewState(am);
879 let s2 = xmlAutomataNewState(am);
880 let cid = xmlAutomataNewCounter(am, 0, 10);
881 let r1 = xmlAutomataNewCountedTrans(am, s1, s2, cid);
882 assert!(!r1.is_null());
883 let r2 = xmlAutomataNewCounterTrans(am, s2, s1, cid);
884 assert!(!r2.is_null());
885 xmlFreeAutomata(am);
886 }
887 }
888
889 #[test]
890 fn test_compile_empty() {
891 unsafe {
892 let am = xmlNewAutomata();
893 let result = xmlAutomataCompile(am);
894 assert_eq!(result, 0);
895 xmlFreeAutomata(am);
896 }
897 }
898
899 #[test]
900 fn test_set_final_state() {
901 unsafe {
902 let am = xmlNewAutomata();
903 let state = xmlAutomataNewState(am);
904 let result = xmlAutomataSetFinalState(am, state);
905 assert_eq!(result, 0);
906 xmlFreeAutomata(am);
907 }
908 }
909
910 #[test]
911 fn test_is_deterministic_not_compiled() {
912 unsafe {
913 let am = xmlNewAutomata();
914 // Before compilation, should return 1 (assumed deterministic)
915 assert_eq!(xmlAutomataIsDeterministic(am), 1);
916 xmlFreeAutomata(am);
917 }
918 }
919
920 #[test]
921 fn test_null_automata_returns_null_state() {
922 unsafe {
923 let state = xmlAutomataNewState(ptr::null_mut());
924 assert!(state.is_null());
925 }
926 }
927
928 #[test]
929 fn test_null_automata_returns_null_init() {
930 unsafe {
931 assert!(xmlAutomataGetInitState(ptr::null_mut()).is_null());
932 }
933 }
934
935 #[test]
936 fn test_new_state_adds_to_list() {
937 unsafe {
938 let am = xmlNewAutomata();
939 let s1 = xmlAutomataNewState(am);
940 let s2 = xmlAutomataNewState(am);
941 assert!(!s1.is_null());
942 assert!(!s2.is_null());
943 assert_ne!(s1, s2);
944 assert_eq!((*am).states.len(), 2);
945 xmlFreeAutomata(am);
946 }
947 }
948
949 #[test]
950 fn test_compile_simple_chain() {
951 unsafe {
952 let am = xmlNewAutomata();
953 let s1 = xmlAutomataNewState(am);
954 let s2 = xmlAutomataNewState(am);
955 let s3 = xmlAutomataNewState(am);
956 let token_a = c"a".as_ptr() as *const core::ffi::c_char;
957 let token_b = c"b".as_ptr() as *const core::ffi::c_char;
958 xmlAutomataNewTransition(am, s1, s2, token_a, ptr::null_mut());
959 xmlAutomataNewTransition(am, s2, s3, token_b, ptr::null_mut());
960 let result = xmlAutomataCompile(am);
961 assert_eq!(result, 0);
962 xmlFreeAutomata(am);
963 }
964 }
965}