Skip to content

Commit

Permalink
Support JS BigInt value to big.Int Go type (rogchap#63)
Browse files Browse the repository at this point in the history
* Support JS BigInt value to big.Int Go type

* simplfy test cases

* handle posible race condition

* debug Go 1.12.7

* nil check on big.Int for Go 1.12.x
  • Loading branch information
rogchap authored Jan 16, 2021
1 parent 9015bb9 commit eba122d
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Support for the BigInt value to the big.Int Go type

## [v0.4.0] - 2021-01-14

### Added
Expand Down
16 changes: 16 additions & 0 deletions v8go.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <cstdlib>
#include <cstring>
#include <iostream>
#include <sstream>
#include <string>

Expand Down Expand Up @@ -266,6 +267,21 @@ uint32_t ValueToUint32(ValuePtr ptr) {
return value->Uint32Value(ctx->ptr.Get(iso)).ToChecked();
}

ValueBigInt ValueToBigInt(ValuePtr ptr) {
LOCAL_VALUE(ptr);
MaybeLocal<BigInt> bint = value->ToBigInt(ctx->ptr.Get(iso));
if (bint.IsEmpty()) {
return {nullptr, 0};
}

int word_count = bint.ToLocalChecked()->WordCount();
int sign_bit = 0;
uint64_t* words = new uint64_t[word_count];
bint.ToLocalChecked()->ToWordsArray(&sign_bit, &word_count, words);
ValueBigInt rtn = {words, word_count, sign_bit};
return rtn;
}

int ValueIsUndefined(ValuePtr ptr) {
LOCAL_VALUE(ptr);
return value->IsUndefined();
Expand Down
7 changes: 7 additions & 0 deletions v8go.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ typedef struct {
size_t number_of_detached_contexts;
} IsolateHStatistics;

typedef struct {
const uint64_t* word_array;
int word_count;
int sign_bit;
} ValueBigInt;

extern void Init();
extern IsolatePtr NewIsolate();
extern void IsolateDispose(IsolatePtr ptr);
Expand All @@ -57,6 +63,7 @@ int64_t ValueToInteger(ValuePtr ptr);
double ValueToNumber(ValuePtr ptr);
const char* ValueToDetailString(ValuePtr ptr);
uint32_t ValueToUint32(ValuePtr ptr);
extern ValueBigInt ValueToBigInt(ValuePtr ptr);
int ValueIsUndefined(ValuePtr ptr);
int ValueIsNull(ValuePtr ptr);
int ValueIsNullOrUndefined(ValuePtr ptr);
Expand Down
26 changes: 23 additions & 3 deletions value.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "C"
import (
"fmt"
"io"
"math/big"
"runtime"
"unsafe"
)
Expand Down Expand Up @@ -43,9 +44,28 @@ func (v *Value) ArrayIndex() (idx uint32, ok bool) {
}

// BigInt perform the equivalent of `BigInt(value)` in JS.
func (v *Value) bigInt() struct{} { // *BigInt
//TODO(rogchap): implement and export public API
panic("not implemented")
func (v *Value) BigInt() *big.Int {
if v == nil {
return nil
}
bint := C.ValueToBigInt(v.ptr)
defer C.free(unsafe.Pointer(bint.word_array))
if bint.word_array == nil {
return nil
}
words := (*[1 << 30]big.Word)(unsafe.Pointer(bint.word_array))[:bint.word_count:bint.word_count]

abs := make([]big.Word, len(words))
copy(abs, words)

b := &big.Int{}
b.SetBits(abs)

if bint.sign_bit == 1 {
b.Neg(b)
}

return b
}

// Boolean perform the equivalent of `Boolean(value)` in JS. This can never fail.
Expand Down
45 changes: 45 additions & 0 deletions value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v8go_test
import (
"fmt"
"math"
"math/big"
"reflect"
"runtime"
"testing"
Expand Down Expand Up @@ -306,6 +307,50 @@ func TestValueUint32(t *testing.T) {
}
}

func TestValueBigInt(t *testing.T) {
t.Parallel()
iso, _ := v8go.NewIsolate()

x, _ := new(big.Int).SetString("36893488147419099136", 10) // larger than a single word size (64bit)

tests := [...]struct {
source string
expected *big.Int
}{
{"BigInt(0)", &big.Int{}},
{"-1n", big.NewInt(-1)},
{"new BigInt(1)", nil}, // bad syntax
{"BigInt(Number.MAX_SAFE_INTEGER)", big.NewInt(1<<53 - 1)},
{"BigInt(Number.MIN_SAFE_INTEGER)", new(big.Int).Neg(big.NewInt(1<<53 - 1))},
{"BigInt(Number.MAX_SAFE_INTEGER) * 2n", big.NewInt(1<<54 - 2)},
{"BigInt(Number.MAX_SAFE_INTEGER) * 4096n", x},
}

for _, tt := range tests {
tt := tt
t.Run(tt.source, func(t *testing.T) {
t.Parallel()
ctx, _ := v8go.NewContext(iso)
val, _ := ctx.RunScript(tt.source, "test.js")
b := val.BigInt()
fmt.Printf("b = %+v\n", b)
fmt.Printf("tt = %+v\n", tt)
if b == nil && tt.expected != nil {
t.Errorf("uexpected <nil> value")
return
}
if b != nil && tt.expected == nil {
t.Errorf("expected <nil>, but got value: %v", b)
return
}
if b != nil && b.Cmp(tt.expected) != 0 {
t.Errorf("unexpected value: expected %v, got %v", tt.expected, b)
}
})
}

}

func TestValueIsXXX(t *testing.T) {
t.Parallel()
iso, _ := v8go.NewIsolate()
Expand Down

0 comments on commit eba122d

Please sign in to comment.