-
Notifications
You must be signed in to change notification settings - Fork 383
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #556
- Loading branch information
Showing
20 changed files
with
1,306 additions
and
126 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.