Skip to content

Commit

Permalink
Add Function.NewInstance to expose the corresponding V8 function (#134)
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanahsmith authored May 31, 2021
1 parent b3de643 commit d9a0b1b
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 4 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 calling constructors functions with NewInstance on Function

## [v0.6.0] - 2021-05-11

### Added
Expand Down
7 changes: 7 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ func getValue(ctx *Context, rtn C.RtnValue) *Value {
return &Value{rtn.value, ctx}
}

func getObject(ctx *Context, rtn C.RtnValue) *Object {
if rtn.value == nil {
return nil
}
return &Object{&Value{rtn.value, ctx}}
}

func getError(rtn C.RtnValue) error {
if rtn.error.msg == nil {
return nil
Expand Down
16 changes: 16 additions & 0 deletions function.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,19 @@ func (fn *Function) Call(args ...Valuer) (*Value, error) {
fn.ctx.deregister()
return getValue(fn.ctx, rtn), getError(rtn)
}

// Invoke a constructor function to create an object instance.
func (fn *Function) NewInstance(args ...Valuer) (*Object, error) {
var argptr *C.ValuePtr
if len(args) > 0 {
var cArgs = make([]C.ValuePtr, len(args))
for i, arg := range args {
cArgs[i] = arg.value().ptr
}
argptr = (*C.ValuePtr)(unsafe.Pointer(&cArgs[0]))
}
fn.ctx.register()
rtn := C.FunctionNewInstance(fn.ptr, C.int(len(args)), argptr)
fn.ctx.deregister()
return getObject(fn.ctx, rtn), getError(rtn)
}
51 changes: 51 additions & 0 deletions function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,57 @@ func TestFunctionCallError(t *testing.T) {
}
}

func TestFunctionNewInstance(t *testing.T) {
t.Parallel()

ctx, err := v8go.NewContext()
failIf(t, err)
iso, err := ctx.Isolate()
failIf(t, err)

value, err := ctx.Global().Get("Error")
failIf(t, err)
fn, err := value.AsFunction()
failIf(t, err)
messageObj, err := v8go.NewValue(iso, "test message")
failIf(t, err)
errObj, err := fn.NewInstance(messageObj)
failIf(t, err)

message, err := errObj.Get("message")
failIf(t, err)
if !message.IsString() {
t.Error("missing error message")
}
want := "test message"
got := message.String()
if got != want {
t.Errorf("want %+v, got: %+v", want, got)
}
}

func TestFunctionNewInstanceError(t *testing.T) {
t.Parallel()

ctx, err := v8go.NewContext()
failIf(t, err)
_, err = ctx.RunScript("function throws() { throw 'error'; }", "script.js")
failIf(t, err)
throwsValue, err := ctx.Global().Get("throws")
failIf(t, err)
fn, _ := throwsValue.AsFunction()

_, err = fn.NewInstance()
if err == nil {
t.Errorf("expected an error, got none")
}
got := *(err.(*v8go.JSError))
want := v8go.JSError{Message: "error", Location: "script.js:1:21"}
if got != want {
t.Errorf("want %+v, got: %+v", want, got)
}
}

func failIf(t *testing.T, err error) {
t.Helper()
if err != nil {
Expand Down
32 changes: 28 additions & 4 deletions v8go.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1160,15 +1160,20 @@ ValuePtr PromiseResult(ValuePtr ptr) {

/********** Function **********/

static void buildCallArguments(Isolate* iso, Local<Value> *argv, int argc, ValuePtr args[])
{
for (int i = 0; i < argc; i++) {
m_value* arg = static_cast<m_value*>(args[i]);
argv[i] = arg->ptr.Get(iso);
}
}

RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr args[]) {
LOCAL_VALUE(ptr)
RtnValue rtn = {nullptr, nullptr};
Local<Function> fn = Local<Function>::Cast(value);
Local<Value> argv[argc];
for (int i = 0; i < argc; i++) {
m_value* arg = static_cast<m_value*>(args[i]);
argv[i] = arg->ptr.Get(iso);
}
buildCallArguments(iso, argv, argc, args);
Local<Value> recv = Undefined(iso);
MaybeLocal<Value> result = fn->Call(local_ctx, recv, argc, argv);
if (result.IsEmpty()) {
Expand All @@ -1183,6 +1188,25 @@ RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr args[]) {
return rtn;
}

RtnValue FunctionNewInstance(ValuePtr ptr, int argc, ValuePtr args[]) {
LOCAL_VALUE(ptr)
RtnValue rtn = {nullptr, nullptr};
Local<Function> fn = Local<Function>::Cast(value);
Local<Value> argv[argc];
buildCallArguments(iso, argv, argc, args);
MaybeLocal<Object> result = fn->NewInstance(local_ctx, argc, argv);
if (result.IsEmpty()) {
rtn.error = ExceptionError(try_catch, iso, local_ctx);
return rtn;
}
m_value* rtnval = new m_value;
rtnval->iso = iso;
rtnval->ctx = ctx;
rtnval->ptr = Persistent<Value, CopyablePersistentTraits<Value>>(iso, result.ToLocalChecked());
rtn.value = tracked_value(ctx, rtnval);
return rtn;
}

/******** Exceptions *********/

ValuePtr ExceptionError(IsolatePtr iso_ptr, const char* message) {
Expand Down
1 change: 1 addition & 0 deletions v8go.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ ValuePtr PromiseCatch(ValuePtr ptr, int callback_ref);
extern ValuePtr PromiseResult(ValuePtr ptr);

extern RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr argv[]);
RtnValue FunctionNewInstance(ValuePtr ptr, int argc, ValuePtr args[]);

extern ValuePtr ExceptionError(IsolatePtr iso_ptr, const char* message);
extern ValuePtr ExceptionRangeError(IsolatePtr iso_ptr, const char* message);
Expand Down

0 comments on commit d9a0b1b

Please sign in to comment.