metrale_grammar/fsm/builder/mod.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// FSM builders — port of `cpp/fsm_builder.{h,cc}`.
4//
5// regex_ir — the regex intermediate representation + NFA construction
6// regex — the regex string parser (`RegexFsmBuilder`)
7// trie — the trie / Aho-Corasick builder (`TrieFsmBuilder`)
8//
9// `GrammarFSMBuilder` (TagDispatch / ByteString / RuleRef / Choices) is
10// NOT ported here — it lives in `cpp/grammar_functor.h` and belongs to a
11// later port wave (W3 functors), since it depends on the grammar AST.
12
13pub mod regex;
14pub mod regex_ir;
15pub mod regex_leaf;
16pub mod regex_parse;
17pub mod trie;
18
19use crate::fsm::with_start_end::FsmWithStartEnd;
20
21/// Builder converting a regex string to an FSM.
22///
23/// Mirrors the C++ `RegexFSMBuilder` (a stateless namespace-like class).
24pub struct RegexFsmBuilder;
25
26impl RegexFsmBuilder {
27 /// Parse `regex` and build its NFA. See [`regex::build_regex`].
28 pub fn build(regex: &str) -> Result<FsmWithStartEnd, String> {
29 regex::build_regex(regex)
30 }
31}
32
33/// Builder converting a pattern list to a trie / Aho-Corasick FSM.
34///
35/// Mirrors the C++ `TrieFSMBuilder`.
36pub struct TrieFsmBuilder;
37
38impl TrieFsmBuilder {
39 /// Build a trie-based FSM. See [`trie::build_trie`] for the full
40 /// contract (overlap rules, back edges, excluded patterns).
41 pub fn build(
42 patterns: &[&[u8]],
43 excluded_patterns: &[&[u8]],
44 allow_overlap: bool,
45 add_back_edges: bool,
46 ) -> Option<trie::TrieBuildResult> {
47 trie::build_trie(patterns, excluded_patterns, allow_overlap, add_back_edges)
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn regex_builder_facade() {
57 let f = RegexFsmBuilder::build("ab*").unwrap();
58 assert!(f.accept_string(b"a"));
59 assert!(f.accept_string(b"abbb"));
60 assert!(!f.accept_string(b"b"));
61 }
62
63 #[test]
64 fn trie_builder_facade() {
65 let pats: Vec<&[u8]> = vec![b"hi", b"hello"];
66 let res = TrieFsmBuilder::build(&pats, &[], true, false).unwrap();
67 assert_eq!(res.end_states.len(), 2);
68 }
69}