1use std::{cell::RefCell, hash::BuildHasherDefault, num::NonZeroUsize, rc::Rc};23use ::regex::Regex;4use jrsonnet_evaluator::{5 error::{ErrorKind::*, Result},6 val::StrValue,7 IStr, ObjValueBuilder, Val,8};9use jrsonnet_macros::builtin;10use lru::LruCache;11use rustc_hash::FxHasher;1213pub struct RegexCacheInner {14 cache: RefCell<LruCache<IStr, Rc<Regex>, BuildHasherDefault<FxHasher>>>,15}16impl Default for RegexCacheInner {17 fn default() -> Self {18 Self {19 cache: RefCell::new(LruCache::with_hasher(20 NonZeroUsize::new(20).unwrap(),21 BuildHasherDefault::default(),22 )),23 }24 }25}26pub type RegexCache = Rc<RegexCacheInner>;27impl RegexCacheInner {28 fn parse(&self, pattern: IStr) -> Result<Rc<Regex>> {29 let mut cache = self.cache.borrow_mut();30 if let Some(found) = cache.get(&pattern) {31 return Ok(found.clone());32 }33 let regex = Regex::new(&pattern)34 .map_err(|e| RuntimeError(format!("regex parse failed: {e}").into()))?;35 let regex = Rc::new(regex);36 cache.push(pattern, regex.clone());37 Ok(regex)38 }39}4041pub fn regex_match_inner(regex: &Regex, str: String) -> Result<Val> {42 let mut out = ObjValueBuilder::with_capacity(3);4344 let mut captures = Vec::with_capacity(regex.captures_len());45 let mut named_captures = ObjValueBuilder::with_capacity(regex.capture_names().len());4647 let Some(captured) = regex.captures(&str) else {48 return Ok(Val::Null);49 };5051 for ele in captured.iter().skip(1) {52 if let Some(ele) = ele {53 captures.push(Val::Str(StrValue::Flat(ele.as_str().into())))54 } else {55 captures.push(Val::Str(StrValue::Flat(IStr::empty())))56 }57 }58 for (i, name) in regex59 .capture_names()60 .skip(1)61 .enumerate()62 .flat_map(|(i, v)| Some((i, v?)))63 {64 let capture = captures[i].clone();65 named_captures.field(name).try_value(capture)?;66 }6768 out.field("string")69 .value(Val::Str(captured.get(0).unwrap().as_str().into()));70 out.field("captures").value(Val::Arr(captures.into()));71 out.field("namedCaptures")72 .value(Val::Obj(named_captures.build()));7374 Ok(Val::Obj(out.build()))75}7677#[builtin(fields(78 cache: RegexCache,79))]80pub fn builtin_regex_partial_match(81 this: &builtin_regex_partial_match,82 pattern: IStr,83 str: String,84) -> Result<Val> {85 let regex = this.cache.parse(pattern)?;86 regex_match_inner(®ex, str)87}8889#[builtin(fields(90 cache: RegexCache,91))]92pub fn builtin_regex_full_match(93 this: &builtin_regex_full_match,94 pattern: StrValue,95 str: String,96) -> Result<Val> {97 let pattern = format!("^{pattern}$").into();98 let regex = this.cache.parse(pattern)?;99 regex_match_inner(®ex, str)100}101102#[builtin]103pub fn builtin_regex_quote_meta(pattern: String) -> String {104 regex::escape(&pattern)105}106107#[builtin(fields(108 cache: RegexCache,109))]110pub fn builtin_regex_replace(111 this: &builtin_regex_replace,112 str: String,113 pattern: IStr,114 to: String,115) -> Result<String> {116 let regex = this.cache.parse(pattern)?;117 let replaced = regex.replace(&str, to);118 Ok(replaced.to_string())119}120121#[builtin(fields(122 cache: RegexCache,123))]124pub fn builtin_regex_global_replace(125 this: &builtin_regex_global_replace,126 str: String,127 pattern: IStr,128 to: String,129) -> Result<String> {130 let regex = this.cache.parse(pattern)?;131 let replaced = regex.replace_all(&str, to);132 Ok(replaced.to_string())133}