Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
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...
commit 1df3a57b40072cc23e07ecb0c98630e37fa85b13 1 parent 2afa3de
Paul Davis authored Paul Davis committed
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)
Please sign in to comment.
Something went wrong with that request. Please try again.