regexp/o
Perl has a regex option /o, which pretends to optimize your code, but actually introduces bugs. Go has been missing out. Until now.
We need regexp of course, but also text/template for interpolation. Add in runtime to get the pc, and we have all the elements for a cache of only once regex.
func Compile(s string, vals any) (*regexp.Regexp, error) {
cacheMtx.Lock()
defer cacheMtx.Unlock()
pc, _, _, _ := runtime.Caller(1)
k := key{s: s, pc: pc}
re := cache[k]
if re != nil {
return re, nil
}
t := template.New("regex")
t, err := t.Parse(s)
if err != nil {
return nil, err
}
var sb strings.Builder
err = t.Execute(&sb, vals)
if err != nil {
return nil, err
}
re, err = regexp.Compile(sb.String())
if err != nil {
return nil, err
}
cache[k] = re
return re, nil
}
type key struct {
s string
pc uintptr
}
var cacheMtx sync.Mutex
var cache = make(map[key]*regexp.Regexp)
And a test program.
func main() {
fmt.Printf("starting\n")
inputs := []string{"Apple", "bananas", "42"}
for i := 0; i < 3; i++ {
re, err := regexpo.Compile("{{ call .Letters }}", map[string]any{
"Letters": func() string {
time.Sleep(1 * time.Second)
fmt.Printf("substitute\n")
return "[a-z]"
},
})
if err != nil {
fmt.Printf("failure: %s\n", err)
return
}
ok := re.MatchString(inputs[i])
fmt.Printf("match: %v\n", ok)
}
fmt.Printf("done.\n")
}
The substitution is only done once.