func operatorKind(operator ast.TokenType) string {
	switch {
	case ast.IsMathOperator(operator):
		return "math"
	case ast.IsCompOperator(operator):
		return "comparison"
	case ast.IsLogicalOperator(operator):
		return "logical"
	}

	// Actually, we shouldn't get here.. because this function is called only
	// after the operator validation!
	return "INVALID"
}
// getCostantNodeType - Given a ast.Node we want to know it's return type
// this method does exactly this, few examples:
// *) StringNode -> ast.TString
// *) UnaryNode -> we base the type by the node type
func getConstantNodeType(n ast.Node) ast.ValueType {
	switch node := n.(type) {
	case *ast.NumberNode:
		if node.IsInt {
			return ast.TInt
		}

		if node.IsFloat {
			return ast.TFloat
		}
	case *ast.DurationNode:
		return ast.TDuration
	case *ast.StringNode:
		return ast.TString
	case *ast.BoolNode:
		return ast.TBool
	case *ast.RegexNode:
		return ast.TRegex

	case *ast.UnaryNode:
		// If this is comparison operator we know for sure the output must be boolean
		if node.Operator == ast.TokenNot {
			return ast.TBool
		}

		// Could be float int or duration
		if node.Operator == ast.TokenMinus {
			return getConstantNodeType(node.Node)
		}

	case *ast.BinaryNode:
		// Check first using only the operator
		if ast.IsCompOperator(node.Operator) || ast.IsLogicalOperator(node.Operator) {
			return ast.TBool
		}
		leftType := getConstantNodeType(node.Left)
		rightType := getConstantNodeType(node.Right)
		// Check known constant types
		return binaryConstantTypes[operationKey{operator: node.Operator, leftType: leftType, rightType: rightType}]
	case *ast.LambdaNode:
		return getConstantNodeType(node.Expression)
	}

	return ast.InvalidType
}