Skip to content

Commit

Permalink
Add regex support
Browse files Browse the repository at this point in the history
  • Loading branch information
TomWright committed Oct 10, 2024
1 parent 797f001 commit 605ec48
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 2 deletions.
5 changes: 5 additions & 0 deletions execution/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ func exprExecutor(expr ast.Expr) (expressionExecutor, error) {
return branchExprExecutor(e)
case ast.ArrayExpr:
return arrayExprExecutor(e)
case ast.RegexExpr:
// Noop
return func(data *model.Value) (*model.Value, error) {
return data, nil
}, nil
default:
return nil, fmt.Errorf("unhandled expression type: %T", e)
}
Expand Down
16 changes: 14 additions & 2 deletions execution/execute_binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,20 @@ func binaryExprExecutor(e ast.BinaryExpr) (expressionExecutor, error) {
return nil, fmt.Errorf("error getting right bool value: %w", err)
}
return model.NewBoolValue(leftBool || rightBool), nil
//case lexer.Like:
//case lexer.NotLike:
case lexer.Like, lexer.NotLike:
leftStr, err := left.StringValue()
if err != nil {
return nil, fmt.Errorf("like requires left side to be a string, got %s", left.Type().String())
}
rightPatt, ok := e.Right.(ast.RegexExpr)
if !ok {
return nil, fmt.Errorf("like requires right side to be a regex pattern")
}
res := rightPatt.Regex.MatchString(leftStr)
if e.Operator.Kind == lexer.NotLike {
res = !res
}
return model.NewBoolValue(res), nil
default:
return nil, fmt.Errorf("unhandled operator: %s", e.Operator.Value)
}
Expand Down
8 changes: 8 additions & 0 deletions execution/execute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ func TestExecuteSelector_HappyPath(t *testing.T) {
s: `2 <= 2`,
out: model.NewBoolValue(true),
}))
t.Run("like", runTest(testCase{
s: `"hello world" =~ r/ello/`,
out: model.NewBoolValue(true),
}))
t.Run("not like", runTest(testCase{
s: `"hello world" !~ r/helloworld/`,
out: model.NewBoolValue(true),
}))
})

t.Run("variables", func(t *testing.T) {
Expand Down
27 changes: 27 additions & 0 deletions execution/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,31 @@ func init() {
}
return model.NewIntValue(intRes), nil
})

RegisterFunc("toString", func(data *model.Value, args model.Values) (*model.Value, error) {
switch data.Type() {
case model.TypeString:
return data, nil
case model.TypeInt:
i, err := data.IntValue()
if err != nil {
return nil, err
}
return model.NewStringValue(fmt.Sprintf("%d", i)), nil
case model.TypeFloat:
i, err := data.FloatValue()
if err != nil {
return nil, err
}
return model.NewStringValue(fmt.Sprintf("%f", i)), nil
case model.TypeBool:
i, err := data.BoolValue()
if err != nil {
return nil, err
}
return model.NewStringValue(fmt.Sprintf("%v", i)), nil
default:
return nil, fmt.Errorf("cannot convert %s to string", data.Type())
}
})
}
8 changes: 8 additions & 0 deletions selector/ast/expression_literal.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ast

import "regexp"

type NumberFloatExpr struct {
Value float64
}
Expand All @@ -23,3 +25,9 @@ type BoolExpr struct {
}

func (BoolExpr) expr() {}

type RegexExpr struct {
Regex *regexp.Regexp
}

func (RegexExpr) expr() {}
1 change: 1 addition & 0 deletions selector/lexer/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const (
Branch
Map
Filter
RegexPattern
)

type Tokens []Token
Expand Down
17 changes: 17 additions & 0 deletions selector/lexer/tokenize.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,19 @@ func (p *Tokenizer) parseCurRune() (Token, error) {
return ptr.To(NewToken(kind, other, pos, l))
}

matchRegexPattern := func(pos int) *Token {
if !(p.src[pos] == 'r' && p.peekRuneEqual(pos+1, '/')) {
return nil
}
start := pos
pos += 2
for !p.peekRuneEqual(pos, '/') {
pos++
}
pos++
return ptr.To(NewToken(RegexPattern, p.src[start+2:pos-1], start, pos-start))
}

if t := matchStr(pos, "null", true, Null); t != nil {
return *t, nil
}
Expand Down Expand Up @@ -225,6 +238,10 @@ func (p *Tokenizer) parseCurRune() (Token, error) {
return *t, nil
}

if t := matchRegexPattern(pos); t != nil {
return *t, nil
}

if unicode.IsDigit(rune(p.src[pos])) {
// Handle whole numbers
for pos < p.srcLen && unicode.IsDigit(rune(p.src[pos])) {
Expand Down
8 changes: 8 additions & 0 deletions selector/lexer/tokenize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ func TestTokenizer_Parse(t *testing.T) {
},
}))

t.Run("regex", runTest(testCase{
in: `r/asd/ r/hello there/`,
out: []TokenKind{
RegexPattern,
RegexPattern,
},
}))

t.Run("everything", runTest(testCase{
in: "foo.bar.baz[1] != 42.123 || foo.bar.baz['hello'] == 42 && x == 'a\\'b' + false true . .... asd... $name null",
out: []TokenKind{
Expand Down
21 changes: 21 additions & 0 deletions selector/parser/parse_literal.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package parser

import (
"fmt"
"regexp"
"strconv"
"strings"

Expand Down Expand Up @@ -62,3 +64,22 @@ func parseNumberLiteral(p *Parser) (ast.Expr, error) {
}, nil
}
}

func parseRegexPattern(p *Parser) (ast.Expr, error) {
if err := p.expect(lexer.RegexPattern); err != nil {
return nil, err
}

pattern := p.current()

p.advance()

comp, err := regexp.Compile(pattern.Value)
if err != nil {
return nil, fmt.Errorf("failed to compile regexp pattern: %w", err)
}

return ast.RegexExpr{
Regex: comp,
}, nil
}
2 changes: 2 additions & 0 deletions selector/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ func (p *Parser) parseExpression(bp bindingPower) (left ast.Expr, err error) {
left, err = parseMap(p)
case lexer.Filter:
left, err = parseFilter(p)
case lexer.RegexPattern:
left, err = parseRegexPattern(p)
default:
return nil, &UnexpectedTokenError{
Token: p.current(),
Expand Down

0 comments on commit 605ec48

Please sign in to comment.