1use std::{cell::RefCell, num::NonZeroUsize, rc::Rc};23use ::regex::Regex;4use jrsonnet_evaluator::{5 error::{ErrorKind::*, Result},6 rustc_hash::FxBuildHasher,7 val::StrValue,8 IStr, ObjValueBuilder, Val,9};10use jrsonnet_gcmodule::Acyclic;11use jrsonnet_macros::builtin;12use lru::LruCache;1314#[derive(Acyclic)]15pub struct RegexCacheInner {16 cache: RefCell<LruCache<IStr, Rc<Regex>, FxBuildHasher>>,17}18impl Default for RegexCacheInner {19 fn default() -> Self {20 Self {21 cache: RefCell::new(LruCache::with_hasher(22 NonZeroUsize::new(20).unwrap(),23 FxBuildHasher::default(),24 )),25 }26 }27}28pub type RegexCache = Rc<RegexCacheInner>;29impl RegexCacheInner {30 fn parse(&self, pattern: IStr) -> Result<Rc<Regex>> {31 let mut cache = self.cache.borrow_mut();32 if let Some(found) = cache.get(&pattern) {33 return Ok(found.clone());34 }35 let regex = Regex::new(&pattern)36 .map_err(|e| RuntimeError(format!("regex parse failed: {e}").into()))?;37 let regex = Rc::new(regex);38 cache.push(pattern, regex.clone());39 Ok(regex)40 }41}4243pub fn regex_match_inner(regex: &Regex, str: String) -> Result<Val> {44 let mut out = ObjValueBuilder::with_capacity(3);4546 let mut captures = Vec::with_capacity(regex.captures_len());47 let mut named_captures = ObjValueBuilder::with_capacity(regex.capture_names().len());4849 let Some(captured) = regex.captures(&str) else {50 return Ok(Val::Null);51 };5253 for ele in captured.iter().skip(1) {54 if let Some(ele) = ele {55 captures.push(Val::Str(StrValue::Flat(ele.as_str().into())));56 } else {57 captures.push(Val::Str(StrValue::Flat(IStr::empty())));58 }59 }60 for (i, name) in regex61 .capture_names()62 .skip(1)63 .enumerate()64 .filter_map(|(i, v)| Some((i, v?)))65 {66 let capture = captures[i].clone();67 named_captures.field(name).try_value(capture)?;68 }6970 out.field("string")71 .value(Val::Str(captured.get(0).unwrap().as_str().into()));72 out.field("captures").value(Val::Arr(captures.into()));73 out.field("namedCaptures")74 .value(Val::Obj(named_captures.build()));7576 Ok(Val::Obj(out.build()))77}7879#[builtin(fields(80 cache: RegexCache,81))]82pub fn builtin_regex_partial_match(83 this: &builtin_regex_partial_match,84 pattern: IStr,85 str: String,86) -> Result<Val> {87 let regex = this.cache.parse(pattern)?;88 regex_match_inner(®ex, str)89}9091#[builtin(fields(92 cache: RegexCache,93))]94pub fn builtin_regex_full_match(95 this: &builtin_regex_full_match,96 pattern: StrValue,97 str: String,98) -> Result<Val> {99 let pattern = format!("^{pattern}$").into();100 let regex = this.cache.parse(pattern)?;101 regex_match_inner(®ex, str)102}103104#[builtin]105pub fn builtin_regex_quote_meta(pattern: String) -> String {106 regex::escape(&pattern)107}108109#[builtin(fields(110 cache: RegexCache,111))]112pub fn builtin_regex_replace(113 this: &builtin_regex_replace,114 str: String,115 pattern: IStr,116 to: String,117) -> Result<String> {118 let regex = this.cache.parse(pattern)?;119 let replaced = regex.replace(&str, to);120 Ok(replaced.to_string())121}122123#[builtin(fields(124 cache: RegexCache,125))]126pub fn builtin_regex_global_replace(127 this: &builtin_regex_global_replace,128 str: String,129 pattern: IStr,130 to: String,131) -> Result<String> {132 let regex = this.cache.parse(pattern)?;133 let replaced = regex.replace_all(&str, to);134 Ok(replaced.to_string())135}