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 }