diff --git a/builtins.go b/builtins.go index e426b069..82f3f954 100644 --- a/builtins.go +++ b/builtins.go @@ -29,6 +29,7 @@ import ( "io" "math" "reflect" + "regexp" "sort" "strconv" "strings" @@ -259,6 +260,29 @@ func builtinTrace(i *interpreter, x value, y value) (value, error) { return y, nil } +func builtinMatch(i *interpreter, strv value, patv value) (value, error) { + str, err := i.getString(strv) + if err != nil { + return nil, err + } + pat, err := i.getString(patv) + if err != nil { + return nil, err + } + + r, err := regexp.Compile(pat.getGoString()) + if err != nil { + return nil, i.Error(fmt.Sprintf("Pattern %s is not valid", pat.getGoString())) + } + + matches := []*cachedThunk{} // to return empty array + for _, a := range r.FindAllString(str.getGoString(), -1) { + matches = append(matches, readyThunk(makeValueString(a))) + } + + return makeValueArray(matches), nil +} + // astMakeArrayElement wraps the function argument of std.makeArray so that // it can be embedded in cachedThunk without needing to execute it ahead of // time. It is equivalent to `local i = 42; func(i)`. It therefore has no @@ -2097,12 +2121,12 @@ func builtinAvg(i *interpreter, arrv value) (value, error) { if err != nil { return nil, err } - + len := float64(arr.length()) if len == 0 { return nil, i.Error("Cannot calculate average of an empty array.") } - + sumValue, err := builtinSum(i, arrv) if err != nil { return nil, err @@ -2112,7 +2136,7 @@ func builtinAvg(i *interpreter, arrv value) (value, error) { return nil, err } - avg := sum.value/len + avg := sum.value / len return makeValueNumber(avg), nil } @@ -2515,6 +2539,7 @@ var funcBuiltins = buildBuiltinMap([]builtin{ &unaryBuiltin{name: "isEmpty", function: builtinIsEmpty, params: ast.Identifiers{"str"}}, &binaryBuiltin{name: "equalsIgnoreCase", function: builtinEqualsIgnoreCase, params: ast.Identifiers{"str1", "str2"}}, &unaryBuiltin{name: "trim", function: builtinTrim, params: ast.Identifiers{"str"}}, + &binaryBuiltin{name: "match", function: builtinMatch, params: ast.Identifiers{"str", "pat"}}, &unaryBuiltin{name: "base64Decode", function: builtinBase64Decode, params: ast.Identifiers{"str"}}, &unaryBuiltin{name: "base64DecodeBytes", function: builtinBase64DecodeBytes, params: ast.Identifiers{"str"}}, &unaryBuiltin{name: "parseInt", function: builtinParseInt, params: ast.Identifiers{"str"}}, diff --git a/linter/internal/types/stdlib.go b/linter/internal/types/stdlib.go index 63c0eed3..462fa6a1 100644 --- a/linter/internal/types/stdlib.go +++ b/linter/internal/types/stdlib.go @@ -98,6 +98,7 @@ func prepareStdlib(g *typeGraph) { "isEmpty": g.newSimpleFuncType(boolType, "str"), "equalsIgnoreCase": g.newSimpleFuncType(boolType, "str1", "str2"), "trim": g.newSimpleFuncType(stringType, "str"), + "match": g.newSimpleFuncType(stringType, "str", "pat"), // TODO(sbarzowski) Fix when they match the documentation "escapeStringBash": g.newSimpleFuncType(stringType, "str_"), "escapeStringDollars": g.newSimpleFuncType(stringType, "str_"), @@ -152,7 +153,7 @@ func prepareStdlib(g *typeGraph) { "minArray": g.newFuncType(anyArrayType, []ast.Parameter{required("arr"), optional("keyF")}), "maxArray": g.newFuncType(anyArrayType, []ast.Parameter{required("arr"), optional("keyF")}), "contains": g.newSimpleFuncType(boolType, "arr", "elem"), - "avg": g.newSimpleFuncType(numberType, "arr"), + "avg": g.newSimpleFuncType(numberType, "arr"), "all": g.newSimpleFuncType(boolArrayType, "arr"), "any": g.newSimpleFuncType(boolArrayType, "arr"), "remove": g.newSimpleFuncType(anyArrayType, "arr", "elem"), diff --git a/testdata/builtinMatch.golden b/testdata/builtinMatch.golden new file mode 100644 index 00000000..b413e25d --- /dev/null +++ b/testdata/builtinMatch.golden @@ -0,0 +1,5 @@ +[ + "peach", + "punch", + "pinch" +] diff --git a/testdata/builtinMatch.jsonnet b/testdata/builtinMatch.jsonnet new file mode 100644 index 00000000..29fe5aae --- /dev/null +++ b/testdata/builtinMatch.jsonnet @@ -0,0 +1 @@ +std.match("peach punch pinch", "p([a-z]+)ch") \ No newline at end of file diff --git a/testdata/builtinMatch.linter.golden b/testdata/builtinMatch.linter.golden new file mode 100644 index 00000000..e69de29b diff --git a/testdata/builtinMatch2.golden b/testdata/builtinMatch2.golden new file mode 100644 index 00000000..db745021 --- /dev/null +++ b/testdata/builtinMatch2.golden @@ -0,0 +1,10 @@ +RUNTIME ERROR: Pattern (a] is not valid +------------------------------------------------- + testdata/builtinMatch2:1:1-24 $ + +std.match("foo", "(a]") + +------------------------------------------------- + During evaluation + + diff --git a/testdata/builtinMatch2.jsonnet b/testdata/builtinMatch2.jsonnet new file mode 100644 index 00000000..56e71cfa --- /dev/null +++ b/testdata/builtinMatch2.jsonnet @@ -0,0 +1 @@ +std.match("foo", "(a]") \ No newline at end of file diff --git a/testdata/builtinMatch2.linter.golden b/testdata/builtinMatch2.linter.golden new file mode 100644 index 00000000..e69de29b