blob: 913d563dbb323f8c2579bff6bd57dd48d5233adb [file] [log] [blame]
// Copyright 2018 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package builtins
import (
"regexp"
"sync"
"go.starlark.net/starlark"
)
// RegexpMatcher returns a function (with given name) that allows Starlark code
// to do regular expression matches:
//
// def submatches(pattern, str):
// """Returns a tuple of submatches with the leftmost match of the regular
// expression.
//
// The returned tuple has the full match as a first item, followed by
// subexpression matches.
//
// If the string doesn't match the expression returns an empty tuple. Fails if
// the regular expression can't be compiled.
// """
//
// Uses Go regexp engine, which is slightly different from Python's. API also
// explicitly does NOT try to mimic Python's 're' module.
//
// Each separate instance of the builtin holds a cache of compiled regular
// expressions internally. The cache is never cleaned up. Errors are not cached,
// since we don't expect to see them often.
//
// Safe for concurrent use.
func RegexpMatcher(name string) *starlark.Builtin {
cache := regexpCache{r: make(map[string]*regexp.Regexp)}
return starlark.NewBuiltin(name, func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var pattern, str starlark.String
err := starlark.UnpackArgs(name, args, kwargs,
"pattern", &pattern,
"str", &str,
)
if err != nil {
return nil, err
}
groups, err := cache.matches(pattern.GoString(), str.GoString())
if err != nil {
return nil, err
}
tup := make(starlark.Tuple, len(groups))
for i, s := range groups {
tup[i] = starlark.String(s)
}
return tup, nil
})
}
type regexpCache struct {
m sync.RWMutex
r map[string]*regexp.Regexp
}
func (c *regexpCache) matches(pat, str string) ([]string, error) {
exp, err := c.exp(pat)
if err != nil {
return nil, err
}
return exp.FindStringSubmatch(str), nil
}
func (c *regexpCache) exp(pat string) (*regexp.Regexp, error) {
c.m.RLock()
exp, _ := c.r[pat]
c.m.RUnlock()
if exp != nil {
return exp, nil
}
c.m.Lock()
defer c.m.Unlock()
if exp, _ = c.r[pat]; exp != nil {
return exp, nil
}
exp, err := regexp.Compile(pat)
if err != nil {
return nil, err
}
c.r[pat] = exp
return exp, nil
}