tramlines_core/grammar/
regex.rs1use std::{cell::RefCell, collections::HashMap};
4
5use regex::Regex;
6
7#[derive(Debug, Default)]
10pub struct RegexCache {
11 map: RefCell<HashMap<String, Result<Regex, String>>>,
12}
13
14impl RegexCache {
15 pub fn new() -> Self {
17 Self::default()
18 }
19
20 pub fn get(&self, pattern: &str) -> Result<Regex, String> {
25 if let Some(hit) = self.map.borrow().get(pattern) {
26 return hit.clone();
27 }
28 let compiled = Regex::new(pattern).map_err(|e| reason(&e));
29 self.map
30 .borrow_mut()
31 .insert(pattern.to_string(), compiled.clone());
32 compiled
33 }
34}
35
36pub(crate) fn reason(e: ®ex::Error) -> String {
39 let report = e.to_string();
40 let last = report.lines().rev().find(|l| !l.trim().is_empty());
41 last.unwrap_or(&report)
42 .trim()
43 .trim_start_matches("error: ")
44 .to_string()
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 #[test]
52 fn compiles_once_and_remembers_failures() {
53 let cache = RegexCache::new();
54 let a = cache.get("^a+$").unwrap();
55 assert!(a.is_match("aaa"));
56 assert!(cache.get("^a+$").is_ok());
57 let e = cache.get("(").unwrap_err();
58 assert_eq!(cache.get("(").unwrap_err(), e);
59 assert!(!e.contains('\n'), "{e}");
60 assert_eq!(cache.map.borrow().len(), 2);
61 }
62}