Skip to content
Browse files

Initial JS iteration of Python objects.

This passes the basic tests. I need to add a check to detect if we're doing 'for
v in obj' vs 'for each (v in obj)' iteration as they behave differently in
JavaScript.
  • Loading branch information...
1 parent 2afa3de commit 1df3a57b40072cc23e07ecb0c98630e37fa85b13 Paul Davis committed with Paul Davis May 9, 2009
Showing with 451 additions and 68 deletions.
  1. +2 −2 go
  2. +14 −10 spidermonkey/integer.c
  3. +1 −0 spidermonkey/integer.h
  4. +340 −0 spidermonkey/pyiter.c
  5. +19 −0 spidermonkey/pyiter.h
  6. +45 −34 spidermonkey/pyobject.c
  7. +1 −0 spidermonkey/spidermonkey.h
  8. +29 −22 tests/test-iterate.py
View
4 go
@@ -1,3 +1,3 @@
#! /bin/bash
-python setup.py build
-gdb --command=go.comm --batch python2.5
+#python setup.py build
+gdb --command=go.comm --batch python
View
24 spidermonkey/integer.c
@@ -12,35 +12,39 @@ jsval
py2js_integer(Context* cx, PyObject* obj)
{
long pyval;
- jsval ret = JSVAL_VOID;
if(PyInt_Check(obj))
{
pyval = PyInt_AsLong(obj);
- if(PyErr_Occurred()) goto error;
+ if(PyErr_Occurred()) return JSVAL_VOID;
}
else
{
pyval = PyLong_AsLong(obj);
- if(PyErr_Occurred()) goto error;
+ if(PyErr_Occurred()) return JSVAL_VOID;
}
-
+
+ return long2js_integer(cx, pyval);
+}
+
+jsval
+long2js_integer(Context* cx, long pyval)
+{
+ jsval ret = JSVAL_VOID;
+
if(INT_FITS_IN_JSVAL(pyval))
{
ret = INT_TO_JSVAL(pyval);
- goto success;
+ goto done;
}
if(!JS_NewNumberValue(cx->cx, pyval, &ret))
{
PyErr_SetString(PyExc_ValueError, "Failed to convert number.");
- goto error;
+ goto done;
}
- goto success;
-
-error:
-success:
+done:
return ret;
}
View
1 spidermonkey/integer.h
@@ -10,6 +10,7 @@
#define PYSM_INTEGER_H
jsval py2js_integer(Context* cx, PyObject* obj);
+jsval long2js_integer(Context* cx, long val);
PyObject* js2py_integer(Context* cx, jsval val);
#endif
View
340 spidermonkey/pyiter.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2009 Paul J. Davis <paul.joseph.davis@gmail.com>
+ *
+ * This file is part of the python-spidermonkey package released
+ * under the MIT license.
+ *
+ */
+
+#include "spidermonkey.h"
+
+PyObject*
+get_js_slot(JSContext* cx, JSObject* obj, int slot)
+{
+ jsval priv;
+
+ if(!JS_GetReservedSlot(cx, obj, slot, &priv))
+ {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to get slot data.");
+ return NULL;
+ }
+
+ return (PyObject*) JSVAL_TO_PRIVATE(priv);
+}
+
+void
+finalize(JSContext* jscx, JSObject* jsobj)
+{
+ Context* pycx = (Context*) JS_GetContextPrivate(jscx);
+ PyObject* pyobj = NULL;
+ PyObject* pyiter = NULL;
+
+ if(pycx == NULL)
+ {
+ fprintf(stderr, "*** NO PYTHON CONTEXT ***\n");
+ return;
+ }
+
+ pyobj = get_js_slot(jscx, jsobj, 0);
+ Py_DECREF(pyobj);
+
+ pyiter = get_js_slot(jscx, jsobj, 1);
+ Py_DECREF(pyiter);
+
+ Py_DECREF(pycx);
+}
+
+JSBool
+call(JSContext* jscx, JSObject* jsobj, uintN argc, jsval* argv, jsval* rval)
+{
+ *rval = argv[-2];
+ return JS_TRUE;
+}
+
+JSBool
+def_next(JSContext* jscx, JSObject* jsobj, uintN argc, jsval* argv, jsval* rval)
+{
+ Context* pycx = NULL;
+ PyObject* iter = NULL;
+ PyObject* next = NULL;
+ JSBool ret = JS_FALSE;
+
+ // For StopIteration throw
+ JSObject* glbl = JS_GetGlobalObject(jscx);
+ jsval exc = JSVAL_VOID;
+
+ pycx = (Context*) JS_GetContextPrivate(jscx);
+ if(pycx == NULL)
+ {
+ JS_ReportError(jscx, "Failed to get JS Context.");
+ goto done;
+ }
+
+ iter = get_js_slot(jscx, jsobj, 1);
+ if(!PyIter_Check(iter))
+ {
+ JS_ReportError(jscx, "Object is not an iterator.");
+ goto done;
+ }
+
+ next = PyIter_Next(iter);
+ if(next == NULL && PyErr_Occurred())
+ {
+ goto done;
+ }
+ else if(next == NULL)
+ {
+ if(JS_GetProperty(jscx, glbl, "StopIteration", &exc))
+ {
+ JS_SetPendingException(jscx, exc);
+ }
+ else
+ {
+ JS_ReportError(jscx, "Failed to get StopIteration object.");
+ }
+ goto done;
+ }
+
+ *rval = py2js(pycx, next);
+ if(*rval != JSVAL_VOID) ret = JS_TRUE;
+
+done:
+ Py_XDECREF(next);
+ return ret;
+}
+
+JSBool
+seq_next(JSContext* jscx, JSObject* jsobj, uintN argc, jsval* argv, jsval* rval)
+{
+ Context* pycx = NULL;
+ PyObject* pyobj = NULL;
+ PyObject* iter = NULL;
+ PyObject* next = NULL;
+ JSObject* jsiter = NULL;
+ JSBool ret = JS_FALSE;
+ long maxval = -1;
+ long currval = -1;
+
+ // For StopIteration throw
+ JSObject* glbl = JS_GetGlobalObject(jscx);
+ jsval exc = JSVAL_VOID;
+
+ pycx = (Context*) JS_GetContextPrivate(jscx);
+ if(pycx == NULL)
+ {
+ JS_ReportError(jscx, "Failed to get JS Context.");
+ goto done;
+ }
+
+ pyobj = get_js_slot(jscx, jsobj, 0);
+ if(!PySequence_Check(pyobj))
+ {
+ JS_ReportError(jscx, "Object is not a sequence.");
+ goto done;
+ }
+
+ maxval = PyObject_Length(pyobj);
+ if(maxval < 0) goto done;
+
+ iter = get_js_slot(jscx, jsobj, 1);
+ if(!PyInt_Check(iter))
+ {
+ JS_ReportError(jscx, "Object is not an integer.");
+ goto done;
+ }
+
+ currval = PyInt_AsLong(iter);
+ if(currval == -1 && PyErr_Occurred())
+ {
+ goto done;
+ }
+
+ if(currval + 1 > maxval)
+ {
+ if(JS_GetProperty(jscx, glbl, "StopIteration", &exc))
+ {
+ JS_SetPendingException(jscx, exc);
+ }
+ else
+ {
+ JS_ReportError(jscx, "Failed to get StopIteration object.");
+ }
+ goto done;
+ }
+
+ next = PyInt_FromLong(currval + 1);
+ if(next == NULL) goto done;
+
+ if(!JS_SetReservedSlot(jscx, jsobj, 1, PRIVATE_TO_JSVAL(next)))
+ {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to store base object.");
+ goto done;
+ }
+
+ *rval = py2js(pycx, iter);
+ next = iter;
+ if(*rval != JSVAL_VOID) ret = JS_TRUE;
+
+done:
+ Py_XDECREF(next);
+ return ret;
+}
+
+static JSClass
+js_iter_class = {
+ "PyJSIteratorClass",
+ JSCLASS_HAS_RESERVED_SLOTS(2),
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+ finalize,
+ NULL, // get object ops
+ NULL, // check access
+ call,
+ NULL, // constructor
+ NULL, // xdr object
+ NULL, // has instance
+ NULL, // mark
+ NULL // reserved slots
+};
+
+static JSFunctionSpec js_def_iter_functions[] = {
+ {"next", def_next, 0, 0, 0},
+ {0, 0, 0, 0, 0}
+};
+
+static JSFunctionSpec js_seq_iter_functions[] = {
+ {"next", seq_next, 0, 0, 0},
+ {0, 0, 0, 0, 0}
+};
+
+JSBool
+new_py_def_iter(Context* cx, PyObject* obj, jsval* rval)
+{
+ PyObject* pyiter = NULL;
+ PyObject* attached = NULL;
+ JSObject* jsiter = NULL;
+ jsval jsv = JSVAL_VOID;
+ JSBool ret = JS_FALSE;
+
+ // Initialize the return value
+ *rval = JSVAL_VOID;
+
+ pyiter = PyObject_GetIter(obj);
+ if(pyiter == NULL)
+ {
+ if(PyErr_GivenExceptionMatches(PyErr_Occurred(), PyExc_TypeError))
+ {
+ PyErr_Clear();
+ ret = JS_TRUE;
+ goto success;
+ }
+ else
+ {
+ goto error;
+ }
+ }
+
+ jsiter = JS_NewObject(cx->cx, &js_iter_class, NULL, NULL);
+ if(jsiter == NULL) goto error;
+
+ if(!JS_DefineFunctions(cx->cx, jsiter, js_def_iter_functions))
+ {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to define iter funcions.");
+ goto error;
+ }
+
+ attached = obj;
+ Py_INCREF(attached);
+ jsv = PRIVATE_TO_JSVAL(attached);
+ if(!JS_SetReservedSlot(cx->cx, jsiter, 0, jsv))
+ {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to store base object.");
+ goto error;
+ }
+
+ jsv = PRIVATE_TO_JSVAL(pyiter);
+ if(!JS_SetReservedSlot(cx->cx, jsiter, 1, jsv))
+ {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to store iter object.");
+ goto error;
+ }
+
+ Py_INCREF(cx);
+ *rval = OBJECT_TO_JSVAL(jsiter);
+ ret = JS_TRUE;
+ goto success;
+
+error:
+ Py_XDECREF(pyiter);
+ Py_XDECREF(attached);
+success:
+ return ret;
+}
+
+JSBool
+new_py_seq_iter(Context* cx, PyObject* obj, jsval* rval)
+{
+ PyObject* pyiter = NULL;
+ PyObject* attached = NULL;
+ JSObject* jsiter = NULL;
+ jsval jsv = JSVAL_VOID;
+ JSBool ret = JS_FALSE;
+
+ // Initialize the return value
+ *rval = JSVAL_VOID;
+
+ // Our counting state
+ pyiter = PyInt_FromLong(0);
+ if(pyiter == NULL) goto error;
+
+ jsiter = JS_NewObject(cx->cx, &js_iter_class, NULL, NULL);
+ if(jsiter == NULL) goto error;
+
+ if(!JS_DefineFunctions(cx->cx, jsiter, js_seq_iter_functions))
+ {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to define iter funcions.");
+ goto error;
+ }
+
+ attached = obj;
+ Py_INCREF(attached);
+ jsv = PRIVATE_TO_JSVAL(attached);
+ if(!JS_SetReservedSlot(cx->cx, jsiter, 0, jsv))
+ {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to store base object.");
+ goto error;
+ }
+
+ jsv = PRIVATE_TO_JSVAL(pyiter);
+ if(!JS_SetReservedSlot(cx->cx, jsiter, 1, jsv))
+ {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to store iter object.");
+ goto error;
+ }
+
+ Py_INCREF(cx);
+ *rval = OBJECT_TO_JSVAL(jsiter);
+ ret = JS_TRUE;
+ goto success;
+
+error:
+ Py_XDECREF(pyiter);
+ Py_XDECREF(attached);
+success:
+ return ret;
+}
+
+JSBool
+new_py_iter(Context* cx, PyObject* obj, jsval* rval)
+{
+ if(PySequence_Check(obj))
+ {
+ return new_py_seq_iter(cx, obj, rval);
+ }
+ return new_py_def_iter(cx, obj, rval);
+}
View
19 spidermonkey/pyiter.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2009 Paul J. Davis <paul.joseph.davis@gmail.com>
+ *
+ * This file is part of the python-spidermonkey package released
+ * under the MIT license.
+ *
+ */
+
+#ifndef PYSM_PYITER_H
+#define PYSM_PYITER_H
+
+/*
+ This is a bit of glue between Python and JavaScript
+ iterators.
+*/
+
+JSBool new_py_iter(Context* cx, PyObject* obj, jsval* rval);
+
+#endif
View
79 spidermonkey/pyobject.c
@@ -7,6 +7,7 @@
*/
#include "spidermonkey.h"
+#include "libjs/jsobj.h"
/*
This is a fairly unsafe operation in so much as
@@ -82,21 +83,43 @@ js_get_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* val)
Context* pycx = NULL;
PyObject* pyobj = NULL;
PyObject* pykey = NULL;
+ PyObject* utf8 = NULL;
PyObject* pyval = NULL;
JSBool ret = JS_FALSE;
-
+ const char* data;
+
pycx = (Context*) JS_GetContextPrivate(jscx);
if(pycx == NULL)
{
PyErr_SetString(PyExc_RuntimeError, "Failed to get JS Context.");
- goto error;
+ goto done;
}
pyobj = get_py_obj(jscx, jsobj);
- if(pyobj == NULL) goto error;
+ if(pyobj == NULL) goto done;
pykey = js2py(pycx, key);
- if(pykey == NULL) goto error;
+ if(pykey == NULL) goto done;
+
+ utf8 = PyUnicode_AsUTF8String(pykey);
+ if(utf8 == NULL) goto done;
+
+ // Yeah. It's ugly as sin.
+ if(PyString_Check(utf8))
+ {
+ data = PyString_AsString(utf8);
+ if(data == NULL) goto done;
+
+ if(strcmp("__iterator__", data) == 0)
+ {
+ if(!new_py_iter(pycx, pyobj, val)) goto done;
+ if(*val != JSVAL_VOID)
+ {
+ ret = JS_TRUE;
+ goto done;
+ }
+ }
+ }
pyval = PyObject_GetItem(pyobj, pykey);
if(pyval == NULL)
@@ -108,20 +131,19 @@ js_get_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* val)
PyErr_Clear();
ret = JS_TRUE;
*val = JSVAL_VOID;
- goto success;
+ goto done;
}
}
*val = py2js(pycx, pyval);
- if(*val == JSVAL_VOID) goto error;
+ if(*val == JSVAL_VOID) goto done;
ret = JS_TRUE;
- goto success;
-error:
-success:
+done:
Py_XDECREF(pykey);
Py_XDECREF(pyval);
-
+ Py_XDECREF(utf8);
+
return ret;
}
@@ -178,18 +200,6 @@ js_set_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* val)
return ret;
}
-JSBool
-js_enumerate(JSContext* jscx, JSObject* jsobj)
-{
- return JS_TRUE;
-}
-
-JSBool
-js_resolve(JSContext* cx, JSObject* obj, jsval key)
-{
- return JS_TRUE;
-}
-
void
js_finalize(JSContext* jscx, JSObject* jsobj)
{
@@ -355,14 +365,15 @@ js_ctor(JSContext* jscx, JSObject* jsobj, uintN argc, jsval* argv, jsval* rval)
}
JSClass*
-create_class(Context* cx, PyTypeObject* type)
+create_class(Context* cx, PyObject* pyobj)
{
PyObject* curr = NULL;
JSClass* jsclass = NULL;
JSClass* ret = NULL;
char* classname = NULL;
-
- curr = Context_get_class(cx, type->tp_name);
+ int flags = JSCLASS_HAS_RESERVED_SLOTS(1);
+
+ curr = Context_get_class(cx, pyobj->ob_type->tp_name);
if(curr != NULL) return (JSClass*) HashCObj_AsVoidPtr(curr);
jsclass = (JSClass*) malloc(sizeof(JSClass));
@@ -372,29 +383,28 @@ create_class(Context* cx, PyTypeObject* type)
goto error;
}
- classname = (char*) malloc(strlen(type->tp_name)*sizeof(char));
+ classname = (char*) malloc(strlen(pyobj->ob_type->tp_name)*sizeof(char));
if(classname == NULL)
{
PyErr_NoMemory();
goto error;
}
- strcpy((char*) classname, type->tp_name);
+ strcpy((char*) classname, pyobj->ob_type->tp_name);
jsclass->name = classname;
- jsclass->flags = JSCLASS_HAS_RESERVED_SLOTS(1);
+ jsclass->flags = flags;
jsclass->addProperty = js_add_prop;
jsclass->delProperty = js_del_prop;
jsclass->getProperty = js_get_prop;
jsclass->setProperty = js_set_prop;
- jsclass->enumerate = js_enumerate;
- jsclass->resolve = js_resolve;
+ jsclass->enumerate = JS_EnumerateStub;
+ jsclass->resolve = JS_ResolveStub;
jsclass->convert = JS_ConvertStub;
jsclass->finalize = js_finalize;
jsclass->getObjectOps = NULL;
jsclass->checkAccess = NULL;
jsclass->call = js_call;
- //jsclass->construct = NULL;
jsclass->construct = js_ctor;
jsclass->xdrObject = NULL;
jsclass->hasInstance = NULL;
@@ -403,7 +413,7 @@ create_class(Context* cx, PyTypeObject* type)
curr = HashCObj_FromVoidPtr(jsclass);
if(curr == NULL) goto error;
- if(Context_add_class(cx, type->tp_name, curr) < 0) goto error;
+ if(Context_add_class(cx, pyobj->ob_type->tp_name, curr) < 0) goto error;
ret = jsclass;
goto success;
@@ -425,7 +435,7 @@ py2js_object(Context* cx, PyObject* pyobj)
jsval pyval;
jsval ret = JSVAL_VOID;
- klass = create_class(cx, pyobj->ob_type);
+ klass = create_class(cx, pyobj);
if(klass == NULL) goto error;
jsobj = JS_NewObject(cx->cx, klass, NULL, NULL);
@@ -434,7 +444,8 @@ py2js_object(Context* cx, PyObject* pyobj)
PyErr_SetString(PyExc_RuntimeError, "Failed to create JS object.");
goto error;
}
-
+
+ // do the attached = pyobj dance to only DECREF if we get passed INCREF
attached = pyobj;
// INCREF for the value stored in JS
Py_INCREF(attached);
View
1 spidermonkey/spidermonkey.h
@@ -22,6 +22,7 @@
#include "double.h"
#include "pyobject.h"
+#include "pyiter.h"
#include "jsobject.h"
#include "jsarray.h"
View
51 tests/test-iterate.py
@@ -4,26 +4,33 @@
# under the MIT license.
import t
-class Foo(object):
- def __init__(self):
- self.blam = 8
- self.zing = "yessiree"
-
-@t.glbl("zip", Foo())
-def test_iter_py(cx, glbl):
- js = """
- var ret = [];
- for(var v in zip) {
- ret.push(v)
- }
- ret;
- """
- t.eq(cx.execute(js), ["blam", "zing"])
-
-@t.cx()
-def test_iter_js(cx):
- ret = cx.execute('var f = {"foo": 1, "domino": "daily"}; f;')
- keys = [k for k in ret]
- keys.sort()
- t.eq(keys, ["domino", "foo"])
+@t.rt()
+def test_iter_py(rt):
+ pairs = [
+ ({"foo": "bar", "baz": "bam"}, ["foo", "baz"]),
+ (["a", "b", "c"], [0, 1, 2])
+ ]
+ def check(a, b):
+ cx = rt.new_context()
+ cx.add_global("zip", a)
+ js = """
+ var ret = [];
+ for(var v in zip) {ret.push(v);}
+ ret;
+ """
+ t.eq(cx.execute(js), b)
+ map(lambda x: check(*x), pairs)
+@t.rt()
+def test_iter_js(rt):
+ pairs = [
+ ('var f = {"foo": 1, "domino": "daily"}; f;', ["domino", "foo"]),
+ ('["foo", 1, "bing"]', [0, 1, 2])
+ ]
+ def check(a, b):
+ cx = rt.new_context()
+ ret = cx.execute(a)
+ data = [k for k in ret]
+ data.sort()
+ t.eq(data, b)
+ map(lambda x: check(*x), pairs)

0 comments on commit 1df3a57

Please sign in to comment.
Something went wrong with that request. Please try again.