changeset 37:d4efcbb06964

Added a new PYM_JSFunction type, PYM_JSContext.define_property(), and PYM_JSContext.new_function(). Also fixed a memory leak.
author Atul Varma <varmaa@toolness.com>
date Thu, 02 Jul 2009 22:42:31 -0700
parents 04a6e9a67ae5
children 6cd870a2b81e
files context.c function.c function.h manage.py object.c object.h pymonkey.c test_pymonkey.py utils.c
diffstat 9 files changed, 279 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- a/context.c	Thu Jul 02 15:29:07 2009 -0700
+++ b/context.c	Thu Jul 02 22:42:31 2009 -0700
@@ -1,5 +1,6 @@
 #include "context.h"
 #include "object.h"
+#include "function.h"
 #include "utils.h"
 
 static void
@@ -34,7 +35,7 @@
 
   // If this fails, we don't need to worry about cleaning up
   // obj because it'll get cleaned up at the next GC.
-  return (PyObject *) PYM_newJSObject(self, obj);
+  return (PyObject *) PYM_newJSObject(self, obj, NULL);
 }
 
 static PyObject *
@@ -121,87 +122,44 @@
   return pyRval;
 }
 
-static JSBool dispatchJSFunctionToPython(JSContext *cx,
-                                         JSObject *obj,
-                                         uintN argc,
-                                         jsval *argv,
-                                         jsval *rval)
+static PyObject *
+PYM_defineProperty(PYM_JSContextObject *self, PyObject *args)
 {
-  jsval callee = JS_ARGV_CALLEE(argv);
-  jsval jsCallable;
-  if (!JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(callee), 0, &jsCallable)) {
-    JS_ReportError(cx, "JS_GetReservedSlot() failed.");
-    return JS_FALSE;
-  }
-  PyObject *callable = (PyObject *) JSVAL_TO_PRIVATE(jsCallable);
+  PYM_JSObject *object;
+  PyObject *value;
+  const char *name;
+
+  if (!PyArg_ParseTuple(args, "O!sO", &PYM_JSObjectType, &object,
+                        &name, &value))
+    return NULL;
+
+  jsval jsValue;
 
-  // TODO: Convert args and 'this' parameter.
-  PyObject *args = PyTuple_New(0);
-  if (args == NULL) {
-    JS_ReportOutOfMemory(cx);
-    return JS_FALSE;
+  if (PYM_pyObjectToJsval(self->cx, value, &jsValue) == -1)
+    return NULL;
+
+  // TODO: Support unicode naming.
+  if (!JS_DefineProperty(self->cx, object->obj, name, jsValue,
+                         NULL, NULL, JSPROP_ENUMERATE)) {
+    // TODO: There's probably an exception pending on self->cx,
+    // what should we do about it?
+    PyErr_SetString(PYM_error, "JS_DefineProperty() failed");
+    return NULL;
   }
 
-  PyObject *result = PyObject_Call(callable, args, NULL);
-  if (result == NULL) {
-    // TODO: Get the actual exception.
-    JS_ReportError(cx, "Python function failed.");
-    return JS_FALSE;
-  }
-
-  int error = PYM_pyObjectToJsval(cx, result, rval);
-  Py_DECREF(result);
-
-  if (error) {
-    // TODO: Get the actual exception.
-    JS_ReportError(cx, "Python function failed.");
-    return JS_FALSE;
-  }
-  return JS_TRUE;
+  Py_RETURN_NONE;
 }
 
 static PyObject *
