Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented BigInt #597

Merged
merged 17 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 struct {
i *big.Int
}

func (v valueBigInt) ToInteger() int64 {
return v.i.Int64()
}

func (v valueBigInt) toString() String {
return asciiString(v.i.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 v.i.String()
}

func (v valueBigInt) ToFloat() float64 {
f, _ := new(big.Float).SetInt(v.i).Float64()
return f
}

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

func (v valueBigInt) ToBoolean() bool {
return v.i.Sign() != 0
}

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

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

func (v valueBigInt) Equals(other Value) bool {
switch o := other.(type) {
case valueBigInt:
return v.i.Cmp(o.i) == 0
case valueInt:
return v.i.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 v.i.Cmp(i) == 0
}
return false
case String:
bigInt, err := stringToBigInt(o.toTrimmedUTF8())
if err != nil {
return false
}
return bigInt.Cmp(v.i) == 0
case valueBool:
return v.i.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 v.i.Cmp(o.i) == 0
}
return false
}

func (v valueBigInt) Export() interface{} {
return v.i
}

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 v.i.Sign() < 0 {
sign = 0x01
} else {
sign = 0x00
}
_ = hash.WriteByte(sign)
_, _ = hash.Write(v.i.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.class == classBigInt && 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 := 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.i.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(bigint.i, 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 := toBigInt(call.Argument(1))
return valueBigInt{new(big.Int).Mod(bigint.i, new(big.Int).Lsh(big.NewInt(1), uint(bits)))}
}

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, classBigInt)
}

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(classBigInt), 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(classBigInt), 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 = classBigInt
}
return ret
}
Loading
Loading