Example #1
0
// Binary operands:   =, ==, !=, OR, AND, >, <, >=, <=, LIKE, contains
//
//       x == y,   x = y
//       x != y
//       x OR y
//       x > y
//       x < =
//
func walkBinary(ctx expr.EvalContext, node *expr.BinaryNode) (value.Value, bool) {
	ar, aok := Eval(ctx, node.Args[0])
	br, bok := Eval(ctx, node.Args[1])

	//u.Debugf("walkBinary: aok?%v ar:%v %T  node=%s", aok, ar, ar, node.Args[0])
	//u.Debugf("walkBinary: bok?%v br:%v %T  node=%s", bok, br, br, node.Args[1])
	//u.Debugf("walkBinary: l:%v  r:%v  %T  %T node=%s", ar, br, ar, br, node)
	// If we could not evaluate either we can shortcut
	if !aok && !bok {
		switch node.Operator.T {
		case lex.TokenLogicOr, lex.TokenOr:
			return value.NewBoolValue(false), true
		case lex.TokenEqualEqual, lex.TokenEqual:
			// We don't alllow nil == nil here bc we have a NilValue type
			// that we would use for that
			return value.NewBoolValue(false), true
		case lex.TokenNE:
			return value.NewBoolValue(false), true
		case lex.TokenGT, lex.TokenGE, lex.TokenLT, lex.TokenLE, lex.TokenLike:
			return value.NewBoolValue(false), true
		}
		//u.Debugf("walkBinary not ok: op=%s %v  l:%v  r:%v  %T  %T", node.Operator, node, ar, br, ar, br)
		return nil, false
	}

	// Else if we can only evaluate one, we can short circuit as well
	if !aok || !bok {
		switch node.Operator.T {
		case lex.TokenAnd, lex.TokenLogicAnd:
			return value.NewBoolValue(false), true
		case lex.TokenEqualEqual, lex.TokenEqual:
			return value.NewBoolValue(false), true
		case lex.TokenNE:
			// they are technically not equal?
			return value.NewBoolValue(true), true
		case lex.TokenGT, lex.TokenGE, lex.TokenLT, lex.TokenLE, lex.TokenLike:
			return value.NewBoolValue(false), true
		}
		//u.Debugf("walkBinary not ok: op=%s %v  l:%v  r:%v  %T  %T", node.Operator, node, ar, br, ar, br)
		// need to fall through to below
	}

	switch at := ar.(type) {
	case value.IntValue:
		switch bt := br.(type) {
		case value.IntValue:
			//u.Debugf("doing operate ints  %v %v  %v", at, node.Operator.V, bt)
			n := operateInts(node.Operator, at, bt)
			return n, true
		case value.StringValue:
			bi, err := strconv.ParseInt(bt.Val(), 10, 64)
			if err == nil {
				n, err := operateIntVals(node.Operator, at.Val(), bi)
				if err != nil {
					return nil, false
				}
				return n, true
			}
		case value.NumberValue:
			//u.Debugf("doing operate ints/numbers  %v %v  %v", at, node.Operator.V, bt)
			n := operateNumbers(node.Operator, at.NumberValue(), bt)
			return n, true
		case value.SliceValue:
			switch node.Operator.T {
			case lex.TokenIN:
				for _, val := range bt.Val() {
					switch valt := val.(type) {
					case value.StringValue:
						if at.Val() == valt.IntValue().Val() {
							return value.BoolValueTrue, true
						}
					case value.IntValue:
						if at.Val() == valt.Val() {
							return value.BoolValueTrue, true
						}
					case value.NumberValue:
						if at.Val() == valt.Int() {
							return value.BoolValueTrue, true
						}
					default:
						u.Debugf("Could not coerce to number: T:%T  v:%v", val, val)
					}
				}
				return value.NewBoolValue(false), true
			default:
				u.Debugf("unsupported op for SliceValue op:%v rhT:%T", node.Operator, br)
				return nil, false
			}
		case nil, value.NilValue:
			return nil, false
		default:
			u.Errorf("unknown type:  %T %v", bt, bt)
		}
	case value.NumberValue:
		switch bt := br.(type) {
		case value.IntValue:
			n := operateNumbers(node.Operator, at, bt.NumberValue())
			return n, true
		case value.NumberValue:
			n := operateNumbers(node.Operator, at, bt)
			return n, true
		case value.SliceValue:
			for _, val := range bt.Val() {
				switch valt := val.(type) {
				case value.StringValue:
					if at.Val() == valt.NumberValue().Val() {
						return value.BoolValueTrue, true
					}
				case value.IntValue:
					if at.Val() == valt.NumberValue().Val() {
						return value.BoolValueTrue, true
					}
				case value.NumberValue:
					if at.Val() == valt.Val() {
						return value.BoolValueTrue, true
					}
				default:
					u.Debugf("Could not coerce to number: T:%T  v:%v", val, val)
				}
			}
			return value.BoolValueFalse, true
		//case value.StringValue:
		case nil, value.NilValue:
			return nil, false
		default:
			u.Errorf("unknown type:  %T %v", bt, bt)
		}
	case value.BoolValue:
		switch bt := br.(type) {
		case value.BoolValue:
			atv, btv := at.Value().(bool), bt.Value().(bool)
			switch node.Operator.T {
			case lex.TokenLogicAnd, lex.TokenAnd:
				return value.NewBoolValue(atv && btv), true
			case lex.TokenLogicOr, lex.TokenOr:
				return value.NewBoolValue(atv || btv), true
			case lex.TokenEqualEqual, lex.TokenEqual:
				return value.NewBoolValue(atv == btv), true
			case lex.TokenNE:
				return value.NewBoolValue(atv != btv), true
			default:
				u.Warnf("bool binary?:  %#v  %v %v", node, at, bt)
			}
		case nil, value.NilValue:
			switch node.Operator.T {
			case lex.TokenLogicAnd:
				return value.NewBoolValue(false), true
			case lex.TokenLogicOr, lex.TokenOr:
				return at, true
			case lex.TokenEqualEqual, lex.TokenEqual:
				return value.NewBoolValue(false), true
			case lex.TokenNE:
				return value.NewBoolValue(true), true
			// case lex.TokenGE, lex.TokenGT, lex.TokenLE, lex.TokenLT:
			// 	return value.NewBoolValue(false), true
			default:
				u.Warnf("right side nil binary:  %q", node)
				return nil, false
			}
		default:
			//u.Warnf("br: %#v", br)
			//u.Errorf("at?%T  %v  coerce?%v bt? %T     %v", at, at.Value(), at.CanCoerce(stringRv), bt, bt.Value())
			return nil, false
		}
	case value.StringValue:
		switch bt := br.(type) {
		case value.StringValue:
			// Nice, both strings
			return operateStrings(node.Operator, at, bt), true
		case nil, value.NilValue:
			switch node.Operator.T {
			case lex.TokenEqualEqual, lex.TokenEqual:
				if at.Nil() {
					return value.NewBoolValue(true), true
				}
				return value.NewBoolValue(false), true
			case lex.TokenNE:
				if at.Nil() {
					return value.NewBoolValue(false), true
				}
				return value.NewBoolValue(true), true
			default:
				u.Debugf("unsupported op: %v", node.Operator)
				return nil, false
			}
		case value.SliceValue:
			switch node.Operator.T {
			case lex.TokenIN:
				for _, val := range bt.Val() {
					if at.Val() == val.ToString() {
						return value.NewBoolValue(true), true
					}
				}
				return value.NewBoolValue(false), true
			default:
				u.Debugf("unsupported op for SliceValue op:%v rhT:%T", node.Operator, br)
				return nil, false
			}
		case value.StringsValue:
			switch node.Operator.T {
			case lex.TokenIN:
				for _, val := range bt.Val() {
					if at.Val() == val {
						return value.NewBoolValue(true), true
					}
				}
				return value.NewBoolValue(false), true
			default:
				u.Debugf("unsupported op for Strings op:%v rhT:%T", node.Operator, br)
				return nil, false
			}
		case value.MapIntValue:
			switch node.Operator.T {
			case lex.TokenIN:
				for key, _ := range bt.Val() {
					if at.Val() == key {
						return value.NewBoolValue(true), true
					}
				}
				return value.NewBoolValue(false), true
			default:
				u.Debugf("unsupported op for MapInt op:%v rhT:%T", node.Operator, br)
				return nil, false
			}
		case value.BoolValue:
			if value.IsBool(at.Val()) {
				//u.Warnf("bool eval:  %v %v %v  :: %v", value.BoolStringVal(at.Val()), node.Operator.T.String(), bt.Val(), value.NewBoolValue(value.BoolStringVal(at.Val()) == bt.Val()))
				switch node.Operator.T {
				case lex.TokenEqualEqual, lex.TokenEqual:
					return value.NewBoolValue(value.BoolStringVal(at.Val()) == bt.Val()), true
				case lex.TokenNE:
					return value.NewBoolValue(value.BoolStringVal(at.Val()) != bt.Val()), true
				default:
					u.Debugf("unsupported op: %v", node.Operator)
					return nil, false
				}
			} else {
				// Should we evaluate strings that are non-nil to be = true?
				u.Debugf("not handled: boolean %v %T=%v  expr: %s", node.Operator, at.Value(), at.Val(), node.String())
				return nil, false
			}
		default:
			// TODO:  this doesn't make sense, we should be able to operate on other types
			if at.CanCoerce(int64Rv) {
				switch bt := br.(type) {
				case value.StringValue:
					n := operateNumbers(node.Operator, at.NumberValue(), bt.NumberValue())
					return n, true
				case value.IntValue:
					n := operateNumbers(node.Operator, at.NumberValue(), bt.NumberValue())
					return n, true
				case value.NumberValue:
					n := operateNumbers(node.Operator, at.NumberValue(), bt)
					return n, true
				default:
					u.Errorf("at?%T  %v  coerce?%v bt? %T     %v", at, at.Value(), at.CanCoerce(stringRv), bt, bt.Value())
				}
			} else {
				u.Errorf("at?%T  %v  coerce?%v bt? %T     %v", at, at.Value(), at.CanCoerce(stringRv), br, br)
			}
		}
	case value.SliceValue:
		switch node.Operator.T {
		case lex.TokenContains:
			switch bval := br.(type) {
			case nil, value.NilValue:
				return nil, false
			case value.StringValue:
				// [x,y,z] contains str
				for _, val := range at.Val() {
					if strings.Contains(val.ToString(), bval.Val()) {
						return value.BoolValueTrue, true
					}
				}
				return value.BoolValueFalse, true
			case value.IntValue:
				// [] contains int
				for _, val := range at.Val() {
					//u.Infof("int contains? %v %v", val.Value(), br.Value())
					if eq, _ := value.Equal(val, br); eq {
						return value.BoolValueTrue, true
					}
				}
				return value.BoolValueFalse, true
			}
		case lex.TokenLike:
			switch bv := br.(type) {
			case value.StringValue:
				// [x,y,z] LIKE str
				for _, val := range at.Val() {
					if boolVal, ok := LikeCompare(val.ToString(), bv.Val()); ok && boolVal.Val() == true {
						return boolVal, true
					}
				}
				return value.BoolValueFalse, true
			}
		case lex.TokenIntersects:
			switch bt := br.(type) {
			case nil, value.NilValue:
				return nil, false
			case value.SliceValue:
				for _, aval := range at.Val() {
					for _, bval := range bt.Val() {
						if eq, _ := value.Equal(aval, bval); eq {
							return value.BoolValueTrue, true
						}
					}
				}
				return value.BoolValueFalse, true
			case value.StringsValue:
				for _, aval := range at.Val() {
					for _, bstr := range bt.Val() {
						if aval.ToString() == bstr {
							return value.BoolValueTrue, true
						}
					}
				}
				return value.BoolValueFalse, true
			}
		}
		return nil, false
	case value.StringsValue:
		switch node.Operator.T {
		case lex.TokenContains:
			switch bv := br.(type) {
			case value.StringValue:
				// [x,y,z] contains str
				for _, val := range at.Val() {
					//u.Infof("str contains? %v %v", val, bv.Val())
					if strings.Contains(val, bv.Val()) {
						return value.BoolValueTrue, true
					}
				}
				return value.BoolValueFalse, true
			}
		case lex.TokenLike:

			switch bv := br.(type) {
			case value.StringValue:
				// [x,y,z] LIKE str
				for _, val := range at.Val() {
					boolVal, ok := LikeCompare(val, bv.Val())
					//u.Debugf("%s like %s ?? ok?%v  result=%v", val, bv.Val(), ok, boolVal)
					if ok && boolVal.Val() == true {
						return boolVal, true
					}
				}
				return value.BoolValueFalse, true
			}
		case lex.TokenIntersects:
			switch bt := br.(type) {
			case nil, value.NilValue:
				return nil, false
			case value.SliceValue:
				for _, astr := range at.Val() {
					for _, bval := range bt.Val() {
						if astr == bval.ToString() {
							return value.BoolValueTrue, true
						}
					}
				}
				return value.BoolValueFalse, true
			case value.StringsValue:
				for _, astr := range at.Val() {
					for _, bstr := range bt.Val() {
						if astr == bstr {
							return value.BoolValueTrue, true
						}
					}
				}
				return value.BoolValueFalse, true
			}
		}
		return nil, false
	case value.TimeValue:
		rht := time.Time{}
		lht := at.Val()
		var err error
		switch bv := br.(type) {
		case value.TimeValue:
			rht = bv.Val()
		case value.StringValue:
			te := bv.Val()
			if len(te) > 3 && strings.ToLower(te[:3]) == "now" {
				// Is date math
				rht, err = datemath.Eval(te[3:])
			} else {
				rht, err = dateparse.ParseAny(te)
			}
			if err != nil {
				u.Warnf("error? %s err=%v", te, err)
				return value.BoolValueFalse, false
			}
		case value.IntValue:
			// really?  we are going to try ints?
			rht, err = dateparse.ParseAny(bv.ToString())
			if err != nil {
				return value.BoolValueFalse, false
			}
			if rht.Year() < 1800 || rht.Year() > 2300 {
				return value.BoolValueFalse, false
			}
		default:
			//u.Warnf("un-handled? %#v", bv)
		}
		// if rht.IsZero() {
		// 	return nil, false
		// }
		switch node.Operator.T {
		case lex.TokenEqual, lex.TokenEqualEqual:
			if lht.Unix() == rht.Unix() {
				return value.BoolValueTrue, true
			}
			return value.BoolValueFalse, true
		case lex.TokenGT:
			// lhexpr > rhexpr
			if lht.Unix() > rht.Unix() {
				return value.BoolValueTrue, true
			}
			return value.BoolValueFalse, true
		case lex.TokenGE:
			// lhexpr >= rhexpr
			if lht.Unix() >= rht.Unix() {
				return value.BoolValueTrue, true
			}
			return value.BoolValueFalse, true
		case lex.TokenLT:
			// lhexpr < rhexpr
			if lht.Unix() < rht.Unix() {
				return value.BoolValueTrue, true
			}
			return value.BoolValueFalse, true
		case lex.TokenLE:
			// lhexpr <= rhexpr
			if lht.Unix() <= rht.Unix() {
				return value.BoolValueTrue, true
			}
			return value.BoolValueFalse, true
		default:
			u.Warnf("unhandled date op %v", node.Operator)
		}
		return nil, false
	case nil, value.NilValue:
		switch node.Operator.T {
		case lex.TokenLogicAnd:
			return value.NewBoolValue(false), true
		case lex.TokenLogicOr, lex.TokenOr:
			switch bt := br.(type) {
			case value.BoolValue:
				return bt, true
			default:
				return value.NewBoolValue(false), true
			}
		case lex.TokenEqualEqual, lex.TokenEqual:
			// does nil==nil  = true ??
			switch br.(type) {
			case nil, value.NilValue:
				return value.NewBoolValue(true), true
			default:
				return value.NewBoolValue(false), true
			}
		case lex.TokenNE:
			return value.NewBoolValue(true), true
		// case lex.TokenGE, lex.TokenGT, lex.TokenLE, lex.TokenLT:
		// 	return value.NewBoolValue(false), true
		case lex.TokenContains, lex.TokenLike, lex.TokenIN:
			return value.NewBoolValue(false), true
		default:
			//u.Debugf("left side nil binary:  %q", node)
			return nil, false
		}
	default:
		u.Debugf("Unknown op?  %T  %T  %v", ar, at, ar)
		return value.NewErrorValue(fmt.Sprintf("unsupported left side value: %T in %s", at, node)), false
	}

	return value.NewErrorValue(fmt.Sprintf("unsupported binary expression: %s", node)), false
}
Example #2
0
func walkBinary(ctx expr.EvalContext, node *expr.BinaryNode) (value.Value, bool) {
	ar, aok := Eval(ctx, node.Args[0])
	br, bok := Eval(ctx, node.Args[1])
	if !aok || !bok {
		// If !aok, but token is a Negate?
		u.Debugf("walkBinary not ok: op=%s %v  l:%v  r:%v  %T  %T", node.Operator, node, ar, br, ar, br)
		//u.Debugf("node: %s   --- %s", node.Args[0], node.Args[1])
		// if ar != nil && br != nil {
		// 	u.Debugf("not ok: %v  l:%v  r:%v", node, ar.ToString(), br.ToString())
		// }
		return nil, false
	}
	// if ar == nil {
	// 	u.Warnf("Wat? %q node0: %#v", node.Args[0], node.Args[0])
	// 	//return nil, false
	// }
	// if br == nil {
	// 	u.Warnf("wat2  %q node1: %#v", node.Args[1], node.Args[1])
	// 	//return nil, false
	// }
	//u.Debugf("node.Args: %#v", node.Args)
	//u.Debugf("walkBinary: %v  l:%v  r:%v  %T  %T", node, ar, br, ar, br)
	switch at := ar.(type) {
	case value.IntValue:
		switch bt := br.(type) {
		case value.IntValue:
			//u.Debugf("doing operate ints  %v %v  %v", at, node.Operator.V, bt)
			n := operateInts(node.Operator, at, bt)
			return n, true
		case value.NumberValue:
			//u.Debugf("doing operate ints/numbers  %v %v  %v", at, node.Operator.V, bt)
			n := operateNumbers(node.Operator, at.NumberValue(), bt)
			return n, true
		default:
			u.Errorf("unknown type:  %T %v", bt, bt)
		}
	case value.NumberValue:
		switch bt := br.(type) {
		case value.IntValue:
			n := operateNumbers(node.Operator, at, bt.NumberValue())
			return n, true
		case value.NumberValue:
			n := operateNumbers(node.Operator, at, bt)
			return n, true
		default:
			u.Errorf("unknown type:  %T %v", bt, bt)
		}
	case value.BoolValue:
		switch bt := br.(type) {
		case value.BoolValue:
			atv, btv := at.Value().(bool), bt.Value().(bool)
			switch node.Operator.T {
			case lex.TokenLogicAnd:
				return value.NewBoolValue(atv && btv), true
			case lex.TokenLogicOr, lex.TokenOr:
				return value.NewBoolValue(atv || btv), true
			case lex.TokenEqualEqual, lex.TokenEqual:
				return value.NewBoolValue(atv == btv), true
			case lex.TokenNE:
				return value.NewBoolValue(atv != btv), true
			default:
				u.Warnf("bool binary?:  %#v  %v %v", node, at, bt)
			}
		case nil, value.NilValue:
			switch node.Operator.T {
			case lex.TokenLogicAnd:
				return value.NewBoolValue(false), true
			case lex.TokenLogicOr, lex.TokenOr:
				return at, true
			case lex.TokenEqualEqual, lex.TokenEqual:
				return value.NewBoolValue(false), true
			case lex.TokenNE:
				return value.NewBoolValue(true), true
			// case lex.TokenGE, lex.TokenGT, lex.TokenLE, lex.TokenLT:
			// 	return value.NewBoolValue(false), true
			default:
				u.Warnf("right side nil binary:  %q", node)
				return nil, true
			}
		default:
			u.Warnf("br: %#v", br)
			u.Errorf("at?%T  %v  coerce?%v bt? %T     %v", at, at.Value(), at.CanCoerce(stringRv), bt, bt.Value())
		}
	case value.StringValue:
		switch bt := br.(type) {
		case value.StringValue:
			// Nice, both strings
			return operateStrings(node.Operator, at, bt), true
		case nil, value.NilValue:
			switch node.Operator.T {
			case lex.TokenEqualEqual, lex.TokenEqual:
				if at.Nil() {
					return value.NewBoolValue(true), true
				}
				return value.NewBoolValue(false), true
			case lex.TokenNE:
				if at.Nil() {
					return value.NewBoolValue(false), true
				}
				return value.NewBoolValue(true), true
			default:
				u.Warnf("unsupported op: %v", node.Operator)
				return nil, false
			}
		case value.BoolValue:
			if value.IsBool(at.Val()) {
				//u.Warnf("bool eval:  %v %v %v  :: %v", value.BoolStringVal(at.Val()), node.Operator.T.String(), bt.Val(), value.NewBoolValue(value.BoolStringVal(at.Val()) == bt.Val()))
				switch node.Operator.T {
				case lex.TokenEqualEqual, lex.TokenEqual:
					return value.NewBoolValue(value.BoolStringVal(at.Val()) == bt.Val()), true
				case lex.TokenNE:
					return value.NewBoolValue(value.BoolStringVal(at.Val()) != bt.Val()), true
				default:
					u.Warnf("unsupported op: %v", node.Operator)
					return nil, false
				}
			} else {
				// Should we evaluate strings that are non-nil to be = true?
				u.Debugf("not handled: boolean %v %T=%v  expr: %s", node.Operator, at.Value(), at.Val(), node.String())
				return nil, false
			}
		default:
			// TODO:  this doesn't make sense, we should be able to operate on other types
			if at.CanCoerce(int64Rv) {
				switch bt := br.(type) {
				case value.StringValue:
					n := operateNumbers(node.Operator, at.NumberValue(), bt.NumberValue())
					return n, true
				case value.IntValue:
					n := operateNumbers(node.Operator, at.NumberValue(), bt.NumberValue())
					return n, true
				case value.NumberValue:
					n := operateNumbers(node.Operator, at.NumberValue(), bt)
					return n, true
				default:
					u.Errorf("at?%T  %v  coerce?%v bt? %T     %v", at, at.Value(), at.CanCoerce(stringRv), bt, bt.Value())
				}
			} else {
				u.Errorf("at?%T  %v  coerce?%v bt? %T     %v", at, at.Value(), at.CanCoerce(stringRv), br, br)
			}
		}
	case nil, value.NilValue:
		switch node.Operator.T {
		case lex.TokenLogicAnd:
			return value.NewBoolValue(false), true
		case lex.TokenLogicOr, lex.TokenOr:
			switch bt := br.(type) {
			case value.BoolValue:
				return bt, true
			default:
				return value.NewBoolValue(false), true
			}
		case lex.TokenEqualEqual, lex.TokenEqual:
			// does nil==nil  = true ??
			switch br.(type) {
			case nil, value.NilValue:
				return value.NewBoolValue(true), true
			default:
				return value.NewBoolValue(false), true
			}
		case lex.TokenNE:
			return value.NewBoolValue(true), true
		// case lex.TokenGE, lex.TokenGT, lex.TokenLE, lex.TokenLT:
		// 	return value.NewBoolValue(false), true
		default:
			u.Debugf("left side nil binary:  %q", node)
			return nil, true
		}
	default:
		u.Debugf("Unknown op?  %T  %T  %v", ar, at, ar)
		return value.NewErrorValue(fmt.Sprintf("unsupported left side value: %T in %s", at, node)), false
	}

	return value.NewErrorValue(fmt.Sprintf("unsupported binary expression: %s", node)), false
}
Example #3
0
func walkBinary(ctx expr.EvalContext, node *expr.BinaryNode) value.Value {
	ar, aok := Eval(ctx, node.Args[0])
	br, bok := Eval(ctx, node.Args[1])
	if !aok || !bok {
		u.Warnf("not ok: %v  l:%v  r:%v  %T  %T", node, ar, br, ar, br)
		return nil
	}
	//u.Debugf("node.Args: %#v", node.Args)
	//u.Debugf("walkBinary: %v  l:%v  r:%v  %T  %T", node, ar, br, ar, br)
	switch at := ar.(type) {
	case value.IntValue:
		switch bt := br.(type) {
		case value.IntValue:
			//u.Debugf("doing operate ints  %v %v  %v", at, node.Operator.V, bt)
			n := operateInts(node.Operator, at, bt)
			return n
		case value.NumberValue:
			//u.Debugf("doing operate ints/numbers  %v %v  %v", at, node.Operator.V, bt)
			n := operateNumbers(node.Operator, at.NumberValue(), bt)
			return n
		default:
			u.Errorf("unknown type:  %T %v", bt, bt)
			panic(ErrUnknownOp)
		}
	case value.NumberValue:
		switch bt := br.(type) {
		case value.IntValue:
			n := operateNumbers(node.Operator, at, bt.NumberValue())
			return n
		case value.NumberValue:
			n := operateNumbers(node.Operator, at, bt)
			return n
		default:
			u.Errorf("unknown type:  %T %v", bt, bt)
			panic(ErrUnknownOp)
		}
	case value.BoolValue:
		switch bt := br.(type) {
		case value.BoolValue:
			atv, btv := at.Value().(bool), bt.Value().(bool)
			switch node.Operator.T {
			case lex.TokenLogicAnd:
				return value.NewBoolValue(atv && btv)
			case lex.TokenLogicOr:
				return value.NewBoolValue(atv || btv)
			case lex.TokenEqualEqual, lex.TokenEqual:
				return value.NewBoolValue(atv == btv)
			case lex.TokenNE:
				return value.NewBoolValue(atv != btv)
			default:
				u.Infof("bool binary?:  %v  %v", at, bt)
				panic(ErrUnknownOp)
			}

		default:
			u.Errorf("at?%T  %v  coerce?%v bt? %T     %v", at, at.Value(), at.CanCoerce(stringRv), bt, bt.Value())
			panic(ErrUnknownOp)
		}
	case value.StringValue:
		switch bt := br.(type) {
		case value.StringValue:
			// Nice, both strings
			return operateStrings(node.Operator, at, bt)
		case value.BoolValue:
			if value.IsBool(at.Val()) {
				//u.Warnf("bool eval:  %v %v %v  :: %v", value.BoolStringVal(at.Val()), node.Operator.T.String(), bt.Val(), value.NewBoolValue(value.BoolStringVal(at.Val()) == bt.Val()))
				switch node.Operator.T {
				case lex.TokenEqualEqual, lex.TokenEqual:
					return value.NewBoolValue(value.BoolStringVal(at.Val()) == bt.Val())
				case lex.TokenNE:
					return value.NewBoolValue(value.BoolStringVal(at.Val()) != bt.Val())
				}
			}
		default:
			// TODO:  this doesn't make sense, we should be able to operate on other types
			if at.CanCoerce(int64Rv) {
				switch bt := br.(type) {
				case value.StringValue:
					n := operateNumbers(node.Operator, at.NumberValue(), bt.NumberValue())
					return n
				case value.IntValue:
					n := operateNumbers(node.Operator, at.NumberValue(), bt.NumberValue())
					return n
				case value.NumberValue:
					n := operateNumbers(node.Operator, at.NumberValue(), bt)
					return n
				default:
					u.Errorf("at?%T  %v  coerce?%v bt? %T     %v", at, at.Value(), at.CanCoerce(stringRv), bt, bt.Value())
					panic(ErrUnknownOp)
				}
			} else {
				u.Errorf("at?%T  %v  coerce?%v bt? %T     %v", at, at.Value(), at.CanCoerce(stringRv), br, br)
			}
		}

		// case nil:
		// 	// TODO, remove this case?  is this valid?  used?
		// 	switch bt := br.(type) {
		// 	case StringValue:
		// 		n := operateNumbers(node.Operator, NumberNaNValue, bt.NumberValue())
		// 		return n
		// 	case IntValue:
		// 		n := operateNumbers(node.Operator, NumberNaNValue, bt.NumberValue())
		// 		return n
		// 	case NumberValue:
		// 		n := operateNumbers(node.Operator, NumberNaNValue, bt)
		// 		return n
		// 	case nil:
		// 		u.Errorf("a && b nil? at?%v  %v    %v", at, bt, node.Operator)
		// 	default:
		// 		u.Errorf("nil at?%v  %T      %v", at, bt, node.Operator)
		// 		panic(ErrUnknownOp)
		// 	}
		// default:
		u.Errorf("Unknown op?  %T  %T  %v", ar, at, ar)
		panic(ErrUnknownOp)
	}

	return nil
}