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  }