github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/vals/concat.go (about) 1 package vals 2 3 import ( 4 "errors" 5 "fmt" 6 "math/big" 7 ) 8 9 // Concatter wraps the Concat method. See Concat for how it is used. 10 type Concatter interface { 11 // Concat concatenates the receiver with another value, the receiver being 12 // the left operand. If concatenation is not supported for the given value, 13 // the method can return the special error type ErrCatNotImplemented. 14 Concat(v interface{}) (interface{}, error) 15 } 16 17 // RConcatter wraps the RConcat method. See Concat for how it is used. 18 type RConcatter interface { 19 RConcat(v interface{}) (interface{}, error) 20 } 21 22 // ErrConcatNotImplemented is a special error value used to signal that 23 // concatenation is not implemented. See Concat for how it is used. 24 var ErrConcatNotImplemented = errors.New("concat not implemented") 25 26 type cannotConcat struct { 27 lhsKind string 28 rhsKind string 29 } 30 31 func (err cannotConcat) Error() string { 32 return fmt.Sprintf("cannot concatenate %s and %s", err.lhsKind, err.rhsKind) 33 } 34 35 // Concat concatenates two values. If both operands are strings, it returns lhs 36 // + rhs, nil. If the left operand implements Concatter, it calls 37 // lhs.Concat(rhs). If lhs doesn't implement the interface or returned 38 // ErrConcatNotImplemented, it then calls rhs.RConcat(lhs). If all attempts 39 // fail, it returns nil and an error. 40 func Concat(lhs, rhs interface{}) (interface{}, error) { 41 if v, ok := tryConcatBuiltins(lhs, rhs); ok { 42 return v, nil 43 } 44 45 if lhs, ok := lhs.(Concatter); ok { 46 v, err := lhs.Concat(rhs) 47 if err != ErrConcatNotImplemented { 48 return v, err 49 } 50 } 51 52 if rhs, ok := rhs.(RConcatter); ok { 53 v, err := rhs.RConcat(lhs) 54 if err != ErrConcatNotImplemented { 55 return v, err 56 } 57 } 58 59 return nil, cannotConcat{Kind(lhs), Kind(rhs)} 60 } 61 62 func tryConcatBuiltins(lhs, rhs interface{}) (interface{}, bool) { 63 switch lhs := lhs.(type) { 64 case string, int, *big.Int, *big.Rat, float64: 65 switch rhs := rhs.(type) { 66 case string, int, *big.Int, *big.Rat, float64: 67 return ToString(lhs) + ToString(rhs), true 68 } 69 } 70 71 return nil, false 72 }