Bläddra i källkod

Added support for native constructors. See #45

Dmitry Panov 8 år sedan
förälder
incheckning
4ed8e66074
5 ändrade filer med 143 tillägg och 3 borttagningar
  1. 34 0
      README.md
  2. 3 3
      func.go
  3. 12 0
      object.go
  4. 34 0
      runtime.go
  5. 60 0
      runtime_test.go

+ 34 - 0
README.md

@@ -47,6 +47,8 @@ are converted to the corresponding JavaScript primitives.
 
 
 *func(FunctionCall) Value* is treated as a native JavaScript function.
 *func(FunctionCall) Value* is treated as a native JavaScript function.
 
 
+*func(ConstructorCall) Value* is treated as a JavaScript constructor (see Native Constructors).
+
 *map[string]interface{}* is converted into a host object that largely behaves like a JavaScript Object.
 *map[string]interface{}* is converted into a host object that largely behaves like a JavaScript Object.
 
 
 *[]interface{}* is converted into a host object that behaves largely like a JavaScript Array, however it's not extensible
 *[]interface{}* is converted into a host object that behaves largely like a JavaScript Array, however it's not extensible
@@ -72,6 +74,38 @@ A JS value can be exported into its default Go representation using Value.Export
 
 
 Alternatively it can be exported into a specific Go variable using Runtime.ExportTo() method.
 Alternatively it can be exported into a specific Go variable using Runtime.ExportTo() method.
 
 
+Native Constructors
+-------------------
+
+In order to implement a constructor function in Go:
+```go
+func MyObject(call goja.ConstructorCall) Value {
+    // call.This contains the newly created object as per http://www.ecma-international.org/ecma-262/5.1/index.html#sec-13.2.2
+    // call.Arguments contain arguments passed to the function
+
+    call.This.Set("method", method)
+
+    //...
+
+    // If return value is a non-nil *Object, it will be used instead of call.This
+    // This way it is possible to return a Go struct or a map converted
+    // into goja.Value using runtime.ToValue(), however in this case
+    // instanceof will not work as expected.
+    return nil
+}
+
+runtime.Set("MyObject", MyObject)
+
+```
+
+Then it can be used in JS as follows:
+
+```js
+var o = new MyObject(arg);
+var o1 = MyObject(arg); // same thing
+o instanceof MyObject && o1 instanceof MyObject; // true
+```
+
 Regular Expressions
 Regular Expressions
 -------------------
 -------------------
 
 

+ 3 - 3
func.go

@@ -178,7 +178,7 @@ func (f *baseFuncObject) hasInstance(v Value) bool {
 	return false
 	return false
 }
 }
 
 
