package govaluate import ( "errors" "fmt" "math" "regexp" "reflect" ) const ( logicalErrorFormat string = "Value '%v' cannot be used with the logical operator '%v', it is not a bool" modifierErrorFormat string = "Value '%v' cannot be used with the modifier '%v', it is not a number" comparatorErrorFormat string = "Value '%v' cannot be used with the comparator '%v', it is not a number" ternaryErrorFormat string = "Value '%v' cannot be used with the ternary operator '%v', it is not a bool" prefixErrorFormat string = "Value '%v' cannot be used with the prefix '%v'" ) type evaluationOperator func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) type stageTypeCheck func(value interface{}) bool type stageCombinedTypeCheck func(left interface{}, right interface{}) bool type evaluationStage struct { symbol OperatorSymbol leftStage, rightStage *evaluationStage // the operation that will be used to evaluate this stage (such as adding [left] to [right] and return the result) operator evaluationOperator // ensures that both left and right values are appropriate for this stage. Returns an error if they aren't operable. leftTypeCheck stageTypeCheck rightTypeCheck stageTypeCheck // if specified, will override whatever is used in "leftTypeCheck" and "rightTypeCheck". // primarily used for specific operators that don't care which side a given type is on, but still requires one side to be of a given type // (like string concat) typeCheck stageCombinedTypeCheck // regardless of which type check is used, this string format will be used as the error message for type errors typeErrorFormat string } var ( _true = interface{}(true) _false = interface{}(false) ) func (this *evaluationStage) swapWith(other *evaluationStage) { temp := *other other.setToNonStage(*this) this.setToNonStage(temp) } func (this *evaluationStage) setToNonStage(other evaluationStage) { this.symbol = other.symbol this.operator = other.operator this.leftTypeCheck = other.leftTypeCheck this.rightTypeCheck = other.rightTypeCheck this.typeCheck = other.typeCheck this.typeErrorFormat = other.typeErrorFormat } func (this *evaluationStage) isShortCircuitable() bool { switch this.symbol { case AND: fallthrough case OR: fallthrough case TERNARY_TRUE: fallthrough case TERNARY_FALSE: fallthrough case COALESCE: return true } return false } func noopStageRight(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return right, nil } func addStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { // string concat if either are strings if isString(left) || isString(right) { return fmt.Sprintf("%v%v", left, right), nil } return left.(float64) + right.(float64), nil } func subtractStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return left.(float64) - right.(float64), nil } func multiplyStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return left.(float64) * right.(float64), nil } func divideStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return left.(float64) / right.(float64), nil } func exponentStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return math.Pow(left.(float64), right.(float64)), nil } func modulusStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return math.Mod(left.(float64), right.(float64)), nil } func gteStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { if isString(left) && isString(right) { return boolIface(left.(string) >= right.(string)), nil } return boolIface(left.(float64) >= right.(float64)), nil } func gtStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { if isString(left) && isString(right) { return boolIface(left.(string) > right.(string)), nil } return boolIface(left.(float64) > right.(float64)), nil } func lteStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { if isString(left) && isString(right) { return boolIface(left.(string) <= right.(string)), nil } return boolIface(left.(float64) <= right.(float64)), nil } func ltStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { if isString(left) && isString(right) { return boolIface(left.(string) < right.(string)), nil } return boolIface(left.(float64) < right.(float64)), nil } func equalStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return boolIface(reflect.DeepEqual(left, right)), nil } func notEqualStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return boolIface(!reflect.DeepEqual(left, right)), nil } func andStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return boolIface(left.(bool) && right.(bool)), nil } func orStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return boolIface(left.(bool) || right.(bool)), nil } func negateStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return -right.(float64), nil } func invertStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return boolIface(!right.(bool)), nil } func bitwiseNotStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return float64(^int64(right.(float64))), nil } func ternaryIfStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { if left.(bool) { return right, nil } return nil, nil } func ternaryElseStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { if left != nil { return left, nil } return right, nil } func regexStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { var pattern *regexp.Regexp var err error switch right.(type) { case string: pattern, err = regexp.Compile(right.(string)) if err != nil { return nil, errors.New(fmt.Sprintf("Unable to compile regexp pattern '%v': %v", right, err)) } case *regexp.Regexp: pattern = right.(*regexp.Regexp) } return pattern.Match([]byte(left.(string))), nil } func notRegexStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { ret, err := regexStage(left, right, parameters) if err != nil { return nil, err } return !(ret.(bool)), nil } func bitwiseOrStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return float64(int64(left.(float64)) | int64(right.(float64))), nil } func bitwiseAndStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return float64(int64(left.(float64)) & int64(right.(float64))), nil } func bitwiseXORStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return float64(int64(left.(float64)) ^ int64(right.(float64))), nil } func leftShiftStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return float64(uint64(left.(float64)) << uint64(right.(float64))), nil } func rightShiftStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return float64(uint64(left.(float64)) >> uint64(right.(float64))), nil } func makeParameterStage(parameterName string) evaluationOperator { return func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { value, err := parameters.Get(parameterName) if err != nil { return nil, err } return value, nil } } func makeLiteralStage(literal interface{}) evaluationOperator { return func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { return literal, nil } } func makeFunctionStage(function ExpressionFunction) evaluationOperator { return func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { if right == nil { return function() } switch right.(type) { case []interface{}: return function(right.([]interface{})...) default: return function(right) } } } func separatorStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { var ret []interface{} switch left.(type) { case []interface{}: ret = append(left.([]interface{}), right) default: ret = []interface{}{left, right} } return ret, nil } func inStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { for _, value := range right.([]interface{}) { if left == value { return true, nil } } return false, nil } // func isString(value interface{}) bool { switch value.(type) { case string: return true } return false } func isRegexOrString(value interface{}) bool { switch value.(type) { case string: return true case *regexp.Regexp: return true } return false } func isBool(value interface{}) bool { switch value.(type) { case bool: return true } return false } func isFloat64(value interface{}) bool { switch value.(type) { case float64: return true } return false } /* Addition usually means between numbers, but can also mean string concat. String concat needs one (or both) of the sides to be a string. */ func additionTypeCheck(left interface{}, right interface{}) bool { if isFloat64(left) && isFloat64(right) { return true } if !isString(left) && !isString(right) { return false } return true } /* Comparison can either be between numbers, or lexicographic between two strings, but never between the two. */ func comparatorTypeCheck(left interface{}, right interface{}) bool { if isFloat64(left) && isFloat64(right) { return true } if isString(left) && isString(right) { return true } return false } func isArray(value interface{}) bool { switch value.(type) { case []interface{}: return true } return false } /* Converting a boolean to an interface{} requires an allocation. We can use interned bools to avoid this cost. */ func boolIface(b bool) interface{} { if b { return _true } return _false }