Skip to content

Commit

Permalink
Support for global property handlers.
Browse files Browse the repository at this point in the history
If you want to be able to global properties using some sort of active logic you
can now pass an object that has __getitem__ and __setitem__ defined to
Runtime.new_context(). Something like:

    >>> rt = spidermonkey.Runtime()
    >>> cx = rt.new_context({"foo": "bar"})
    >>> cx.execute("foo;")
    'bar'

[#9 state:resolved]
  • Loading branch information
Paul Davis committed May 10, 2009
1 parent 786f808 commit 4d97105
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 18 deletions.
191 changes: 174 additions & 17 deletions spidermonkey/context.c
Expand Up @@ -9,16 +9,151 @@
#include "spidermonkey.h"
#include "libjs/jsobj.h"

JSBool
get_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
{
Context* pycx = NULL;
PyObject* pykey = NULL;
PyObject* pyval = NULL;
JSBool ret = JS_FALSE;

pycx = (Context*) JS_GetContextPrivate(jscx);
if(pycx == NULL)
{
JS_ReportError(jscx, "Failed to get Python context.");
goto done;
}

// Bail if there's no registered global handler.
if(pycx->global == NULL)
{
ret = JS_TRUE;
goto done;
}

pykey = js2py(pycx, key);
if(pykey == NULL) goto done;

pyval = PyObject_GetItem(pycx->global, pykey);
if(pyval == NULL)
{
if(PyErr_GivenExceptionMatches(PyErr_Occurred(), PyExc_KeyError))
{
PyErr_Clear();
ret = JS_TRUE;
}
goto done;
}

*rval = py2js(pycx, pyval);
if(*rval == JSVAL_VOID) goto done;
ret = JS_TRUE;

done:
Py_XDECREF(pykey);
Py_XDECREF(pyval);
return ret;
}

JSBool
set_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
{
Context* pycx = NULL;
PyObject* pykey = NULL;
PyObject* pyval = NULL;
JSBool ret = JS_FALSE;

pycx = (Context*) JS_GetContextPrivate(jscx);
if(pycx == NULL)
{
JS_ReportError(jscx, "Failed to get Python context.");
goto done;
}

// Bail if there's no registered global handler.
if(pycx->global == NULL)
{
ret = JS_TRUE;
goto done;
}

pykey = js2py(pycx, key);
if(pykey == NULL) goto done;

pyval = js2py(pycx, *rval);
if(pyval == NULL) goto done;

if(PyObject_SetItem(pycx->global, pykey, pyval) < 0) goto done;

ret = JS_TRUE;

done:
Py_XDECREF(pykey);
Py_XDECREF(pyval);
return ret;
}

JSBool
resolve(JSContext* jscx, JSObject* jsobj, jsval key)
{
Context* pycx = NULL;
PyObject* pykey = NULL;
jsid pid;
JSBool ret = JS_FALSE;

pycx = (Context*) JS_GetContextPrivate(jscx);
if(pycx == NULL)
{
JS_ReportError(jscx, "Failed to get Python context.");
goto done;
}

// Bail if there's no registered global handler.
if(pycx->global == NULL)
{
ret = JS_TRUE;
goto done;
}

pykey = js2py(pycx, key);
if(pykey == NULL) goto done;

if(!PyMapping_HasKey(pycx->global, pykey))
{
ret = JS_TRUE;
goto done;
}

if(!JS_ValueToId(jscx, key, &pid))
{
JS_ReportError(jscx, "Failed to convert property id.");
goto done;
}

if(!js_DefineProperty(jscx, pycx->root, pid, JSVAL_VOID, NULL, NULL,
JSPROP_SHARED, NULL))
{
JS_ReportError(jscx, "Failed to define property.");
goto done;
}

ret = JS_TRUE;

done:
Py_XDECREF(pykey);
return ret;
}

static JSClass
js_global_class = {
"JSGlobalObjectClass",
JSCLASS_GLOBAL_FLAGS,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
JS_PropertyStub,
get_prop,
set_prop,
JS_EnumerateStub,
JS_ResolveStub,
resolve,
JS_ConvertStub,
JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
Expand All @@ -29,8 +164,21 @@ Context_new(PyTypeObject* type, PyObject* args, PyObject* kwargs)
{
Context* self = NULL;
Runtime* runtime = NULL;
PyObject* global = NULL;

if(!PyArg_ParseTuple(args, "O!", RuntimeType, &runtime)) goto error;
if(!PyArg_ParseTuple(
args,
"O!|O",
RuntimeType, &runtime,
&global
)) goto error;

if(global != NULL && !PyMapping_Check(global))
{
PyErr_SetString(PyExc_TypeError,
"Global handler must provide item access.");
goto error;
}

self = (Context*) type->tp_alloc(type, 0);
if(self == NULL) goto error;
Expand All @@ -52,19 +200,6 @@ Context_new(PyTypeObject* type, PyObject* args, PyObject* kwargs)

JS_BeginRequest(self->cx);

self->root = JS_NewObject(self->cx, &js_global_class, NULL, NULL);
if(self->root == NULL)
{
PyErr_SetString(PyExc_RuntimeError, "Error creating root object.");
goto error;
}

if(!JS_InitStandardClasses(self->cx, self->root))
{
PyErr_SetString(PyExc_RuntimeError, "Error initializing JS VM.");
goto error;
}

/*
* Notice that we don't add a ref to the Python context for
* the copy stored on the JSContext*. I'm pretty sure this
Expand All @@ -78,6 +213,27 @@ Context_new(PyTypeObject* type, PyObject* args, PyObject* kwargs)
*
*/
JS_SetContextPrivate(self->cx, self);

// Setup the root of the property lookup doodad.
self->root = JS_NewObject(self->cx, &js_global_class, NULL, NULL);
if(self->root == NULL)
{
PyErr_SetString(PyExc_RuntimeError, "Error creating root object.");
goto error;
}

if(!JS_InitStandardClasses(self->cx, self->root))
{
PyErr_SetString(PyExc_RuntimeError, "Error initializing JS VM.");
goto error;
}

// Don't setup the global handler until after the standard classes
// have been initialized.
// XXX: Does anyone know if finalize is called if new fails?
if(global != NULL) Py_INCREF(global);
self->global = global;

JS_SetErrorReporter(self->cx, report_error_cb);

Py_INCREF(runtime);
Expand Down Expand Up @@ -109,6 +265,7 @@ Context_dealloc(Context* self)
JS_DestroyContext(self->cx);
}

Py_XDECREF(self->global);
Py_XDECREF(self->objects);
Py_XDECREF(self->classes);
Py_DECREF(self->rt);
Expand Down
1 change: 1 addition & 0 deletions spidermonkey/context.h
Expand Up @@ -17,6 +17,7 @@
typedef struct {
PyObject_HEAD
Runtime* rt;
PyObject* global;
JSContext* cx;
JSObject* root;
PyDictObject* classes;
Expand Down
12 changes: 11 additions & 1 deletion spidermonkey/runtime.c
Expand Up @@ -48,8 +48,18 @@ Runtime_new_context(Runtime* self, PyObject* args, PyObject* kwargs)
{
PyObject* cx = NULL;
PyObject* tpl = NULL;
PyObject* global = NULL;

if(!PyArg_ParseTuple(args, "|O", &global)) goto error;

tpl = Py_BuildValue("(O)", self);
if(global == NULL)
{
tpl = Py_BuildValue("(O)", self);
}
else
{
tpl = Py_BuildValue("(OO)", self, global);
}
if(tpl == NULL) goto error;

cx = PyObject_CallObject((PyObject*) ContextType, tpl);
Expand Down
38 changes: 38 additions & 0 deletions tests/test-global.py
Expand Up @@ -59,3 +59,41 @@ def test_js_rem_attr(cx, glbl):
t.has(glbl, "blam")
cx.execute("delete rain.blam;")
t.hasnot(glbl, "blam")

@t.rt()
def test_py_with_global(rt):
rt.new_context({})

@t.rt()
def test_py_with_invalid_global(rt):
t.raises(TypeError, rt.new_context, "Break!")

@t.rt()
def test_py_get_global(rt):
glbl = {"foo": "bar"}
cx = rt.new_context(glbl)
t.eq(cx.execute("foo;"), "bar")

@t.rt()
def test_py_set_global(rt):
glbl = {}
cx = rt.new_context(glbl)
cx.execute("foo = 71;")
t.eq(cx.execute("foo;"), 71);
t.eq(glbl["foo"], 71)

class ActiveGlobal(object):
def __init__(self):
self.data = {}
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value * 2

@t.rt()
def test_py_with_active_global(rt):
glbl = ActiveGlobal()
cx = rt.new_context(glbl)
cx.execute("foo = 4;")
t.eq(cx.execute("foo;"), 8)
t.eq(glbl.data["foo"], 8);

0 comments on commit 4d97105

Please sign in to comment.