diff --git a/CHANGELOG.md b/CHANGELOG.md index 398fb1196..deba91287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ kapacitor replay-live query -task cpu_alert -query 'SELECT usage_idle FROM teleg - [#540](https://github.com/influxdata/kapacitor/issues/540): Fixes bug with log level API endpoint. - [#521](https://github.com/influxdata/kapacitor/issues/521): EvalNode now honors groups. +- [#561](https://github.com/influxdata/kapacitor/issues/561): Fixes bug when lambda expressions would return error about types with nested binary expressions. ## v0.13.1 [2016-05-13] diff --git a/tick/lex.go b/tick/lex.go index 5be42f42b..685376423 100644 --- a/tick/lex.go +++ b/tick/lex.go @@ -51,11 +51,18 @@ const ( //end mathematical operators end_tok_operator_math - // begin comparison operators - begin_tok_operator_comp + // begin logical operators + begin_tok_operator_logic TokenAnd TokenOr + + // end logical operators + end_tok_operator_logic + + // begin comparison operators + begin_tok_operator_comp + TokenEqual TokenNotEqual TokenLess @@ -182,6 +189,10 @@ func IsCompOperator(typ TokenType) bool { return typ > begin_tok_operator_comp && typ < end_tok_operator_comp } +func IsLogicalOperator(typ TokenType) bool { + return typ > begin_tok_operator_logic && typ < end_tok_operator_logic +} + // token represents a token or text string returned from the scanner. type token struct { typ TokenType diff --git a/tick/stateful/eval_binary_node.go b/tick/stateful/eval_binary_node.go index 700732c73..d456f24bd 100644 --- a/tick/stateful/eval_binary_node.go +++ b/tick/stateful/eval_binary_node.go @@ -44,8 +44,8 @@ func (rc resultContainer) value() interface{} { // left side or right side type ErrSide struct { error - IsLeftSide bool - IsRightSide bool + IsLeft bool + IsRight bool } // Evaluation functions @@ -64,17 +64,20 @@ type EvalBinaryNode struct { // Saving a cache version NodeEvaluator so we don't need to cast // in every EvalBool call - leftSideEvaluator NodeEvaluator - leftSideType ValueType + leftEvaluator NodeEvaluator + leftType ValueType - rightSideEvaluator NodeEvaluator - rightSideType ValueType + rightEvaluator NodeEvaluator + rightType ValueType // Return type returnType ValueType } func NewEvalBinaryNode(node *tick.BinaryNode) (*EvalBinaryNode, error) { + if !tick.IsExprOperator(node.Operator) { + return nil, fmt.Errorf("unknown binary operator %v", node.Operator) + } expression := &EvalBinaryNode{ operator: node.Operator, returnType: getConstantNodeType(node), @@ -90,23 +93,26 @@ func NewEvalBinaryNode(node *tick.BinaryNode) (*EvalBinaryNode, error) { return nil, fmt.Errorf("Failed to handle right node: %v", err) } - expression.leftSideEvaluator = leftSideEvaluator - expression.rightSideEvaluator = rightSideEvaluator + expression.leftEvaluator = leftSideEvaluator + expression.rightEvaluator = rightSideEvaluator if isDynamicNode(node.Left) || isDynamicNode(node.Right) { expression.evaluationFn = expression.evaluateDynamicNode } else { - expression.leftSideType = getConstantNodeType(node.Left) - expression.rightSideType = getConstantNodeType(node.Right) + expression.leftType = getConstantNodeType(node.Left) + expression.rightType = getConstantNodeType(node.Right) - expression.evaluationFn = expression.findEvaluationFn(expression.leftSideType, expression.rightSideType) + expression.evaluationFn = expression.lookupEvaluationFn() + if expression.evaluationFn == nil { + return nil, expression.determineError(nil, ExecutionState{}) + } } return expression, nil } func (n *EvalBinaryNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) { - return findNodeTypes(n.returnType, []NodeEvaluator{n.leftSideEvaluator, n.rightSideEvaluator}, scope, executionState) + return findNodeTypes(n.returnType, []NodeEvaluator{n.leftEvaluator, n.rightEvaluator}, scope, executionState) } func (n *EvalBinaryNode) EvalRegex(scope *tick.Scope, executionState ExecutionState) (*regexp.Regexp, error) { @@ -178,11 +184,11 @@ func (e *EvalBinaryNode) EvalInt(scope *tick.Scope, executionState ExecutionStat func (e *EvalBinaryNode) eval(scope *tick.Scope, executionState ExecutionState) (resultContainer, *ErrSide) { if e.evaluationFn == nil { - err := e.determineError(scope, executionState, e.operator, e.leftSideEvaluator, e.rightSideEvaluator) + err := e.determineError(scope, executionState) return boolFalseResultContainer, &ErrSide{error: err} } - evaluationResult, err := e.evaluationFn(scope, executionState, e.leftSideEvaluator, e.rightSideEvaluator) + evaluationResult, err := e.evaluationFn(scope, executionState, e.leftEvaluator, e.rightEvaluator) // This case can in dynamic nodes, // for example: RefNode("value") > NumberNode("float64") @@ -192,18 +198,18 @@ func (e *EvalBinaryNode) eval(scope *tick.Scope, executionState ExecutionState) if err != nil { if typeGuardErr, isTypeGuardError := err.error.(ErrTypeGuardFailed); isTypeGuardError { // Fix the type info, thanks to the type guard info - if err.IsLeftSide { - e.leftSideType = typeGuardErr.ActualType + if err.IsLeft { + e.leftType = typeGuardErr.ActualType } - if err.IsRightSide { - e.rightSideType = typeGuardErr.ActualType + if err.IsRight { + e.rightType = typeGuardErr.ActualType } - // re-find the evaluation fn - e.evaluationFn = e.findEvaluationFn(e.leftSideType, e.rightSideType) + // redefine the evaluation fn + e.evaluationFn = e.lookupEvaluationFn() - // recurse (so we handle nil evaluationFn, and etc) + // try again return e.eval(scope, executionState) } } @@ -214,8 +220,8 @@ func (e *EvalBinaryNode) eval(scope *tick.Scope, executionState ExecutionState) // evaluateDynamicNode fetches the value of the right and left node at evaluation time (aka "runtime") // and find the matching evaluation function for the givne types - this is where the "specialisation" happens. func (e *EvalBinaryNode) evaluateDynamicNode(scope *tick.Scope, executionState ExecutionState, left, right NodeEvaluator) (resultContainer, *ErrSide) { - var leftSideType ValueType - var rightSideType ValueType + var leftType ValueType + var rightType ValueType var err error // For getting the type we must pass new execution state, since the node can be stateful (like function call) @@ -225,72 +231,69 @@ func (e *EvalBinaryNode) evaluateDynamicNode(scope *tick.Scope, executionState E // 2. we evaluate the second time in "EvalBool" typeExecutionState := CreateExecutionState() - if leftSideType, err = left.Type(scope, typeExecutionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + if leftType, err = left.Type(scope, typeExecutionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsLeft: true} } - if rightSideType, err = right.Type(scope, typeExecutionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + if rightType, err = right.Type(scope, typeExecutionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsRight: true} } - e.leftSideType = leftSideType - e.rightSideType = rightSideType + e.leftType = leftType + e.rightType = rightType - e.evaluationFn = e.findEvaluationFn(e.leftSideType, e.rightSideType) + e.evaluationFn = e.lookupEvaluationFn() return e.eval(scope, executionState) } -func (e *EvalBinaryNode) determineError(scope *tick.Scope, executionState ExecutionState, operator tick.TokenType, left, right NodeEvaluator) error { - // Validate the evaluation parameters: - // *) not support types like arrays - // *) not comparison operator - // *) invalid operator for the given type - - typeExecutionState := CreateExecutionState() - - leftValueType, err := left.Type(scope, typeExecutionState) - if err != nil { - return fmt.Errorf("Can't get the type of the left node: %s", err) - } - leftTypeName := leftValueType.String() +// Return an understandable error which is most specific to the issue. +func (e *EvalBinaryNode) determineError(scope *tick.Scope, executionState ExecutionState) error { + if scope != nil { + typeExecutionState := CreateExecutionState() + leftType, err := e.leftEvaluator.Type(scope, typeExecutionState) + if err != nil { + return fmt.Errorf("can't get the type of the left node: %s", err) + } + e.leftType = leftType - if leftValueType == InvalidType { - return errors.New("left value is invalid value type") - } + if leftType == InvalidType { + return errors.New("left value is invalid value type") + } - rightValueType, err := right.Type(scope, typeExecutionState) - if err != nil { - return fmt.Errorf("Can't get the type of the right node: %s", err) - } - rightTypeName := rightValueType.String() + rightType, err := e.rightEvaluator.Type(scope, typeExecutionState) + if err != nil { + return fmt.Errorf("can't get the type of the right node: %s", err) + } + e.rightType = rightType - if rightValueType == InvalidType { - return errors.New("right value is invalid value type") + if rightType == InvalidType { + return errors.New("right value is invalid value type") + } } - err = isValidOperator(e.operator, leftValueType, rightValueType) - if err != nil { - return err + if !typeToBinaryOperators[e.leftType][e.operator] { + return fmt.Errorf("invalid %s operator %v for type %s", operatorKind(e.operator), e.operator, e.leftType) + } else if !typeToBinaryOperators[e.leftType][e.operator] { + return fmt.Errorf("invalid %s operator %v for type %s", operatorKind(e.operator), e.operator, e.rightType) } - return fmt.Errorf("mismatched type to binary operator. got %s %v %s. see bool(), int(), float()", leftTypeName, e.operator, rightTypeName) + return fmt.Errorf("mismatched type to binary operator. got %s %v %s. see bool(), int(), float(), string()", e.leftType, e.operator, e.rightType) } -func (e *EvalBinaryNode) findEvaluationFn(leftType, rightType ValueType) evaluationFn { - return evaluationFuncs[operationKey{operator: e.operator, leftType: leftType, rightType: rightType}] +func (e *EvalBinaryNode) lookupEvaluationFn() evaluationFn { + return evaluationFuncs[operationKey{operator: e.operator, leftType: e.leftType, rightType: e.rightType}] } -// Type to comparison operator - for comparison operator validation (see: isValidBinaryOperator) -// The key is value type where the value is set of TokenType var typeToBinaryOperators = (func() map[ValueType]map[tick.TokenType]bool { // This map is built at "runtime" because we don't to have tight coupling // every time we had new "comparison operator" / "math operator" to update this map // and the performance cost is neglibile for doing so. - result := make(map[ValueType]map[tick.TokenType]bool, 0) + result := make(map[ValueType]map[tick.TokenType]bool) for opKey := range evaluationFuncs { + // Left typeSet, exists := result[opKey.leftType] if !exists { @@ -299,43 +302,29 @@ var typeToBinaryOperators = (func() map[ValueType]map[tick.TokenType]bool { } typeSet[opKey.operator] = true - result[opKey.leftType] = typeSet - } - return result -})() - -// isValidOperator returns whether the operator and left/right nodes are valid for comparison, if not -// false will be returned with correct error message -func isValidOperator(operator tick.TokenType, leftNodeType, rightNodeType ValueType) error { - if !tick.IsExprOperator(operator) { - return fmt.Errorf("return: unknown operator %v", operator) - } - - var nodeType ValueType + // Right + typeSet, exists = result[opKey.rightType] - // Only for TRegex we determine the validity of the operator by the right node - if rightNodeType == TRegex { - nodeType = rightNodeType - } else { - nodeType = leftNodeType - } + if !exists { + result[opKey.rightType] = make(map[tick.TokenType]bool, 0) + typeSet = result[opKey.rightType] + } - isValid := typeToBinaryOperators[nodeType][operator] - if !isValid { - return fmt.Errorf("invalid %s %s operator %v", nodeType.String(), operatorType(operator), operator) + typeSet[opKey.operator] = true } - return nil -} + return result +})() -func operatorType(operator tick.TokenType) string { - if tick.IsMathOperator(operator) { +func operatorKind(operator tick.TokenType) string { + switch { + case tick.IsMathOperator(operator): return "math" - } - - if tick.IsCompOperator(operator) { + case tick.IsCompOperator(operator): return "comparison" + case tick.IsLogicalOperator(operator): + return "logical" } // Actually, we shouldn't get here.. because this function is called only diff --git a/tick/stateful/eval_function_node_test.go b/tick/stateful/eval_function_node_test.go index 38af08427..3a642882a 100644 --- a/tick/stateful/eval_function_node_test.go +++ b/tick/stateful/eval_function_node_test.go @@ -283,7 +283,7 @@ func TestEvalFunctionNode_ComplexNodes(t *testing.T) { func TestStatefulExpression_Integration_EvalBool_SanityCallingFunction(t *testing.T) { scope := tick.NewScope() - se := mustCompileExpression(t, &tick.BinaryNode{ + se := mustCompileExpression(&tick.BinaryNode{ Operator: tick.TokenEqual, Left: &tick.FunctionNode{ Func: "count", diff --git a/tick/stateful/evaluation_funcs.go b/tick/stateful/evaluation_funcs.go index 9e799ed46..86beb1257 100644 --- a/tick/stateful/evaluation_funcs.go +++ b/tick/stateful/evaluation_funcs.go @@ -26,7 +26,7 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalBool(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } // Short circuit evaluation @@ -35,7 +35,7 @@ var evaluationFuncs = map[operationKey]evaluationFn{ } if right, err = rightNode.EvalBool(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left && right, IsBoolValue: true}, nil @@ -48,7 +48,7 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalBool(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } // Short circuit evaluation @@ -57,7 +57,7 @@ var evaluationFuncs = map[operationKey]evaluationFn{ } if right, err = rightNode.EvalBool(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left || right, IsBoolValue: true}, nil @@ -70,11 +70,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalBool(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalBool(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left == right, IsBoolValue: true}, nil @@ -86,11 +86,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalBool(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalBool(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left != right, IsBoolValue: true}, nil @@ -102,11 +102,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left < right, IsBoolValue: true}, nil @@ -119,11 +119,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left <= right, IsBoolValue: true}, nil @@ -136,11 +136,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: float64(left) != right, IsBoolValue: true}, nil @@ -153,11 +153,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left >= right, IsBoolValue: true}, nil @@ -170,11 +170,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left == right, IsBoolValue: true}, nil @@ -187,11 +187,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left != right, IsBoolValue: true}, nil @@ -204,11 +204,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left != right, IsBoolValue: true}, nil @@ -221,11 +221,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left <= right, IsBoolValue: true}, nil @@ -238,11 +238,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left == right, IsBoolValue: true}, nil @@ -255,11 +255,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left > right, IsBoolValue: true}, nil @@ -272,11 +272,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left > float64(right), IsBoolValue: true}, nil @@ -289,11 +289,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left >= float64(right), IsBoolValue: true}, nil @@ -306,11 +306,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left == float64(right), IsBoolValue: true}, nil @@ -323,11 +323,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: float64(left) <= right, IsBoolValue: true}, nil @@ -340,11 +340,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: float64(left) == right, IsBoolValue: true}, nil @@ -357,11 +357,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left != float64(right), IsBoolValue: true}, nil @@ -374,11 +374,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left < float64(right), IsBoolValue: true}, nil @@ -391,11 +391,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left < right, IsBoolValue: true}, nil @@ -408,11 +408,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left >= right, IsBoolValue: true}, nil @@ -425,11 +425,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left > right, IsBoolValue: true}, nil @@ -442,11 +442,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left <= float64(right), IsBoolValue: true}, nil @@ -459,11 +459,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: float64(left) >= right, IsBoolValue: true}, nil @@ -476,11 +476,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: float64(left) > right, IsBoolValue: true}, nil @@ -493,11 +493,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: float64(left) < right, IsBoolValue: true}, nil @@ -510,11 +510,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left > right, IsBoolValue: true}, nil @@ -527,11 +527,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left >= right, IsBoolValue: true}, nil @@ -544,11 +544,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left < right, IsBoolValue: true}, nil @@ -561,11 +561,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left <= right, IsBoolValue: true}, nil @@ -578,11 +578,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left == right, IsBoolValue: true}, nil @@ -595,11 +595,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: left != right, IsBoolValue: true}, nil @@ -612,11 +612,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalRegex(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: !right.MatchString(left), IsBoolValue: true}, nil @@ -629,11 +629,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalString(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalRegex(scope, executionState); err != nil { - return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + return boolFalseResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{BoolValue: right.MatchString(left), IsBoolValue: true}, nil @@ -649,11 +649,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + return emptyResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + return emptyResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{Float64Value: left + right, IsFloat64Value: true}, nil @@ -665,11 +665,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + return emptyResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + return emptyResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{Float64Value: left - right, IsFloat64Value: true}, nil @@ -681,11 +681,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + return emptyResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + return emptyResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{Float64Value: left * right, IsFloat64Value: true}, nil @@ -697,11 +697,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalFloat(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + return emptyResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalFloat(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + return emptyResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{Float64Value: left / right, IsFloat64Value: true}, nil @@ -713,11 +713,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + return emptyResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + return emptyResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{Int64Value: left + right, IsInt64Value: true}, nil @@ -729,11 +729,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + return emptyResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + return emptyResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{Int64Value: left - right, IsInt64Value: true}, nil @@ -745,11 +745,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + return emptyResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + return emptyResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{Int64Value: left * right, IsInt64Value: true}, nil @@ -761,11 +761,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + return emptyResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + return emptyResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{Int64Value: left / right, IsInt64Value: true}, nil @@ -777,11 +777,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalInt(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + return emptyResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalInt(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + return emptyResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{Int64Value: left % right, IsInt64Value: true}, nil @@ -796,11 +796,11 @@ var evaluationFuncs = map[operationKey]evaluationFn{ var err error if left, err = leftNode.EvalString(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + return emptyResultContainer, &ErrSide{error: err, IsLeft: true} } if right, err = rightNode.EvalString(scope, executionState); err != nil { - return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + return emptyResultContainer, &ErrSide{error: err, IsRight: true} } return resultContainer{StringValue: left + right, IsStringValue: true}, nil diff --git a/tick/stateful/expr_dynamic_test.go b/tick/stateful/expr_dynamic_test.go index 518a401a6..5bac6285d 100644 --- a/tick/stateful/expr_dynamic_test.go +++ b/tick/stateful/expr_dynamic_test.go @@ -29,7 +29,7 @@ type testCase struct { // runDyanmicTestCase is when we want to change the "dyanmism" of // a node - type change or value change func runDynamicTestCase(t *testing.T, tc testCase) { - se := mustCompileExpression(t, tc.Node) + se := mustCompileExpression(tc.Node) for i, expectation := range tc.Expectations { scope := tick.NewScope() @@ -111,7 +111,7 @@ func TestExpression_BinaryNode_DynamicTestCases(t *testing.T) { { IsEvalNum: true, Value: int64(5), - ExpectedError: errors.New("mismatched type to binary operator. got int64 + float64. see bool(), int(), float()"), + ExpectedError: errors.New("mismatched type to binary operator. got int64 + float64. see bool(), int(), float(), string()"), ExpectedResult: nil, }, }, @@ -147,6 +147,55 @@ func TestExpression_BinaryNode_DynamicTestCases(t *testing.T) { }, }) + runDynamicTestCase(t, testCase{ + Title: "BinaryNode - Nested BinaryNodes can determine correct type", + + Node: &tick.BinaryNode{ + Operator: tick.TokenAnd, + Left: &tick.BinaryNode{ + Operator: tick.TokenLess, + Left: &tick.ReferenceNode{ + Reference: "value", + }, + Right: &tick.NumberNode{ + IsFloat: true, + Float64: float64(10), + }, + }, + Right: &tick.BinaryNode{ + Operator: tick.TokenGreater, + Left: &tick.ReferenceNode{ + Reference: "value", + }, + Right: &tick.NumberNode{ + IsFloat: true, + Float64: float64(7), + }, + }, + }, + + Expectations: []valueExpectation{ + { + IsEvalBool: true, + Value: float64(8), + ExpectedError: nil, + ExpectedResult: true, + }, + { + IsEvalBool: true, + Value: int64(5), + ExpectedError: nil, + ExpectedResult: false, + }, + { + IsEvalBool: true, + Value: int64(11), + ExpectedError: nil, + ExpectedResult: false, + }, + }, + }) + } func TestExpression_UnaryNode_DyanmicTestCases(t *testing.T) { diff --git a/tick/stateful/expr_test.go b/tick/stateful/expr_test.go index eb79a0ea0..5490e0108 100644 --- a/tick/stateful/expr_test.go +++ b/tick/stateful/expr_test.go @@ -2,8 +2,8 @@ package stateful_test import ( "errors" + "fmt" "regexp" - "strings" "testing" "time" @@ -18,7 +18,7 @@ type keyStruct struct { } func TestExpression_EvalNum_KeepsFunctionsState(t *testing.T) { - se := mustCompileExpression(t, &tick.FunctionNode{ + se := mustCompileExpression(&tick.FunctionNode{ Func: "sigma", Args: []tick.Node{&tick.ReferenceNode{Reference: "value"}}, }) @@ -119,7 +119,7 @@ func TestExpression_Eval_NotSupportedNode(t *testing.T) { func TestExpression_Eval_NodeAndEvalTypeNotMatching(t *testing.T) { // Test EvalBool against BinaryNode that returns math result - se := mustCompileExpression(t, &tick.BinaryNode{ + se := mustCompileExpression(&tick.BinaryNode{ Operator: tick.TokenPlus, Left: &tick.NumberNode{ IsFloat: true, @@ -150,14 +150,17 @@ func TestExpression_EvalBool_BoolNode(t *testing.T) { operators := []tick.TokenType{tick.TokenEqual, tick.TokenNotEqual, tick.TokenAnd, tick.TokenOr, tick.TokenLess} createBoolNode := func(v interface{}) tick.Node { - if strValue, isString := v.(string); isString { + switch value := v.(type) { + case bool: + return &tick.BoolNode{ + Bool: value, + } + case string: return &tick.StringNode{ - Literal: strValue, + Literal: value, } - } - - return &tick.BoolNode{ - Bool: v.(bool), + default: + panic(fmt.Sprintf("unexpected type %T", v)) } } @@ -187,40 +190,38 @@ func TestExpression_EvalBool_BoolNode(t *testing.T) { keyStruct{false, false, tick.TokenOr}: false, }, map[keyStruct]error{ // Check invalid bool operator - keyStruct{true, true, tick.TokenLess}: errors.New("invalid boolean comparison operator <"), - keyStruct{true, false, tick.TokenLess}: errors.New("invalid boolean comparison operator <"), - keyStruct{false, true, tick.TokenLess}: errors.New("invalid boolean comparison operator <"), - keyStruct{false, false, tick.TokenLess}: errors.New("invalid boolean comparison operator <"), + keyStruct{true, true, tick.TokenLess}: errors.New("invalid comparison operator < for type boolean"), + keyStruct{true, false, tick.TokenLess}: errors.New("invalid comparison operator < for type boolean"), + keyStruct{false, true, tick.TokenLess}: errors.New("invalid comparison operator < for type boolean"), + keyStruct{false, false, tick.TokenLess}: errors.New("invalid comparison operator < for type boolean"), // (Redundant test case) - keyStruct{true, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("invalid boolean comparison operator <"), - keyStruct{true, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("invalid boolean comparison operator <"), - keyStruct{false, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("invalid boolean comparison operator <"), - keyStruct{false, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("invalid boolean comparison operator <"), + keyStruct{true, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("invalid comparison operator < for type boolean"), + keyStruct{false, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("invalid comparison operator < for type boolean"), // Left: True, Right: "NON_BOOL_VALUE" - keyStruct{true, "NON_BOOL_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got boolean == string. see bool(), int(), float()"), - keyStruct{true, "NON_BOOL_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got boolean != string. see bool(), int(), float()"), - keyStruct{true, "NON_BOOL_VALUE", tick.TokenAnd}: errors.New("mismatched type to binary operator. got boolean AND string. see bool(), int(), float()"), - keyStruct{true, "NON_BOOL_VALUE", tick.TokenOr}: errors.New("mismatched type to binary operator. got boolean OR string. see bool(), int(), float()"), + keyStruct{true, "NON_BOOL_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got boolean == string. see bool(), int(), float(), string()"), + keyStruct{true, "NON_BOOL_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got boolean != string. see bool(), int(), float(), string()"), + keyStruct{true, "NON_BOOL_VALUE", tick.TokenAnd}: errors.New("mismatched type to binary operator. got boolean AND string. see bool(), int(), float(), string()"), + keyStruct{true, "NON_BOOL_VALUE", tick.TokenOr}: errors.New("mismatched type to binary operator. got boolean OR string. see bool(), int(), float(), string()"), // Left: False, Right: "NON_BOOL_VALUE" - keyStruct{false, "NON_BOOL_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got boolean == string. see bool(), int(), float()"), - keyStruct{false, "NON_BOOL_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got boolean != string. see bool(), int(), float()"), - keyStruct{false, "NON_BOOL_VALUE", tick.TokenAnd}: errors.New("mismatched type to binary operator. got boolean AND string. see bool(), int(), float()"), - keyStruct{false, "NON_BOOL_VALUE", tick.TokenOr}: errors.New("mismatched type to binary operator. got boolean OR string. see bool(), int(), float()"), + keyStruct{false, "NON_BOOL_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got boolean == string. see bool(), int(), float(), string()"), + keyStruct{false, "NON_BOOL_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got boolean != string. see bool(), int(), float(), string()"), + keyStruct{false, "NON_BOOL_VALUE", tick.TokenAnd}: errors.New("mismatched type to binary operator. got boolean AND string. see bool(), int(), float(), string()"), + keyStruct{false, "NON_BOOL_VALUE", tick.TokenOr}: errors.New("mismatched type to binary operator. got boolean OR string. see bool(), int(), float(), string()"), // Left: "NON_BOOL_VALUE", Right: True - keyStruct{"NON_BOOL_VALUE", true, tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == bool. see bool(), int(), float()"), - keyStruct{"NON_BOOL_VALUE", true, tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got string != bool. see bool(), int(), float()"), - keyStruct{"NON_BOOL_VALUE", true, tick.TokenAnd}: errors.New("mismatched type to binary operator. got string AND bool. see bool(), int(), float()"), - keyStruct{"NON_BOOL_VALUE", true, tick.TokenOr}: errors.New("mismatched type to binary operator. got string OR bool. see bool(), int(), float()"), + keyStruct{"NON_BOOL_VALUE", true, tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == bool. see bool(), int(), float(), string()"), + keyStruct{"NON_BOOL_VALUE", true, tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got string != bool. see bool(), int(), float(), string()"), + keyStruct{"NON_BOOL_VALUE", true, tick.TokenAnd}: errors.New("mismatched type to binary operator. got string AND bool. see bool(), int(), float(), string()"), + keyStruct{"NON_BOOL_VALUE", true, tick.TokenOr}: errors.New("invalid comparison operator OR for type string"), // Left: "NON_BOOL_VALUE", Right: False - keyStruct{"NON_BOOL_VALUE", false, tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == bool. see bool(), int(), float()"), - keyStruct{"NON_BOOL_VALUE", false, tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got string != bool. see bool(), int(), float()"), - keyStruct{"NON_BOOL_VALUE", false, tick.TokenAnd}: errors.New("mismatched type to binary operator. got string AND bool. see bool(), int(), float()"), - keyStruct{"NON_BOOL_VALUE", false, tick.TokenOr}: errors.New("mismatched type to binary operator. got string OR bool. see bool(), int(), float()"), + keyStruct{"NON_BOOL_VALUE", false, tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == bool. see bool(), int(), float(), string()"), + keyStruct{"NON_BOOL_VALUE", false, tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got string != bool. see bool(), int(), float(), string()"), + keyStruct{"NON_BOOL_VALUE", false, tick.TokenAnd}: errors.New("mismatched type to binary operator. got string AND bool. see bool(), int(), float(), string()"), + keyStruct{"NON_BOOL_VALUE", false, tick.TokenOr}: errors.New("invalid comparison operator OR for type string"), }) } @@ -336,44 +337,44 @@ func TestExpression_EvalBool_NumberNode(t *testing.T) { keyStruct{int64(5), int64(5), tick.TokenLessEqual}: true, }, map[keyStruct]error{ // Invalid operator - keyStruct{float64(5), float64(5), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), - keyStruct{float64(5), float64(10), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), - keyStruct{float64(5), int64(5), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), - keyStruct{float64(10), float64(5), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), - keyStruct{float64(10), float64(10), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), - keyStruct{float64(10), int64(5), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), - keyStruct{int64(5), float64(5), tick.TokenOr}: errors.New("invalid int64 comparison operator OR"), - keyStruct{int64(5), float64(10), tick.TokenOr}: errors.New("invalid int64 comparison operator OR"), - keyStruct{int64(5), int64(5), tick.TokenOr}: errors.New("invalid int64 comparison operator OR"), + keyStruct{float64(5), float64(5), tick.TokenOr}: errors.New("invalid logical operator OR for type float64"), + keyStruct{float64(5), float64(10), tick.TokenOr}: errors.New("invalid logical operator OR for type float64"), + keyStruct{float64(5), int64(5), tick.TokenOr}: errors.New("invalid logical operator OR for type float64"), + keyStruct{float64(10), float64(5), tick.TokenOr}: errors.New("invalid logical operator OR for type float64"), + keyStruct{float64(10), float64(10), tick.TokenOr}: errors.New("invalid logical operator OR for type float64"), + keyStruct{float64(10), int64(5), tick.TokenOr}: errors.New("invalid logical operator OR for type float64"), + keyStruct{int64(5), float64(5), tick.TokenOr}: errors.New("invalid logical operator OR for type int64"), + keyStruct{int64(5), float64(10), tick.TokenOr}: errors.New("invalid logical operator OR for type int64"), + keyStruct{int64(5), int64(5), tick.TokenOr}: errors.New("invalid logical operator OR for type int64"), // (Redundant case) - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenOr}: errors.New("invalid int64 comparison operator OR"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenOr}: errors.New("invalid logical operator OR for type float64"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenOr}: errors.New("invalid logical operator OR for type float64"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenOr}: errors.New("invalid logical operator OR for type int64"), // Left is float64(5), Right is "NON_INT_VALUE" - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got float64 == string. see bool(), int(), float()"), - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got float64 != string. see bool(), int(), float()"), - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenGreater}: errors.New("mismatched type to binary operator. got float64 > string. see bool(), int(), float()"), - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenGreaterEqual}: errors.New("mismatched type to binary operator. got float64 >= string. see bool(), int(), float()"), - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenLess}: errors.New("mismatched type to binary operator. got float64 < string. see bool(), int(), float()"), - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenLessEqual}: errors.New("mismatched type to binary operator. got float64 <= string. see bool(), int(), float()"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got float64 == string. see bool(), int(), float(), string()"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got float64 != string. see bool(), int(), float(), string()"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenGreater}: errors.New("mismatched type to binary operator. got float64 > string. see bool(), int(), float(), string()"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenGreaterEqual}: errors.New("mismatched type to binary operator. got float64 >= string. see bool(), int(), float(), string()"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenLess}: errors.New("mismatched type to binary operator. got float64 < string. see bool(), int(), float(), string()"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenLessEqual}: errors.New("mismatched type to binary operator. got float64 <= string. see bool(), int(), float(), string()"), // (Redundant case) Left is float64(10), Right is "NON_INT_VALUE" - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got float64 == string. see bool(), int(), float()"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got float64 != string. see bool(), int(), float()"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenGreater}: errors.New("mismatched type to binary operator. got float64 > string. see bool(), int(), float()"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenGreaterEqual}: errors.New("mismatched type to binary operator. got float64 >= string. see bool(), int(), float()"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenLess}: errors.New("mismatched type to binary operator. got float64 < string. see bool(), int(), float()"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenLessEqual}: errors.New("mismatched type to binary operator. got float64 <= string. see bool(), int(), float()"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got float64 == string. see bool(), int(), float(), string()"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got float64 != string. see bool(), int(), float(), string()"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenGreater}: errors.New("mismatched type to binary operator. got float64 > string. see bool(), int(), float(), string()"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenGreaterEqual}: errors.New("mismatched type to binary operator. got float64 >= string. see bool(), int(), float(), string()"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenLess}: errors.New("mismatched type to binary operator. got float64 < string. see bool(), int(), float(), string()"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenLessEqual}: errors.New("mismatched type to binary operator. got float64 <= string. see bool(), int(), float(), string()"), // Left is int64(5), Right is "NON_INT_VALUE" - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got int64 == string. see bool(), int(), float()"), - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got int64 != string. see bool(), int(), float()"), - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenGreater}: errors.New("mismatched type to binary operator. got int64 > string. see bool(), int(), float()"), - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenGreaterEqual}: errors.New("mismatched type to binary operator. got int64 >= string. see bool(), int(), float()"), - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenLess}: errors.New("mismatched type to binary operator. got int64 < string. see bool(), int(), float()"), - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenLessEqual}: errors.New("mismatched type to binary operator. got int64 <= string. see bool(), int(), float()"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got int64 == string. see bool(), int(), float(), string()"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got int64 != string. see bool(), int(), float(), string()"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenGreater}: errors.New("mismatched type to binary operator. got int64 > string. see bool(), int(), float(), string()"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenGreaterEqual}: errors.New("mismatched type to binary operator. got int64 >= string. see bool(), int(), float(), string()"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenLess}: errors.New("mismatched type to binary operator. got int64 < string. see bool(), int(), float(), string()"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenLessEqual}: errors.New("mismatched type to binary operator. got int64 <= string. see bool(), int(), float(), string()"), }) } @@ -433,67 +434,74 @@ func TestExpression_EvalBool_StringNode(t *testing.T) { keyStruct{"b", "b", tick.TokenLessEqual}: true, }, map[keyStruct]error{ // Invalid operator - keyStruct{"a", "a", tick.TokenOr}: errors.New("invalid string comparison operator OR"), - keyStruct{"a", "b", tick.TokenOr}: errors.New("invalid string comparison operator OR"), - keyStruct{"b", "a", tick.TokenOr}: errors.New("invalid string comparison operator OR"), - keyStruct{"b", "b", tick.TokenOr}: errors.New("invalid string comparison operator OR"), + keyStruct{"a", "a", tick.TokenOr}: errors.New("invalid logical operator OR for type string"), + keyStruct{"a", "b", tick.TokenOr}: errors.New("invalid logical operator OR for type string"), + keyStruct{"b", "a", tick.TokenOr}: errors.New("invalid logical operator OR for type string"), + keyStruct{"b", "b", tick.TokenOr}: errors.New("invalid logical operator OR for type string"), - keyStruct{"a", int64(123), tick.TokenOr}: errors.New("invalid string comparison operator OR"), - keyStruct{"b", int64(123), tick.TokenOr}: errors.New("invalid string comparison operator OR"), + keyStruct{"a", int64(123), tick.TokenOr}: errors.New("invalid logical operator OR for type string"), + keyStruct{"b", int64(123), tick.TokenOr}: errors.New("invalid logical operator OR for type string"), // Left is "a", Right is int64(123) - keyStruct{"a", int64(123), tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == int64. see bool(), int(), float()"), - keyStruct{"a", int64(123), tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got string != int64. see bool(), int(), float()"), - keyStruct{"a", int64(123), tick.TokenGreater}: errors.New("mismatched type to binary operator. got string > int64. see bool(), int(), float()"), - keyStruct{"a", int64(123), tick.TokenGreaterEqual}: errors.New("mismatched type to binary operator. got string >= int64. see bool(), int(), float()"), - keyStruct{"a", int64(123), tick.TokenLess}: errors.New("mismatched type to binary operator. got string < int64. see bool(), int(), float()"), - keyStruct{"a", int64(123), tick.TokenLessEqual}: errors.New("mismatched type to binary operator. got string <= int64. see bool(), int(), float()"), + keyStruct{"a", int64(123), tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == int64. see bool(), int(), float(), string()"), + keyStruct{"a", int64(123), tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got string != int64. see bool(), int(), float(), string()"), + keyStruct{"a", int64(123), tick.TokenGreater}: errors.New("mismatched type to binary operator. got string > int64. see bool(), int(), float(), string()"), + keyStruct{"a", int64(123), tick.TokenGreaterEqual}: errors.New("mismatched type to binary operator. got string >= int64. see bool(), int(), float(), string()"), + keyStruct{"a", int64(123), tick.TokenLess}: errors.New("mismatched type to binary operator. got string < int64. see bool(), int(), float(), string()"), + keyStruct{"a", int64(123), tick.TokenLessEqual}: errors.New("mismatched type to binary operator. got string <= int64. see bool(), int(), float(), string()"), // Left is "b", Right is int64(123) - keyStruct{"b", int64(123), tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == int64. see bool(), int(), float()"), - keyStruct{"b", int64(123), tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got string != int64. see bool(), int(), float()"), - keyStruct{"b", int64(123), tick.TokenGreater}: errors.New("mismatched type to binary operator. got string > int64. see bool(), int(), float()"), - keyStruct{"b", int64(123), tick.TokenGreaterEqual}: errors.New("mismatched type to binary operator. got string >= int64. see bool(), int(), float()"), - keyStruct{"b", int64(123), tick.TokenLess}: errors.New("mismatched type to binary operator. got string < int64. see bool(), int(), float()"), - keyStruct{"b", int64(123), tick.TokenLessEqual}: errors.New("mismatched type to binary operator. got string <= int64. see bool(), int(), float()"), + keyStruct{"b", int64(123), tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == int64. see bool(), int(), float(), string()"), + keyStruct{"b", int64(123), tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got string != int64. see bool(), int(), float(), string()"), + keyStruct{"b", int64(123), tick.TokenGreater}: errors.New("mismatched type to binary operator. got string > int64. see bool(), int(), float(), string()"), + keyStruct{"b", int64(123), tick.TokenGreaterEqual}: errors.New("mismatched type to binary operator. got string >= int64. see bool(), int(), float(), string()"), + keyStruct{"b", int64(123), tick.TokenLess}: errors.New("mismatched type to binary operator. got string < int64. see bool(), int(), float(), string()"), + keyStruct{"b", int64(123), tick.TokenLessEqual}: errors.New("mismatched type to binary operator. got string <= int64. see bool(), int(), float(), string()"), }) } func TestExpression_EvalBool_RegexNode(t *testing.T) { - leftValues := []interface{}{"abc", "cba"} + pattern := regexp.MustCompile(`^(.*)c$`) + + leftValues := []interface{}{"abc", "cba", pattern} // Right values are regex, but we are supplying strings because the keyStruct and maps don't play nice together - // so we mark regex with prefix of "R!" and createStringOrRegexNode will convert it to regex - rightValues := []interface{}{"R!^(.*)c$"} + // so we mark regex with prefix of regexp.MustCompile(``) and createStringOrRegexNode will convert it to regex + rightValues := []interface{}{pattern} operators := []tick.TokenType{tick.TokenRegexEqual, tick.TokenRegexNotEqual, tick.TokenEqual} createStringOrRegexNode := func(v interface{}) tick.Node { - stringValue := v.(string) - if strings.Index(stringValue, "R!") == 0 { + switch value := v.(type) { + case string: + return &tick.StringNode{ + Literal: value, + } + case *regexp.Regexp: return &tick.RegexNode{ - Regex: regexp.MustCompile(strings.TrimPrefix(stringValue, "R!")), + Regex: value, } + default: + panic(fmt.Sprintf("unexpected type %T", v)) } - - return &tick.StringNode{ - Literal: stringValue, - } - } runCompiledEvalBoolTests(t, createStringOrRegexNode, leftValues, rightValues, operators, map[keyStruct]interface{}{ // Left is "abc", Right is regex "(.*)c" - keyStruct{"abc", "R!^(.*)c$", tick.TokenRegexEqual}: true, - keyStruct{"abc", "R!^(.*)c$", tick.TokenRegexNotEqual}: false, + keyStruct{"abc", pattern, tick.TokenRegexEqual}: true, + keyStruct{"abc", pattern, tick.TokenRegexNotEqual}: false, // Left is "cba", Right is regex "(.*)c" - keyStruct{"cba", "R!^(.*)c$", tick.TokenRegexEqual}: false, - keyStruct{"cba", "R!^(.*)c$", tick.TokenRegexNotEqual}: true, + keyStruct{"cba", pattern, tick.TokenRegexEqual}: false, + keyStruct{"cba", pattern, tick.TokenRegexNotEqual}: true, }, map[keyStruct]error{ // Errors for invalid operators - keyStruct{"abc", "R!^(.*)c$", tick.TokenEqual}: errors.New("invalid regex comparison operator =="), - keyStruct{"cba", "R!^(.*)c$", tick.TokenEqual}: errors.New("invalid regex comparison operator =="), + keyStruct{"abc", pattern, tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == regex. see bool(), int(), float(), string()"), + keyStruct{"cba", pattern, tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == regex. see bool(), int(), float(), string()"), + keyStruct{pattern, "cba", tick.TokenEqual}: errors.New("invalid comparison operator == for type regex"), + keyStruct{pattern, pattern, tick.TokenRegexEqual}: errors.New("mismatched type to binary operator. got regex =~ regex. see bool(), int(), float(), string()"), + keyStruct{pattern, pattern, tick.TokenRegexNotEqual}: errors.New("mismatched type to binary operator. got regex !~ regex. see bool(), int(), float(), string()"), + keyStruct{pattern, pattern, tick.TokenEqual}: errors.New("invalid comparison operator == for type regex"), }) } @@ -543,7 +551,7 @@ func TestExpression_EvalBool_NotSupportedValueLeft(t *testing.T) { } func TestExpression_EvalBool_UnknownOperator(t *testing.T) { - _, err := evalCompiledBoolWithScope(t, tick.NewScope(), &tick.BinaryNode{ + node := &tick.BinaryNode{ Operator: tick.TokenType(666), Left: &tick.StringNode{ Literal: "value", @@ -551,16 +559,14 @@ func TestExpression_EvalBool_UnknownOperator(t *testing.T) { Right: &tick.StringNode{ Literal: "yo", }, - }) - - expectedError := "return: unknown operator 666" - - if err != nil && (err.Error() != expectedError) { - t.Errorf("Unexpected error result: \ngot: %v\nexpected: %v", err.Error(), expectedError) } - + expectedError := "unknown binary operator 666" + _, err := stateful.NewExpression(node) if err == nil { - t.Error("Unexpected error result: but didn't got any error") + t.Fatal("Unexpected error result: but didn't got any error") + } + if got := err.Error(); got != expectedError { + t.Errorf("Unexpected error result: \ngot: %v\nexpected: %v", got, expectedError) } } @@ -693,7 +699,7 @@ func TestExpression_EvalNum_ReferenceNodeDosentExist(t *testing.T) { expectedError := `name "value" is undefined. Names in scope: ` // Check left side - se := mustCompileExpression(t, &tick.ReferenceNode{ + se := mustCompileExpression(&tick.ReferenceNode{ Reference: "value", }) @@ -803,7 +809,7 @@ func TestExpression_EvalString_StringConcatReferenceNode(t *testing.T) { func TestExpression_EvalNum_BinaryNodeWithUnary(t *testing.T) { // -"value" < 0 , yes, of course, this is always true.. - se := mustCompileExpression(t, &tick.BinaryNode{ + se := mustCompileExpression(&tick.BinaryNode{ Operator: tick.TokenLess, Left: &tick.UnaryNode{ Operator: tick.TokenMinus, @@ -834,7 +840,7 @@ func TestExpression_EvalBool_BinaryNodeWithBoolUnaryNode(t *testing.T) { emptyScope := tick.NewScope() - se := mustCompileExpression(t, &tick.BinaryNode{ + se := mustCompileExpression(&tick.BinaryNode{ Operator: tick.TokenEqual, Left: &tick.UnaryNode{ Operator: tick.TokenNot, @@ -857,7 +863,7 @@ func TestExpression_EvalBool_BinaryNodeWithBoolUnaryNode(t *testing.T) { } // now with ref - se = mustCompileExpression(t, &tick.BinaryNode{ + se = mustCompileExpression(&tick.BinaryNode{ Operator: tick.TokenEqual, Left: &tick.UnaryNode{ Operator: tick.TokenNot, @@ -888,7 +894,7 @@ func TestExpression_EvalBool_BinaryNodeWithNumericUnaryNode(t *testing.T) { scope := tick.NewScope() - se := mustCompileExpression(t, &tick.BinaryNode{ + se := mustCompileExpression(&tick.BinaryNode{ Operator: tick.TokenLess, Left: &tick.UnaryNode{ Operator: tick.TokenMinus, @@ -923,7 +929,7 @@ func TestExpression_EvalBool_TwoLevelsDeepBinary(t *testing.T) { scope.Set("b", int64(5)) // a > 10 and b < 10 - se := mustCompileExpression(t, &tick.BinaryNode{ + se := mustCompileExpression(&tick.BinaryNode{ Operator: tick.TokenAnd, Left: &tick.BinaryNode{ @@ -980,7 +986,7 @@ func TestExpression_EvalBool_TwoLevelsDeepBinaryWithEvalNum_Int64(t *testing.T) scope.Set("b", int64(5)) // a > 10 and b < 10 - se := mustCompileExpression(t, &tick.BinaryNode{ + se := mustCompileExpression(&tick.BinaryNode{ Operator: tick.TokenAnd, Left: &tick.BinaryNode{ @@ -1045,7 +1051,7 @@ func TestExpression_EvalBool_TwoLevelsDeepBinaryWithEvalNum_Float64(t *testing.T scope.Set("b", float64(5)) // a > 10 and b < 10 - se := mustCompileExpression(t, &tick.BinaryNode{ + se := mustCompileExpression(&tick.BinaryNode{ Operator: tick.TokenAnd, Left: &tick.BinaryNode{ @@ -1169,54 +1175,54 @@ func TestExpression_EvalNum_NumberNode(t *testing.T) { keyStruct{float64(10), float64(10), tick.TokenDiv}: float64(1), }, map[keyStruct]error{ // Modulo token where left is float - keyStruct{float64(5), float64(5), tick.TokenMod}: errors.New("invalid float64 math operator %"), - keyStruct{float64(5), float64(10), tick.TokenMod}: errors.New("invalid float64 math operator %"), - keyStruct{float64(10), float64(5), tick.TokenMod}: errors.New("invalid float64 math operator %"), - keyStruct{float64(10), float64(10), tick.TokenMod}: errors.New("invalid float64 math operator %"), - keyStruct{float64(5), int64(5), tick.TokenMod}: errors.New("invalid float64 math operator %"), - keyStruct{float64(10), int64(5), tick.TokenMod}: errors.New("invalid float64 math operator %"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenMod}: errors.New("invalid float64 math operator %"), - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenMod}: errors.New("invalid float64 math operator %"), + keyStruct{float64(5), float64(5), tick.TokenMod}: errors.New("invalid math operator % for type float64"), + keyStruct{float64(5), float64(10), tick.TokenMod}: errors.New("invalid math operator % for type float64"), + keyStruct{float64(10), float64(5), tick.TokenMod}: errors.New("invalid math operator % for type float64"), + keyStruct{float64(10), float64(10), tick.TokenMod}: errors.New("invalid math operator % for type float64"), + keyStruct{float64(5), int64(5), tick.TokenMod}: errors.New("invalid math operator % for type float64"), + keyStruct{float64(10), int64(5), tick.TokenMod}: errors.New("invalid math operator % for type float64"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenMod}: errors.New("invalid math operator % for type float64"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenMod}: errors.New("invalid math operator % for type float64"), // Left is int, right is float - keyStruct{int64(5), float64(5), tick.TokenPlus}: errors.New("mismatched type to binary operator. got int64 + float64. see bool(), int(), float()"), - keyStruct{int64(5), float64(5), tick.TokenMinus}: errors.New("mismatched type to binary operator. got int64 - float64. see bool(), int(), float()"), - keyStruct{int64(5), float64(5), tick.TokenMult}: errors.New("mismatched type to binary operator. got int64 * float64. see bool(), int(), float()"), - keyStruct{int64(5), float64(5), tick.TokenDiv}: errors.New("mismatched type to binary operator. got int64 / float64. see bool(), int(), float()"), - keyStruct{int64(5), float64(5), tick.TokenMod}: errors.New("mismatched type to binary operator. got int64 % float64. see bool(), int(), float()"), - keyStruct{int64(5), float64(10), tick.TokenPlus}: errors.New("mismatched type to binary operator. got int64 + float64. see bool(), int(), float()"), - keyStruct{int64(5), float64(10), tick.TokenMinus}: errors.New("mismatched type to binary operator. got int64 - float64. see bool(), int(), float()"), - keyStruct{int64(5), float64(10), tick.TokenMult}: errors.New("mismatched type to binary operator. got int64 * float64. see bool(), int(), float()"), - keyStruct{int64(5), float64(10), tick.TokenDiv}: errors.New("mismatched type to binary operator. got int64 / float64. see bool(), int(), float()"), - keyStruct{int64(5), float64(10), tick.TokenMod}: errors.New("mismatched type to binary operator. got int64 % float64. see bool(), int(), float()"), + keyStruct{int64(5), float64(5), tick.TokenPlus}: errors.New("mismatched type to binary operator. got int64 + float64. see bool(), int(), float(), string()"), + keyStruct{int64(5), float64(5), tick.TokenMinus}: errors.New("mismatched type to binary operator. got int64 - float64. see bool(), int(), float(), string()"), + keyStruct{int64(5), float64(5), tick.TokenMult}: errors.New("mismatched type to binary operator. got int64 * float64. see bool(), int(), float(), string()"), + keyStruct{int64(5), float64(5), tick.TokenDiv}: errors.New("mismatched type to binary operator. got int64 / float64. see bool(), int(), float(), string()"), + keyStruct{int64(5), float64(5), tick.TokenMod}: errors.New("mismatched type to binary operator. got int64 % float64. see bool(), int(), float(), string()"), + keyStruct{int64(5), float64(10), tick.TokenPlus}: errors.New("mismatched type to binary operator. got int64 + float64. see bool(), int(), float(), string()"), + keyStruct{int64(5), float64(10), tick.TokenMinus}: errors.New("mismatched type to binary operator. got int64 - float64. see bool(), int(), float(), string()"), + keyStruct{int64(5), float64(10), tick.TokenMult}: errors.New("mismatched type to binary operator. got int64 * float64. see bool(), int(), float(), string()"), + keyStruct{int64(5), float64(10), tick.TokenDiv}: errors.New("mismatched type to binary operator. got int64 / float64. see bool(), int(), float(), string()"), + keyStruct{int64(5), float64(10), tick.TokenMod}: errors.New("mismatched type to binary operator. got int64 % float64. see bool(), int(), float(), string()"), // Left is float, right is int - keyStruct{float64(5), int64(5), tick.TokenPlus}: errors.New("mismatched type to binary operator. got float64 + int64. see bool(), int(), float()"), - keyStruct{float64(5), int64(5), tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - int64. see bool(), int(), float()"), - keyStruct{float64(5), int64(5), tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * int64. see bool(), int(), float()"), - keyStruct{float64(5), int64(5), tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / int64. see bool(), int(), float()"), + keyStruct{float64(5), int64(5), tick.TokenPlus}: errors.New("mismatched type to binary operator. got float64 + int64. see bool(), int(), float(), string()"), + keyStruct{float64(5), int64(5), tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - int64. see bool(), int(), float(), string()"), + keyStruct{float64(5), int64(5), tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * int64. see bool(), int(), float(), string()"), + keyStruct{float64(5), int64(5), tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / int64. see bool(), int(), float(), string()"), - keyStruct{float64(10), int64(5), tick.TokenPlus}: errors.New("mismatched type to binary operator. got float64 + int64. see bool(), int(), float()"), - keyStruct{float64(10), int64(5), tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - int64. see bool(), int(), float()"), - keyStruct{float64(10), int64(5), tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * int64. see bool(), int(), float()"), - keyStruct{float64(10), int64(5), tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / int64. see bool(), int(), float()"), + keyStruct{float64(10), int64(5), tick.TokenPlus}: errors.New("mismatched type to binary operator. got float64 + int64. see bool(), int(), float(), string()"), + keyStruct{float64(10), int64(5), tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - int64. see bool(), int(), float(), string()"), + keyStruct{float64(10), int64(5), tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * int64. see bool(), int(), float(), string()"), + keyStruct{float64(10), int64(5), tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / int64. see bool(), int(), float(), string()"), // Left is int64, Right is "NON_INT_VALUE" - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenPlus}: errors.New("mismatched type to binary operator. got int64 + string. see bool(), int(), float()"), - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenMinus}: errors.New("mismatched type to binary operator. got int64 - string. see bool(), int(), float()"), - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenMult}: errors.New("mismatched type to binary operator. got int64 * string. see bool(), int(), float()"), - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenDiv}: errors.New("mismatched type to binary operator. got int64 / string. see bool(), int(), float()"), - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenMod}: errors.New("mismatched type to binary operator. got int64 % string. see bool(), int(), float()"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenPlus}: errors.New("mismatched type to binary operator. got int64 + string. see bool(), int(), float(), string()"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenMinus}: errors.New("mismatched type to binary operator. got int64 - string. see bool(), int(), float(), string()"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenMult}: errors.New("mismatched type to binary operator. got int64 * string. see bool(), int(), float(), string()"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenDiv}: errors.New("mismatched type to binary operator. got int64 / string. see bool(), int(), float(), string()"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenMod}: errors.New("mismatched type to binary operator. got int64 % string. see bool(), int(), float(), string()"), // Left is float64, Right is "NON_INT_VALUE" - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenPlus}: errors.New("mismatched type to binary operator. got float64 + string. see bool(), int(), float()"), - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - string. see bool(), int(), float()"), - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * string. see bool(), int(), float()"), - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / string. see bool(), int(), float()"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenPlus}: errors.New("mismatched type to binary operator. got float64 + string. see bool(), int(), float()"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - string. see bool(), int(), float()"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * string. see bool(), int(), float()"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / string. see bool(), int(), float()"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenPlus}: errors.New("mismatched type to binary operator. got float64 + string. see bool(), int(), float(), string()"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - string. see bool(), int(), float(), string()"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * string. see bool(), int(), float(), string()"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / string. see bool(), int(), float(), string()"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenPlus}: errors.New("mismatched type to binary operator. got float64 + string. see bool(), int(), float(), string()"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - string. see bool(), int(), float(), string()"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * string. see bool(), int(), float(), string()"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / string. see bool(), int(), float(), string()"), }) } @@ -1230,7 +1236,10 @@ func runCompiledNumericTests( errorExpectations map[keyStruct]error) { runCompiledEvalTests(t, func(t *testing.T, scope *tick.Scope, n tick.Node) (interface{}, error) { - se := mustCompileExpression(t, n) + se, err := stateful.NewExpression(n) + if err != nil { + return nil, err + } return se.Eval(scope) }, createNodeFn, leftValues, rightValues, operators, expected, errorExpectations) } @@ -1248,7 +1257,10 @@ func runCompiledEvalBoolTests( } func evalCompiledBoolWithScope(t *testing.T, scope *tick.Scope, n tick.Node) (interface{}, error) { - se := mustCompileExpression(t, n) + se, err := stateful.NewExpression(n) + if err != nil { + return nil, err + } return se.EvalBool(scope) } @@ -1266,11 +1278,12 @@ func runCompiledEvalTests( for _, rhs := range rightValues { for _, op := range operators { + t.Log("testing", lhs, op, rhs) key := keyStruct{lhs, rhs, op} exp, isExpectedResultOk := expected[key] errorExpected, isErrorOk := errorExpectations[key] if !isExpectedResultOk && !isErrorOk { - t.Fatalf("Couldn't find an expected result/error for: lhs: %t, rhs: %t, op: %v", lhs, rhs, op) + t.Fatalf("Couldn't find an expected result/error for: lhs: %v, rhs: %v, op: %v", lhs, rhs, op) } // Test simple const values compares @@ -1283,14 +1296,14 @@ func runCompiledEvalTests( // This is bool matching, but not error matching.. if isExpectedResultOk && !isErrorOk && err != nil { - t.Errorf("Got an error while evaluating: %t %v %t - %v\n", lhs, op, rhs, err) + t.Errorf("Got an error while evaluating: %v %v %v -- %T %v %T -- %v\n", lhs, op, rhs, lhs, op, rhs, err) } else { // Expect value can be error or bool if isErrorOk && errorExpected.Error() != err.Error() { t.Errorf("unexpected error result: %v %v %v\ngot: %v\nexp: %v", lhs, op, rhs, err, errorExpected) } else if isExpectedResultOk && exp != result { - t.Errorf("unexpected result: %t %v %t\ngot: %v\nexp: %v", lhs, op, rhs, result, exp) + t.Errorf("unexpected result: %v %v %v\ngot: %v\nexp: %v", lhs, op, rhs, result, exp) } } @@ -1307,12 +1320,12 @@ func runCompiledEvalTests( if isErrorOk { if err == nil { - t.Errorf("reference test: expected an error but got result: %t %v %t\nresult: %t\nerr: %v", lhs, op, rhs, result, err) + t.Errorf("reference test: expected an error but got result: %v %v %v\nresult: %v\nerr: %v", lhs, op, rhs, result, err) } else if errorExpected.Error() != err.Error() { t.Errorf("reference test: unexpected error result: %v %v %v\ngot: %v\nexp: %v", lhs, op, rhs, err, errorExpected) } } else if isExpectedResultOk && exp != result { - t.Errorf("reference test: unexpected bool result: %t %v %t\ngot: %t\nexp: %t", lhs, op, rhs, result, exp) + t.Errorf("reference test: unexpected bool result: %v %v %v\ngot: %v\nexp: %v", lhs, op, rhs, result, exp) } } @@ -1321,10 +1334,10 @@ func runCompiledEvalTests( } } -func mustCompileExpression(t *testing.T, node tick.Node) stateful.Expression { +func mustCompileExpression(node tick.Node) stateful.Expression { se, err := stateful.NewExpression(node) if err != nil { - t.Fatalf("Failed to compile expression: %v", err) + panic(fmt.Sprintf("Failed to compile expression: %v", err)) } return se diff --git a/tick/stateful/types.go b/tick/stateful/types.go index b7b57ed23..60267bddb 100644 --- a/tick/stateful/types.go +++ b/tick/stateful/types.go @@ -109,7 +109,7 @@ func getConstantNodeType(n tick.Node) ValueType { return TString } } - if tick.IsCompOperator(node.Operator) { + if tick.IsCompOperator(node.Operator) || tick.IsLogicalOperator(node.Operator) { return TBool } @@ -145,18 +145,15 @@ func isDynamicNode(n tick.Node) bool { switch node := n.(type) { case *tick.ReferenceNode: return true - case *tick.FunctionNode: return true - case *tick.UnaryNode: - // unary if dynamic only if it's childs are dynamic return isDynamicNode(node.Node) - + case *tick.BinaryNode: + return isDynamicNode(node.Left) || isDynamicNode(node.Right) default: return false } - } // findNodeTypes returns the aggregative type of the nodes (it handles corner case like TNumeric)