Skip to content

Commit

Permalink
Implemented BigInt (#597)
Browse files Browse the repository at this point in the history
Closes #556
  • Loading branch information
shiroyk authored Aug 22, 2024
1 parent 8130cad commit fa6d1ed
Show file tree
Hide file tree
Showing 20 changed files with 1,306 additions and 126 deletions.
369 changes: 369 additions & 0 deletions builtin_bigint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,369 @@
package goja

import (
"fmt"
"hash/maphash"
"math"
"math/big"
"reflect"
"strconv"
"sync"

"github.com/dop251/goja/unistring"
)

type valueBigInt big.Int

func (v *valueBigInt) ToInteger() int64 {
v.ToNumber()
return 0
}

func (v *valueBigInt) toString() String {
return asciiString((*big.Int)(v).String())
}

func (v *valueBigInt) string() unistring.String {
return unistring.String(v.String())
}

func (v *valueBigInt) ToString() Value {
return v
}

func (v *valueBigInt) String() string {
return (*big.Int)(v).String()
}

func (v *valueBigInt) ToFloat() float64 {
v.ToNumber()
return 0
}

func (v *valueBigInt) ToNumber() Value {
panic(typeError("Cannot convert a BigInt value to a number"))
}

func (v *valueBigInt) ToBoolean() bool {
return (*big.Int)(v).Sign() != 0
}

func (v *valueBigInt) ToObject(r *Runtime) *Object {
return r.newPrimitiveObject(v, r.getBigIntPrototype(), classObject)
}

func (v *valueBigInt) SameAs(other Value) bool {
if o, ok := other.(*valueBigInt); ok {
return (*big.Int)(v).Cmp((*big.Int)(o)) == 0
}
return false
}

func (v *valueBigInt) Equals(other Value) bool {
switch o := other.(type) {
case *valueBigInt:
return (*big.Int)(v).Cmp((*big.Int)(o)) == 0
case valueInt:
return (*big.Int)(v).Cmp(big.NewInt(int64(o))) == 0
case valueFloat:
if IsInfinity(o) || math.IsNaN(float64(o)) {
return false
}
if f := big.NewFloat(float64(o)); f.IsInt() {
i, _ := f.Int(nil)
return (*big.Int)(v).Cmp(i) == 0
}
return false
case String:
bigInt, err := stringToBigInt(o.toTrimmedUTF8())
if err != nil {
return false
}
return bigInt.Cmp((*big.Int)(v)) == 0
case valueBool:
return (*big.Int)(v).Int64() == o.ToInteger()
case *Object:
return v.Equals(o.toPrimitiveNumber())
}
return false
}

func (v *valueBigInt) StrictEquals(other Value) bool {
o, ok := other.(*valueBigInt)
if ok {
return (*big.Int)(v).Cmp((*big.Int)(o)) == 0
}
return false
}

func (v *valueBigInt) Export() interface{} {
return new(big.Int).Set((*big.Int)(v))
}

func (v *valueBigInt) ExportType() reflect.Type {
return typeBigInt
}

func (v *valueBigInt) baseObject(rt *Runtime) *Object {
return rt.getBigIntPrototype()
}

func (v *valueBigInt) hash(hash *maphash.Hash) uint64 {
var sign byte
if (*big.Int)(v).Sign() < 0 {
sign = 0x01
} else {
sign = 0x00
}
_ = hash.WriteByte(sign)
_, _ = hash.Write((*big.Int)(v).Bytes())
h := hash.Sum64()
hash.Reset()
return h
}

func toBigInt(value Value) *valueBigInt {
// Undefined Throw a TypeError exception.
// Null Throw a TypeError exception.
// Boolean Return 1n if prim is true and 0n if prim is false.
// BigInt Return prim.
// Number Throw a TypeError exception.
// String 1. Let n be StringToBigInt(prim).
// 2. If n is undefined, throw a SyntaxError exception.
// 3. Return n.
// Symbol Throw a TypeError exception.
switch prim := value.(type) {
case *valueBigInt:
return prim
case String:
bigInt, err := stringToBigInt(prim.toTrimmedUTF8())
if err != nil {
panic(syntaxError(fmt.Sprintf("Cannot convert %s to a BigInt", prim)))
}
return (*valueBigInt)(bigInt)
case valueBool:
return (*valueBigInt)(big.NewInt(prim.ToInteger()))
case *Symbol:
panic(typeError("Cannot convert Symbol to a BigInt"))
case *Object:
return toBigInt(prim.toPrimitiveNumber())
default:
panic(typeError(fmt.Sprintf("Cannot convert %s to a BigInt", prim)))
}
}

func numberToBigInt(v Value) *valueBigInt {
switch v := toNumeric(v).(type) {
case *valueBigInt:
return v
case valueInt:
return (*valueBigInt)(big.NewInt(v.ToInteger()))
case valueFloat:
if IsInfinity(v) || math.IsNaN(float64(v)) {
panic(rangeError(fmt.Sprintf("Cannot convert %s to a BigInt", v)))
}
if f := big.NewFloat(float64(v)); f.IsInt() {
n, _ := f.Int(nil)
return (*valueBigInt)(n)
}
panic(rangeError(fmt.Sprintf("Cannot convert %s to a BigInt", v)))
case *Object:
prim := v.toPrimitiveNumber()
switch prim.(type) {
case valueInt, valueFloat:
return numberToBigInt(prim)
default:
return toBigInt(prim)
}
default:
panic(newTypeError("Cannot convert %s to a BigInt", v))
}
}

func stringToBigInt(str string) (*big.Int, error) {
var bigint big.Int
n, err := stringToInt(str)
if err != nil {
switch {
case isRangeErr(err):
bigint.SetString(str, 0)
case err == strconv.ErrSyntax:
default:
return nil, strconv.ErrSyntax
}
} else {
bigint.SetInt64(n)
}
return &bigint, nil
}

func (r *Runtime) thisBigIntValue(value Value) Value {
switch t := value.(type) {
case *valueBigInt:
return t
case *Object:
switch t := t.self.(type) {
case *primitiveValueObject:
return r.thisBigIntValue(t.pValue)
case *objectGoReflect:
if t.exportType() == typeBigInt && t.valueOf != nil {
return t.valueOf()
}
}
}
panic(r.NewTypeError("requires that 'this' be a BigInt"))
}

func (r *Runtime) bigintproto_valueOf(call FunctionCall) Value {
return r.thisBigIntValue(call.This)
}

func (r *Runtime) bigintproto_toString(call FunctionCall) Value {
x := (*big.Int)(r.thisBigIntValue(call.This).(*valueBigInt))
radix := call.Argument(0)
var radixMV int

if radix == _undefined {
radixMV = 10
} else {
radixMV = int(radix.ToInteger())
if radixMV < 2 || radixMV > 36 {
panic(r.newError(r.getRangeError(), "radix must be an integer between 2 and 36"))
}
}

return asciiString(x.Text(radixMV))
}

func (r *Runtime) bigint_asIntN(call FunctionCall) Value {
if len(call.Arguments) < 2 {
panic(r.NewTypeError("Cannot convert undefined to a BigInt"))
}
bits := r.toIndex(call.Argument(0).ToNumber())
if bits < 0 {
panic(r.NewTypeError("Invalid value: not (convertible to) a safe integer"))
}
bigint := toBigInt(call.Argument(1))

twoToBits := new(big.Int).Lsh(big.NewInt(1), uint(bits))
mod := new(big.Int).Mod((*big.Int)(bigint), twoToBits)
if bits > 0 && mod.Cmp(new(big.Int).Lsh(big.NewInt(1), uint(bits-1))) >= 0 {
return (*valueBigInt)(mod.Sub(mod, twoToBits))
} else {
return (*valueBigInt)(mod)
}
}

func (r *Runtime) bigint_asUintN(call FunctionCall) Value {
if len(call.Arguments) < 2 {
panic(r.NewTypeError("Cannot convert undefined to a BigInt"))
}
bits := r.toIndex(call.Argument(0).ToNumber())
if bits < 0 {
panic(r.NewTypeError("Invalid value: not (convertible to) a safe integer"))
}
bigint := (*big.Int)(toBigInt(call.Argument(1)))
ret := new(big.Int).Mod(bigint, new(big.Int).Lsh(big.NewInt(1), uint(bits)))
return (*valueBigInt)(ret)
}

var bigintTemplate *objectTemplate
var bigintTemplateOnce sync.Once

func getBigIntTemplate() *objectTemplate {
bigintTemplateOnce.Do(func() {
bigintTemplate = createBigIntTemplate()
})
return bigintTemplate
}

func createBigIntTemplate() *objectTemplate {
t := newObjectTemplate()
t.protoFactory = func(r *Runtime) *Object {
return r.getFunctionPrototype()
}

t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) })
t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) })
t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.getBigIntPrototype(), false, false, false) })

