Browse Source

ExportTo() now works for func, Callable, *Object and Value

Dmitry Panov 9 năm trước cách đây
mục cha
commit
44e4703ff8
2 tập tin đã thay đổi với 184 bổ sung0 xóa
  1. 54 0
      runtime.go
  2. 130 0
      runtime_test.go

+ 54 - 0
runtime.go

@@ -16,6 +16,10 @@ const (
 	sqrt1_2 float64 = math.Sqrt2 / 2
 )
 
+var (
+	typeCallable = reflect.TypeOf(Callable(nil))
+)
+
 type global struct {
 	Object   *Object
 	Array    *Object
@@ -1069,6 +1073,19 @@ func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, erro
 		return reflect.ValueOf(uint8(i)).Convert(typ), nil
 	}
 
+	t := reflect.TypeOf(v)
+	if t.AssignableTo(typ) {
+		return reflect.ValueOf(v), nil
+	} else if t.ConvertibleTo(typ) {
+		return reflect.ValueOf(v).Convert(typ), nil
+	}
+
+	if typ == typeCallable {
+		if fn, ok := AssertFunction(v); ok {
+			return reflect.ValueOf(fn), nil
+		}
+	}
+
 	et := v.ExportType()
 
 	if et.AssignableTo(typ) {
@@ -1148,11 +1165,48 @@ func (r *Runtime) toReflectValue(v Value, typ reflect.Type) (reflect.Value, erro
 			}
 			return s, nil
 		}
+	case reflect.Func:
+		if fn, ok := AssertFunction(v); ok {
+			return reflect.MakeFunc(typ, r.wrapJSFunc(fn, typ)), nil
+		}
 	}
 
 	return reflect.Value{}, fmt.Errorf("Could not convert %v to %v", v, typ)
 }
 
+func (r *Runtime) wrapJSFunc(fn Callable, typ reflect.Type) func(args []reflect.Value) (results []reflect.Value) {
+	return func(args []reflect.Value) (results []reflect.Value) {
+		jsArgs := make([]Value, len(args))
+		for i, arg := range args {
+			jsArgs[i] = r.ToValue(arg.Interface())
+		}
+
+		results = make([]reflect.Value, typ.NumOut())
+		res, err := fn(_undefined, jsArgs...)
+		if err == nil {
+			if typ.NumOut() > 0 {
+				results[0], err = r.toReflectValue(res, typ.Out(0))
+			}
+		}
+
+		if err != nil {
+			if typ.NumOut() == 2 && typ.Out(1).Name() == "error" {
+				results[1] = reflect.ValueOf(err).Convert(typ.Out(1))
+			} else {
+				panic(err)
+			}
+		}
+
+		for i, v := range results {
+			if !v.IsValid() {
+				results[i] = reflect.Zero(typ.Out(i))
+			}
+		}
+
+		return
+	}
+}
+
 // ExportTo converts a JavaScript value into the specified Go value. The second parameter must be a non-nil pointer.
 // Returns error if conversion is not possible.
 func (r *Runtime) ExportTo(v Value, target interface{}) error {

+ 130 - 0
runtime_test.go

@@ -460,6 +460,136 @@ func TestRuntime_ExportToStruct(t *testing.T) {
 
 }
 
+func TestRuntime_ExportToFunc(t *testing.T) {
+	const SCRIPT = `
+	function f(param) {
+		return +param + 2;
+	}
+	`
+
+	vm := New()
+	_, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var fn func(string) string
+	vm.ExportTo(vm.Get("f"), &fn)
+
+	if res := fn("40"); res != "42" {
+		t.Fatalf("Unexpected value: %q", res)
+	}
+}
+
+func TestRuntime_ExportToFuncThrow(t *testing.T) {
+	const SCRIPT = `
+	function f(param) {
+		throw new Error("testing");
+	}
+	`
+
+	vm := New()
+	_, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var fn func(string) (string, error)
+	err = vm.ExportTo(vm.Get("f"), &fn)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err := fn("40"); err != nil {
+		if ex, ok := err.(*Exception); ok {
+			if msg := ex.Error(); msg != "Error: testing" {
+				t.Fatalf("Msg: %q", msg)
+			}
+		} else {
+			t.Fatalf("Error is not *Exception (%T): %v", err, err)
+		}
+	} else {
+		t.Fatal("Expected error")
+	}
+}
+
+func TestRuntime_ExportToFuncFail(t *testing.T) {
+	const SCRIPT = `
+	function f(param) {
+		return +param + 2;
+	}
+	`
+
+	type T struct {
+		Field1 int
+	}
+
+	var fn func(string) (T, error)
+
+	vm := New()
+	_, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = vm.ExportTo(vm.Get("f"), &fn)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err := fn("40"); err == nil {
+		t.Fatal("Expected error")
+	}
+}
+
+func TestRuntime_ExportToCallable(t *testing.T) {
+	const SCRIPT = `
+	function f(param) {
+		return +param + 2;
+	}
+	`
+	vm := New()
+	_, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var c Callable
+	err = vm.ExportTo(vm.Get("f"), &c)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	res, err := c(Undefined(), vm.ToValue("40"))
+	if err != nil {
+		t.Fatal(err)
+	} else if !res.StrictEquals(vm.ToValue(42)) {
+		t.Fatalf("Unexpected value: %v", res)
+	}
+}
+
+func TestRuntime_ExportToObject(t *testing.T) {
+	const SCRIPT = `
+	var o = {"test": 42};
+	o;
+	`
+	vm := New()
+	_, err := vm.RunString(SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var o *Object
+	err = vm.ExportTo(vm.Get("o"), &o)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if v := o.Get("test"); !v.StrictEquals(vm.ToValue(42)) {
+		t.Fatalf("Unexpected value: %v", v)
+	}
+}
+
 func TestGoFuncError(t *testing.T) {
 	const SCRIPT = `
 	try {