Mercurial > pymonkey
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,