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 }