-PYM_defineFunction(PYM_JSContextObject *self, PyObject *args)
+PYM_newFunction(PYM_JSContextObject *self, PyObject *args)
 {
-  PYM_JSObject *object;
   PyObject *callable;
   const char *name;
 
-  if (!PyArg_ParseTuple(args, "O!Os", &PYM_JSObjectType, &object,
-                        &callable, &name))
+  if (!PyArg_ParseTuple(args, "Os", &callable, &name))
     return NULL;
 
-  if (!PyCallable_Check(callable)) {
-    PyErr_SetString(PyExc_TypeError, "Callable must be callable");
-    return NULL;
-  }
-
-  // TODO: Support unicode naming.
-  JSFunction *func = JS_DefineFunction(self->cx, object->obj, name,
-                                       dispatchJSFunctionToPython,
-                                       0, JSPROP_ENUMERATE);
-  if (func == NULL) {
-    PyErr_SetString(PYM_error, "JS_DefineFunction() failed");
-    return NULL;
-  }
-
-  JSObject *funcObj = JS_GetFunctionObject(func);
-
-  if (funcObj == NULL) {
-    PyErr_SetString(PYM_error, "JS_GetFunctionObject() failed");
-    return NULL;
-  }
-
-  if (!JS_SetReservedSlot(self->cx, funcObj, 0,
-                          PRIVATE_TO_JSVAL(callable))) {
-    PyErr_SetString(PYM_error, "JS_SetReservedSlot() failed");
-    return NULL;
-  }
-
-  // TODO: When to decref?
-  Py_INCREF(callable);
-
-  Py_RETURN_NONE;
+  return (PyObject *) PYM_newJSFunction(self, callable, name);
 }
 
 static PyMethodDef PYM_JSContextMethods[] = {
@@ -217,9 +175,12 @@
    "Evaluate the given JavaScript code in the context of the given "
    "global object, using the given filename"
    "and line number information."},
-  {"define_function",
-   (PyCFunction) PYM_defineFunction, METH_VARARGS,
-   "Defines a function callable from JS."},
+  {"new_function",
+   (PyCFunction) PYM_newFunction, METH_VARARGS,
+   "Creates a new function callable from JS."},
+  {"define_property",
+   (PyCFunction) PYM_defineProperty, METH_VARARGS,
+   "Defines a property on an object."},
   {"get_property", (PyCFunction) PYM_getProperty, METH_VARARGS,
    "Gets the given property for the given JavaScript object."},
   {NULL, NULL, 0, NULL}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/function.c	Thu Jul 02 22:42:31 2009 -0700
