1use crate::{PersistentVector, Value};
31use cljrs_gc::{GcPtr, GcVisitor, MarkVisitor, Trace};
32use std::borrow::Cow;
33use std::fmt;
34use std::sync::Mutex;
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(engine::Regex);
53
54#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct PatternError(String);
58
59impl fmt::Display for PatternError {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.write_str(&self.0)
62 }
63}
64
65impl std::error::Error for PatternError {}
66
67impl Pattern {
68 pub fn new(pattern: &str) -> Result<Pattern, PatternError> {
70 engine::Regex::new(pattern)
71 .map(Pattern)
72 .map_err(|e| PatternError(e.to_string()))
73 }
74
75 pub fn as_str(&self) -> &str {
77 self.0.as_str()
78 }
79
80 pub fn captures<'h>(&self, haystack: &'h str) -> Option<Captures<'h>> {
82 self.0.captures(haystack).map(Captures)
83 }
84
85 pub fn captures_at<'h>(&self, haystack: &'h str, start: usize) -> Option<Captures<'h>> {
89 self.0.captures_at(haystack, start).map(Captures)
90 }
91
92 pub fn replace<'h>(&self, haystack: &'h str, replacement: &str) -> Cow<'h, str> {
95 self.0.replace(haystack, replacement)
96 }
97
98 pub fn replace_all<'h>(&self, haystack: &'h str, replacement: &str) -> Cow<'h, str> {
100 self.0.replace_all(haystack, replacement)
101 }
102
103 pub fn split<'h>(&self, haystack: &'h str) -> impl Iterator<Item = &'h str> {
105 self.0.split(haystack)
106 }
107
108 pub fn splitn<'h>(&self, haystack: &'h str, limit: usize) -> impl Iterator<Item = &'h str> {
111 self.0.splitn(haystack, limit)
112 }
113}
114
115impl fmt::Display for Pattern {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 f.write_str(self.as_str())
118 }
119}
120
121impl Trace for Pattern {
122 fn trace(&self, _: &mut MarkVisitor) {}
123}
124
125#[derive(Debug)]
130pub struct Captures<'h>(engine::Captures<'h>);
131
132impl<'h> Captures<'h> {
133 pub fn full(&self) -> &'h str {
135 self.whole().as_str()
136 }
137
138 pub fn end(&self) -> usize {
140 self.whole().end()
141 }
142
143 pub fn group_count(&self) -> usize {
145 self.0.len()
146 }
147
148 pub fn groups(&self) -> impl Iterator<Item = Option<&'h str>> + '_ {
151 self.0.iter().map(|g| g.map(|m| m.as_str()))
152 }
153
154 fn whole(&self) -> engine::Match<'h> {
157 self.0
158 .get(0)
159 .expect("group 0 always participates in a successful match")
160 }
161}
162
163#[derive(Debug, Clone)]
164pub enum MatchPhase {
165 New,
166 Matching(usize),
167 Complete,
168}
169
170#[derive(Debug, Clone)]
171struct MatcherState {
172 phase: MatchPhase,
173 last_match: Option<MatchResult>,
174}
175
176#[derive(Debug)]
177pub struct Matcher {
178 pub pattern: GcPtr<Pattern>,
179 haystack: GcPtr<String>,
180 state: Mutex<MatcherState>,
181 match_all: bool,
182}
183
184#[derive(Debug, Clone)]
185pub struct MatchResult {
186 pub full: String,
187 pub groups: Vec<Option<String>>,
188}
189
190impl Clone for Matcher {
191 fn clone(&self) -> Matcher {
192 let state = self.state.lock().unwrap().clone();
193 Matcher {
194 pattern: self.pattern.clone(),
195 haystack: self.haystack.clone(),
196 state: Mutex::new(state.clone()),
197 match_all: self.match_all,
198 }
199 }
200}
201
202impl Trace for Matcher {
203 fn trace(&self, visitor: &mut MarkVisitor) {
204 visitor.visit(&self.pattern);
205 visitor.visit(&self.haystack);
206 }
207}
208
209impl Matcher {
210 pub fn new(pattern: Pattern, source: String, match_all: bool) -> Self {
211 Self {
212 pattern: GcPtr::new(pattern),
213 haystack: GcPtr::new(source),
214 state: Mutex::new(MatcherState {
215 phase: MatchPhase::New,
216 last_match: None,
217 }),
218 match_all,
219 }
220 }
221
222 pub fn next(&self) -> MatchPhase {
223 let mut state = self.state.lock().unwrap();
224 match state.phase {
225 MatchPhase::New => match self.pattern.get().captures(self.haystack.get()) {
226 Some(cap) => {
227 if !self.match_all || cap.group_count() == self.haystack.get().len() {
232 *state = MatcherState {
233 phase: MatchPhase::Matching(cap.end()),
234 last_match: Some(MatchResult::new(&cap)),
235 }
236 }
237 }
238 None => {
239 *state = MatcherState {
240 phase: MatchPhase::Complete,
241 last_match: None,
242 }
243 }
244 },
245 MatchPhase::Matching(n) => {
246 match self.pattern.get().captures_at(self.haystack.get(), n) {
247 Some(cap) => {
248 *state = MatcherState {
249 phase: MatchPhase::Matching(cap.end()),
250 last_match: Some(MatchResult::new(&cap)),
251 }
252 }
253 None => {
254 *state = MatcherState {
255 phase: MatchPhase::Complete,
256 last_match: None,
257 };
258 }
259 }
260 }
261 MatchPhase::Complete => {}
262 }
263 state.phase.clone()
264 }
265
266 pub fn capture(&self) -> Option<MatchResult> {
267 let state = self.state.lock().unwrap();
268 state.last_match.clone()
269 }
270
271 pub fn phase(&self) -> MatchPhase {
272 self.state.lock().unwrap().phase.clone()
273 }
274}
275
276impl MatchResult {
277 pub fn new(cap: &Captures<'_>) -> Self {
278 Self {
279 full: cap.full().to_string(),
280 groups: cap.groups().map(|g| g.map(|e| e.to_string())).collect(),
281 }
282 }
283
284 pub fn to_value(&self) -> Value {
285 if self.groups.len() == 1 || self.groups.iter().skip(1).all(|g| g.is_none()) {
286 Value::Str(GcPtr::new(self.full.to_string()))
287 } else {
288 let groups: Vec<Value> = self
289 .groups
290 .iter()
291 .map(|g| match g {
292 Some(m) => Value::Str(GcPtr::new(m.to_string())),
293 None => Value::Nil,
294 })
295 .collect();
296 Value::Vector(GcPtr::new(PersistentVector::from_iter(groups)))
297 }
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
307 fn pattern_is_send_and_sync() {
308 fn assert_send_sync<T: Send + Sync>() {}
309 assert_send_sync::<Pattern>();
310 }
311
312 #[test]
313 fn captures_expose_groups_in_order() {
314 let p = Pattern::new(r"(\d+)-(\d+)").unwrap();
315 let cap = p.captures("x 12-345 y").unwrap();
316 assert_eq!(cap.full(), "12-345");
317 assert_eq!(cap.group_count(), 3);
318 assert_eq!(cap.end(), 8);
319 assert_eq!(
320 cap.groups().collect::<Vec<_>>(),
321 vec![Some("12-345"), Some("12"), Some("345")]
322 );
323 }
324
325 #[test]
326 fn non_participating_group_is_none() {
327 let p = Pattern::new(r"(a)|(b)").unwrap();
328 let cap = p.captures("b").unwrap();
329 assert_eq!(
330 cap.groups().collect::<Vec<_>>(),
331 vec![Some("b"), None, Some("b")]
332 );
333 }
334
335 #[test]
336 fn captures_at_resumes_after_a_match() {
337 let p = Pattern::new(r"\d+").unwrap();
338 let cap = p.captures_at("a1 b22", 2).unwrap();
339 assert_eq!(cap.full(), "22");
340 }
341
342 #[test]
343 fn invalid_pattern_reports_the_engine_message() {
344 let err = Pattern::new(r"(").unwrap_err();
345 assert!(!err.to_string().is_empty());
346 }
347
348 #[test]
349 fn split_replace_and_display() {
350 let p = Pattern::new(r",\s*").unwrap();
351 assert_eq!(p.split("a, b,c").collect::<Vec<_>>(), vec!["a", "b", "c"]);
352 assert_eq!(p.splitn("a, b,c", 2).collect::<Vec<_>>(), vec!["a", "b,c"]);
353 assert_eq!(p.replace("a, b,c", "|"), "a|b,c");
354 assert_eq!(p.replace_all("a, b,c", "|"), "a|b|c");
355 assert_eq!(p.as_str(), r",\s*");
356 assert_eq!(p.to_string(), r",\s*");
357 }
358
359 #[test]
360 fn matcher_walks_every_match_then_completes() {
361 let p = Pattern::new(r"\d+").unwrap();
362 let m = Matcher::new(p, "a1 b22 c333".to_string(), false);
363
364 let mut found = Vec::new();
365 while let MatchPhase::Matching(_) = m.next() {
366 found.push(m.capture().unwrap().full);
367 }
368 assert_eq!(found, vec!["1", "22", "333"]);
369 assert!(matches!(m.phase(), MatchPhase::Complete));
370 assert!(m.capture().is_none());
371 }
372}