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  }