@@ -0,0 +1,149 @@
+#include "function.h"
+#include "utils.h"
+
+static void
+PYM_JSFunctionDealloc(PYM_JSFunction *self)
+{
+  // TODO: What if there's still a reference to the callable in
+  // JS-land?
+  if (self->callable) {
+    Py_DECREF(self->callable);
+    self->callable = NULL;
+  }
+  PYM_JSObjectType.tp_dealloc((PyObject *) self);
+}
+
+static JSBool
+dispatchJSFunctionToPython(JSContext *cx,
+                           JSObject *obj,
+                           uintN argc,
+                           jsval *argv,
+                           jsval *rval)
+{
+  jsval callee = JS_ARGV_CALLEE(argv);
+  jsval jsCallable;
+  if (!JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(callee), 0, &jsCallable)) {
+    JS_ReportError(cx, "JS_GetReservedSlot() failed.");
+    return JS_FALSE;
+  }
+  PyObject *callable = (PyObject *) JSVAL_TO_PRIVATE(jsCallable);
+
+  // TODO: Convert args and 'this' parameter.
+  PyObject *args = PyTuple_New(0);
+  if (args == NULL) {
+    JS_ReportOutOfMemory(cx);
+    return JS_FALSE;
+  }
+
+  PyObject *result = PyObject_Call(callable, args, NULL);
+  if (result == NULL) {
+    // TODO: Get the actual exception.
+    JS_ReportError(cx, "Python function failed.");
+    return JS_FALSE;
+  }
+
+  int error = PYM_pyObjectToJsval(cx, result, rval);
+  Py_DECREF(result);
+
+  if (error) {
+    // TODO: Get the actual exception.
+    JS_ReportError(cx, "Python function failed.");
+    return JS_FALSE;
+  }
+  return JS_TRUE;
+}
+
+PYM_JSFunction *
+PYM_newJSFunction(PYM_JSContextObject *context,
+                  PyObject *callable,
+                  const char *name)
+{
+  if (!PyCallable_Check(callable)) {
+    PyErr_SetString(PyExc_TypeError, "Callable must be callable");
+    return NULL;
+  }
+
+  JSFunction *func = JS_NewFunction(context->cx,
+                                    dispatchJSFunctionToPython, 0,
+                                    0, NULL, name);
+
+  if (func == NULL) {
+    PyErr_SetString(PYM_error, "JS_DefineFunction() failed");
+    return NULL;
+  }
+
+  JSObject *funcObj = JS_GetFunctionObject(func);
+
+  if (funcObj == NULL) {
+    PyErr_SetString(PYM_error, "JS_GetFunctionObject() failed");
+    return NULL;
+  }
+
+  if (!JS_SetReservedSlot(context->cx, funcObj, 0,
+                          PRIVATE_TO_JSVAL(callable))) {
+    PyErr_SetString(PYM_error, "JS_SetReservedSlot() failed");
+    return NULL;
+  }
+
+  PYM_JSFunction *object = PyObject_New(PYM_JSFunction,
+                                        &PYM_JSFunctionType);
+  if (object == NULL)
+    return NULL;
+
+  object->callable = NULL;
+  if (PYM_newJSObject(context, funcObj,
+                      (PYM_JSObject *) object) == NULL)
+    // Note that our object's reference count will have already
+    // been decremented by PYM_newJSObject().
+    return NULL;
+
+  object->callable = callable;
+  Py_INCREF(callable);
+
+  return object;
+}
+
+PyTypeObject PYM_JSFunctionType = {
+  PyObject_HEAD_INIT(NULL)
+  0,                           /*ob_size*/
+  "pymonkey.Function",         /*tp_name*/
+  sizeof(PYM_JSFunction),      /*tp_basicsize*/
+  0,                           /*tp_itemsize*/
+  /*tp_dealloc*/
+  (destructor) PYM_JSFunctionDealloc,
+  0,                           /*tp_print*/
+  0,                           /*tp_getattr*/
+  0,                           /*tp_setattr*/
+  0,                           /*tp_compare*/
+  0,                           /*tp_repr*/
+  0,                           /*tp_as_number*/
+  0,                           /*tp_as_sequence*/
+  0,                           /*tp_as_mapping*/
+  0,                           /*tp_hash */
+  0,                           /*tp_call*/
+  0,                           /*tp_str*/
+  0,                           /*tp_getattro*/
+  0,                           /*tp_setattro*/
+  0,                           /*tp_as_buffer*/
+  /*tp_flags*/
+  Py_TPFLAGS_DEFAULT,
+  /* tp_doc */
+  "JavaScript Function.",
+  0,		               /* tp_traverse */
+  0,		               /* tp_clear */
+  0,		               /* tp_richcompare */
+  0,                           /* tp_weaklistoffset */
+  0,		               /* tp_iter */
+  0,		               /* tp_iternext */
+  0,                           /* tp_methods */
+  0,                           /* tp_members */
+  0,                           /* tp_getset */
+  0,                           /* tp_base */
+  0,                           /* tp_dict */
+  0,                           /* tp_descr_get */
+  0,                           /* tp_descr_set */
+  0,                           /* tp_dictoffset */
+  0,                           /* tp_init */
+  0,                           /* tp_alloc */
+  0,                           /* tp_new */
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/function.h	Thu Jul 02 22:42:31 2009 -0700
@@ -0,0 +1,22 @@
+#ifndef PYM_FUNCTION_H
+#define PYM_FUNCTION_H
+
+#include "object.h"
+#include "context.h"
+
+#include <jsapi.h>
+#include <Python/Python.h>
+
+typedef struct {
+  PYM_JSObject base;
+  PyObject *callable;
+} PYM_JSFunction;
+
+extern PyTypeObject PYM_JSFunctionType;
+
+extern PYM_JSFunction *
+PYM_newJSFunction(PYM_JSContextObject *context,
+                  PyObject *callable,
+                  const char *name);
+
+#endif
--- a/manage.py	Thu Jul 02 15:29:07 2009 -0700
+++ b/manage.py	Thu Jul 02 22:42:31 2009 -0700
@@ -57,6 +57,7 @@
             "pymonkey.c",
             "utils.c",
             "object.c",
+            "function.c",
             "undefined.c",
             "context.c",
             "runtime.c"]
