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