diff --git a/builtin_bigint.go b/builtin_bigint.go new file mode 100644 index 00000000..a50ebcf2 --- /dev/null +++ b/builtin_bigint.go @@ -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 +} diff --git a/builtin_bigint_test.go b/builtin_bigint_test.go new file mode 100644 index 00000000..79df861a --- /dev/null +++ b/builtin_bigint_test.go @@ -0,0 +1,117 @@ +package goja + +import ( + "math/big" + "testing" +) + +func TestBigInt(t *testing.T) { + const SCRIPT = `0xabcdef0123456789abcdef0123n` + b := new(big.Int) + b.SetString("0xabcdef0123456789abcdef0123", 0) + testScript(SCRIPT, (*valueBigInt)(b), t) +} + +func TestBigIntExportTo(t *testing.T) { + vm := New() + + t.Run("bigint exportType", func(t *testing.T) { + v, err := vm.RunString(`BigInt(Number.MAX_SAFE_INTEGER + 10);`) + if err != nil { + t.Fatal(err) + } + if typ := v.ExportType(); typ != typeBigInt { + t.Fatal(typ) + } + }) + + t.Run("bigint", func(t *testing.T) { + var b big.Int + err := vm.ExportTo(vm.ToValue(big.NewInt(10)), &b) + if err != nil { + t.Fatal(err) + } + if b.Cmp(big.NewInt(10)) != 0 { + t.Fatalf("bigint: %s", b.String()) + } + }) +} + +func TestBigIntFormat(t *testing.T) { + const SCRIPT = ` +assert.sameValue((1n).toString(undefined), "1", "radius undefined"); +assert.throws(RangeError, () => { (1n).toString(-1); }, "radius -1"); +assert.throws(RangeError, () => { (1n).toString(37); }, "radius 37"); +assert.sameValue((1n).toString(2), "1", "radius 2"); +assert.sameValue((10n).toString(3), "101", "radius 3"); +` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestBigIntOperator(t *testing.T) { + const SCRIPT = ` +assert.throws(TypeError, () => { 1 - 1n; }, "mix type add"); +assert.throws(TypeError, () => { 1n - 1; }, "mix type add"); +assert.throws(TypeError, () => { 1n + 1; }, "mix type sub"); +assert.throws(TypeError, () => { 1 + 1n; }, "mix type sub"); +assert.throws(TypeError, () => { 1 * 1n; }, "mix type mul"); +assert.throws(TypeError, () => { 1n * 1; }, "mix type mul"); +assert.throws(TypeError, () => { 1 / 1n; }, "mix type div"); +assert.throws(TypeError, () => { 1n / 1; }, "mix type div"); +assert.throws(TypeError, () => { 1 % 1n; }, "mix type mod"); +assert.throws(TypeError, () => { 1n % 1; }, "mix type mod"); +assert.throws(TypeError, () => { 1n ** 1; }, "mix type exp"); +assert.throws(TypeError, () => { 1 ** 1n; }, "mix type exp"); +assert.throws(TypeError, () => { 1 & 1n; }, "mix type and"); +assert.throws(TypeError, () => { 1n & 1; }, "mix type and"); +assert.throws(TypeError, () => { 1 | 1n; }, "mix type or"); +assert.throws(TypeError, () => { 1n | 1; }, "mix type or"); +assert.throws(TypeError, () => { 1 ^ 1n; }, "mix type xor"); +assert.throws(TypeError, () => { 1n ^ 1; }, "mix type xor"); +assert.throws(TypeError, () => { 1 << 1n; }, "mix type lsh"); +assert.throws(TypeError, () => { 1n << 1; }, "mix type lsh"); +assert.throws(TypeError, () => { 1 >> 1n; }, "mix type rsh"); +assert.throws(TypeError, () => { 1n >> 1; }, "mix type rsh"); +assert.throws(TypeError, () => { 1 >>> 1n; }, "mix type ursh"); +assert.throws(TypeError, () => { 1n >>> 1; }, "mix type ursh"); + +assert.sameValue(1n + 1n, 2n, "add"); +assert.sameValue(1n - 1n, 0n, "sub"); +assert.sameValue(1n * 2n, 2n, "mul"); +assert.sameValue(1n / 2n, 0n, "div"); +assert.sameValue(1n % 2n, 1n, "mod"); +assert.sameValue(1n ** 2n, 1n, "exp"); +assert.sameValue(1n & 1n, 1n, "and"); +assert.sameValue(1n | 1n, 1n, "or"); +assert.sameValue(2n ^ 1n, 3n, "xor"); +assert.sameValue(1n << 1n, 2n, "lsh"); +assert.sameValue(4n << -1n, 2n, "neg lsh"); +assert.sameValue(4n >> 1n, 2n, "rsh"); +assert.sameValue(2n >> -2n, 8n, "neg rsh"); + +let a = 1n; +assert.sameValue(++a, 2n, "inc"); +assert.sameValue(--a, 1n, "dec"); + +assert.sameValue(Object(1n) - 1n, 0n, "primitive sub"); +assert.sameValue(Object(Object(1n)) - 1n, 0n, "primitive sub"); +assert.sameValue({ [Symbol.toPrimitive]: () => 1n } - 1n, 0n, "primitive sub"); +assert.sameValue({ valueOf: () => 1n } - 1n, 0n, "valueOf sub"); + +assert.sameValue(1n > 0, true, "gt"); +assert.sameValue(0 > 1n, false, "gt"); +assert.sameValue(Object(1n) > 0, true, "gt"); +assert.sameValue(0 > Object(1n), false, "gt"); + +assert.sameValue(1n < 0, false, "lt"); +assert.sameValue(0 < 1n, true, "lt"); +assert.sameValue(Object(1n) < 0, false, "lt"); +assert.sameValue(0 < Object(1n), true, "lt"); + +assert.sameValue(1n >= 0, true, "ge"); +assert.sameValue(0 >= 1n, false, "ge"); +assert.sameValue(1n <= 0, false, "le"); +assert.sameValue(0 <= 1n, true, "le"); +` + testScriptWithTestLib(SCRIPT, _undefined, t) +} diff --git a/builtin_global.go b/builtin_global.go index 5ef4176b..2c6385ae 100644 --- a/builtin_global.go +++ b/builtin_global.go @@ -2,7 +2,6 @@ package goja import ( "errors" - "github.com/dop251/goja/unistring" "io" "math" "regexp" @@ -10,6 +9,8 @@ import ( "strings" "sync" "unicode/utf8" + + "github.com/dop251/goja/unistring" ) const hexUpper = "0123456789ABCDEF" @@ -339,6 +340,7 @@ func createGlobalObjectTemplate() *objectTemplate { t.putStr("Array", func(r *Runtime) Value { return valueProp(r.getArray(), true, false, true) }) t.putStr("String", func(r *Runtime) Value { return valueProp(r.getString(), true, false, true) }) t.putStr("Number", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) }) + t.putStr("BigInt", func(r *Runtime) Value { return valueProp(r.getBigInt(), true, false, true) }) t.putStr("RegExp", func(r *Runtime) Value { return valueProp(r.getRegExp(), true, false, true) }) t.putStr("Date", func(r *Runtime) Value { return valueProp(r.getDate(), true, false, true) }) t.putStr("Boolean", func(r *Runtime) Value { return valueProp(r.getBoolean(), true, false, true) }) diff --git a/builtin_json.go b/builtin_json.go index e99771cf..cd4a7bca 100644 --- a/builtin_json.go +++ b/builtin_json.go @@ -281,8 +281,9 @@ func (ctx *_builtinJSON_stringifyContext) do(v Value) bool { func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool { value := nilSafe(holder.get(key, nil)) - if object, ok := value.(*Object); ok { - if toJSON, ok := object.self.getStr("toJSON", nil).(*Object); ok { + switch value.(type) { + case *Object, *valueBigInt: + if toJSON, ok := ctx.r.getVStr(value, "toJSON").(*Object); ok { if c, ok := toJSON.self.assertCallable(); ok { value = c(FunctionCall{ This: value, @@ -334,6 +335,9 @@ func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool { value = valueFalse } } + if o1.exportType() == typeBigInt { + value = o1.val.ordinaryToPrimitiveNumber() + } } } } @@ -357,6 +361,8 @@ func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool { } case valueNull: ctx.buf.WriteString("null") + case *valueBigInt: + ctx.r.typeErrorResult(true, "Do not know how to serialize a BigInt") case *Object: for _, object := range ctx.stack { if value1.SameAs(object) { diff --git a/builtin_string.go b/builtin_string.go index 43369c41..067c615d 100644 --- a/builtin_string.go +++ b/builtin_string.go @@ -1,13 +1,14 @@ package goja import ( - "github.com/dop251/goja/unistring" "math" "strings" "sync" "unicode/utf16" "unicode/utf8" + "github.com/dop251/goja/unistring" + "github.com/dop251/goja/parser" "golang.org/x/text/collate" "golang.org/x/text/language" @@ -332,7 +333,7 @@ func (r *Runtime) stringproto_indexOf(call FunctionCall) Value { r.checkObjectCoercible(call.This) value := call.This.toString() target := call.Argument(0).toString() - pos := call.Argument(1).ToInteger() + pos := call.Argument(1).ToNumber().ToInteger() if pos < 0 { pos = 0 diff --git a/builtin_typedarrays.go b/builtin_typedarrays.go index b9274604..38c0376b 100644 --- a/builtin_typedarrays.go +++ b/builtin_typedarrays.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "sort" + "strings" "sync" "unsafe" @@ -290,6 +291,20 @@ func (r *Runtime) dataViewProto_getUint32(call FunctionCall) Value { panic(r.NewTypeError("Method DataView.prototype.getUint32 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) } +func (r *Runtime) dataViewProto_getBigInt64(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + return (*valueBigInt)(dv.viewedArrayBuf.getBigInt64(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0).ToNumber()), call.Argument(1), 8))) + } + panic(r.NewTypeError("Method DataView.prototype.getBigInt64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getBigUint64(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + return (*valueBigInt)(dv.viewedArrayBuf.getBigUint64(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0).ToNumber()), call.Argument(1), 8))) + } + panic(r.NewTypeError("Method DataView.prototype.getBigUint64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + func (r *Runtime) dataViewProto_setFloat32(call FunctionCall) Value { if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { idxVal := r.toIndex(call.Argument(0)) @@ -378,6 +393,28 @@ func (r *Runtime) dataViewProto_setUint32(call FunctionCall) Value { panic(r.NewTypeError("Method DataView.prototype.setUint32 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) } +func (r *Runtime) dataViewProto_setBigInt64(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := toBigInt64(call.Argument(1)) + idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 8) + dv.viewedArrayBuf.setBigInt64(idx, val, bo) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setBigInt64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_setBigUint64(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := toBigUint64(call.Argument(1)) + idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 8) + dv.viewedArrayBuf.setBigUint64(idx, val, bo) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setBigUint64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + func (r *Runtime) typedArrayProto_getBuffer(call FunctionCall) Value { if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { return ta.viewedArrayBuf.val @@ -975,6 +1012,7 @@ func (r *Runtime) typedArrayProto_set(call FunctionCall) Value { copy(ta.viewedArrayBuf.data[(ta.offset+targetOffset)*ta.elemSize:], src.viewedArrayBuf.data[src.offset*src.elemSize:(src.offset+srcLen)*src.elemSize]) } else { + checkTypedArrayMixBigInt(src.defaultCtor, ta.defaultCtor) curSrc := uintptr(unsafe.Pointer(&src.viewedArrayBuf.data[src.offset*src.elemSize])) endSrc := curSrc + uintptr(srcLen*src.elemSize) curDst := uintptr(unsafe.Pointer(&ta.viewedArrayBuf.data[(ta.offset+targetOffset)*ta.elemSize])) @@ -1173,6 +1211,7 @@ func (r *Runtime) typedArrayProto_with(call FunctionCall) Value { if !ok { panic(r.NewTypeError("%s is not a valid TypedArray", r.objectproto_toString(FunctionCall{This: call.This}))) } + ta.viewedArrayBuf.ensureNotDetached(true) length := ta.length relativeIndex := call.Argument(0).ToInteger() var actualIndex int @@ -1186,10 +1225,13 @@ func (r *Runtime) typedArrayProto_with(call FunctionCall) Value { panic(r.newError(r.getRangeError(), "Invalid typed array index")) } - // TODO BigInt - // 7. If O.[[ContentType]] is BIGINT, let numericValue be ? ToBigInt(value). - // 8. Else, let numericValue be ? ToNumber(value). - numericValue := call.Argument(1).ToNumber() + var numericValue Value + switch ta.typedArray.(type) { + case *bigInt64Array, *bigUint64Array: + numericValue = toBigInt(call.Argument(1)) + default: + numericValue = call.Argument(1).ToNumber() + } a := r.typedArrayCreate(ta.defaultCtor, intToValue(int64(length))) for k := 0; k < length; k++ { @@ -1210,6 +1252,7 @@ func (r *Runtime) typedArrayProto_toReversed(call FunctionCall) Value { if !ok { panic(r.NewTypeError("%s is not a valid TypedArray", r.objectproto_toString(FunctionCall{This: call.This}))) } + ta.viewedArrayBuf.ensureNotDetached(true) length := ta.length a := r.typedArrayCreate(ta.defaultCtor, intToValue(int64(length))) @@ -1229,6 +1272,7 @@ func (r *Runtime) typedArrayProto_toSorted(call FunctionCall) Value { if !ok { panic(r.NewTypeError("%s is not a valid TypedArray", r.objectproto_toString(FunctionCall{This: call.This}))) } + ta.viewedArrayBuf.ensureNotDetached(true) var compareFn func(FunctionCall) Value arg := call.Argument(0) @@ -1426,6 +1470,15 @@ func (r *Runtime) _newTypedArrayFromArrayBuffer(ab *arrayBufferObject, args []Va return ta.val } +func checkTypedArrayMixBigInt(src, dst *Object) { + srcType := src.self.getStr("name", nil).String() + if strings.HasPrefix(srcType, "Big") { + if !strings.HasPrefix(dst.self.getStr("name", nil).String(), "Big") { + panic(errMixBigIntType) + } + } +} + func (r *Runtime) _newTypedArrayFromTypedArray(src *typedArrayObject, newTarget *Object, taCtor typedArrayObjectCtor, proto *Object) *Object { dst := r.allocateTypedArray(newTarget, 0, taCtor, proto) src.viewedArrayBuf.ensureNotDetached(true) @@ -1437,6 +1490,8 @@ func (r *Runtime) _newTypedArrayFromTypedArray(src *typedArrayObject, newTarget copy(dst.viewedArrayBuf.data, src.viewedArrayBuf.data[src.offset*src.elemSize:]) dst.length = src.length return dst.val + } else { + checkTypedArrayMixBigInt(src.defaultCtor, newTarget) } dst.length = l for i := 0; i < l; i++ { @@ -1506,6 +1561,14 @@ func (r *Runtime) newFloat64Array(args []Value, newTarget, proto *Object) *Objec return r._newTypedArray(args, newTarget, r.newFloat64ArrayObject, proto) } +func (r *Runtime) newBigInt64Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newBigInt64ArrayObject, proto) +} + +func (r *Runtime) newBigUint64Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newBigUint64ArrayObject, proto) +} + func (r *Runtime) createArrayBufferProto(val *Object) objectImpl { b := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) byteLengthProp := &valueProperty{ @@ -1578,6 +1641,8 @@ func addTypedArrays(t *objectTemplate) { t.putStr("Int32Array", func(r *Runtime) Value { return valueProp(r.getInt32Array(), true, false, true) }) t.putStr("Float32Array", func(r *Runtime) Value { return valueProp(r.getFloat32Array(), true, false, true) }) t.putStr("Float64Array", func(r *Runtime) Value { return valueProp(r.getFloat64Array(), true, false, true) }) + t.putStr("BigInt64Array", func(r *Runtime) Value { return valueProp(r.getBigInt64Array(), true, false, true) }) + t.putStr("BigUint64Array", func(r *Runtime) Value { return valueProp(r.getBigUint64Array(), true, false, true) }) } func createTypedArrayProtoTemplate() *objectTemplate { @@ -1781,6 +1846,26 @@ func (r *Runtime) getFloat64Array() *Object { return ret } +func (r *Runtime) getBigInt64Array() *Object { + ret := r.global.BigInt64Array + if ret == nil { + ret = &Object{runtime: r} + r.global.BigInt64Array = ret + r.createTypedArrayCtor(ret, r.newBigInt64Array, "BigInt64Array", 8) + } + return ret +} + +func (r *Runtime) getBigUint64Array() *Object { + ret := r.global.BigUint64Array + if ret == nil { + ret = &Object{runtime: r} + r.global.BigUint64Array = ret + r.createTypedArrayCtor(ret, r.newBigUint64Array, "BigUint64Array", 8) + } + return ret +} + func createDataViewProtoTemplate() *objectTemplate { t := newObjectTemplate() t.protoFactory = func(r *Runtime) *Object { @@ -1819,6 +1904,8 @@ func createDataViewProtoTemplate() *objectTemplate { t.putStr("getUint8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint8, "getUint8", 1) }) t.putStr("getUint16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint16, "getUint16", 1) }) t.putStr("getUint32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint32, "getUint32", 1) }) + t.putStr("getBigInt64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getBigInt64, "getBigInt64", 1) }) + t.putStr("getBigUint64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getBigUint64, "getBigUint64", 1) }) t.putStr("setFloat32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setFloat32, "setFloat32", 2) }) t.putStr("setFloat64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setFloat64, "setFloat64", 2) }) t.putStr("setInt8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setInt8, "setInt8", 2) }) @@ -1827,6 +1914,8 @@ func createDataViewProtoTemplate() *objectTemplate { t.putStr("setUint8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint8, "setUint8", 2) }) t.putStr("setUint16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint16, "setUint16", 2) }) t.putStr("setUint32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint32, "setUint32", 2) }) + t.putStr("setBigInt64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setBigInt64, "setBigInt64", 2) }) + t.putStr("setBigUint64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setBigUint64, "setBigUint64", 2) }) t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString("DataView"), false, false, true) }) diff --git a/compiler_expr.go b/compiler_expr.go index 477580ae..5245f266 100644 --- a/compiler_expr.go +++ b/compiler_expr.go @@ -1,6 +1,8 @@ package goja import ( + "math/big" + "github.com/dop251/goja/ast" "github.com/dop251/goja/file" "github.com/dop251/goja/token" @@ -2446,7 +2448,7 @@ func (c *compiler) emitThrow(v Value) { if o, ok := v.(*Object); ok { t := nilSafe(o.self.getStr("name", nil)).toString().String() switch t { - case "TypeError": + case "TypeError", "RangeError": c.emit(loadDynamic(t)) msg := o.self.getStr("message", nil) if msg != nil { @@ -3228,6 +3230,8 @@ func (c *compiler) compileNumberLiteral(v *ast.NumberLiteral) compiledExpr { val = intToValue(num) case float64: val = floatToValue(num) + case *big.Int: + val = (*valueBigInt)(num) default: c.assert(false, int(v.Idx)-1, "Unsupported number literal type: %T", v.Value) panic("unreachable") diff --git a/compiler_test.go b/compiler_test.go index c9ba80e3..fd47617a 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -14,9 +14,18 @@ function $ERROR(message) { throw new Error(message); } -function Test262Error() { +function Test262Error(message) { + this.message = message || ""; } +Test262Error.prototype.toString = function () { + return "Test262Error: " + this.message; +}; + +Test262Error.thrower = (message) => { + throw new Test262Error(message); +}; + function assert(mustBeTrue, message) { if (mustBeTrue === true) { return; diff --git a/map_test.go b/map_test.go index 7d41e767..8a743e41 100644 --- a/map_test.go +++ b/map_test.go @@ -3,6 +3,7 @@ package goja import ( "hash/maphash" "math" + "math/big" "strconv" "testing" ) @@ -25,6 +26,8 @@ func TestMapHash(t *testing.T) { testMapHashVal(floatToValue(1.2345), floatToValue(1.2345), true, t) testMapHashVal(SymIterator, SymToStringTag, false, t) testMapHashVal(SymIterator, SymIterator, true, t) + testMapHashVal((*valueBigInt)(big.NewInt(1)), (*valueBigInt)(big.NewInt(-1)), false, t) + testMapHashVal((*valueBigInt)(big.NewInt(1)), (*valueBigInt)(big.NewInt(1)), true, t) // The following tests introduce indeterministic behaviour //testMapHashVal(asciiString("Test"), asciiString("Test1"), false, t) diff --git a/parser/lexer.go b/parser/lexer.go index 68d56d20..320943a9 100644 --- a/parser/lexer.go +++ b/parser/lexer.go @@ -3,6 +3,7 @@ package parser import ( "errors" "fmt" + "math/big" "strconv" "strings" "unicode" @@ -911,7 +912,9 @@ func parseNumberLiteral(literal string) (value interface{}, err error) { err = parseIntErr if err.(*strconv.NumError).Err == strconv.ErrRange { - if len(literal) > 2 && literal[0] == '0' && (literal[1] == 'X' || literal[1] == 'x') { + if len(literal) > 2 && + literal[0] == '0' && (literal[1] == 'X' || literal[1] == 'x') && + literal[len(literal)-1] != 'n' { // Could just be a very large number (e.g. 0x8000000000000000) var value float64 literal = literal[2:] @@ -926,6 +929,21 @@ func parseNumberLiteral(literal string) (value interface{}, err error) { } } + if len(literal) > 1 && literal[len(literal)-1] == 'n' { + if literal[0] == '0' { + if len(literal) > 2 && isDecimalDigit(rune(literal[1])) { + goto error + } + } + // Parse as big.Int + bigInt := new(big.Int) + _, ok := bigInt.SetString(literal[:len(literal)-1], 0) + if !ok { + goto error + } + return bigInt, nil + } + error: return nil, errors.New("Illegal numeric literal") } @@ -1171,6 +1189,10 @@ func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) } } end: + if self.chr == 'n' || self.chr == 'N' { + self.read() + return tkn, self.str[offset:self.chrOffset] + } if isIdentifierStart(self.chr) || isDecimalDigit(self.chr) { return token.ILLEGAL, self.str[offset:self.chrOffset] } diff --git a/parser/lexer_test.go b/parser/lexer_test.go index 31254df7..c639be84 100644 --- a/parser/lexer_test.go +++ b/parser/lexer_test.go @@ -264,6 +264,23 @@ Second line \ token.NUMBER, "12.3", 5, ) + test(`1n`, + token.NUMBER, "1n", 1, + ) + + test(`1n 9007199254740991n`, + token.NUMBER, "1n", 1, + token.NUMBER, "9007199254740991n", 4, + ) + + test(`0xabn`, + token.NUMBER, "0xabn", 1, + ) + + test(`0xabcdef0123456789abcdef0123n`, + token.NUMBER, "0xabcdef0123456789abcdef0123n", 1, + ) + test("/ /=", token.SLASH, "", 1, token.QUOTIENT_ASSIGN, "", 3, diff --git a/parser/parser_test.go b/parser/parser_test.go index 43924194..e09a7d6c 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -2,6 +2,7 @@ package parser import ( "errors" + "math/big" "regexp" "strings" "testing" @@ -1041,6 +1042,18 @@ func Test_parseNumberLiteral(t *testing.T) { test("0", 0) test("0x8000000000000000", float64(9.223372036854776e+18)) + + test("1n", big.NewInt(1)) + + test("-1n", big.NewInt(-1)) + + test("0x23n", big.NewInt(35)) + + test("0xabcdef01n", big.NewInt(2882400001)) + + var n big.Int + n.SetString("0xabcdef0123456789abcdef0123", 0) + test("0xabcdef0123456789abcdef0123n", &n) }) } diff --git a/runtime.go b/runtime.go index ca869f68..fe889944 100644 --- a/runtime.go +++ b/runtime.go @@ -7,6 +7,7 @@ import ( "go/ast" "hash/maphash" "math" + "math/big" "math/bits" "math/rand" "reflect" @@ -33,6 +34,7 @@ var ( typeValue = reflect.TypeOf((*Value)(nil)).Elem() typeObject = reflect.TypeOf((*Object)(nil)) typeTime = reflect.TypeOf(time.Time{}) + typeBigInt = reflect.TypeOf((*big.Int)(nil)) typeBytes = reflect.TypeOf(([]byte)(nil)) ) @@ -52,6 +54,7 @@ type global struct { Function *Object String *Object Number *Object + BigInt *Object Boolean *Object RegExp *Object Date *Object @@ -76,6 +79,8 @@ type global struct { Int32Array *Object Float32Array *Object Float64Array *Object + BigInt64Array *Object + BigUint64Array *Object WeakSet *Object WeakMap *Object @@ -96,6 +101,7 @@ type global struct { ObjectPrototype *Object ArrayPrototype *Object NumberPrototype *Object + BigIntPrototype *Object StringPrototype *Object BooleanPrototype *Object FunctionPrototype *Object @@ -829,7 +835,18 @@ func (r *Runtime) newPrimitiveObject(value Value, proto *Object, class string) * func (r *Runtime) builtin_Number(call FunctionCall) Value { if len(call.Arguments) > 0 { - return call.Arguments[0].ToNumber() + switch t := call.Arguments[0].(type) { + case *Object: + primValue := t.toPrimitiveNumber() + if bigint, ok := primValue.(*valueBigInt); ok { + return intToValue((*big.Int)(bigint).Int64()) + } + return primValue.ToNumber() + case *valueBigInt: + return intToValue((*big.Int)(t).Int64()) + default: + return t.ToNumber() + } } else { return valueInt(0) } @@ -838,7 +855,19 @@ func (r *Runtime) builtin_Number(call FunctionCall) Value { func (r *Runtime) builtin_newNumber(args []Value, proto *Object) *Object { var v Value if len(args) > 0 { - v = args[0].ToNumber() + switch t := args[0].(type) { + case *Object: + primValue := t.toPrimitiveNumber() + if bigint, ok := primValue.(*valueBigInt); ok { + v = intToValue((*big.Int)(bigint).Int64()) + } else { + v = primValue.ToNumber() + } + case *valueBigInt: + v = intToValue((*big.Int)(t).Int64()) + default: + v = t.ToNumber() + } } else { v = intToValue(0) } @@ -1831,6 +1860,8 @@ func (r *Runtime) toValue(i interface{}, origValue reflect.Value) Value { return floatToValue(float64(i)) case float64: return floatToValue(i) + case *big.Int: + return (*valueBigInt)(new(big.Int).Set(i)) case map[string]interface{}: if i == nil { return _null diff --git a/string.go b/string.go index 632f1e3b..0eaf3ef9 100644 --- a/string.go +++ b/string.go @@ -24,6 +24,7 @@ var ( stringString String = asciiString("string") stringSymbol String = asciiString("symbol") stringNumber String = asciiString("number") + stringBigInt String = asciiString("bigint") stringNaN String = asciiString("NaN") stringInfinity = asciiString("Infinity") stringNegInfinity = asciiString("-Infinity") diff --git a/string_ascii.go b/string_ascii.go index a74dd638..ebe1eed2 100644 --- a/string_ascii.go +++ b/string_ascii.go @@ -4,6 +4,7 @@ import ( "hash/maphash" "io" "math" + "math/big" "reflect" "strconv" "strings" @@ -246,6 +247,14 @@ func (s asciiString) Equals(other Value) bool { return false } + if o, ok := other.(*valueBigInt); ok { + bigInt, err := stringToBigInt(s.toTrimmedUTF8()) + if err != nil { + return false + } + return bigInt.Cmp((*big.Int)(o)) == 0 + } + if o, ok := other.(*Object); ok { return s.Equals(o.toPrimitive()) } diff --git a/tc39_test.go b/tc39_test.go index a7ff6937..11c5c88e 100644 --- a/tc39_test.go +++ b/tc39_test.go @@ -180,25 +180,6 @@ var ( "test/language/expressions/class/cpn-class-expr-computed-property-name-from-integer-separators.js": true, "test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-integer-separators.js": true, - // BigInt - "test/built-ins/Object/seal/seal-biguint64array.js": true, - "test/built-ins/Object/seal/seal-bigint64array.js": true, - "test/built-ins/Array/prototype/toSorted/comparefn-not-a-function.js": true, - "test/built-ins/TypedArray/prototype/toReversed/this-value-invalid.js": true, - "test/built-ins/TypedArray/prototype/toSorted/comparefn-not-a-function.js": true, - "test/built-ins/TypedArray/prototype/toSorted/this-value-invalid.js": true, - "test/built-ins/RegExp/prototype/sticky/this-val-non-obj.js": true, - "test/built-ins/RegExp/prototype/source/this-val-non-obj.js": true, - "test/built-ins/RegExp/prototype/multiline/this-val-non-obj.js": true, - "test/built-ins/RegExp/prototype/ignoreCase/this-val-non-obj.js": true, - "test/built-ins/RegExp/prototype/unicode/this-val-non-obj.js": true, - "test/built-ins/RegExp/prototype/dotAll/this-val-non-obj.js": true, - "test/built-ins/RegExp/prototype/global/this-val-non-obj.js": true, - "test/built-ins/RegExp/prototype/flags/this-val-non-obj.js": true, - "test/built-ins/Iterator/prototype/Symbol.iterator/return-val.js": true, - "test/built-ins/DataView/prototype/setBigUint64/not-a-constructor.js": true, - "test/built-ins/DataView/prototype/getBigUint64/not-a-constructor.js": true, - // Regexp "test/language/literals/regexp/invalid-range-negative-lookbehind.js": true, "test/language/literals/regexp/invalid-range-lookbehind.js": true, @@ -227,7 +208,6 @@ var ( featuresBlackList = []string{ "async-iteration", "Symbol.asyncIterator", - "BigInt", "resizable-arraybuffer", "regexp-named-groups", "regexp-duplicate-named-groups", @@ -327,10 +307,6 @@ func init() { "test/language/eval-code/direct/async-gen-", - // BigInt - "test/built-ins/TypedArrayConstructors/BigUint64Array/", - "test/built-ins/TypedArrayConstructors/BigInt64Array/", - // restricted unicode regexp syntax "test/language/literals/regexp/u-", diff --git a/typedarrays.go b/typedarrays.go index 9af03503..67c46772 100644 --- a/typedarrays.go +++ b/typedarrays.go @@ -2,6 +2,7 @@ package goja import ( "math" + "math/big" "reflect" "strconv" "unsafe" @@ -65,6 +66,8 @@ type uint32Array []byte type int32Array []byte type float32Array []byte type float64Array []byte +type bigInt64Array []byte +type bigUint64Array []byte type typedArrayObject struct { baseObject @@ -630,6 +633,136 @@ func (a *float64Array) exportType() reflect.Type { return typeFloat64Array } +func (a *bigInt64Array) toRaw(value Value) uint64 { + return toBigInt64(value).Uint64() +} + +func (a *bigInt64Array) ptr(idx int) *int64 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*int64)(unsafe.Pointer(uintptr(p) + uintptr(idx)*8)) +} + +func (a *bigInt64Array) get(idx int) Value { + return (*valueBigInt)(big.NewInt(*a.ptr(idx))) +} + +func toBigInt64(v Value) *big.Int { + n := (*big.Int)(toBigInt(v)) + + twoTo64 := new(big.Int).Lsh(big.NewInt(1), 64) + twoTo63 := new(big.Int).Lsh(big.NewInt(1), 63) + + int64bit := new(big.Int).Mod(n, twoTo64) + if int64bit.Cmp(twoTo63) >= 0 { + return int64bit.Sub(int64bit, twoTo64) + } + return int64bit +} + +func (a *bigInt64Array) set(idx int, value Value) { + *(a.ptr(idx)) = toBigInt64(value).Int64() +} + +func (a *bigInt64Array) getRaw(idx int) uint64 { + return uint64(*a.ptr(idx)) +} + +func (a *bigInt64Array) setRaw(idx int, raw uint64) { + *(a.ptr(idx)) = int64(raw) +} + +func (a *bigInt64Array) less(i, j int) bool { + return *(a.ptr(i)) < *(a.ptr(j)) +} + +func (a *bigInt64Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *bigInt64Array) typeMatch(v Value) bool { + if _, ok := v.(*valueBigInt); ok { + return true + } + return false +} + +func (a *bigInt64Array) export(offset int, length int) interface{} { + var res []int64 + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&res)) + sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*8 + sliceHeader.Len = length + sliceHeader.Cap = length + return res +} + +var typeBigInt64Array = reflect.TypeOf(([]int64)(nil)) + +func (a *bigInt64Array) exportType() reflect.Type { + return typeBigInt64Array +} + +func (a *bigUint64Array) toRaw(value Value) uint64 { + return toBigUint64(value).Uint64() +} + +func (a *bigUint64Array) ptr(idx int) *uint64 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*uint64)(unsafe.Pointer(uintptr(p) + uintptr(idx)*8)) +} + +func (a *bigUint64Array) get(idx int) Value { + return (*valueBigInt)(new(big.Int).SetUint64(*a.ptr(idx))) +} + +func toBigUint64(v Value) *big.Int { + n := (*big.Int)(toBigInt(v)) + return new(big.Int).Mod(n, new(big.Int).Lsh(big.NewInt(1), 64)) +} + +func (a *bigUint64Array) set(idx int, value Value) { + *(a.ptr(idx)) = toBigUint64(value).Uint64() +} + +func (a *bigUint64Array) getRaw(idx int) uint64 { + return *a.ptr(idx) +} + +func (a *bigUint64Array) setRaw(idx int, raw uint64) { + *(a.ptr(idx)) = raw +} + +func (a *bigUint64Array) less(i, j int) bool { + return *(a.ptr(i)) < *(a.ptr(j)) +} + +func (a *bigUint64Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *bigUint64Array) typeMatch(v Value) bool { + if _, ok := v.(*valueBigInt); ok { + return true + } + return false +} + +func (a *bigUint64Array) export(offset int, length int) interface{} { + var res []uint64 + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&res)) + sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*8 + sliceHeader.Len = length + sliceHeader.Cap = length + return res +} + +var typeBigUint64Array = reflect.TypeOf(([]uint64)(nil)) + +func (a *bigUint64Array) exportType() reflect.Type { + return typeBigUint64Array +} + func (a *typedArrayObject) _getIdx(idx int) Value { if 0 <= idx && idx < a.length { if !a.viewedArrayBuf.ensureNotDetached(false) { @@ -698,7 +831,12 @@ func (a *typedArrayObject) isValidIntegerIndex(idx int) bool { } func (a *typedArrayObject) _putIdx(idx int, v Value) { - v = v.ToNumber() + switch a.typedArray.(type) { + case *bigInt64Array, *bigUint64Array: + v = toBigInt(v) + default: + v = v.ToNumber() + } if a.isValidIntegerIndex(idx) { a.typedArray.set(idx+a.offset, v) } @@ -715,7 +853,7 @@ func (a *typedArrayObject) setOwnStr(p unistring.String, v Value, throw bool) bo return true } if idx == 0 { - v.ToNumber() // make sure it throws + toNumeric(v) // make sure it throws return true } return a.baseObject.setOwnStr(p, v, throw) @@ -938,6 +1076,14 @@ func (r *Runtime) newFloat64ArrayObject(buf *arrayBufferObject, offset, length i return r._newTypedArrayObject(buf, offset, length, 8, r.global.Float64Array, (*float64Array)(&buf.data), proto) } +func (r *Runtime) newBigInt64ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 8, r.global.BigInt64Array, (*bigInt64Array)(&buf.data), proto) +} + +func (r *Runtime) newBigUint64ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 8, r.global.BigUint64Array, (*bigUint64Array)(&buf.data), proto) +} + func (o *dataViewObject) getIdxAndByteOrder(getIdx int, littleEndianVal Value, size int) (int, byteOrder) { o.viewedArrayBuf.ensureNotDetached(true) if getIdx+size > o.byteLen { @@ -1026,6 +1172,52 @@ func (o *arrayBufferObject) setUint32(idx int, val uint32, byteOrder byteOrder) } } +func (o *arrayBufferObject) getBigInt64(idx int, byteOrder byteOrder) *big.Int { + var b []byte + if byteOrder == nativeEndian { + b = o.data[idx : idx+8] + } else { + b = make([]byte, 8) + d := o.data[idx : idx+8] + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7] = d[7], d[6], d[5], d[4], d[3], d[2], d[1], d[0] + } + return big.NewInt(*((*int64)(unsafe.Pointer(&b[0])))) +} + +func (o *arrayBufferObject) getBigUint64(idx int, byteOrder byteOrder) *big.Int { + var b []byte + if byteOrder == nativeEndian { + b = o.data[idx : idx+8] + } else { + b = make([]byte, 8) + d := o.data[idx : idx+8] + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7] = d[7], d[6], d[5], d[4], d[3], d[2], d[1], d[0] + } + return new(big.Int).SetUint64(*((*uint64)(unsafe.Pointer(&b[0])))) +} + +func (o *arrayBufferObject) setBigInt64(idx int, val *big.Int, byteOrder byteOrder) { + if byteOrder == nativeEndian { + *(*int64)(unsafe.Pointer(&o.data[idx])) = val.Int64() + } else { + n := val.Int64() + b := (*[8]byte)(unsafe.Pointer(&n)) + d := o.data[idx : idx+8] + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7] = b[7], b[6], b[5], b[4], b[3], b[2], b[1], b[0] + } +} + +func (o *arrayBufferObject) setBigUint64(idx int, val *big.Int, byteOrder byteOrder) { + if byteOrder == nativeEndian { + *(*uint64)(unsafe.Pointer(&o.data[idx])) = val.Uint64() + } else { + n := val.Uint64() + b := (*[8]byte)(unsafe.Pointer(&n)) + d := o.data[idx : idx+8] + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7] = b[7], b[6], b[5], b[4], b[3], b[2], b[1], b[0] + } +} + func (o *arrayBufferObject) getUint16(idx int, byteOrder byteOrder) uint16 { var b []byte if byteOrder == nativeEndian { diff --git a/typedarrays_test.go b/typedarrays_test.go index 2da2e8a3..92653d6a 100644 --- a/typedarrays_test.go +++ b/typedarrays_test.go @@ -513,4 +513,32 @@ func TestTypedArrayExport(t *testing.T) { } }) + t.Run("bigint64", func(t *testing.T) { + v, err := vm.RunString("new BigInt64Array([18446744073709551617n, 2n])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]int64); ok { + if len(a) != 2 || a[0] != 1 || a[1] != 2 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + }) + + t.Run("biguint64", func(t *testing.T) { + v, err := vm.RunString("new BigUint64Array([18446744073709551617n, 2n])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]uint64); ok { + if len(a) != 2 || a[0] != 1 || a[1] != 2 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + }) + } diff --git a/value.go b/value.go index e5031e72..6ca3d72d 100644 --- a/value.go +++ b/value.go @@ -4,6 +4,7 @@ import ( "fmt" "hash/maphash" "math" + "math/big" "reflect" "strconv" "unsafe" @@ -141,6 +142,7 @@ type valueProperty struct { var ( errAccessBeforeInit = referenceError("Cannot access a variable before initialization") errAssignToConst = typeError("Assignment to constant variable.") + errMixBigIntType = typeError("Cannot mix BigInt and other types, use explicit conversions") ) func propGetter(o Value, v Value, r *Runtime) *Object { @@ -218,6 +220,8 @@ func (i valueInt) Equals(other Value) bool { switch o := other.(type) { case valueInt: return i == o + case *valueBigInt: + return (*big.Int)(o).Cmp(big.NewInt(int64(i))) == 0 case valueFloat: return float64(i) == float64(o) case String: @@ -643,6 +647,15 @@ func (f valueFloat) Equals(other Value) bool { return f == o case valueInt: return float64(f) == float64(o) + case *valueBigInt: + if IsInfinity(f) || math.IsNaN(float64(f)) { + return false + } + if f := big.NewFloat(float64(f)); f.IsInt() { + i, _ := f.Int(nil) + return (*big.Int)(o).Cmp(i) == 0 + } + return false case String, valueBool: return float64(f) == o.ToFloat() case *Object: @@ -728,7 +741,7 @@ func (o *Object) Equals(other Value) bool { } switch o1 := other.(type) { - case valueInt, valueFloat, String, *Symbol: + case valueInt, valueFloat, *valueBigInt, String, *Symbol: return o.toPrimitive().Equals(other) case valueBool: return o.Equals(o1.ToNumber()) diff --git a/vm.go b/vm.go index c09e00da..35e55940 100644 --- a/vm.go +++ b/vm.go @@ -3,6 +3,7 @@ package goja import ( "fmt" "math" + "math/big" "strconv" "strings" "sync" @@ -415,17 +416,20 @@ func floatToValue(f float64) (result Value) { return valueFloat(f) } -func assertInt64(v Value) (int64, bool) { - num := v.ToNumber() - if i, ok := num.(valueInt); ok { - return int64(i), true - } - if f, ok := num.(valueFloat); ok { - if i, ok := floatToInt(float64(f)); ok { - return i, true +func toNumeric(value Value) Value { + switch v := value.(type) { + case valueInt, *valueBigInt: + return v + case valueFloat: + return floatToValue(float64(v)) + case *Object: + primValue := v.toPrimitiveNumber() + if bigint, ok := primValue.(*valueBigInt); ok { + return bigint } + return primValue.ToNumber() } - return 0, false + return value.ToNumber() } func (s *valueStack) expand(idx int) { @@ -1236,7 +1240,7 @@ type _toNumber struct{} var toNumber _toNumber func (_toNumber) exec(vm *vm) { - vm.stack[vm.sp-1] = vm.stack[vm.sp-1].ToNumber() + vm.stack[vm.sp-1] = toNumeric(vm.stack[vm.sp-1]) vm.pc++ } @@ -1270,13 +1274,26 @@ func (_add) exec(vm *vm) { } ret = leftString.Concat(rightString) } else { - if leftInt, ok := left.(valueInt); ok { - if rightInt, ok := right.(valueInt); ok { - ret = intToValue(int64(leftInt) + int64(rightInt)) + switch left := left.(type) { + case valueInt: + switch right := right.(type) { + case valueInt: + ret = intToValue(int64(left) + int64(right)) + case *valueBigInt: + panic(errMixBigIntType) + default: + ret = floatToValue(float64(left) + right.ToFloat()) + } + case *valueBigInt: + if right, ok := right.(*valueBigInt); ok { + ret = (*valueBigInt)(new(big.Int).Add((*big.Int)(left), (*big.Int)(right))) } else { - ret = floatToValue(float64(leftInt) + right.ToFloat()) + panic(errMixBigIntType) + } + default: + if _, ok := right.(*valueBigInt); ok { + panic(errMixBigIntType) } - } else { ret = floatToValue(left.ToFloat() + right.ToFloat()) } } @@ -1294,13 +1311,30 @@ func (_sub) exec(vm *vm) { right := vm.stack[vm.sp-1] left := vm.stack[vm.sp-2] + left = toNumeric(left) + right = toNumeric(right) + var result Value - if left, ok := left.(valueInt); ok { - if right, ok := right.(valueInt); ok { + switch left := left.(type) { + case valueInt: + switch right := right.(type) { + case valueInt: result = intToValue(int64(left) - int64(right)) goto end + case *valueBigInt: + panic(errMixBigIntType) + } + case valueFloat: + if _, ok := right.(*valueBigInt); ok { + panic(errMixBigIntType) + } + case *valueBigInt: + if right, ok := right.(*valueBigInt); ok { + result = (*valueBigInt)(new(big.Int).Sub((*big.Int)(left), (*big.Int)(right))) + goto end } + panic(errMixBigIntType) } result = floatToValue(left.ToFloat() - right.ToFloat()) @@ -1315,13 +1349,15 @@ type _mul struct{} var mul _mul func (_mul) exec(vm *vm) { - left := vm.stack[vm.sp-2] - right := vm.stack[vm.sp-1] + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) var result Value - if left, ok := assertInt64(left); ok { - if right, ok := assertInt64(right); ok { + switch left := left.(type) { + case valueInt: + switch right := right.(type) { + case valueInt: if left == 0 && right == -1 || left == -1 && right == 0 { result = _negativeZero goto end @@ -1329,11 +1365,22 @@ func (_mul) exec(vm *vm) { res := left * right // check for overflow if left == 0 || right == 0 || res/left == right { - result = intToValue(res) + result = intToValue(int64(res)) goto end } - + case *valueBigInt: + panic(errMixBigIntType) + } + case valueFloat: + if _, ok := right.(*valueBigInt); ok { + panic(errMixBigIntType) } + case *valueBigInt: + if right, ok := right.(*valueBigInt); ok { + result = (*valueBigInt)(new(big.Int).Mul((*big.Int)(left), (*big.Int)(right))) + goto end + } + panic(errMixBigIntType) } result = floatToValue(left.ToFloat() * right.ToFloat()) @@ -1350,7 +1397,29 @@ var exp _exp func (_exp) exec(vm *vm) { vm.sp-- - vm.stack[vm.sp-1] = pow(vm.stack[vm.sp-1], vm.stack[vm.sp]) + x := vm.stack[vm.sp-1] + y := vm.stack[vm.sp] + + x = toNumeric(x) + y = toNumeric(y) + + var result Value + if x, ok := x.(*valueBigInt); ok { + if y, ok := y.(*valueBigInt); ok { + if (*big.Int)(y).Cmp(big.NewInt(0)) < 0 { + panic(vm.r.newError(vm.r.getRangeError(), "exponent must be positive")) + } + result = (*valueBigInt)(new(big.Int).Exp((*big.Int)(x), (*big.Int)(y), nil)) + goto end + } + panic(errMixBigIntType) + } else if _, ok := y.(*valueBigInt); ok { + panic(errMixBigIntType) + } + + result = pow(x, y) +end: + vm.stack[vm.sp-1] = result vm.pc++ } @@ -1359,10 +1428,32 @@ type _div struct{} var div _div func (_div) exec(vm *vm) { - left := vm.stack[vm.sp-2].ToFloat() - right := vm.stack[vm.sp-1].ToFloat() - - var result Value + leftValue := toNumeric(vm.stack[vm.sp-2]) + rightValue := toNumeric(vm.stack[vm.sp-1]) + + var ( + result Value + left, right float64 + ) + + if left, ok := leftValue.(*valueBigInt); ok { + if right, ok := rightValue.(*valueBigInt); ok { + if (*big.Int)(right).Cmp(big.NewInt(0)) == 0 { + panic(vm.r.newError(vm.r.getRangeError(), "Division by zero")) + } + if (*big.Int)(left).CmpAbs((*big.Int)(right)) < 0 { + result = (*valueBigInt)(big.NewInt(0)) + } else { + i, _ := new(big.Int).QuoRem((*big.Int)(left), (*big.Int)(right), big.NewInt(0)) + result = (*valueBigInt)(i) + } + goto end + } + panic(errMixBigIntType) + } else if _, ok := rightValue.(*valueBigInt); ok { + panic(errMixBigIntType) + } + left, right = leftValue.ToFloat(), rightValue.ToFloat() if math.IsNaN(left) || math.IsNaN(right) { result = _NaN @@ -1418,25 +1509,48 @@ type _mod struct{} var mod _mod func (_mod) exec(vm *vm) { - left := vm.stack[vm.sp-2] - right := vm.stack[vm.sp-1] + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) var result Value - if leftInt, ok := assertInt64(left); ok { - if rightInt, ok := assertInt64(right); ok { - if rightInt == 0 { + switch left := left.(type) { + case valueInt: + switch right := right.(type) { + case valueInt: + if right == 0 { result = _NaN goto end } - r := leftInt % rightInt - if r == 0 && leftInt < 0 { + r := left % right + if r == 0 && left < 0 { result = _negativeZero } else { - result = intToValue(leftInt % rightInt) + result = intToValue(int64(left % right)) + } + goto end + case *valueBigInt: + panic(errMixBigIntType) + } + case valueFloat: + if _, ok := right.(*valueBigInt); ok { + panic(errMixBigIntType) + } + case *valueBigInt: + if right, ok := right.(*valueBigInt); ok { + switch { + case (*big.Int)(right).Cmp(big.NewInt(0)) == 0: + panic(vm.r.newError(vm.r.getRangeError(), "Division by zero")) + case (*big.Int)(left).Cmp(big.NewInt(0)) < 0: + abs := new(big.Int).Abs((*big.Int)(left)) + v := new(big.Int).Mod(abs, (*big.Int)(right)) + result = (*valueBigInt)(v.Neg(v)) + default: + result = (*valueBigInt)(new(big.Int).Mod((*big.Int)(left), (*big.Int)(right))) } goto end } + panic(errMixBigIntType) } result = floatToValue(math.Mod(left.ToFloat(), right.ToFloat())) @@ -1455,13 +1569,16 @@ func (_neg) exec(vm *vm) { var result Value - if i, ok := assertInt64(operand); ok { - if i == 0 { + switch n := toNumeric(operand).(type) { + case *valueBigInt: + result = (*valueBigInt)(new(big.Int).Neg((*big.Int)(n))) + case valueInt: + if n == 0 { result = _negativeZero } else { - result = valueInt(-i) + result = -n } - } else { + default: f := operand.ToFloat() if !math.IsNaN(f) { f = -f @@ -1489,14 +1606,15 @@ var inc _inc func (_inc) exec(vm *vm) { v := vm.stack[vm.sp-1] - if i, ok := assertInt64(v); ok { - v = intToValue(i + 1) - goto end + switch n := v.(type) { + case *valueBigInt: + v = (*valueBigInt)(new(big.Int).Add((*big.Int)(n), big.NewInt(1))) + case valueInt: + v = intToValue(int64(n + 1)) + default: + v = valueFloat(n.ToFloat() + 1) } - v = valueFloat(v.ToFloat() + 1) - -end: vm.stack[vm.sp-1] = v vm.pc++ } @@ -1508,14 +1626,15 @@ var dec _dec func (_dec) exec(vm *vm) { v := vm.stack[vm.sp-1] - if i, ok := assertInt64(v); ok { - v = intToValue(i - 1) - goto end + switch n := v.(type) { + case *valueBigInt: + v = (*valueBigInt)(new(big.Int).Sub((*big.Int)(n), big.NewInt(1))) + case valueInt: + v = intToValue(int64(n - 1)) + default: + v = valueFloat(n.ToFloat() - 1) } - v = valueFloat(v.ToFloat() - 1) - -end: vm.stack[vm.sp-1] = v vm.pc++ } @@ -1525,9 +1644,23 @@ type _and struct{} var and _and func (_and) exec(vm *vm) { - left := toInt32(vm.stack[vm.sp-2]) - right := toInt32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left & right)) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + var result Value + + if left, ok := left.(*valueBigInt); ok { + if right, ok := right.(*valueBigInt); ok { + result = (*valueBigInt)(new(big.Int).And((*big.Int)(left), (*big.Int)(right))) + goto end + } + panic(errMixBigIntType) + } else if _, ok := right.(*valueBigInt); ok { + panic(errMixBigIntType) + } + + result = intToValue(int64(toInt32(left) & toInt32(right))) +end: + vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } @@ -1537,9 +1670,23 @@ type _or struct{} var or _or func (_or) exec(vm *vm) { - left := toInt32(vm.stack[vm.sp-2]) - right := toInt32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left | right)) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + var result Value + + if left, ok := left.(*valueBigInt); ok { + if right, ok := right.(*valueBigInt); ok { + result = (*valueBigInt)(new(big.Int).Or((*big.Int)(left), (*big.Int)(right))) + goto end + } + panic(errMixBigIntType) + } else if _, ok := right.(*valueBigInt); ok { + panic(errMixBigIntType) + } + + result = intToValue(int64(toInt32(left) | toInt32(right))) +end: + vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } @@ -1549,9 +1696,23 @@ type _xor struct{} var xor _xor func (_xor) exec(vm *vm) { - left := toInt32(vm.stack[vm.sp-2]) - right := toInt32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left ^ right)) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + var result Value + + if left, ok := left.(*valueBigInt); ok { + if right, ok := right.(*valueBigInt); ok { + result = (*valueBigInt)(new(big.Int).Xor((*big.Int)(left), (*big.Int)(right))) + goto end + } + panic(errMixBigIntType) + } else if _, ok := right.(*valueBigInt); ok { + panic(errMixBigIntType) + } + + result = intToValue(int64(toInt32(left) ^ toInt32(right))) +end: + vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } @@ -1561,8 +1722,14 @@ type _bnot struct{} var bnot _bnot func (_bnot) exec(vm *vm) { - op := toInt32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-1] = intToValue(int64(^op)) + v := vm.stack[vm.sp-1] + switch n := toNumeric(v).(type) { + case *valueBigInt: + v = (*valueBigInt)(new(big.Int).Not((*big.Int)(n))) + default: + v = intToValue(int64(^toInt32(n))) + } + vm.stack[vm.sp-1] = v vm.pc++ } @@ -1571,9 +1738,28 @@ type _sal struct{} var sal _sal func (_sal) exec(vm *vm) { - left := toInt32(vm.stack[vm.sp-2]) - right := toUint32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left << (right & 0x1F))) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + var result Value + + if left, ok := left.(*valueBigInt); ok { + if right, ok := right.(*valueBigInt); ok { + n := uint((*big.Int)(right).Uint64()) + if (*big.Int)(right).Sign() < 0 { + result = (*valueBigInt)(new(big.Int).Rsh((*big.Int)(left), n)) + } else { + result = (*valueBigInt)(new(big.Int).Lsh((*big.Int)(left), n)) + } + goto end + } + panic(errMixBigIntType) + } else if _, ok := right.(*valueBigInt); ok { + panic(errMixBigIntType) + } + + result = intToValue(int64(toInt32(left) << (toUint32(right) & 0x1F))) +end: + vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } @@ -1583,9 +1769,28 @@ type _sar struct{} var sar _sar func (_sar) exec(vm *vm) { - left := toInt32(vm.stack[vm.sp-2]) - right := toUint32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left >> (right & 0x1F))) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + var result Value + + if left, ok := left.(*valueBigInt); ok { + if right, ok := right.(*valueBigInt); ok { + n := uint((*big.Int)(right).Uint64()) + if (*big.Int)(right).Sign() < 0 { + result = (*valueBigInt)(new(big.Int).Lsh((*big.Int)(left), n)) + } else { + result = (*valueBigInt)(new(big.Int).Rsh((*big.Int)(left), n)) + } + goto end + } + panic(errMixBigIntType) + } else if _, ok := right.(*valueBigInt); ok { + panic(errMixBigIntType) + } + + result = intToValue(int64(toInt32(left) >> (toUint32(right) & 0x1F))) +end: + vm.stack[vm.sp-2] = result vm.sp-- vm.pc++ } @@ -1595,9 +1800,17 @@ type _shr struct{} var shr _shr func (_shr) exec(vm *vm) { - left := toUint32(vm.stack[vm.sp-2]) - right := toUint32(vm.stack[vm.sp-1]) - vm.stack[vm.sp-2] = intToValue(int64(left >> (right & 0x1F))) + left := toNumeric(vm.stack[vm.sp-2]) + right := toNumeric(vm.stack[vm.sp-1]) + + if _, ok := left.(*valueBigInt); ok { + _ = toNumeric(right) + panic(vm.r.NewTypeError("BigInts have no unsigned right shift, use >> instead")) + } else if _, ok := right.(*valueBigInt); ok { + panic(vm.r.NewTypeError("BigInts have no unsigned right shift, use >> instead")) + } + + vm.stack[vm.sp-2] = intToValue(int64(toUint32(left) >> (toUint32(right) & 0x1F))) vm.sp-- vm.pc++ } @@ -4203,31 +4416,94 @@ func toPrimitive(v Value) Value { func cmp(px, py Value) Value { var ret bool - var nx, ny float64 + xs, isPxString := px.(String) + ys, isPyString := py.(String) - if xs, ok := px.(String); ok { - if ys, ok := py.(String); ok { - ret = xs.CompareTo(ys) < 0 + if isPxString && isPyString { + ret = xs.CompareTo(ys) < 0 + goto end + } else { + if px, ok := px.(*valueBigInt); ok && isPyString { + ny, err := stringToBigInt(ys.toTrimmedUTF8()) + if err != nil { + return _undefined + } + ret = (*big.Int)(px).Cmp(ny) < 0 + goto end + } + if py, ok := py.(*valueBigInt); ok && isPxString { + nx, err := stringToBigInt(xs.toTrimmedUTF8()) + if err != nil { + return _undefined + } + ret = nx.Cmp((*big.Int)(py)) < 0 goto end } } - if xi, ok := px.(valueInt); ok { - if yi, ok := py.(valueInt); ok { - ret = xi < yi + px = toNumeric(px) + py = toNumeric(py) + + switch nx := px.(type) { + case valueInt: + switch ny := py.(type) { + case valueInt: + ret = nx < ny + goto end + case *valueBigInt: + ret = big.NewInt(int64(nx)).Cmp((*big.Int)(ny)) < 0 + goto end + } + case valueFloat: + switch ny := py.(type) { + case *valueBigInt: + switch { + case math.IsNaN(float64(nx)): + return _undefined + case nx == _negativeInf: + ret = true + goto end + } + if nx := big.NewFloat(float64(nx)); nx.IsInt() { + nx, _ := nx.Int(nil) + ret = nx.Cmp((*big.Int)(ny)) < 0 + } else { + ret = nx.Cmp(new(big.Float).SetInt((*big.Int)(ny))) < 0 + } + goto end + } + case *valueBigInt: + switch ny := py.(type) { + case valueInt: + ret = (*big.Int)(nx).Cmp(big.NewInt(int64(ny))) < 0 + goto end + case valueFloat: + switch { + case math.IsNaN(float64(ny)): + return _undefined + case ny == _positiveInf: + ret = true + goto end + } + if ny := big.NewFloat(float64(ny)); ny.IsInt() { + ny, _ := ny.Int(nil) + ret = (*big.Int)(nx).Cmp(ny) < 0 + } else { + ret = new(big.Float).SetInt((*big.Int)(nx)).Cmp(ny) < 0 + } + goto end + case *valueBigInt: + ret = (*big.Int)(nx).Cmp((*big.Int)(ny)) < 0 goto end } } - nx = px.ToFloat() - ny = py.ToFloat() - - if math.IsNaN(nx) || math.IsNaN(ny) { + if nx, ny := px.ToFloat(), py.ToFloat(); math.IsNaN(nx) || math.IsNaN(ny) { return _undefined + } else { + ret = nx < ny } - ret = nx < ny - end: if ret { return valueTrue @@ -4581,6 +4857,8 @@ func (_typeof) exec(vm *vm) { r = stringString case valueInt, valueFloat: r = stringNumber + case *valueBigInt: + r = stringBigInt case *Symbol: r = stringSymbol default: