blob: ebb1b351923f697980d7922a8f32a9986b86da7d [file] [log] [blame]
// Copyright 2023 The Shac 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 engine
import (
"context"
"regexp"
"sync"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
)
// ctxReMatch implements ctx.re.match.
//
// Returns a struct with the first match and its capturing groups. Returns None
// otherwise.
//
// It uses the RE2 engine as specified at https://golang.org/s/re2syntax.
//
// Make sure to update //doc/stdlib.star whenever this function is modified.
func ctxReMatch(ctx context.Context, s *shacState, name string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
str, r, err := reCommonPreamble(name, args, kwargs)
if err != nil {
return nil, err
}
m := r.FindStringSubmatchIndex(str)
if m == nil {
return starlark.None, nil
}
return matchToGroup(str, m), nil
}
// ctxReAllMatches implements ctx.re.allmatches.
//
// It returns a tuple of structs with all the matches and their capturing
// groups. If the file is large or the search is expected to end early, use
// ctx.re.match instead.
//
// It uses the RE2 engine as specified at https://golang.org/s/re2syntax.
//
// Make sure to update //doc/stdlib.star whenever this function is modified.
func ctxReAllMatches(ctx context.Context, s *shacState, name string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
str, r, err := reCommonPreamble(name, args, kwargs)
if err != nil {
return nil, err
}
matches := r.FindAllStringSubmatchIndex(str, -1)
// Always return a tuple even if no match is found to make client code
// simpler.
out := make(starlark.Tuple, len(matches))
for i, match := range matches {
// Create a struct for each match.
out[i] = matchToGroup(str, match)
}
return out, nil
}
// matchToGroup creates a struct for the match.
//
// It expects the return value from regexp.Regexp.FindStringSubmatchIndex.
//
// Make sure to update //doc/stdlib.star whenever this function is modified.
func matchToGroup(s string, groups []int) starlark.Value {
g := make(starlark.Tuple, len(groups)/2)
for j := 0; j < len(groups)/2; j++ {
start, end := groups[2*j], groups[2*j+1]
// Group indices will be -1 if a capture group was optional and did not
// match, e.g. `ctx.re.match(r"a(b)?", "a")`.
if start < 0 && end < 0 {
g[j] = starlark.None
} else {
g[j] = starlark.String(s[start:end])
}
}
return starlarkstruct.FromStringDict(starlark.String("match"),
starlark.StringDict{
// offset from the initial string in bytes.
"offset": starlark.MakeInt(groups[0]),
"groups": g,
})
}
// reCommonPreamble implements the common code for functions in ctx.re.*.
//
// Make sure to update //doc/stdlib.star whenever this function is modified.
func reCommonPreamble(name string, args starlark.Tuple, kwargs []starlark.Tuple) (string, *regexp.Regexp, error) {
var argpattern, argstr starlark.String
if err := starlark.UnpackArgs(name, args, kwargs, "pattern", &argpattern, "str", &argstr); err != nil {
return "", nil, err
}
r, err := reCache.compile(string(argpattern))
if err != nil {
return "", nil, err
}
return string(argstr), r, nil
}
// Support functions.
var reCache = reCacheImpl{r: map[string]*regexp.Regexp{}}
type reCacheImpl struct {
m sync.Mutex
r map[string]*regexp.Regexp
}
func (c *reCacheImpl) compile(pat string) (*regexp.Regexp, error) {
c.m.Lock()
defer c.m.Unlock()
if r := c.r[pat]; r != nil {
return r, nil
}
r, err := regexp.Compile(pat)
if err != nil {
return nil, err
}
c.r[pat] = r
return r, nil
}