github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/runtime/numconv.go (about)

     1  package runtime
     2  
     3  import (
     4  	"strconv"
     5  	"strings"
     6  )
     7  
     8  // NumberType represents a type of number
     9  type NumberType uint16
    10  
    11  const (
    12  	// IsFloat is the type of Floats
    13  	IsFloat NumberType = 1 << iota
    14  	// IsInt is the type of Ints
    15  	IsInt
    16  	// NaN is the type of values which are not numbers
    17  	NaN
    18  	// NaI is a type for values which a not Ints
    19  	NaI
    20  )
    21  
    22  func numberType(v Value) NumberType {
    23  	switch v.iface.(type) {
    24  	case int64:
    25  		return IsInt
    26  	case float64:
    27  		return IsFloat
    28  	default:
    29  		return NaN
    30  	}
    31  }
    32  
    33  // ToNumber returns x as a Float or Int, and the type (IsFloat, IsInt or NaN).
    34  func ToNumber(v Value) (int64, float64, NumberType) {
    35  	switch v.iface.(type) {
    36  	case int64:
    37  		return v.AsInt(), 0, IsInt
    38  	case float64:
    39  		return 0, v.AsFloat(), IsFloat
    40  	case string:
    41  		s := v.AsString()
    42  		return StringToNumber(strings.TrimSpace(s))
    43  	}
    44  	return 0, 0, NaN
    45  }
    46  
    47  // ToNumberValue returns the Value v as a Float or Int, and if it is a number.
    48  // If it is not a number, it returns v unchanged and NaN.
    49  func ToNumberValue(v Value) (Value, NumberType) {
    50  	switch v.NumberType() {
    51  	case IntType:
    52  		return v, IsInt
    53  	case FloatType:
    54  		return v, IsFloat
    55  	}
    56  	if s, ok := v.TryString(); ok {
    57  		n, f, tp := StringToNumber(strings.TrimSpace(s))
    58  		switch tp {
    59  		case IsInt:
    60  			return IntValue(n), IsInt
    61  		case IsFloat:
    62  			return FloatValue(f), IsFloat
    63  		}
    64  	}
    65  	return v, NaN
    66  }
    67  
    68  // ToInt returns v as an Int and true if v is actually a valid integer.
    69  func ToInt(v Value) (int64, bool) {
    70  	if n, ok := v.TryInt(); ok {
    71  		return n, true
    72  	}
    73  	if f, ok := v.TryFloat(); ok {
    74  		n, tp := FloatToInt(f)
    75  		return n, tp == IsInt
    76  	}
    77  	if s, ok := v.TryString(); ok {
    78  		n, tp := stringToInt(s)
    79  		return n, tp == IsInt
    80  	}
    81  	return 0, false
    82  }
    83  
    84  // ToIntNoString returns v as an Int and true if v is actually a valid integer.
    85  func ToIntNoString(v Value) (int64, bool) {
    86  	switch v.iface.(type) {
    87  	case int64:
    88  		return v.AsInt(), true
    89  	case float64:
    90  		n, tp := FloatToInt(v.AsFloat())
    91  		return n, tp == IsInt
    92  	}
    93  	return 0, false
    94  }
    95  
    96  // ToFloat returns v as a FLoat and true if v is a valid float.
    97  func ToFloat(v Value) (float64, bool) {
    98  	switch v.iface.(type) {
    99  	case int64:
   100  		return float64(v.AsInt()), true
   101  	case float64:
   102  		return v.AsFloat(), true
   103  	case string:
   104  		n, f, tp := StringToNumber(v.AsString())
   105  		switch tp {
   106  		case IsInt:
   107  			return float64(n), true
   108  		case IsFloat:
   109  			return f, true
   110  		}
   111  	}
   112  	return 0, false
   113  }
   114  
   115  // FloatToInt turns a float64 into an int64 if possible.
   116  func FloatToInt(f float64) (int64, NumberType) {
   117  	n := int64(f)
   118  	if float64(n) == f {
   119  		return n, IsInt
   120  	}
   121  	return 0, NaI
   122  }
   123  
   124  //
   125  // String
   126  //
   127  
   128  // stringToInt turns a string into and int64 if possible.
   129  func stringToInt(s string) (int64, NumberType) {
   130  	n, f, tp := StringToNumber(s)
   131  	switch tp {
   132  	case IsInt:
   133  		return n, IsInt
   134  	case IsFloat:
   135  		return FloatToInt(f)
   136  	}
   137  	return 0, NaN
   138  }
   139  
   140  func StringToNumber(s string) (n int64, f float64, tp NumberType) {
   141  	s = strings.TrimSpace(s)
   142  	var err error
   143  	if len(s) == 0 {
   144  		tp = NaN
   145  		return
   146  	}
   147  	var i0 = 0
   148  	// If the string starts with -?0[xX] then it may be an hex number
   149  	if s[0] == '+' {
   150  		s = s[1:]
   151  	} else if s[0] == '-' || s[0] == '+' {
   152  		i0++
   153  	}
   154  	var isHex = len(s) >= 2+i0 && s[i0] == '0' && (s[i0+1] == 'x' || s[i0+1] == 'X')
   155  	var isFloat = isHex && strings.ContainsAny(s, ".pP") || !isHex && strings.ContainsAny(s, ".eE")
   156  	if isFloat {
   157  		// This is to make strconv.ParseFloat happy
   158  		if isHex && !strings.ContainsAny(s, "pP") {
   159  			s = s + "p0"
   160  		}
   161  		f, err = strconv.ParseFloat(s, 64)
   162  		if err != nil && f == 0 {
   163  			tp = NaN
   164  			return
   165  		}
   166  		tp = IsFloat
   167  		return
   168  	}
   169  
   170  	// If s is an hex number, it is parsed as a uint of 64 bits
   171  	if isHex {
   172  		us := s[2+i0:]
   173  		if len(us) > 16 {
   174  			us = us[len(us)-16:]
   175  		}
   176  		un, err := strconv.ParseUint(us, 16, 64)
   177  		if err != nil {
   178  			tp = NaN
   179  			return
   180  		}
   181  		n = int64(un)
   182  		if s[0] == '-' {
   183  			n = -n
   184  		}
   185  		tp = IsInt
   186  		return
   187  	}
   188  	n, err = strconv.ParseInt(s, 10, 64)
   189  	if err != nil {
   190  		if err.(*strconv.NumError).Err == strconv.ErrRange {
   191  			// Try a float instead
   192  			f, err = strconv.ParseFloat(s, 64)
   193  			if err == nil || f != 0 {
   194  				tp = IsFloat
   195  				return
   196  			}
   197  		}
   198  		tp = NaN
   199  		return
   200  	}
   201  	tp = IsInt
   202  	return
   203  }