github.com/dop251/goja@v0.0.0-20240220182346-e401ed450204/builtin_number.go (about) 1 package goja 2 3 import ( 4 "math" 5 "sync" 6 7 "github.com/dop251/goja/ftoa" 8 ) 9 10 func (r *Runtime) toNumber(v Value) Value { 11 switch t := v.(type) { 12 case valueFloat, valueInt: 13 return v 14 case *Object: 15 switch t := t.self.(type) { 16 case *primitiveValueObject: 17 return r.toNumber(t.pValue) 18 case *objectGoReflect: 19 if t.class == classNumber && t.valueOf != nil { 20 return t.valueOf() 21 } 22 } 23 if t == r.global.NumberPrototype { 24 return _positiveZero 25 } 26 } 27 panic(r.NewTypeError("Value is not a number: %s", v)) 28 } 29 30 func (r *Runtime) numberproto_valueOf(call FunctionCall) Value { 31 return r.toNumber(call.This) 32 } 33 34 func (r *Runtime) numberproto_toString(call FunctionCall) Value { 35 var numVal Value 36 switch t := call.This.(type) { 37 case valueFloat, valueInt: 38 numVal = t 39 case *Object: 40 switch t := t.self.(type) { 41 case *primitiveValueObject: 42 numVal = r.toNumber(t.pValue) 43 case *objectGoReflect: 44 if t.class == classNumber { 45 if t.toString != nil { 46 return t.toString() 47 } 48 if t.valueOf != nil { 49 numVal = t.valueOf() 50 } 51 } 52 } 53 if t == r.global.NumberPrototype { 54 return asciiString("0") 55 } 56 } 57 if numVal == nil { 58 panic(r.NewTypeError("Value is not a number")) 59 } 60 var radix int 61 if arg := call.Argument(0); arg != _undefined { 62 radix = int(arg.ToInteger()) 63 } else { 64 radix = 10 65 } 66 67 if radix < 2 || radix > 36 { 68 panic(r.newError(r.getRangeError(), "toString() radix argument must be between 2 and 36")) 69 } 70 71 num := numVal.ToFloat() 72 73 if math.IsNaN(num) { 74 return stringNaN 75 } 76 77 if math.IsInf(num, 1) { 78 return stringInfinity 79 } 80 81 if math.IsInf(num, -1) { 82 return stringNegInfinity 83 } 84 85 if radix == 10 { 86 return asciiString(fToStr(num, ftoa.ModeStandard, 0)) 87 } 88 89 return asciiString(ftoa.FToBaseStr(num, radix)) 90 } 91 92 func (r *Runtime) numberproto_toFixed(call FunctionCall) Value { 93 num := r.toNumber(call.This).ToFloat() 94 prec := call.Argument(0).ToInteger() 95 96 if prec < 0 || prec > 100 { 97 panic(r.newError(r.getRangeError(), "toFixed() precision must be between 0 and 100")) 98 } 99 if math.IsNaN(num) { 100 return stringNaN 101 } 102 return asciiString(fToStr(num, ftoa.ModeFixed, int(prec))) 103 } 104 105 func (r *Runtime) numberproto_toExponential(call FunctionCall) Value { 106 num := r.toNumber(call.This).ToFloat() 107 precVal := call.Argument(0) 108 var prec int64 109 if precVal == _undefined { 110 return asciiString(fToStr(num, ftoa.ModeStandardExponential, 0)) 111 } else { 112 prec = precVal.ToInteger() 113 } 114 115 if math.IsNaN(num) { 116 return stringNaN 117 } 118 if math.IsInf(num, 1) { 119 return stringInfinity 120 } 121 if math.IsInf(num, -1) { 122 return stringNegInfinity 123 } 124 125 if prec < 0 || prec > 100 { 126 panic(r.newError(r.getRangeError(), "toExponential() precision must be between 0 and 100")) 127 } 128 129 return asciiString(fToStr(num, ftoa.ModeExponential, int(prec+1))) 130 } 131 132 func (r *Runtime) numberproto_toPrecision(call FunctionCall) Value { 133 numVal := r.toNumber(call.This) 134 precVal := call.Argument(0) 135 if precVal == _undefined { 136 return numVal.toString() 137 } 138 num := numVal.ToFloat() 139 prec := precVal.ToInteger() 140 141 if math.IsNaN(num) { 142 return stringNaN 143 } 144 if math.IsInf(num, 1) { 145 return stringInfinity 146 } 147 if math.IsInf(num, -1) { 148 return stringNegInfinity 149 } 150 if prec < 1 || prec > 100 { 151 panic(r.newError(r.getRangeError(), "toPrecision() precision must be between 1 and 100")) 152 } 153 154 return asciiString(fToStr(num, ftoa.ModePrecision, int(prec))) 155 } 156 157 func (r *Runtime) number_isFinite(call FunctionCall) Value { 158 switch arg := call.Argument(0).(type) { 159 case valueInt: 160 return valueTrue 161 case valueFloat: 162 f := float64(arg) 163 return r.toBoolean(!math.IsInf(f, 0) && !math.IsNaN(f)) 164 default: 165 return valueFalse 166 } 167 } 168 169 func (r *Runtime) number_isInteger(call FunctionCall) Value { 170 switch arg := call.Argument(0).(type) { 171 case valueInt: 172 return valueTrue 173 case valueFloat: 174 f := float64(arg) 175 return r.toBoolean(!math.IsNaN(f) && !math.IsInf(f, 0) && math.Floor(f) == f) 176 default: 177 return valueFalse 178 } 179 } 180 181 func (r *Runtime) number_isNaN(call FunctionCall) Value { 182 if f, ok := call.Argument(0).(valueFloat); ok && math.IsNaN(float64(f)) { 183 return valueTrue 184 } 185 return valueFalse 186 } 187 188 func (r *Runtime) number_isSafeInteger(call FunctionCall) Value { 189 arg := call.Argument(0) 190 if i, ok := arg.(valueInt); ok && i >= -(maxInt-1) && i <= maxInt-1 { 191 return valueTrue 192 } 193 if arg == _negativeZero { 194 return valueTrue 195 } 196 return valueFalse 197 } 198 199 func createNumberProtoTemplate() *objectTemplate { 200 t := newObjectTemplate() 201 t.protoFactory = func(r *Runtime) *Object { 202 return r.global.ObjectPrototype 203 } 204 205 t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) }) 206 207 t.putStr("toExponential", func(r *Runtime) Value { return r.methodProp(r.numberproto_toExponential, "toExponential", 1) }) 208 t.putStr("toFixed", func(r *Runtime) Value { return r.methodProp(r.numberproto_toFixed, "toFixed", 1) }) 209 t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.numberproto_toString, "toLocaleString", 0) }) 210 t.putStr("toPrecision", func(r *Runtime) Value { return r.methodProp(r.numberproto_toPrecision, "toPrecision", 1) }) 211 t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.numberproto_toString, "toString", 1) }) 212 t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.numberproto_valueOf, "valueOf", 0) }) 213 214 return t 215 } 216 217 var numberProtoTemplate *objectTemplate 218 var numberProtoTemplateOnce sync.Once 219 220 func getNumberProtoTemplate() *objectTemplate { 221 numberProtoTemplateOnce.Do(func() { 222 numberProtoTemplate = createNumberProtoTemplate() 223 }) 224 return numberProtoTemplate 225 } 226 227 func (r *Runtime) getNumberPrototype() *Object { 228 ret := r.global.NumberPrototype 229 if ret == nil { 230 ret = &Object{runtime: r} 231 r.global.NumberPrototype = ret 232 o := r.newTemplatedObject(getNumberProtoTemplate(), ret) 233 o.class = classNumber 234 } 235 return ret 236 } 237 238 func (r *Runtime) getParseFloat() *Object { 239 ret := r.global.parseFloat 240 if ret == nil { 241 ret = r.newNativeFunc(r.builtin_parseFloat, "parseFloat", 1) 242 r.global.parseFloat = ret 243 } 244 return ret 245 } 246 247 func (r *Runtime) getParseInt() *Object { 248 ret := r.global.parseInt 249 if ret == nil { 250 ret = r.newNativeFunc(r.builtin_parseInt, "parseInt", 2) 251 r.global.parseInt = ret 252 } 253 return ret 254 } 255 256 func createNumberTemplate() *objectTemplate { 257 t := newObjectTemplate() 258 t.protoFactory = func(r *Runtime) *Object { 259 return r.getFunctionPrototype() 260 } 261 t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) }) 262 t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("Number"), false, false, true) }) 263 264 t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.getNumberPrototype(), false, false, false) }) 265 266 t.putStr("EPSILON", func(r *Runtime) Value { return valueProp(_epsilon, false, false, false) }) 267 t.putStr("isFinite", func(r *Runtime) Value { return r.methodProp(r.number_isFinite, "isFinite", 1) }) 268 t.putStr("isInteger", func(r *Runtime) Value { return r.methodProp(r.number_isInteger, "isInteger", 1) }) 269 t.putStr("isNaN", func(r *Runtime) Value { return r.methodProp(r.number_isNaN, "isNaN", 1) }) 270 t.putStr("isSafeInteger", func(r *Runtime) Value { return r.methodProp(r.number_isSafeInteger, "isSafeInteger", 1) }) 271 t.putStr("MAX_SAFE_INTEGER", func(r *Runtime) Value { return valueProp(valueInt(maxInt-1), false, false, false) }) 272 t.putStr("MIN_SAFE_INTEGER", func(r *Runtime) Value { return valueProp(valueInt(-(maxInt - 1)), false, false, false) }) 273 t.putStr("MIN_VALUE", func(r *Runtime) Value { return valueProp(valueFloat(math.SmallestNonzeroFloat64), false, false, false) }) 274 t.putStr("MAX_VALUE", func(r *Runtime) Value { return valueProp(valueFloat(math.MaxFloat64), false, false, false) }) 275 t.putStr("NaN", func(r *Runtime) Value { return valueProp(_NaN, false, false, false) }) 276 t.putStr("NEGATIVE_INFINITY", func(r *Runtime) Value { return valueProp(_negativeInf, false, false, false) }) 277 t.putStr("parseFloat", func(r *Runtime) Value { return valueProp(r.getParseFloat(), true, false, true) }) 278 t.putStr("parseInt", func(r *Runtime) Value { return valueProp(r.getParseInt(), true, false, true) }) 279 t.putStr("POSITIVE_INFINITY", func(r *Runtime) Value { return valueProp(_positiveInf, false, false, false) }) 280 281 return t 282 } 283 284 var numberTemplate *objectTemplate 285 var numberTemplateOnce sync.Once 286 287 func getNumberTemplate() *objectTemplate { 288 numberTemplateOnce.Do(func() { 289 numberTemplate = createNumberTemplate() 290 }) 291 return numberTemplate 292 } 293 294 func (r *Runtime) getNumber() *Object { 295 ret := r.global.Number 296 if ret == nil { 297 ret = &Object{runtime: r} 298 r.global.Number = ret 299 r.newTemplatedFuncObject(getNumberTemplate(), ret, r.builtin_Number, 300 r.wrapNativeConstruct(r.builtin_newNumber, ret, r.getNumberPrototype())) 301 } 302 return ret 303 }