-func (f *nativeFuncObject) defaultConstruct(args []Value) Value {
+func (f *nativeFuncObject) defaultConstruct(ccall func(ConstructorCall) *Object, args []Value) *Object {
 	proto := f.getStr("prototype")
 	proto := f.getStr("prototype")
 	var protoObj *Object
 	var protoObj *Object
 	if p, ok := proto.(*Object); ok {
 	if p, ok := proto.(*Object); ok {
@@ -187,12 +187,12 @@ func (f *nativeFuncObject) defaultConstruct(args []Value) Value {
 		protoObj = f.val.runtime.global.ObjectPrototype
 		protoObj = f.val.runtime.global.ObjectPrototype
 	}
 	}
 	obj := f.val.runtime.newBaseObject(protoObj, classObject).val
 	obj := f.val.runtime.newBaseObject(protoObj, classObject).val
-	ret := f.f(FunctionCall{
+	ret := ccall(ConstructorCall{
 		This:      obj,
 		This:      obj,
 		Arguments: args,
 		Arguments: args,
 	})
 	})
 
 
-	if ret, ok := ret.(*Object); ok {
+	if ret != nil {
 		return ret
 		return ret
 	}
 	}
 	return obj
 	return obj

+ 12 - 0
object.go

@@ -87,6 +87,11 @@ type FunctionCall struct {
 	Arguments []Value
 	Arguments []Value
 }
 }
 
 
+type ConstructorCall struct {
+	This      *Object
+	Arguments []Value
+}
+
 func (f FunctionCall) Argument(idx int) Value {
 func (f FunctionCall) Argument(idx int) Value {
 	if idx < len(f.Arguments) {
 	if idx < len(f.Arguments) {
 		return f.Arguments[idx]
 		return f.Arguments[idx]
@@ -94,6 +99,13 @@ func (f FunctionCall) Argument(idx int) Value {
 	return _undefined
 	return _undefined
 }
 }
 
 
+func (f ConstructorCall) Argument(idx int) Value {
+	if idx < len(f.Arguments) {
+		return f.Arguments[idx]
+	}
+	return _undefined
+}
+
 func (o *baseObject) init() {
 func (o *baseObject) init() {
 	o.values = make(map[string]Value)
 	o.values = make(map[string]Value)
 }
 }

+ 34 - 0
runtime.go

@@ -349,6 +349,38 @@ func (r *Runtime) newNativeFuncObj(v *Object, call func(FunctionCall) Value, con
 	return f
 	return f
 }
 }
 
 
+func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name string, length int) *Object {
+	v := &Object{runtime: r}
+
+	f := &nativeFuncObject{
+		baseFuncObject: baseFuncObject{
+			baseObject: baseObject{
+				class:      classFunction,
+				val:        v,
+				extensible: true,
+				prototype:  r.global.FunctionPrototype,
+			},
+		},
+	}
+
+	f.f = func(c FunctionCall) Value {
+		return f.defaultConstruct(call, c.Arguments)
+	}
+
+	f.construct = func(args []Value) *Object {
+		return f.defaultConstruct(call, args)
+	}
+
+	v.self = f
+	f.init(name, length)
+
+	proto := r.NewObject()
+	proto.self._putProp("constructor", v, true, false, true)
+	f._putProp("prototype", proto, true, false, false)
+
+	return v
+}
+
 func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(args []Value) *Object, name string, proto *Object, length int) *Object {
 func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, construct func(args []Value) *Object, name string, proto *Object, length int) *Object {
 	v := &Object{runtime: r}
 	v := &Object{runtime: r}
 
 
@@ -867,6 +899,8 @@ func (r *Runtime) ToValue(i interface{}) Value {
 		}
 		}
 	case func(FunctionCall) Value:
 	case func(FunctionCall) Value:
 		return r.newNativeFunc(i, nil, "", nil, 0)
 		return r.newNativeFunc(i, nil, "", nil, 0)
+	case func(ConstructorCall) *Object:
+		return r.newNativeConstructor(i, "", 0)
 	case int:
 	case int:
 		return intToValue(int64(i))
 		return intToValue(int64(i))
 	case int8:
 	case int8:

+ 60 - 0
runtime_test.go

@@ -906,6 +906,66 @@ func TestReflectNullValueArgument(t *testing.T) {
 	rt.RunString(`fn(null);`)
 	rt.RunString(`fn(null);`)
 }
 }
 
 
+type testNativeConstructHelper struct {
+	rt   *Runtime
+	base int64
+	// any other state
+}
+
+func (t *testNativeConstructHelper) calc(call FunctionCall) Value {
+	return t.rt.ToValue(t.base + call.Argument(0).ToInteger())
+}
+
+func TestNativeConstruct(t *testing.T) {
+	const SCRIPT = `
+	var f = new F(40);
+	f instanceof F && f.method() === 42 && f.calc(2) === 42;
+	`
+
+	rt := New()
+
+	method := func(call FunctionCall) Value {
+		return rt.ToValue(42)
+	}
+
+	rt.Set("F", func(call ConstructorCall) *Object { // constructor signature (as opposed to 'func(FunctionCall) Value')
+		h := &testNativeConstructHelper{
+			rt:   rt,
+			base: call.Argument(0).ToInteger(),
+		}
+		call.This.Set("method", method)
+		call.This.Set("calc", h.calc)
+		return nil // or any other *Object which will be used instead of call.This
+	})
+
+	prg := MustCompile("test.js", SCRIPT, false)
+
+	res, err := rt.RunProgram(prg)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !res.StrictEquals(valueTrue) {
+		t.Fatalf("Unexpected result: %v", res)
+	}
+
+	if fn, ok := AssertFunction(rt.Get("F")); ok {
+		v, err := fn(nil, rt.ToValue(42))
+		if err != nil {
+			t.Fatal(err)
+		}
+		if o, ok := v.(*Object); ok {
+			if o.Get("method") == nil {
+				t.Fatal("No method")
+			}
+		} else {
+			t.Fatal("Not an object")
+		}
+	} else {
+		t.Fatal("Not a function")
+	}
+}
+
 /*
 /*
 func TestArrayConcatSparse(t *testing.T) {
 func TestArrayConcatSparse(t *testing.T) {
 function foo(a,b,c)
 function foo(a,b,c)