Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/lexicon.html
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,15 @@ <h3 class="title-3">
- returns a copy of this string converted to lowercase.
</span>
</li>
<li>
<span>
<code class="code"
><span class="fn-name">matches</span><span class="fn-p">(</span
><span class="fn-arg">pattern</span><span class="fn-p">)</span></code
>
- returns true if the string matches the regular expression given by <code class="code">pattern</code>.
</span>
</li>
</ul>
</section>

Expand Down
2 changes: 2 additions & 0 deletions rules/builtins.build_defs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ def upper(self:str) -> str:
pass
def lower(self:str) -> str:
pass
def matches(self:str, pattern: str) -> bool:
pass

def fail(msg:str):
pass
Expand Down
16 changes: 16 additions & 0 deletions src/parse/asp/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"path/filepath"
"reflect"
"regexp"
"slices"
"sort"
"strconv"
Expand Down Expand Up @@ -98,6 +99,7 @@ func registerBuiltins(s *scope) {
"count": setNativeCode(s, "count", strCount),
"upper": setNativeCode(s, "upper", strUpper),
"lower": setNativeCode(s, "lower", strLower),
"matches": setNativeCode(s, "matches", strMatches),
}
s.interpreter.stringMethods["format"].kwargs = true
s.interpreter.dictMethods = map[string]*pyFunc{
Expand Down Expand Up @@ -645,6 +647,20 @@ func strLower(s *scope, args []pyObject) pyObject {
return pyString(strings.ToLower(self))
}

func strMatches(s *scope, args []pyObject) pyObject {
self := string(args[0].(pyString))
pattern := string(args[1].(pyString))
compiledRegex := s.interpreter.regexCache.Get(pattern)
if compiledRegex == nil {
compiled, err := regexp.Compile(pattern)
s.Assert(err == nil, "%s", err)
// We don't need to check if another task inserted the regex first, as it will be an identical result.
s.interpreter.regexCache.Add(pattern, compiled)
compiledRegex = compiled
}
return newPyBool(compiledRegex.MatchString(self))
}

func boolType(s *scope, args []pyObject) pyObject {
return newPyBool(args[0].IsTruthy())
}
Expand Down
12 changes: 8 additions & 4 deletions src/parse/asp/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"iter"
"path/filepath"
"reflect"
"regexp"
"runtime/debug"
"runtime/pprof"
"strings"
Expand All @@ -31,6 +32,8 @@ type interpreter struct {
limiter semaphore

stringMethods, dictMethods, configMethods map[string]*pyFunc

regexCache *cmap.Map[string, *regexp.Regexp]
}

// newInterpreter creates and returns a new interpreter instance.
Expand All @@ -42,10 +45,11 @@ func newInterpreter(state *core.BuildState, p *Parser) *interpreter {
locals: map[string]pyObject{},
}
i := &interpreter{
scope: s,
parser: p,
configs: map[*core.BuildState]*pyConfig{},
limiter: make(semaphore, state.Config.Parse.NumThreads),
scope: s,
parser: p,
configs: map[*core.BuildState]*pyConfig{},
limiter: make(semaphore, state.Config.Parse.NumThreads),
regexCache: cmap.New[string, *regexp.Regexp](cmap.SmallShardCount, cmap.XXHash),
}
// If we're creating an interpreter for a subrepo, we should share the subinclude cache.
if p.interpreter != nil {
Expand Down
7 changes: 7 additions & 0 deletions test/builtins/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
subinclude("//test/build_defs")

please_repo_e2e_test(
name = "strings_test",
plz_command = "plz build",
repo = "strings",
)
2 changes: 2 additions & 0 deletions test/builtins/strings/.plzconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[parse]
BuildFileName = BUILD_FILE
45 changes: 45 additions & 0 deletions test/builtins/strings/BUILD_FILE
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

assert ",".join(["a","b","c"]) == "a,b,c"

assert "a,b,c".split(",") == ["a", "b", "c"]

assert "abc".replace("bc", "ab") == "aab"

pre, sep, post = "a,b,c".partition(",")
assert pre == "a" and sep == "," and post == "b,c"

pre, sep, post = "a,b,c".rpartition(",")
assert pre == "a,b" and sep == "," and post == "c"

assert "abc".startswith("ab") == True

assert "abc".endswith("bc") == True

assert "a{var1},b{var2},c{var3}".format(var1="a", var2=2, var3=[3]) == "aa,b2,c[3]"

assert "abcba".lstrip("a") == "bcba"

assert "abcba".rstrip("a") == "abcb"

assert "abcba".strip("a") == "bcb"

assert "abc".removeprefix("ab") == "c"

assert "abc".removesuffix("bc") == "a"

assert "abcba".find("b") == 1

assert "abcba".rfind("b") == 3

assert "abcba".count("b") == 2

assert "abc".upper() == "ABC"

assert "ABC".lower() == "abc"


assert "abc".matches("a.c")

assert "abbbbbbc".matches("a.*c")

assert not "abc".matches("$b")
Loading