// Copyright (c) 2021 Gitpod GmbH. All rights reserved. // Licensed under the GNU Affero General Public License (AGPL). // See License-AGPL.txt in the project root for license information. package lang import ( "context" "fmt" "reflect" "regexp" "strconv" "text/scanner" "unicode" "github.com/PaesslerAG/gval" ) var Lang = gval.NewLanguage( // Logic gval.InfixShortCircuit("&&", func(lhs interface{}) (interface{}, bool) { return false, lhs == false }), gval.InfixShortCircuit("||", func(lhs interface{}) (interface{}, bool) { return true, lhs == true }), gval.InfixBoolOperator("&&", func(lhs, rhs bool) (interface{}, error) { return lhs && rhs, nil }), gval.InfixBoolOperator("||", func(lhs, rhs bool) (interface{}, error) { return lhs || rhs, nil }), gval.InfixBoolOperator("==", func(lhs, rhs bool) (interface{}, error) { return lhs == rhs, nil }), gval.InfixBoolOperator("!=", func(lhs, rhs bool) (interface{}, error) { return lhs != rhs, nil }), // Arithmetic gval.InfixNumberOperator("==", func(lhs, rhs float64) (interface{}, error) { return lhs == rhs, nil }), gval.InfixNumberOperator("!=", func(lhs, rhs float64) (interface{}, error) { return lhs != rhs, nil }), gval.InfixNumberOperator("<", func(lhs, rhs float64) (interface{}, error) { return lhs < rhs, nil }), gval.InfixNumberOperator("<=", func(lhs, rhs float64) (interface{}, error) { return lhs <= rhs, nil }), gval.InfixNumberOperator(">", func(lhs, rhs float64) (interface{}, error) { return lhs > rhs, nil }), gval.InfixNumberOperator(">=", func(lhs, rhs float64) (interface{}, error) { return lhs >= rhs, nil }), // Text gval.InfixEvalOperator("~~", regEx), // Base gval.InfixOperator("==", func(lhs, rhs interface{}) (interface{}, error) { return reflect.DeepEqual(lhs, rhs), nil }), gval.InfixOperator("!=", func(lhs, rhs interface{}) (interface{}, error) { return !reflect.DeepEqual(lhs, rhs), nil }), gval.PrefixExtension(scanner.Int, parseNumber), gval.PrefixExtension(scanner.Float, parseNumber), gval.PrefixExtension(scanner.RawString, parseString), gval.Constant("true", true), gval.Constant("false", false), gval.Parentheses(), gval.Precedence("||", 20), gval.Precedence("&&", 21), gval.Precedence("==", 40), gval.Precedence("!=", 40), gval.Precedence("~~", 40), gval.Precedence("<", 40), gval.Precedence("<=", 40), gval.Precedence(">", 40), gval.Precedence(">=", 40), gval.PrefixMetaPrefix(scanner.Ident, parseIdent), ) var Fields []string func Compile(expression string) (gval.Evaluable, error) { return Lang.NewEvaluable(expression) } func Execute(eval gval.Evaluable, data map[string]interface{}) (interface{}, error) { return eval(context.Background(), data) } func parseString(c context.Context, p *gval.Parser) (gval.Evaluable, error) { s, err := strconv.Unquote(p.TokenText()) if err != nil { return nil, fmt.Errorf("could not parse string: %s", err) } return p.Const(s), nil } func parseNumber(c context.Context, p *gval.Parser) (gval.Evaluable, error) { n, err := strconv.ParseFloat(p.TokenText(), 64) if err != nil { return nil, err } return p.Const(n), nil } func regEx(a, b gval.Evaluable) (gval.Evaluable, error) { if !b.IsConst() { return func(c context.Context, o interface{}) (interface{}, error) { a, err := a.EvalString(c, o) if err != nil { return nil, err } b, err := b.EvalString(c, o) if err != nil { return nil, err } matched, err := regexp.MatchString(b, a) return matched, err }, nil } s, err := b.EvalString(context.TODO(), nil) if err != nil { return nil, err } regex, err := regexp.Compile(s) if err != nil { return nil, err } return func(c context.Context, v interface{}) (interface{}, error) { s, err := a.EvalString(c, v) if err != nil { return nil, err } return regex.MatchString(s), nil }, nil } func parseIdent(c context.Context, p *gval.Parser) (call string, alternative func() (gval.Evaluable, error), err error) { token := p.TokenText() return token, func() (gval.Evaluable, error) { tok := token for { scan := p.Scan() curr := p.TokenText() switch scan { case '-': fallthrough case '>': // Disambiguate greater than (>) operator to obtain correct parsing r := p.Peek() if unicode.IsLetter(r) || r == '_' || r == '-' || r == '[' || unicode.IsDigit(r) { scan = p.Scan() } else { p.Camouflage("variable") Fields = append(Fields, tok) return p.Var(p.Const(tok)), nil } // Continue scanning the identifier switch scan { case scanner.Int: fallthrough case scanner.Ident: fallthrough case '[': tok += curr + p.TokenText() continue default: return nil, p.Expected("field", scanner.Ident) } case scanner.Int: tok += p.TokenText() switch p.Scan() { case ']': tok += "]" default: return nil, p.Expected("array closing bracket", ']') } default: p.Camouflage("variable", '>', '-') Fields = append(Fields, tok) return p.Var(p.Const(tok)), nil } } }, nil }