--- a/object.c	Thu Jul 02 15:29:07 2009 -0700
+++ b/object.c	Thu Jul 02 22:42:31 2009 -0700
@@ -27,6 +27,8 @@
     Py_DECREF(self->runtime);
     self->runtime = NULL;
   }
+
+  self->ob_type->tp_free((PyObject *) self);
 }
 
 PyTypeObject PYM_JSObjectType = {
@@ -51,7 +53,8 @@
   0,                           /*tp_getattro*/
   0,                           /*tp_setattro*/
   0,                           /*tp_as_buffer*/
-  Py_TPFLAGS_DEFAULT,          /*tp_flags*/
+  /*tp_flags*/
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
   /* tp_doc */
   "JavaScript Object.",
   0,		               /* tp_traverse */
@@ -74,7 +77,8 @@
 };
 
 PYM_JSObject *PYM_newJSObject(PYM_JSContextObject *context,
-                              JSObject *obj) {
+                              JSObject *obj,
+                              PYM_JSObject *subclass) {
   jsid uniqueId;
   if (!JS_GetObjectId(context->cx, obj, &uniqueId)) {
     PyErr_SetString(PYM_error, "JS_GetObjectId() failed");
@@ -93,8 +97,14 @@
     return (PYM_JSObject *) cached->value;
   }
 
-  PYM_JSObject *object = PyObject_New(PYM_JSObject,
-                                      &PYM_JSObjectType);
+  PYM_JSObject *object;
+
+  if (subclass)
+    object = subclass;
+  else
+    object = PyObject_New(PYM_JSObject,
+                          &PYM_JSObjectType);
+
   if (object == NULL)
     return NULL;
 
--- a/object.h	Thu Jul 02 15:29:07 2009 -0700
+++ b/object.h	Thu Jul 02 22:42:31 2009 -0700
@@ -18,6 +18,7 @@
 extern PyTypeObject PYM_JSObjectType;
 
 extern PYM_JSObject *
-PYM_newJSObject(PYM_JSContextObject *context, JSObject *obj);
+PYM_newJSObject(PYM_JSContextObject *context, JSObject *obj,
+                PYM_JSObject *subclass);
 
 #endif
--- a/pymonkey.c	Thu Jul 02 15:29:07 2009 -0700
+++ b/pymonkey.c	Thu Jul 02 22:42:31 2009 -0700
@@ -2,6 +2,7 @@
 #include "runtime.h"
 #include "context.h"
 #include "object.h"
+#include "function.h"
 #include "utils.h"
 
 static PyMethodDef PYM_methods[] = {
@@ -44,4 +45,11 @@
 
   Py_INCREF(&PYM_JSObjectType);
   PyModule_AddObject(module, "Object", (PyObject *) &PYM_JSObjectType);
+
+  PYM_JSFunctionType.tp_base = &PYM_JSObjectType;
+  if (!PyType_Ready(&PYM_JSFunctionType) < 0)
+    return;
+
+  Py_INCREF(&PYM_JSFunctionType);
+  PyModule_AddObject(module, "Function", (PyObject *) &PYM_JSFunctionType);
 }
--- a/test_pymonkey.py	Thu Jul 02 15:29:07 2009 -0700
+++ b/test_pymonkey.py	Thu Jul 02 22:42:31 2009 -0700
@@ -13,7 +13,8 @@
         cx = pymonkey.Runtime().new_context()
         obj = cx.new_object()
         cx.init_standard_classes(obj)
-        cx.define_function(obj, func, func.__name__)
+        jsfunc = cx.new_function(func, func.__name__)
+        cx.define_property(obj, func.__name__, jsfunc)
         return cx.evaluate_script(obj, code, '<string>', 1)
 
     def testJsWrappedPythonFunctionReturnsUnicode(self):
@@ -58,6 +59,28 @@
         self.assertEqual(self._evalJsWrappedPyFunc(hai2u, 'hai2u()'),
                          2147483647)
 
+    def testDefinePropertyWorksWithObject(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        foo = cx.new_object()
+        cx.define_property(obj, "foo", foo)
+        self.assertEqual(
+            cx.evaluate_script(obj, 'foo', '<string>', 1),
+            foo
+            )
+
+    def testDefinePropertyWorksWithString(self):
+        cx = pymonkey.Runtime().new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        foo = cx.new_object()
+        cx.define_property(obj, "foo", u"hello")
+        self.assertEqual(
+            cx.evaluate_script(obj, 'foo', '<string>', 1),
+            u"hello"
+            )
+
     def testObjectIsIdentityPreserving(self):
         cx = pymonkey.Runtime().new_context()
         obj = cx.new_object()
@@ -89,10 +112,21 @@
     def testObjectIsInstance(self):
         obj = pymonkey.Runtime().new_context().new_object()
         self.assertTrue(isinstance(obj, pymonkey.Object))
+        self.assertFalse(isinstance(obj, pymonkey.Function))
 
     def testObjectTypeCannotBeInstantiated(self):
         self.assertRaises(TypeError, pymonkey.Object)
 
+    def testFunctionIsInstance(self):
+        def boop():
+            pass
+        obj = pymonkey.Runtime().new_context().new_function(boop, "boop")
+        self.assertTrue(isinstance(obj, pymonkey.Object))
+        self.assertTrue(isinstance(obj, pymonkey.Function))
+
+    def testFunctionTypeCannotBeInstantiated(self):
+        self.assertRaises(TypeError, pymonkey.Function)
+
     def testGetRuntimeWorks(self):
         rt = pymonkey.Runtime()
         cx = rt.new_context()
--- a/utils.c	Thu Jul 02 15:29:07 2009 -0700
+++ b/utils.c	Thu Jul 02 22:42:31 2009 -0700
@@ -50,6 +50,19 @@
   if (PyFloat_Check(object))
     return PYM_doubleToJsval(cx, PyFloat_AS_DOUBLE(object), rval);
 
+  if (PyObject_TypeCheck(object, &PYM_JSObjectType)) {
+    PYM_JSObject *jsObject = (PYM_JSObject *) object;
+    JSRuntime *rt = JS_GetRuntime(cx);
+    if (rt != jsObject->runtime->rt) {
+      PyErr_SetString(PyExc_ValueError,
+                      "JS object and JS context are from different "
+                      "JS runtimes");
+      return -1;
+    }
+    *rval = OBJECT_TO_JSVAL(jsObject->obj);
+    return 0;
+  }
+
   if (object == Py_True) {
     *rval = JSVAL_TRUE;
     return 0;
@@ -106,7 +119,8 @@
   }
 
   if (JSVAL_IS_OBJECT(value))
-    return (PyObject *) PYM_newJSObject(context, JSVAL_TO_OBJECT(value));
+    return (PyObject *) PYM_newJSObject(context, JSVAL_TO_OBJECT(value),
+                                        NULL);
 
   // TODO: Support more types.
   PyErr_SetString(PyExc_NotImplementedError,