flak rss random

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.

Posted 04 Aug 2025 20:18 by tedu Updated: 04 Aug 2025 20:18
Tagged: go programming
V
V