1use crate::{PersistentVector, Value};
31use cljrs_gc::{GcPtr, GcVisitor, MarkVisitor, Trace};
32use std::borrow::Cow;
33use std::fmt;
34use std::sync::{Mutex, OnceLock};
35
36#[cfg(feature = "regex-full")]
37use regex as engine;
38#[cfg(all(feature = "small-regex", not(feature = "regex-full")))]
39use regex_lite as engine;
40
41#[cfg(not(any(feature = "regex-full", feature = "small-regex")))]
42compile_error!(
43 "cljrs-value needs a regex engine: keep the default `regex-full` feature, \
44 or enable `small-regex` to use regex-lite instead."
45);
46
47#[derive(Debug, Clone)]
52pub struct Pattern {
53 re: engine::Regex,
54 anchored: OnceLock<Option<engine::Regex>>,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct PatternError(String);
67
68impl fmt::Display for PatternError {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 f.write_str(&self.0)
71 }
72}
73
74impl std::error::Error for PatternError {}
75
76impl Pattern {
77 pub fn new(pattern: &str) -> Result<Pattern, PatternError> {
79 engine::Regex::new(pattern)
80 .map(|re| Pattern {
81 re,
82 anchored: OnceLock::new(),
83 })
84 .map_err(|e| PatternError(e.to_string()))
85 }
86
87 pub fn as_str(&self) -> &str {
89 self.re.as_str()
90 }
91
92 pub fn captures<'h>(&self, haystack: &'h str) -> Option<Captures<'h>> {
94 self.re.captures(haystack).map(Captures)
95 }
96
97 pub fn captures_full<'h>(&self, haystack: &'h str) -> Option<Captures<'h>> {
107 match self.anchored() {
108 Some(re) => re.captures(haystack).map(Captures),
109 None => self
114 .captures(haystack)
115 .filter(|cap| cap.start() == 0 && cap.end() == haystack.len()),
116 }
117 }
118
119 fn anchored(&self) -> Option<&engine::Regex> {
122 self.anchored
123 .get_or_init(|| engine::Regex::new(&format!(r"\A(?:{})\z", self.as_str())).ok())
124 .as_ref()
125 }
126
127 pub fn captures_at<'h>(&self, haystack: &'h str, start: usize) -> Option<Captures<'h>> {
131 self.re.captures_at(haystack, start).map(Captures)
132 }
133
134 pub fn replace<'h>(&self, haystack: &'h str, replacement: &str) -> Cow<'h, str> {
137 self.re.replace(haystack, replacement)
138 }
139
140 pub fn replace_all<'h>(&self, haystack: &'h str, replacement: &str) -> Cow<'h, str> {
142 self.re.replace_all(haystack, replacement)
143 }
144
145 pub fn split<'h>(&self, haystack: &'h str) -> impl Iterator<Item = &'h str> {
147 self.re.split(haystack)
148 }
149
150 pub fn splitn<'h>(&self, haystack: &'h str, limit: usize) -> impl Iterator<Item = &'h str> {
153 self.re.splitn(haystack, limit)
154 }
155}
156
157impl fmt::Display for Pattern {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 f.write_str(self.as_str())
160 }
161}
162
163impl Trace for Pattern {
164 fn trace(&self, _: &mut MarkVisitor) {}
165}
166
167#[derive(Debug)]
172pub struct Captures<'h>(engine::Captures<'h>);
173
174impl<'h> Captures<'h> {
175 pub fn full(&self) -> &'h str {
177 self.whole().as_str()
178 }
179
180 pub fn start(&self) -> usize {
183 self.whole().start()
184 }
185
186 pub fn end(&self) -> usize {
188 self.whole().end()
189 }
190
191 pub fn group_count(&self) -> usize {
193 self.0.len()
194 }
195
196 pub fn groups(&self) -> impl Iterator<Item = Option<&'h str>> + '_ {
199 self.0.iter().map(|g| g.map(|m| m.as_str()))
200 }
201
202 fn whole(&self) -> engine::Match<'h> {
205 self.0
206 .get(0)
207 .expect("group 0 always participates in a successful match")
208 }
209}
210
211#[derive(Debug, Clone)]
212pub enum MatchPhase {
213 New,
214 Matching(usize),
218 Complete,
219}
220
221#[derive(Debug, Clone)]
222struct MatcherState {
223 phase: MatchPhase,
224 last_match: Option<MatchResult>,
225}
226
227#[derive(Debug)]
228pub struct Matcher {
229 pub pattern: GcPtr<Pattern>,
230 haystack: GcPtr<String>,
231 state: Mutex<MatcherState>,
232 match_all: bool,
233}
234
235#[derive(Debug, Clone)]
236pub struct MatchResult {
237 pub full: String,
238 pub groups: Vec<Option<String>>,
239}
240
241impl Clone for Matcher {
242 fn clone(&self) -> Matcher {
243 let state = self.state.lock().unwrap().clone();
244 Matcher {
245 pattern: self.pattern.clone(),
246 haystack: self.haystack.clone(),
247 state: Mutex::new(state.clone()),
248 match_all: self.match_all,
249 }
250 }
251}
252
253impl Trace for Matcher {
254 fn trace(&self, visitor: &mut MarkVisitor) {
255 visitor.visit(&self.pattern);
256 visitor.visit(&self.haystack);
257 }
258}
259
260impl Matcher {
261 pub fn new(pattern: Pattern, source: String, match_all: bool) -> Self {
262 Self::from_ptr(GcPtr::new(pattern), source, match_all)
263 }
264
265 pub fn from_ptr(pattern: GcPtr<Pattern>, source: String, match_all: bool) -> Self {
269 Self {
270 pattern,
271 haystack: GcPtr::new(source),
272 state: Mutex::new(MatcherState {
273 phase: MatchPhase::New,
274 last_match: None,
275 }),
276 match_all,
277 }
278 }
279
280 pub fn next(&self) -> MatchPhase {
281 let mut state = self.state.lock().unwrap();
282 let pattern = self.pattern.get();
283 let haystack = self.haystack.get();
284 match state.phase {
285 MatchPhase::New => {
286 let cap = if self.match_all {
290 pattern.captures_full(haystack)
291 } else {
292 pattern.captures(haystack)
293 };
294 *state = Self::step(cap, haystack);
295 }
296 MatchPhase::Matching(n) => {
297 let cap = if self.match_all || n > haystack.len() {
302 None
303 } else {
304 pattern.captures_at(haystack, n)
305 };
306 *state = Self::step(cap, haystack);
307 }
308 MatchPhase::Complete => {}
309 }
310 state.phase.clone()
311 }
312
313 fn step(cap: Option<Captures<'_>>, haystack: &str) -> MatcherState {
316 match cap {
317 Some(cap) => MatcherState {
318 phase: MatchPhase::Matching(resume_from(&cap, haystack)),
319 last_match: Some(MatchResult::new(&cap)),
320 },
321 None => MatcherState {
322 phase: MatchPhase::Complete,
323 last_match: None,
324 },
325 }
326 }
327
328 pub fn capture(&self) -> Option<MatchResult> {
329 let state = self.state.lock().unwrap();
330 state.last_match.clone()
331 }
332
333 pub fn phase(&self) -> MatchPhase {
334 self.state.lock().unwrap().phase.clone()
335 }
336}
337
338fn resume_from(cap: &Captures<'_>, haystack: &str) -> usize {
344 let end = cap.end();
345 if cap.start() != end {
346 return end;
347 }
348 match haystack[end..].chars().next() {
350 Some(c) => end + c.len_utf8(),
351 None => end + 1,
352 }
353}
354
355impl MatchResult {
356 pub fn new(cap: &Captures<'_>) -> Self {
357 Self {
358 full: cap.full().to_string(),
359 groups: cap.groups().map(|g| g.map(|e| e.to_string())).collect(),
360 }
361 }
362
363 pub fn to_value(&self) -> Value {
364 if self.groups.len() == 1 || self.groups.iter().skip(1).all(|g| g.is_none()) {
365 Value::Str(GcPtr::new(self.full.to_string()))
366 } else {
367 let groups: Vec<Value> = self
368 .groups
369 .iter()
370 .map(|g| match g {
371 Some(m) => Value::Str(GcPtr::new(m.to_string())),
372 None => Value::Nil,
373 })
374 .collect();
375 Value::Vector(GcPtr::new(PersistentVector::from_iter(groups)))
376 }
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[test]
386 fn pattern_is_send_and_sync() {
387 fn assert_send_sync<T: Send + Sync>() {}
388 assert_send_sync::<Pattern>();
389 }
390
391 #[test]
392 fn captures_expose_groups_in_order() {
393 let p = Pattern::new(r"(\d+)-(\d+)").unwrap();
394 let cap = p.captures("x 12-345 y").unwrap();
395 assert_eq!(cap.full(), "12-345");
396 assert_eq!(cap.group_count(), 3);
397 assert_eq!(cap.end(), 8);
398 assert_eq!(
399 cap.groups().collect::<Vec<_>>(),
400 vec![Some("12-345"), Some("12"), Some("345")]
401 );
402 }
403
404 #[test]
405 fn non_participating_group_is_none() {
406 let p = Pattern::new(r"(a)|(b)").unwrap();
407 let cap = p.captures("b").unwrap();
408 assert_eq!(
409 cap.groups().collect::<Vec<_>>(),
410 vec![Some("b"), None, Some("b")]
411 );
412 }
413
414 #[test]
415 fn captures_at_resumes_after_a_match() {
416 let p = Pattern::new(r"\d+").unwrap();
417 let cap = p.captures_at("a1 b22", 2).unwrap();
418 assert_eq!(cap.full(), "22");
419 }
420
421 #[test]
422 fn invalid_pattern_reports_the_engine_message() {
423 let err = Pattern::new(r"(").unwrap_err();
424 assert!(!err.to_string().is_empty());
425 }
426
427 #[test]
428 fn split_replace_and_display() {
429 let p = Pattern::new(r",\s*").unwrap();
430 assert_eq!(p.split("a, b,c").collect::<Vec<_>>(), vec!["a", "b", "c"]);
431 assert_eq!(p.splitn("a, b,c", 2).collect::<Vec<_>>(), vec!["a", "b,c"]);
432 assert_eq!(p.replace("a, b,c", "|"), "a|b,c");
433 assert_eq!(p.replace_all("a, b,c", "|"), "a|b|c");
434 assert_eq!(p.as_str(), r",\s*");
435 assert_eq!(p.to_string(), r",\s*");
436 }
437
438 fn full_match(pattern: &str, haystack: &str) -> Option<MatchResult> {
442 let m = Matcher::new(Pattern::new(pattern).unwrap(), haystack.to_string(), true);
443 m.next();
444 m.capture()
445 }
446
447 #[test]
448 fn match_all_requires_the_whole_haystack() {
449 for (pattern, haystack) in [
450 (r"\d+", "42"),
451 (r"\d+", "424"),
452 (r"\d+", "4"),
453 (r"a+", "aaa"),
454 (r".*", "hello"),
455 ] {
456 assert_eq!(
457 full_match(pattern, haystack).map(|c| c.full),
458 Some(haystack.to_string()),
459 "{pattern} should match all of {haystack}"
460 );
461 }
462
463 let cap = full_match(r"(\d+)-(\d+)", "12-345").unwrap();
464 assert_eq!(cap.full, "12-345");
465 assert_eq!(
466 cap.groups,
467 vec![Some("12-345".into()), Some("12".into()), Some("345".into())]
468 );
469
470 assert!(full_match(r"(a)(b)", "abc").is_none());
472 assert!(full_match(r"(a)", "ab").is_none());
473 assert!(full_match(r"\d+", "42x").is_none());
474 assert!(full_match(r"(a)(b)", "xab").is_none());
476 assert!(full_match(r"\d+", "x42").is_none());
477 assert!(full_match(r"\d+", "abc").is_none());
479 }
480
481 #[test]
485 fn match_all_beats_leftmost_first_preference() {
486 assert_eq!(full_match(r"a|ab", "ab").map(|c| c.full), Some("ab".into()));
487 assert_eq!(
488 full_match(r"(a|ab)(c|bc)", "abc").map(|c| c.full),
489 Some("abc".into())
490 );
491 assert_eq!(
493 full_match(r".*?", "hello").map(|c| c.full),
494 Some("hello".into())
495 );
496 assert_eq!(
497 full_match(r"(\w+?)(\d*)", "ab12").map(|c| c.groups),
498 Some(vec![
499 Some("ab12".into()),
500 Some("ab".into()),
501 Some("12".into())
502 ])
503 );
504 let p = Pattern::new(r"a|ab").unwrap();
506 assert_eq!(p.captures("ab").unwrap().full(), "a");
507 }
508
509 #[test]
512 fn match_all_preserves_groups_and_inline_flags() {
513 let cap = full_match(r"(?i)(a)(b)", "AB").unwrap();
514 assert_eq!(cap.full, "AB");
515 assert_eq!(
516 cap.groups,
517 vec![Some("AB".into()), Some("A".into()), Some("B".into())]
518 );
519 assert_eq!(full_match(r"", "").map(|c| c.full), Some(String::new()));
521 assert!(full_match(r"", "a").is_none());
522 }
523
524 #[test]
528 fn match_all_yields_at_most_one_match() {
529 let m = Matcher::new(Pattern::new(r"a*").unwrap(), "aaa".to_string(), true);
530 assert!(matches!(m.next(), MatchPhase::Matching(3)));
531 assert_eq!(m.capture().unwrap().full, "aaa");
532 assert!(matches!(m.next(), MatchPhase::Complete));
533 assert!(m.capture().is_none());
534 assert!(matches!(m.next(), MatchPhase::Complete));
535 }
536
537 #[test]
540 fn match_all_completes_when_the_match_is_partial() {
541 let m = Matcher::new(Pattern::new(r"(a)").unwrap(), "ab".to_string(), true);
542 assert!(matches!(m.next(), MatchPhase::Complete));
543 assert!(m.capture().is_none());
544
545 let mut steps = 0;
546 while let MatchPhase::New | MatchPhase::Matching(_) = m.next() {
547 steps += 1;
548 assert!(steps < 10, "matcher never reached a terminal state");
549 }
550 }
551
552 fn drain(pattern: &str, haystack: &str) -> Vec<String> {
555 let m = Matcher::new(Pattern::new(pattern).unwrap(), haystack.to_string(), false);
556 let mut found = Vec::new();
557 while let MatchPhase::Matching(_) = m.next() {
558 found.push(m.capture().unwrap().full);
559 assert!(
560 found.len() < 16,
561 "matcher never reached Complete: {found:?}"
562 );
563 }
564 assert!(matches!(m.phase(), MatchPhase::Complete));
565 found
566 }
567
568 #[test]
572 fn zero_width_matches_advance_and_terminate() {
573 assert_eq!(drain(r"a*", "aaa"), vec!["aaa", ""]);
574 assert_eq!(drain(r"a*", "bab"), vec!["", "a", "", ""]);
575 assert_eq!(drain(r"", "ab"), vec!["", "", ""]);
576 assert_eq!(drain(r"", ""), vec![""]);
577 assert_eq!(drain(r"x*", "ab"), vec!["", "", ""]);
578 }
579
580 #[test]
583 fn zero_width_advance_respects_utf8_boundaries() {
584 assert_eq!(drain(r"x*", "é"), vec!["", ""]);
585 assert_eq!(drain(r"x*", "日本"), vec!["", "", ""]);
586 assert_eq!(drain(r"é*", "éé"), vec!["éé", ""]);
587 }
588
589 #[test]
590 fn matcher_walks_every_match_then_completes() {
591 let p = Pattern::new(r"\d+").unwrap();
592 let m = Matcher::new(p, "a1 b22 c333".to_string(), false);
593
594 let mut found = Vec::new();
595 while let MatchPhase::Matching(_) = m.next() {
596 found.push(m.capture().unwrap().full);
597 }
598 assert_eq!(found, vec!["1", "22", "333"]);
599 assert!(matches!(m.phase(), MatchPhase::Complete));
600 assert!(m.capture().is_none());
601 }
602}