1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
package vals
import (
"math"
"math/big"
"unsafe"
)
// Ordering relationship between two Elvish values.
type Ordering uint8
// Possible Ordering values.
const (
CmpLess Ordering = iota
CmpEqual
CmpMore
CmpUncomparable
)
// Cmp compares two Elvish values and returns the ordering relationship between
// them. Cmp(a, b) returns CmpEqual iff Equal(a, b) is true or both a and b are
// NaNs.
func Cmp(a, b any) Ordering {
return cmpInner(a, b, Cmp)
}
func cmpInner(a, b any, recurse func(a, b any) Ordering) Ordering {
// Keep the branches in the same order as [Equal].
switch a := a.(type) {
case nil:
if b == nil {
return CmpEqual
}
case bool:
if b, ok := b.(bool); ok {
switch {
case a == b:
return CmpEqual
//lint:ignore S1002 using booleans as values, not conditions
case a == false: // b == true is implicit
return CmpLess
default: // a == true && b == false
return CmpMore
}
}
case int, *big.Int, *big.Rat, float64:
switch b.(type) {
case int, *big.Int, *big.Rat, float64:
a, b := UnifyNums2(a, b, 0)
switch a := a.(type) {
case int:
return compareBuiltin(a, b.(int))
case *big.Int:
return compareBuiltin(a.Cmp(b.(*big.Int)), 0)
case *big.Rat:
return compareBuiltin(a.Cmp(b.(*big.Rat)), 0)
case float64:
return compareFloat(a, b.(float64))
default:
panic("unreachable")
}
}
case string:
if b, ok := b.(string); ok {
return compareBuiltin(a, b)
}
case List:
if b, ok := b.(List); ok {
aIt := a.Iterator()
bIt := b.Iterator()
for aIt.HasElem() && bIt.HasElem() {
o := recurse(aIt.Elem(), bIt.Elem())
if o != CmpEqual {
return o
}
aIt.Next()
bIt.Next()
}
switch {
case a.Len() == b.Len():
return CmpEqual
case a.Len() < b.Len():
return CmpLess
default: // a.Len() > b.Len()
return CmpMore
}
}
default:
if Equal(a, b) {
return CmpEqual
}
}
return CmpUncomparable
}
func compareBuiltin[T interface{ int | uintptr | string }](a, b T) Ordering {
if a < b {
return CmpLess
} else if a > b {
return CmpMore
}
return CmpEqual
}
func compareFloat(a, b float64) Ordering {
// For the sake of ordering, NaN's are considered equal to each
// other and smaller than all numbers
switch {
case math.IsNaN(a):
if math.IsNaN(b) {
return CmpEqual
}
return CmpLess
case math.IsNaN(b):
return CmpMore
case a < b:
return CmpLess
case a > b:
return CmpMore
default: // a == b
return CmpEqual
}
}
// CmpTotal is similar to [Cmp], but uses an artificial total ordering to avoid
// returning [CmpUncomparable]:
//
// - If a and b have different types, it compares their types instead. The
// ordering of types is guaranteed to be consistent during one Elvish
// session, but is otherwise undefined.
//
// - If a and b have the same type but are considered uncomparable by [Cmp],
// it returns [CmpEqual] instead of [CmpUncomparable].
//
// All the underlying Go types of Elvish's number type are considered the same
// type.
//
// This function is mainly useful for sorting Elvish values that are not
// considered comparable by [Cmp]. Using this function as a comparator groups
// values by their types and sorts types that are comparable.
func CmpTotal(a, b any) Ordering {
if o := compareBuiltin(typeOf(a), typeOf(b)); o != CmpEqual {
return o
}
if o := cmpInner(a, b, CmpTotal); o != CmpUncomparable {
return o
}
return CmpEqual
}
var typeOfInt, typeOfMap uintptr
func typeOf(x any) uintptr {
switch x.(type) {
case *big.Int, *big.Rat, float64:
return typeOfInt
case StructMap:
return typeOfMap
}
// The first word of an empty interface is a pointer to the type descriptor.
return *(*uintptr)(unsafe.Pointer(&x))
}
func init() {
typeOfInt = typeOf(0)
typeOfMap = typeOf(EmptyMap)
}
|