370 lines
9.5 KiB
Go
370 lines
9.5 KiB
Go
|
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
|
||
|
}
|