github.com/primecitizens/pcz/std@v0.2.1/ffi/js/number.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright 2023 The Prime Citizens 3 4 package js 5 6 import ( 7 "unsafe" 8 9 "github.com/primecitizens/pcz/std/core/assert" 10 "github.com/primecitizens/pcz/std/core/math" 11 "github.com/primecitizens/pcz/std/core/num" 12 "github.com/primecitizens/pcz/std/ffi/js/bindings" 13 ) 14 15 type bigintTypes interface { 16 ~uint | ~uint64 | ~uintptr | ~int64 17 } 18 19 func NewBigInt[T bigintTypes](value T) BigInt[T] { 20 if unsafe.Sizeof(value) != 8 { 21 assert.Throw("unexpected", "size") 22 } 23 24 return BigInt[T]{ 25 ref: bindings.BigInt( 26 bindings.Ref(Bool(num.IsSignedType[T]())), 27 Pointer(&value), 28 ), 29 } 30 } 31 32 type BigInt[T bigintTypes] struct { 33 ref bindings.Ref 34 } 35 36 func (b BigInt[T]) FromRef(ref Ref) BigInt[T] { 37 return BigInt[T]{ 38 ref: bindings.Ref(ref), 39 } 40 } 41 42 func (b BigInt[T]) Ref() Ref { 43 return Ref(b.ref) 44 } 45 46 func (b BigInt[T]) Once() BigInt[T] { 47 bindings.Once(b.ref) 48 return b 49 } 50 51 func (b BigInt[T]) Free() { 52 bindings.Free(b.ref) 53 } 54 55 func (b BigInt[T]) Get() T { 56 var value T 57 if unsafe.Sizeof(value) != 8 { 58 assert.Throw("unexpected", "size") 59 } 60 61 if bindings.Ref(True) != bindings.GetBigInt( 62 b.ref, 63 bindings.Ref(Bool(num.IsSignedType[T]())), 64 Pointer(&value), 65 ) { 66 assert.Throw("not", "a", "bigint") 67 } 68 69 return value 70 } 71 72 func (b BigInt[T]) Set(value T) bool { 73 if unsafe.Sizeof(value) != 8 { 74 assert.Throw("unexpected", "size") 75 } 76 77 return bindings.Ref(True) == bindings.ReplaceBigInt( 78 b.ref, 79 bindings.Ref(Bool(num.IsSignedType[T]())), 80 Pointer(&value), 81 ) 82 } 83 84 type numTypes interface { 85 ~uint8 | ~uint16 | ~uint32 | ~int8 | ~int16 | ~int32 | ~float32 | ~float64 86 } 87 88 func NewNumber[T numTypes](x T) Number[T] { 89 maxCache := smallIntCacheSize 90 if x != x { 91 return Number[T]{ 92 ref: bindings.Ref(NaN), 93 } 94 } 95 96 if num.IsFloatType[T]() { 97 switch math.Float64bits(float64(x)) { 98 case math.Inf: 99 return Number[T]{ 100 ref: bindings.Ref(Inf), 101 } 102 case math.NegInf: 103 return Number[T]{ 104 ref: bindings.Ref(NegativeInf), 105 } 106 } 107 } 108 109 if x < T(0) || // negative number 110 x > T(maxCache) || // uncached 111 x/2 != 0 { // floating point 112 return Number[T]{ 113 ref: bindings.Ref(bindings.Number(float64(x))), 114 } 115 } 116 117 return Number[T]{ 118 ref: bindings.Ref(Ref(x) + firstSmallIntCache), 119 } 120 } 121 122 // Number represents a js number living in the heap. 123 // 124 // NOTE: This type is defined for code not working directly with imported js functions 125 // (using go:wasmimport) and should be avoided whenever possible. 126 type Number[T numTypes] struct { 127 ref bindings.Ref 128 } 129 130 func (b Number[T]) FromRef(ref Ref) Number[T] { 131 return Number[T]{ 132 ref: bindings.Ref(ref), 133 } 134 } 135 136 func (b Number[T]) Ref() Ref { 137 return Ref(b.ref) 138 } 139 140 func (b Number[T]) Once() Number[T] { 141 bindings.Once(b.ref) 142 return b 143 } 144 145 func (b Number[T]) Free() { 146 bindings.Free(b.ref) 147 } 148 149 func (b Number[T]) Get() T { 150 elemSz, signed, float := num.CheckType[T]() 151 152 if b.ref < bindings.Ref(firstSmallIntCache) { 153 var value float64 154 switch b.ref { 155 case bindings.Ref(NaN): 156 value = math.Float64frombits(math.NaN) 157 case bindings.Ref(Inf): 158 value = math.Float64frombits(math.Inf) 159 case bindings.Ref(NegativeInf): 160 value = math.Float64frombits(math.NegInf) 161 default: 162 assert.Throw("not", "a", "number") 163 } 164 165 if !float { 166 assert.Throw("not", "float") 167 } 168 169 switch elemSz { 170 case 4, 8: 171 default: 172 assert.Unreachable() 173 } 174 175 return T(value) 176 } 177 178 if b.ref <= bindings.Ref(lastSmallIntCache) { 179 x := b.ref - bindings.Ref(firstSmallIntCache) 180 return T(x) 181 } 182 183 var v float64 184 if bindings.Ref(True) != bindings.GetNum(b.ref, Pointer(&v)) { 185 assert.Throw("not", "a", "number") 186 } 187 188 if float { 189 // is floating-point type 190 switch elemSz { 191 case 4: 192 if v > math.MaxFloat32 || v < -math.MaxFloat32 { 193 assert.Throw("out", "of", "range") 194 } 195 case 8: 196 default: 197 assert.Unreachable() 198 } 199 200 return T(v) 201 } 202 203 // is integer type 204 if signed { 205 switch elemSz { 206 case 1: 207 if v > math.MaxInt8 || v < math.MinInt8 { 208 assert.Throw("out", "of", "range") 209 } 210 case 2: 211 if v > math.MaxInt16 || v < math.MinInt16 { 212 assert.Throw("out", "of", "range") 213 } 214 case 4: 215 if v > math.MaxInt32 || v < math.MinInt32 { 216 assert.Throw("out", "of", "range") 217 } 218 default: 219 assert.Unreachable() 220 } 221 } 222 223 if v < 0 { 224 assert.Throw("underflow") 225 } 226 227 switch elemSz { 228 case 1: 229 if v > math.MaxUint8 { 230 assert.Throw("overflow") 231 } 232 case 2: 233 if v > math.MaxUint16 { 234 assert.Throw("overflow") 235 } 236 case 4: 237 if v > math.MaxUint32 { 238 assert.Throw("overflow") 239 } 240 default: 241 assert.Unreachable() 242 } 243 244 return T(v) 245 } 246 247 func (b *Number[T]) Set(value T) bool { 248 if b.ref <= bindings.Ref(lastSmallIntCache) { 249 b.ref = NewNumber(value).ref 250 return true 251 } 252 253 return bindings.Ref(True) == bindings.ReplaceNum( 254 b.ref, float64(value), 255 ) 256 } 257 258 func (b Number[T]) IsNaN() bool { 259 return b.ref == bindings.Ref(NaN) 260 } 261 262 func (b Number[T]) IsInf(sign int) bool { 263 return (sign >= 0 && b.ref == bindings.Ref(Inf)) || 264 (sign <= 0 && b.ref == bindings.Ref(NegativeInf)) 265 }