| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package opconst defines the representation and operations for VDL constants. |
| package opconst |
| |
| import ( |
| "errors" |
| "fmt" |
| "math" |
| "math/big" |
| "strconv" |
| |
| "v.io/v23/vdl" |
| ) |
| |
| var ( |
| bigIntZero = new(big.Int) |
| bigRatZero = new(big.Rat) |
| bigIntOne = big.NewInt(1) |
| bigRatAbsMin32 = new(big.Rat).SetFloat64(math.SmallestNonzeroFloat32) |
| bigRatAbsMax32 = new(big.Rat).SetFloat64(math.MaxFloat32) |
| bigRatAbsMin64 = new(big.Rat).SetFloat64(math.SmallestNonzeroFloat64) |
| bigRatAbsMax64 = new(big.Rat).SetFloat64(math.MaxFloat64) |
| maxShiftSize = big.NewInt(2000) // arbitrary large value |
| |
| errInvalidConst = errors.New("invalid const") |
| errConvertNil = errors.New("invalid conversion to untyped const") |
| errDivZero = errors.New("divide by zero") |
| ) |
| |
| // Const represents a constant value, similar in spirit to Go constants. Consts |
| // may be typed or untyped. Typed consts represent unchanging Values; all |
| // Values may be converted into valid typed consts, and all typed consts may be |
| // converted into valid Values. Untyped consts belong to one of the following |
| // categories: |
| // untyped boolean |
| // untyped string |
| // untyped integer |
| // untyped rational |
| // Literal consts are untyped, as are expressions only containing untyped |
| // consts. The result of comparison operations is untyped boolean. |
| // |
| // Operations are represented by UnaryOp and BinaryOp, and are supported on |
| // Consts, but not Values. We support common logical, bitwise, comparison and |
| // arithmetic operations. Not all operations are supported on all consts. |
| // |
| // Binary ops where both sides are typed consts return errors on type |
| // mismatches; e.g. uint32(1) + uint64(1) is an invalid binary add. Ops on |
| // typed consts also return errors on loss of precision; e.g. uint32(1.1) |
| // returns an error. |
| // |
| // Binary ops where one or both sides are untyped consts perform implicit type |
| // conversion. E.g. uint32(1) + 1 is a valid binary add, where the |
| // right-hand-side is the untyped integer const 1, which is coerced to the |
| // uint32 type before the op is performed. Operations only containing untyped |
| // consts are performed with "infinite" precision. |
| // |
| // The zero Const is invalid. |
| type Const struct { |
| // rep holds the underlying representation, it may be one of: |
| // bool - Represents typed and untyped boolean constants. |
| // string - Represents typed and untyped string constants. |
| // *big.Int - Represents typed and untyped integer constants. |
| // *big.Rat - Represents typed and untyped rational constants. |
| // *Value - Represents all other typed constants. |
| rep interface{} |
| |
| // repType holds the type of rep. If repType is nil the constant is untyped, |
| // otherwise the constant is typed, and rep must match the kind of repType. |
| // If rep is a *Value, repType is always non-nil. |
| repType *vdl.Type |
| } |
| |
| // Boolean returns an untyped boolean Const. |
| func Boolean(x bool) Const { return Const{x, nil} } |
| |
| // String returns an untyped string Const. |
| func String(x string) Const { return Const{x, nil} } |
| |
| // Integer returns an untyped integer Const. |
| func Integer(x *big.Int) Const { return Const{x, nil} } |
| |
| // Rational returns an untyped rational Const. |
| func Rational(x *big.Rat) Const { return Const{x, nil} } |
| |
| // TODO(toddw): Use big.Float to represent floating point, rather than big.Rat. |
| // We'll still use big.Rat to represent rationals, e.g. integer division. |
| |
| // FromValue returns a typed Const based on value v. |
| func FromValue(v *vdl.Value) Const { |
| if v.Type().IsBytes() { |
| // Represent []byte and [N]byte as a string, so that conversions are easy. |
| return Const{string(v.Bytes()), v.Type()} |
| } |
| switch v.Kind() { |
| case vdl.Bool: |
| if v.Type() == vdl.BoolType { // Treat unnamed bool as untyped bool. |
| return Boolean(v.Bool()) |
| } |
| return Const{v.Bool(), v.Type()} |
| case vdl.String: |
| if v.Type() == vdl.StringType { // Treat unnamed string as untyped string. |
| return String(v.RawString()) |
| } |
| return Const{v.RawString(), v.Type()} |
| case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64: |
| return Const{new(big.Int).SetUint64(v.Uint()), v.Type()} |
| case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64: |
| return Const{new(big.Int).SetInt64(v.Int()), v.Type()} |
| case vdl.Float32, vdl.Float64: |
| return Const{new(big.Rat).SetFloat64(v.Float()), v.Type()} |
| default: |
| return Const{v, v.Type()} |
| } |
| } |
| |
| // IsValid returns true iff the c represents a const; it returns false for the |
| // zero Const. |
| func (c Const) IsValid() bool { |
| return c.rep != nil |
| } |
| |
| // Type returns the type of c. Nil indicates c is an untyped const. |
| func (c Const) Type() *vdl.Type { |
| return c.repType |
| } |
| |
| // Convert converts c to the target type t, and returns the resulting const. |
| // Returns an error if t is nil; you're not allowed to convert into an untyped |
| // const. |
| func (c Const) Convert(t *vdl.Type) (Const, error) { |
| if t == nil { |
| return Const{}, errConvertNil |
| } |
| // If we're trying to convert to Any or Union, or if c is already a vdl.Value, |
| // use vdl.Convert to convert as a vdl.Value. |
| _, isValue := c.rep.(*vdl.Value) |
| if isValue || t.Kind() == vdl.Any || t.Kind() == vdl.Union { |
| src, err := c.ToValue() |
| if err != nil { |
| return Const{}, err |
| } |
| dst := vdl.ZeroValue(t) |
| if err := vdl.Convert(dst, src); err != nil { |
| return Const{}, err |
| } |
| return FromValue(dst), nil |
| } |
| // Otherwise use makeConst to convert as a Const. |
| return makeConst(c.rep, t) |
| } |
| |
| func (c Const) String() string { |
| if !c.IsValid() { |
| return "invalid" |
| } |
| if v, ok := c.rep.(*vdl.Value); ok { |
| return v.String() |
| } |
| if c.repType == nil { |
| // E.g. 12345 |
| return cRepString(c.rep) |
| } |
| // E.g. int32(12345) |
| return c.typeString() + "(" + cRepString(c.rep) + ")" |
| } |
| |
| func (c Const) typeString() string { |
| return cRepTypeString(c.rep, c.repType) |
| } |
| |
| // cRepString returns a human-readable string representing the const value. |
| func cRepString(rep interface{}) string { |
| switch trep := rep.(type) { |
| case nil: |
| return "" // invalid const |
| case bool: |
| if trep { |
| return "true" |
| } |
| return "false" |
| case string: |
| return strconv.Quote(trep) |
| case *big.Int: |
| return trep.String() |
| case *big.Rat: |
| if trep.IsInt() { |
| return trep.Num().String() + ".0" |
| } |
| frep, _ := trep.Float64() |
| return strconv.FormatFloat(frep, 'g', -1, 64) |
| case *vdl.Value: |
| return trep.String() |
| default: |
| panic(fmt.Errorf("val: unhandled const type %T value %v", rep, rep)) |
| } |
| } |
| |
| // cRepTypeString returns a human-readable string representing the type of |
| // the const value. |
| func cRepTypeString(rep interface{}, t *vdl.Type) string { |
| if t != nil { |
| return t.String() |
| } |
| switch rep.(type) { |
| case nil: |
| return "invalid" |
| case bool: |
| return "untyped boolean" |
| case string: |
| return "untyped string" |
| case *big.Int: |
| return "untyped integer" |
| case *big.Rat: |
| return "untyped rational" |
| default: |
| panic(fmt.Errorf("val: unhandled const type %T value %v", rep, rep)) |
| } |
| } |
| |
| // ToValue converts Const c to a Value. |
| func (c Const) ToValue() (*vdl.Value, error) { |
| if c.rep == nil { |
| return nil, errInvalidConst |
| } |
| // All const defs must have a type. We implicitly assign bool and string, but |
| // the user must explicitly assign a type for numeric consts. |
| if c.repType == nil { |
| switch c.rep.(type) { |
| case bool: |
| c.repType = vdl.BoolType |
| case string: |
| c.repType = vdl.StringType |
| default: |
| return nil, fmt.Errorf("%s must be assigned a type", c) |
| } |
| } |
| // Create a value of the appropriate type. |
| vx := vdl.ZeroValue(c.repType) |
| switch trep := c.rep.(type) { |
| case bool: |
| switch vx.Kind() { |
| case vdl.Bool: |
| vx.AssignBool(trep) |
| return vx, nil |
| } |
| case string: |
| switch { |
| case vx.Kind() == vdl.String: |
| vx.AssignString(trep) |
| return vx, nil |
| case vx.Type().IsBytes(): |
| if vx.Kind() == vdl.Array { |
| if vx.Len() != len(trep) { |
| return nil, fmt.Errorf("%s has a different length than %v", c, vx.Type()) |
| } |
| } |
| vx.AssignBytes([]byte(trep)) |
| return vx, nil |
| } |
| case *big.Int: |
| switch vx.Kind() { |
| case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64: |
| vx.AssignUint(trep.Uint64()) |
| return vx, nil |
| case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64: |
| vx.AssignInt(trep.Int64()) |
| return vx, nil |
| } |
| case *big.Rat: |
| switch vx.Kind() { |
| case vdl.Float32, vdl.Float64: |
| f64, _ := trep.Float64() |
| vx.AssignFloat(f64) |
| return vx, nil |
| } |
| case *vdl.Value: |
| return trep, nil |
| } |
| // Type mismatches shouldn't occur, since makeConst always ensures the rep and |
| // repType are in sync. If something's wrong we want to know about it. |
| panic(fmt.Errorf("val: mismatched const rep type for %v", c)) |
| } |
| |
| func errNotSupported(rep interface{}, t *vdl.Type) error { |
| return fmt.Errorf("%s not supported", cRepTypeString(rep, t)) |
| } |
| |
| // EvalUnary returns the result of evaluating (op x). |
| func EvalUnary(op UnaryOp, x Const) (Const, error) { |
| if x.rep == nil { |
| return Const{}, errInvalidConst |
| } |
| if _, ok := x.rep.(*vdl.Value); ok { |
| // There are no valid unary ops on *Value consts. |
| return Const{}, errNotSupported(x.rep, x.repType) |
| } |
| switch op { |
| case LogicNot: |
| switch tx := x.rep.(type) { |
| case bool: |
| return makeConst(!tx, x.repType) |
| } |
| case Pos: |
| switch x.rep.(type) { |
| case *big.Int, *big.Rat: |
| return x, nil |
| } |
| case Neg: |
| switch tx := x.rep.(type) { |
| case *big.Int: |
| return makeConst(new(big.Int).Neg(tx), x.repType) |
| case *big.Rat: |
| return makeConst(new(big.Rat).Neg(tx), x.repType) |
| } |
| case BitNot: |
| ix, err := constToInt(x) |
| if err != nil { |
| return Const{}, err |
| } |
| // big.Int.Not implements bit-not for signed integers, but we need to |
| // special-case unsigned integers. E.g. ^int8(1)=-2, ^uint8(1)=254 |
| not := new(big.Int) |
| switch { |
| case x.repType != nil && x.repType.Kind() == vdl.Byte: |
| not.SetUint64(uint64(^uint8(ix.Uint64()))) |
| case x.repType != nil && x.repType.Kind() == vdl.Uint16: |
| not.SetUint64(uint64(^uint16(ix.Uint64()))) |
| case x.repType != nil && x.repType.Kind() == vdl.Uint32: |
| not.SetUint64(uint64(^uint32(ix.Uint64()))) |
| case x.repType != nil && x.repType.Kind() == vdl.Uint64: |
| not.SetUint64(^ix.Uint64()) |
| default: |
| not.Not(ix) |
| } |
| return makeConst(not, x.repType) |
| } |
| return Const{}, errNotSupported(x.rep, x.repType) |
| } |
| |
| // EvalBinary returns the result of evaluating (x op y). |
| func EvalBinary(op BinaryOp, x, y Const) (Const, error) { |
| if x.rep == nil || y.rep == nil { |
| return Const{}, errInvalidConst |
| } |
| switch op { |
| case LeftShift, RightShift: |
| // Shift ops are special since they require an integer lhs and unsigned rhs. |
| return evalShift(op, x, y) |
| } |
| // All other binary ops behave similarly. First we perform implicit |
| // conversion of x and y. If either side is untyped, we may need to |
| // implicitly convert it to the type of the other side. If both sides are |
| // typed they need to match. The resulting tx and ty are guaranteed to have |
| // the same type, and resType tells us which type we need to convert the |
| // result into when we're done. |
| cx, cy, resType, err := coerceConsts(x, y) |
| if err != nil { |
| return Const{}, err |
| } |
| // Now we perform the actual binary op. |
| var res interface{} |
| switch op { |
| case LogicOr, LogicAnd: |
| res, err = opLogic(op, cx, cy, resType) |
| case EQ, NE, LT, LE, GT, GE: |
| res, err = opComp(op, cx, cy, resType) |
| resType = nil // comparisons always result in untyped bool. |
| case Add, Sub, Mul, Div: |
| res, err = opArith(op, cx, cy, resType) |
| case Mod, BitAnd, BitOr, BitXor: |
| res, err = opIntArith(op, cx, cy, resType) |
| default: |
| err = errNotSupported(cx, resType) |
| } |
| if err != nil { |
| return Const{}, err |
| } |
| // As a final step we convert to the result type. |
| return makeConst(res, resType) |
| } |
| |
| func opLogic(op BinaryOp, x, y interface{}, resType *vdl.Type) (interface{}, error) { |
| switch tx := x.(type) { |
| case bool: |
| switch op { |
| case LogicOr: |
| return tx || y.(bool), nil |
| case LogicAnd: |
| return tx && y.(bool), nil |
| } |
| } |
| return nil, errNotSupported(x, resType) |
| } |
| |
| func opComp(op BinaryOp, x, y interface{}, resType *vdl.Type) (interface{}, error) { |
| switch tx := x.(type) { |
| case bool: |
| switch op { |
| case EQ: |
| return tx == y.(bool), nil |
| case NE: |
| return tx != y.(bool), nil |
| } |
| case string: |
| return compString(op, tx, y.(string)), nil |
| case *big.Int: |
| return opCmpToBool(op, tx.Cmp(y.(*big.Int))), nil |
| case *big.Rat: |
| return opCmpToBool(op, tx.Cmp(y.(*big.Rat))), nil |
| case *vdl.Value: |
| switch op { |
| case EQ: |
| return vdl.EqualValue(tx, y.(*vdl.Value)), nil |
| case NE: |
| return !vdl.EqualValue(tx, y.(*vdl.Value)), nil |
| } |
| } |
| return nil, errNotSupported(x, resType) |
| } |
| |
| func opArith(op BinaryOp, x, y interface{}, resType *vdl.Type) (interface{}, error) { |
| switch tx := x.(type) { |
| case string: |
| if op == Add { |
| return tx + y.(string), nil |
| } |
| case *big.Int: |
| return arithBigInt(op, tx, y.(*big.Int)) |
| case *big.Rat: |
| return arithBigRat(op, tx, y.(*big.Rat)) |
| } |
| return nil, errNotSupported(x, resType) |
| } |
| |
| func opIntArith(op BinaryOp, x, y interface{}, resType *vdl.Type) (interface{}, error) { |
| ix, err := constToInt(Const{x, resType}) |
| if err != nil { |
| return nil, err |
| } |
| iy, err := constToInt(Const{y, resType}) |
| if err != nil { |
| return nil, err |
| } |
| return arithBigInt(op, ix, iy) |
| } |
| |
| func evalShift(op BinaryOp, x, y Const) (Const, error) { |
| // lhs must be an integer. |
| ix, err := constToInt(x) |
| if err != nil { |
| return Const{}, err |
| } |
| // rhs must be a small unsigned integer. |
| iy, err := constToInt(y) |
| if err != nil { |
| return Const{}, err |
| } |
| if iy.Sign() < 0 { |
| return Const{}, fmt.Errorf("shift amount %v isn't unsigned", cRepString(iy)) |
| } |
| if iy.Cmp(maxShiftSize) > 0 { |
| return Const{}, fmt.Errorf("shift amount %v greater than max allowed %v", cRepString(iy), cRepString(maxShiftSize)) |
| } |
| // Perform the shift and convert it back to the lhs type. |
| return makeConst(shiftBigInt(op, ix, uint(iy.Uint64())), x.repType) |
| } |
| |
| // bigRatToInt converts rational to integer values as long as there isn't any |
| // loss in precision, checking resType to make sure the conversion is allowed. |
| func bigRatToInt(rat *big.Rat, resType *vdl.Type) (*big.Int, error) { |
| // As a special-case we allow untyped rat consts to be converted to integers, |
| // as long as they can do so without loss of precision. This is safe since |
| // untyped rat consts have "unbounded" precision. Typed float consts may have |
| // been rounded at some point, so we don't allow this. This is the same |
| // behavior as Go. |
| if resType != nil { |
| return nil, fmt.Errorf("can't convert typed %s to integer", cRepTypeString(rat, resType)) |
| } |
| if !rat.IsInt() { |
| return nil, fmt.Errorf("converting %s %s to integer loses precision", cRepTypeString(rat, resType), cRepString(rat)) |
| } |
| return new(big.Int).Set(rat.Num()), nil |
| } |
| |
| // constToInt converts x to an integer value as long as there isn't any loss in |
| // precision. |
| func constToInt(x Const) (*big.Int, error) { |
| switch tx := x.rep.(type) { |
| case *big.Int: |
| return tx, nil |
| case *big.Rat: |
| return bigRatToInt(tx, x.repType) |
| } |
| return nil, fmt.Errorf("can't convert %s to integer", x.typeString()) |
| } |
| |
| // makeConst creates a Const with value rep and type totype, performing overflow |
| // and conversion checks on numeric values. If totype is nil the resulting |
| // const is untyped. |
| // |
| // TODO(toddw): Update to handle conversions to optional types. |
| func makeConst(rep interface{}, totype *vdl.Type) (Const, error) { |
| if rep == nil { |
| return Const{}, errInvalidConst |
| } |
| if totype == nil { |
| if v, ok := rep.(*vdl.Value); ok { |
| return Const{}, fmt.Errorf("can't make typed value %s untyped", v.Type()) |
| } |
| return Const{rep, nil}, nil |
| } |
| switch trep := rep.(type) { |
| case bool: |
| if totype.Kind() == vdl.Bool { |
| return Const{trep, totype}, nil |
| } |
| case string: |
| if totype.Kind() == vdl.String || totype.IsBytes() { |
| return Const{trep, totype}, nil |
| } |
| case *big.Int: |
| switch totype.Kind() { |
| case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64: |
| if err := checkOverflowInt(trep, totype.Kind()); err != nil { |
| return Const{}, err |
| } |
| return Const{trep, totype}, nil |
| case vdl.Float32, vdl.Float64: |
| return makeConst(new(big.Rat).SetInt(trep), totype) |
| } |
| case *big.Rat: |
| switch totype.Kind() { |
| case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64: |
| // The only way we reach this conversion from big.Rat to a typed integer |
| // is for explicit type conversions. We pass a nil Type to bigRatToInt |
| // indicating trep is untyped, to allow all conversions from float to int |
| // as long as trep is actually an integer. |
| irep, err := bigRatToInt(trep, nil) |
| if err != nil { |
| return Const{}, err |
| } |
| return makeConst(irep, totype) |
| case vdl.Float32, vdl.Float64: |
| frep, err := convertTypedRat(trep, totype.Kind()) |
| if err != nil { |
| return Const{}, err |
| } |
| return Const{frep, totype}, nil |
| } |
| } |
| return Const{}, fmt.Errorf("can't convert %s to %v", cRepString(rep), cRepTypeString(rep, totype)) |
| } |
| |
| func bitLenInt(kind vdl.Kind) int { |
| switch kind { |
| case vdl.Byte, vdl.Int8: |
| return 8 |
| case vdl.Uint16, vdl.Int16: |
| return 16 |
| case vdl.Uint32, vdl.Int32: |
| return 32 |
| case vdl.Uint64, vdl.Int64: |
| return 64 |
| default: |
| panic(fmt.Errorf("val: bitLen unhandled kind %v", kind)) |
| } |
| } |
| |
| // checkOverflowInt returns an error iff converting b to the typed integer will |
| // cause overflow. |
| func checkOverflowInt(b *big.Int, kind vdl.Kind) error { |
| switch bitlen := bitLenInt(kind); kind { |
| case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64: |
| if b.Sign() < 0 || b.BitLen() > bitlen { |
| return fmt.Errorf("const %v overflows uint%d", cRepString(b), bitlen) |
| } |
| case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64: |
| // Account for two's complement, where e.g. int8 ranges from -128 to 127 |
| if b.Sign() >= 0 { |
| // Positives and 0 - just check bitlen, accounting for the sign bit. |
| if b.BitLen() >= bitlen { |
| return fmt.Errorf("const %v overflows int%d", cRepString(b), bitlen) |
| } |
| } else { |
| // Negatives need to take an extra value into account (e.g. -128 for int8) |
| bplus1 := new(big.Int).Add(b, bigIntOne) |
| if bplus1.BitLen() >= bitlen { |
| return fmt.Errorf("const %v overflows int%d", cRepString(b), bitlen) |
| } |
| } |
| default: |
| panic(fmt.Errorf("val: checkOverflowInt unhandled kind %v", kind)) |
| } |
| return nil |
| } |
| |
| // checkOverflowRat returns an error iff converting b to the typed rat will |
| // cause overflow or underflow. |
| func checkOverflowRat(b *big.Rat, kind vdl.Kind) error { |
| // Exact zero is special cased in ieee754. |
| if b.Cmp(bigRatZero) == 0 { |
| return nil |
| } |
| // TODO(toddw): perhaps allow slightly smaller and larger values, to account |
| // for ieee754 round-to-even rules. |
| switch abs := new(big.Rat).Abs(b); kind { |
| case vdl.Float32: |
| if abs.Cmp(bigRatAbsMin32) < 0 { |
| return fmt.Errorf("const %v underflows float32", cRepString(b)) |
| } |
| if abs.Cmp(bigRatAbsMax32) > 0 { |
| return fmt.Errorf("const %v overflows float32", cRepString(b)) |
| } |
| case vdl.Float64: |
| if abs.Cmp(bigRatAbsMin64) < 0 { |
| return fmt.Errorf("const %v underflows float64", cRepString(b)) |
| } |
| if abs.Cmp(bigRatAbsMax64) > 0 { |
| return fmt.Errorf("const %v overflows float64", cRepString(b)) |
| } |
| default: |
| panic(fmt.Errorf("val: checkOverflowRat unhandled kind %v", kind)) |
| } |
| return nil |
| } |
| |
| // convertTypedRat converts b to the typed rat, rounding as necessary. |
| func convertTypedRat(b *big.Rat, kind vdl.Kind) (*big.Rat, error) { |
| if err := checkOverflowRat(b, kind); err != nil { |
| return nil, err |
| } |
| switch f64, _ := b.Float64(); kind { |
| case vdl.Float32: |
| return new(big.Rat).SetFloat64(float64(float32(f64))), nil |
| case vdl.Float64: |
| return new(big.Rat).SetFloat64(f64), nil |
| default: |
| panic(fmt.Errorf("val: convertTypedRat unhandled kind %v", kind)) |
| } |
| } |
| |
| // coerceConsts performs implicit conversion of cl and cr based on their |
| // respective types. Returns the converted values vl and vr which are |
| // guaranteed to be of the same type represented by the returned Type, which may |
| // be nil if both consts are untyped. |
| func coerceConsts(cl, cr Const) (interface{}, interface{}, *vdl.Type, error) { |
| var err error |
| if cl.repType != nil && cr.repType != nil { |
| // Both consts are typed - their types must match (no implicit conversion). |
| if cl.repType != cr.repType { |
| return nil, nil, nil, fmt.Errorf("type mismatch %v and %v", cl.typeString(), cr.typeString()) |
| } |
| return cl.rep, cr.rep, cl.repType, nil |
| } |
| if cl.repType != nil { |
| // Convert rhs to the type of the lhs. |
| cr, err = makeConst(cr.rep, cl.repType) |
| if err != nil { |
| return nil, nil, nil, err |
| } |
| return cl.rep, cr.rep, cl.repType, nil |
| } |
| if cr.repType != nil { |
| // Convert lhs to the type of the rhs. |
| cl, err = makeConst(cl.rep, cr.repType) |
| if err != nil { |
| return nil, nil, nil, err |
| } |
| return cl.rep, cr.rep, cr.repType, nil |
| } |
| // Both consts are untyped, might need to implicitly promote untyped consts. |
| switch vl := cl.rep.(type) { |
| case bool: |
| switch vr := cr.rep.(type) { |
| case bool: |
| return vl, vr, nil, nil |
| } |
| case string: |
| switch vr := cr.rep.(type) { |
| case string: |
| return vl, vr, nil, nil |
| } |
| case *big.Int: |
| switch vr := cr.rep.(type) { |
| case *big.Int: |
| return vl, vr, nil, nil |
| case *big.Rat: |
| // Promote lhs to rat |
| return new(big.Rat).SetInt(vl), vr, nil, nil |
| } |
| case *big.Rat: |
| switch vr := cr.rep.(type) { |
| case *big.Int: |
| // Promote rhs to rat |
| return vl, new(big.Rat).SetInt(vr), nil, nil |
| case *big.Rat: |
| return vl, vr, nil, nil |
| } |
| } |
| return nil, nil, nil, fmt.Errorf("mismatched %s and %s", cl.typeString(), cr.typeString()) |
| } |
| |
| func compString(op BinaryOp, l, r string) bool { |
| switch op { |
| case EQ: |
| return l == r |
| case NE: |
| return l != r |
| case LT: |
| return l < r |
| case LE: |
| return l <= r |
| case GT: |
| return l > r |
| case GE: |
| return l >= r |
| default: |
| panic(fmt.Errorf("val: unhandled op %q", op)) |
| } |
| } |
| |
| func opCmpToBool(op BinaryOp, cmp int) bool { |
| switch op { |
| case EQ: |
| return cmp == 0 |
| case NE: |
| return cmp != 0 |
| case LT: |
| return cmp < 0 |
| case LE: |
| return cmp <= 0 |
| case GT: |
| return cmp > 0 |
| case GE: |
| return cmp >= 0 |
| default: |
| panic(fmt.Errorf("val: unhandled op %q", op)) |
| } |
| } |
| |
| func arithBigInt(op BinaryOp, l, r *big.Int) (*big.Int, error) { |
| switch op { |
| case Add: |
| return new(big.Int).Add(l, r), nil |
| case Sub: |
| return new(big.Int).Sub(l, r), nil |
| case Mul: |
| return new(big.Int).Mul(l, r), nil |
| case Div: |
| if r.Cmp(bigIntZero) == 0 { |
| return nil, errDivZero |
| } |
| return new(big.Int).Quo(l, r), nil |
| case Mod: |
| if r.Cmp(bigIntZero) == 0 { |
| return nil, errDivZero |
| } |
| return new(big.Int).Rem(l, r), nil |
| case BitAnd: |
| return new(big.Int).And(l, r), nil |
| case BitOr: |
| return new(big.Int).Or(l, r), nil |
| case BitXor: |
| return new(big.Int).Xor(l, r), nil |
| default: |
| panic(fmt.Errorf("val: unhandled op %q", op)) |
| } |
| } |
| |
| func arithBigRat(op BinaryOp, l, r *big.Rat) (*big.Rat, error) { |
| switch op { |
| case Add: |
| return new(big.Rat).Add(l, r), nil |
| case Sub: |
| return new(big.Rat).Sub(l, r), nil |
| case Mul: |
| return new(big.Rat).Mul(l, r), nil |
| case Div: |
| if r.Cmp(bigRatZero) == 0 { |
| return nil, errDivZero |
| } |
| inv := new(big.Rat).Inv(r) |
| return inv.Mul(inv, l), nil |
| default: |
| panic(fmt.Errorf("val: unhandled op %q", op)) |
| } |
| } |
| |
| func shiftBigInt(op BinaryOp, l *big.Int, n uint) *big.Int { |
| switch op { |
| case LeftShift: |
| return new(big.Int).Lsh(l, n) |
| case RightShift: |
| return new(big.Int).Rsh(l, n) |
| default: |
| panic(fmt.Errorf("val: unhandled op %q", op)) |
| } |
| } |