metrale_grammar/fsm/builder/
regex.rs1use crate::fsm::with_start_end::FsmWithStartEnd;
12
13use super::regex_ir::{REPEAT_NO_UPPER_BOUND, RegexIr, RegexState, RegexSymbol};
14use super::regex_parse::{finalize_ir, parse_group_close};
15
16#[derive(Debug, Clone)]
18pub(crate) enum StackItem {
19 State(RegexState),
20 Ctrl(u8),
21}
22
23pub fn build_regex(regex: &str) -> Result<FsmWithStartEnd, String> {
25 let ir = parse_to_ir(regex)?;
26 ir.build()
27}
28
29fn check_repeat(regex: &[u8], mut start: usize) -> Result<(i32, i32, usize), String> {
32 if regex[start] != b'{' {
33 return Err("Invalid repeat format1".to_string());
34 }
35 start += 1;
36 let mut lower = 0i32;
37 let mut upper = REPEAT_NO_UPPER_BOUND;
38 let skip_spaces = |s: &mut usize| {
39 while *s < regex.len() && regex[*s] == b' ' {
40 *s += 1;
41 }
42 };
43 skip_spaces(&mut start);
44 let mut num = String::new();
45 while start < regex.len() && regex[start].is_ascii_digit() {
46 num.push(regex[start] as char);
47 start += 1;
48 }
49 if num.is_empty() {
50 return Err("Invalid repeat format2".to_string());
51 }
52 lower = num.parse().unwrap_or(lower);
53 skip_spaces(&mut start);
54 if start >= regex.len() {
55 return Err("Invalid repeat format5".to_string());
56 }
57 if regex[start] == b'}' {
58 return Ok((lower, lower, start));
59 }
60 if regex[start] != b',' {
61 return Err("Invalid repeat format3".to_string());
62 }
63 start += 1;
64 skip_spaces(&mut start);
65 if start < regex.len() && regex[start] == b'}' {
66 return Ok((lower, upper, start));
67 }
68 num.clear();
69 while start < regex.len() && regex[start].is_ascii_digit() {
70 num.push(regex[start] as char);
71 start += 1;
72 }
73 if num.is_empty() {
74 return Err("Invalid repeat format4".to_string());
75 }
76 upper = num.parse().unwrap_or(upper);
77 skip_spaces(&mut start);
78 if start >= regex.len() || regex[start] != b'}' {
79 return Err("Invalid repeat format5".to_string());
80 }
81 Ok((lower, upper, start))
82}
83
84fn parse_to_ir(regex: &str) -> Result<RegexIr, String> {
86 let bytes = regex.as_bytes();
87 let mut stack: Vec<StackItem> = Vec::new();
88 let mut left_bracket: i64 = -1;
89 let n = bytes.len();
90 let mut i = 0usize;
91
92 while i < n {
93 let c = bytes[i];
94 if i == 0 && c == b'^' {
96 i += 1;
97 continue;
98 }
99 if i == n - 1 && c == b'$' {
100 i += 1;
101 continue;
102 }
103 if c == b'[' {
105 if left_bracket != -1 {
106 return Err("Nested middle bracket!".to_string());
107 }
108 left_bracket = i as i64;
109 i += 1;
110 continue;
111 }
112 if c == b']' {
113 if left_bracket == -1 {
114 return Err("Invalid middle bracket!".to_string());
115 }
116 let lb = left_bracket as usize;
117 stack.push(StackItem::State(RegexState::Leaf {
118 regex: bytes[lb..=i].to_vec(),
119 }));
120 left_bracket = -1;
121 i += 1;
122 continue;
123 }
124 if left_bracket != -1 {
125 if c == b'\\' {
127 i += 1;
128 }
129 i += 1;
130 continue;
131 }
132 if c == b'+' || c == b'*' || c == b'?' {
134 let top = stack.pop();
135 let child = match top {
136 Some(StackItem::State(s)) => s,
137 _ => return Err("Invalid regex: no state before operator!".to_string()),
138 };
139 let symbol = match c {
140 b'+' => RegexSymbol::Plus,
141 b'*' => RegexSymbol::Star,
142 _ => RegexSymbol::Optional,
143 };
144 stack.push(StackItem::State(RegexState::Symbol {
145 symbol,
146 state: Box::new(child),
147 }));
148 i += 1;
149 continue;
150 }
151 if c == b'(' || c == b'|' {
153 stack.push(StackItem::Ctrl(c));
154 if c == b'('
156 && i + 2 < n
157 && bytes[i + 1] == b'?'
158 && matches!(bytes[i + 2], b':' | b'!' | b'=')
159 {
160 i += 3;
161 continue;
162 }
163 i += 1;
164 continue;
165 }
166 if c == b')' {
168 parse_group_close(&mut stack)?;
169 i += 1;
170 continue;
171 }
172 if c == b'{' {
174 let top = stack.pop();
175 let child = match top {
176 Some(StackItem::State(s)) => s,
177 _ => return Err("Invalid regex: no state before repeat!".to_string()),
178 };
179 let (lower, upper, end) = check_repeat(bytes, i)?;
180 stack.push(StackItem::State(RegexState::Repeat {
181 states: vec![child],
182 lower_bound: lower,
183 upper_bound: upper,
184 }));
185 i = end + 1;
186 continue;
187 }
188 let leaf_regex = if c != b'\\' {
191 let s = bytes[i..i + 1].to_vec();
192 i += 1;
193 s
194 } else {
195 let s = bytes[i..(i + 2).min(n)].to_vec();
196 i += 2;
197 s
198 };
199 stack.push(StackItem::State(RegexState::Leaf { regex: leaf_regex }));
200 }
201
202 finalize_ir(stack)
203}
204
205#[cfg(test)]
209#[path = "regex_tests.rs"]
210mod tests;