t.putStr("asIntN", func(r *Runtime) Value { return r.methodProp(r.bigint_asIntN, "asIntN", 2) })
t.putStr("asUintN", func(r *Runtime) Value { return r.methodProp(r.bigint_asUintN, "asUintN", 2) })

return t
}

func (r *Runtime) builtin_BigInt(call FunctionCall) Value {
if len(call.Arguments) > 0 {
switch v := call.Argument(0).(type) {
case *valueBigInt, valueInt, valueFloat, *Object:
return numberToBigInt(v)
default:
return toBigInt(v)
}
}
return (*valueBigInt)(big.NewInt(0))
}

func (r *Runtime) builtin_newBigInt(args []Value, newTarget *Object) *Object {
if newTarget != nil {
panic(r.NewTypeError("BigInt is not a constructor"))
}
var v Value
if len(args) > 0 {
v = numberToBigInt(args[0])
} else {
v = (*valueBigInt)(big.NewInt(0))
}
return r.newPrimitiveObject(v, newTarget, classObject)
}

func (r *Runtime) getBigInt() *Object {
ret := r.global.BigInt
if ret == nil {
ret = &Object{runtime: r}
r.global.BigInt = ret
r.newTemplatedFuncObject(getBigIntTemplate(), ret, r.builtin_BigInt,
r.wrapNativeConstruct(r.builtin_newBigInt, ret, r.getBigIntPrototype()))
}
return ret
}

func createBigIntProtoTemplate() *objectTemplate {
t := newObjectTemplate()
t.protoFactory = func(r *Runtime) *Object {
return r.global.ObjectPrototype
}

t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(0), false, false, true) })
t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) })
t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getBigInt(), true, false, true) })

t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.bigintproto_toString, "toLocaleString", 0) })
t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.bigintproto_toString, "toString", 0) })
t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.bigintproto_valueOf, "valueOf", 0) })
t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString("BigInt"), false, false, true) })

return t
}

var bigintProtoTemplate *objectTemplate
var bigintProtoTemplateOnce sync.Once

func getBigIntProtoTemplate() *objectTemplate {
bigintProtoTemplateOnce.Do(func() {
bigintProtoTemplate = createBigIntProtoTemplate()
})
return bigintProtoTemplate
}

func (r *Runtime) getBigIntPrototype() *Object {
ret := r.global.BigIntPrototype
if ret == nil {
ret = &Object{runtime: r}
r.global.BigIntPrototype = ret
o := r.newTemplatedObject(getBigIntProtoTemplate(), ret)
o.class = classObject
}
return ret
}
Loading

0 comments on commit fa6d1ed

Please sign in to comment.