1use std::sync::Arc;
7use std::time::{Duration, Instant};
8
9use super::buffer::RingBuffer;
10use super::cache::RegexCache;
11use super::pattern::{Pattern, PatternSet};
12use crate::types::Match;
13
14pub struct Matcher {
16 buffer: RingBuffer,
18 cache: Arc<RegexCache>,
20 default_timeout: Duration,
22 search_window: Option<usize>,
24}
25
26impl Matcher {
27 #[must_use]
29 pub fn new(buffer_size: usize) -> Self {
30 Self {
31 buffer: RingBuffer::new(buffer_size),
32 cache: Arc::new(RegexCache::with_default_size()),
33 default_timeout: Duration::from_secs(30),
34 search_window: None,
35 }
36 }
37
38 #[must_use]
40 pub fn with_cache(buffer_size: usize, cache: Arc<RegexCache>) -> Self {
41 Self {
42 buffer: RingBuffer::new(buffer_size),
43 cache,
44 default_timeout: Duration::from_secs(30),
45 search_window: None,
46 }
47 }
48
49 pub const fn set_default_timeout(&mut self, timeout: Duration) {
51 self.default_timeout = timeout;
52 }
53
54 pub const fn set_search_window(&mut self, size: Option<usize>) {
59 self.search_window = size;
60 }
61
62 pub fn append(&mut self, data: &[u8]) {
64 self.buffer.append(data);
65 }
66
67 #[must_use]
69 pub const fn buffer(&self) -> &RingBuffer {
70 &self.buffer
71 }
72
73 #[must_use]
75 pub fn buffer_str(&mut self) -> String {
76 self.buffer.as_str_lossy()
77 }
78
79 pub fn clear(&mut self) {
81 self.buffer.clear();
82 }
83
84 #[must_use]
86 pub fn try_match(&mut self, pattern: &Pattern) -> Option<MatchResult> {
87 let text = self.get_search_text();
88
89 match pattern {
90 Pattern::Literal(s) => text.find(s).map(|pos| MatchResult {
91 pattern_index: 0,
92 start: self.adjust_position(pos),
93 end: self.adjust_position(pos + s.len()),
94 captures: Vec::new(),
95 }),
96 Pattern::Regex(compiled) => compiled.find(&text).map(|m| {
97 let captures = compiled.captures(&text);
98 MatchResult {
99 pattern_index: 0,
100 start: self.adjust_position(m.start()),
101 end: self.adjust_position(m.end()),
102 captures,
103 }
104 }),
105 Pattern::Glob(glob) => {
106 self.try_glob_match(glob, &text)
107 .map(|(start, end)| MatchResult {
108 pattern_index: 0,
109 start: self.adjust_position(start),
110 end: self.adjust_position(end),
111 captures: Vec::new(),
112 })
113 }
114 Pattern::Bytes(n) => (self.buffer.len() >= *n).then_some(MatchResult {
119 pattern_index: 0,
120 start: 0,
121 end: *n,
122 captures: Vec::new(),
123 }),
124 Pattern::Eof | Pattern::Timeout(_) => None,
125 }
126 }
127
128 #[must_use]
130 pub fn try_match_any(&mut self, patterns: &PatternSet) -> Option<MatchResult> {
131 let text = self.get_search_text();
132 let buffer_len = self.buffer.len();
133 let mut best: Option<MatchResult> = None;
134
135 for (idx, named) in patterns.iter().enumerate() {
136 let result = if let Pattern::Bytes(n) = &named.pattern {
140 (buffer_len >= *n).then_some(MatchResult {
141 pattern_index: idx,
142 start: 0,
143 end: *n,
144 captures: Vec::new(),
145 })
146 } else {
147 named.pattern.matches(&text).map(|pm| MatchResult {
148 pattern_index: idx,
149 start: self.adjust_position(pm.start),
150 end: self.adjust_position(pm.end),
151 captures: pm.captures,
152 })
153 };
154
155 if let Some(result) = result {
156 match &best {
157 None => best = Some(result),
158 Some(current) if result.start < current.start => best = Some(result),
159 _ => {}
160 }
161 }
162 }
163
164 best
165 }
166
167 pub fn consume_match(&mut self, result: &MatchResult) -> Match {
169 let before = self.buffer.consume_before(result.start);
170 let matched_bytes = self.buffer.consume(result.end - result.start);
171 let matched = String::from_utf8_lossy(&matched_bytes).into_owned();
172 let after = self.buffer_str();
173
174 Match::new(result.pattern_index, matched, before, after)
175 .with_captures(result.captures.clone())
176 }
177
178 #[must_use]
180 pub fn get_timeout(&self, patterns: &PatternSet) -> Duration {
181 patterns.min_timeout().unwrap_or(self.default_timeout)
182 }
183
184 #[must_use]
186 pub const fn cache(&self) -> &Arc<RegexCache> {
187 &self.cache
188 }
189
190 fn get_search_text(&mut self) -> String {
192 match self.search_window {
193 Some(window) => {
194 let tail = self.buffer.tail(window);
195 String::from_utf8_lossy(&tail).into_owned()
196 }
197 None => self.buffer.as_str_lossy(),
198 }
199 }
200
201 fn adjust_position(&self, pos: usize) -> usize {
203 match self.search_window {
204 Some(window) => {
205 let buffer_len = self.buffer.len();
206 let offset = buffer_len.saturating_sub(window);
207 offset + pos
208 }
209 None => pos,
210 }
211 }
212
213 #[allow(clippy::unused_self)]
215 fn try_glob_match(&self, pattern: &str, text: &str) -> Option<(usize, usize)> {
216 if let Some(rest) = pattern.strip_prefix('*') {
219 if let Some(inner) = rest.strip_suffix('*') {
220 text.find(inner).map(|pos| (pos, pos + inner.len()))
222 } else {
223 let suffix = rest;
225 if text.ends_with(suffix) {
226 let start = text.len() - suffix.len();
227 Some((start, text.len()))
228 } else {
229 None
230 }
231 }
232 } else if let Some(prefix) = pattern.strip_suffix('*') {
233 if text.starts_with(prefix) {
235 Some((0, prefix.len()))
236 } else {
237 None
238 }
239 } else {
240 text.find(pattern).map(|pos| (pos, pos + pattern.len()))
241 }
242 }
243}
244
245impl Default for Matcher {
246 fn default() -> Self {
247 Self::new(super::buffer::DEFAULT_CAPACITY)
248 }
249}
250
251#[derive(Debug, Clone)]
253pub struct MatchResult {
254 pub pattern_index: usize,
256 pub start: usize,
258 pub end: usize,
260 pub captures: Vec<String>,
262}
263
264impl MatchResult {
265 #[must_use]
267 pub const fn len(&self) -> usize {
268 self.end - self.start
269 }
270
271 #[must_use]
273 pub const fn is_empty(&self) -> bool {
274 self.start == self.end
275 }
276}
277
278pub struct ExpectState {
280 patterns: PatternSet,
282 start_time: Instant,
284 timeout: Duration,
286 eof_detected: bool,
288}
289
290impl ExpectState {
291 #[must_use]
293 pub fn new(patterns: PatternSet, timeout: Duration) -> Self {
294 Self {
295 patterns,
296 start_time: Instant::now(),
297 timeout,
298 eof_detected: false,
299 }
300 }
301
302 #[must_use]
304 pub fn is_timed_out(&self) -> bool {
305 self.start_time.elapsed() >= self.timeout
306 }
307
308 #[must_use]
310 pub fn remaining_time(&self) -> Duration {
311 self.timeout.saturating_sub(self.start_time.elapsed())
312 }
313
314 pub const fn set_eof(&mut self) {
316 self.eof_detected = true;
317 }
318
319 #[must_use]
321 pub const fn is_eof(&self) -> bool {
322 self.eof_detected
323 }
324
325 #[must_use]
327 pub const fn patterns(&self) -> &PatternSet {
328 &self.patterns
329 }
330
331 #[must_use]
333 pub fn expects_eof(&self) -> bool {
334 self.patterns.has_eof()
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn matcher_literal() {
344 let mut matcher = Matcher::new(1024);
345 matcher.append(b"hello world");
346
347 let pattern = Pattern::literal("world");
348 let result = matcher.try_match(&pattern);
349 assert!(result.is_some());
350
351 let m = result.unwrap();
352 assert_eq!(m.start, 6);
353 assert_eq!(m.end, 11);
354 }
355
356 #[test]
357 fn matcher_regex() {
358 let mut matcher = Matcher::new(1024);
359 matcher.append(b"value: 42");
360
361 let pattern = Pattern::regex(r"\d+").unwrap();
362 let result = matcher.try_match(&pattern);
363 assert!(result.is_some());
364
365 let m = result.unwrap();
366 assert_eq!(m.start, 7);
367 assert_eq!(m.end, 9);
368 }
369
370 #[test]
371 fn matcher_consume() {
372 let mut matcher = Matcher::new(1024);
373 matcher.append(b"prefix|match|suffix");
374
375 let pattern = Pattern::literal("match");
376 let result = matcher.try_match(&pattern).unwrap();
377 let m = matcher.consume_match(&result);
378
379 assert_eq!(m.before, "prefix|");
380 assert_eq!(m.matched, "match");
381 assert_eq!(m.after, "|suffix");
382 }
383
384 #[test]
385 fn matcher_pattern_set() {
386 let mut matcher = Matcher::new(1024);
387 matcher.append(b"error: something went wrong");
388
389 let mut patterns = PatternSet::new();
390 patterns
391 .add(Pattern::literal("success"))
392 .add(Pattern::literal("error"));
393
394 let result = matcher.try_match_any(&patterns);
395 assert!(result.is_some());
396 assert_eq!(result.unwrap().pattern_index, 1);
397 }
398
399 #[test]
400 fn matcher_bytes_waits_then_matches() {
401 let mut matcher = Matcher::new(1024);
402 let pattern = Pattern::bytes(5);
403
404 matcher.append(b"abc");
406 assert!(
407 matcher.try_match(&pattern).is_none(),
408 "Bytes(5) must not match with only 3 bytes buffered"
409 );
410
411 matcher.append(b"defgh");
413 let result = matcher.try_match(&pattern).expect("Bytes(5) should match");
414 assert_eq!(result.start, 0);
415 assert_eq!(result.end, 5);
416
417 let m = matcher.consume_match(&result);
418 assert_eq!(m.matched, "abcde");
419 }
420
421 #[test]
422 fn matcher_bytes_in_pattern_set() {
423 let mut matcher = Matcher::new(1024);
424 matcher.append(b"abcdef");
425
426 let mut patterns = PatternSet::new();
427 patterns.add(Pattern::literal("zzz")).add(Pattern::bytes(4));
428
429 let result = matcher
430 .try_match_any(&patterns)
431 .expect("Bytes(4) should match in the set");
432 assert_eq!(result.pattern_index, 1);
433 assert_eq!(result.end - result.start, 4);
434 }
435
436 #[test]
437 fn expect_state_timeout() {
438 let patterns = PatternSet::from_patterns(vec![Pattern::literal("test")]);
439 let state = ExpectState::new(patterns, Duration::from_millis(10));
440
441 assert!(!state.is_timed_out());
442 std::thread::sleep(Duration::from_millis(20));
443 assert!(state.is_timed_out());
444 }
445}