Skip to content

Commit ddb81d4

Browse files
author
Paul Davis
committed
Iteration over JavaSript objects in Python.
You can now do the 'for val in obj' syntax when obj is a wrapped JavaScript object. Thanks Mark Lee for the patch showing how to do this for arrays. [#10 state:resolved]
1 parent 1df3a57 commit ddb81d4

File tree

9 files changed

+255
-31
lines changed

9 files changed

+255
-31
lines changed

THANKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
Thanks For Patches and Pointers
33
===============================
44
benoitc - build system tweaks, compiling on OpenBSD
5+
Mark Lee - Patch for iterating JS arrays in Python

spidermonkey/jsarray.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ Array_get_item(Object* self, Py_ssize_t idx)
3333
jsval rval;
3434
jsint pos = (jsint) idx;
3535

36+
if(idx >= Array_length(self))
37+
{
38+
PyErr_SetString(PyExc_IndexError, "List index out of range.");
39+
return NULL;
40+
}
41+
3642
if(!JS_GetElement(self->cx->cx, self->obj, pos, &rval))
3743
{
3844
PyErr_SetString(PyExc_AttributeError, "Failed to get array item.");
@@ -60,6 +66,12 @@ Array_set_item(Object* self, Py_ssize_t idx, PyObject* val)
6066
return 0;
6167
}
6268

69+
PyObject*
70+
Array_iterator(Object* self)
71+
{
72+
return PySeqIter_New(self);
73+
}
74+
6375
static PyMemberDef Array_members[] = {
6476
{0, 0, 0, 0}
6577
};
@@ -108,7 +120,7 @@ PyTypeObject _ArrayType = {
108120
0, /*tp_clear*/
109121
0, /*tp_richcompare*/
110122
0, /*tp_weaklistoffset*/
111-
0, /*tp_iter*/
123+
(getiterfunc)Array_iterator, /*tp_iter*/
112124
0, /*tp_iternext*/
113125
Array_methods, /*tp_methods*/
114126
Array_members, /*tp_members*/

spidermonkey/jsiterator.c

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright 2009 Paul J. Davis <paul.joseph.davis@gmail.com>
3+
*
4+
* This file is part of the python-spidermonkey package released
5+
* under the MIT license.
6+
*
7+
*/
8+
9+
#include "spidermonkey.h"
10+
11+
PyObject*
12+
Iterator_Wrap(Context* cx, JSObject* obj)
13+
{
14+
Iterator* self = NULL;
15+
PyObject* tpl = NULL;
16+
PyObject* ret = NULL;
17+
JSObject* iter = NULL;
18+
jsval priv;
19+
20+
// Build our new python object.
21+
tpl = Py_BuildValue("(O)", cx);
22+
if(tpl == NULL) goto error;
23+
24+
self = (Iterator*) PyObject_CallObject((PyObject*) IteratorType, tpl);
25+
if(self == NULL) goto error;
26+
27+
// Attach a JS property iterator.
28+
self->iter = JS_NewPropertyIterator(cx->cx, obj);
29+
if(self->iter == NULL) goto error;
30+
31+
self->root = OBJECT_TO_JSVAL(self->iter);
32+
if(!JS_AddRoot(cx->cx, &(self->root)))
33+
{
34+
PyErr_SetString(PyExc_RuntimeError, "Failed to set GC root.");
35+
goto error;
36+
}
37+
38+
ret = (PyObject*) self;
39+
goto success;
40+
41+
error:
42+
Py_XDECREF(self);
43+
ret = NULL; // In case it was AddRoot
44+
success:
45+
Py_XDECREF(tpl);
46+
return (PyObject*) ret;
47+
}
48+
49+
PyObject*
50+
Iterator_new(PyTypeObject* type, PyObject* args, PyObject* kwargs)
51+
{
52+
Context* cx = NULL;
53+
Iterator* self = NULL;
54+
55+
if(!PyArg_ParseTuple(args, "O!", ContextType, &cx)) goto error;
56+
57+
self = (Iterator*) type->tp_alloc(type, 0);
58+
if(self == NULL) goto error;
59+
60+
Py_INCREF(cx);
61+
self->cx = cx;
62+
self->iter = NULL;
63+
goto success;
64+
65+
error:
66+
ERROR("spidermonkey.Iterator.new");
67+
success:
68+
return (PyObject*) self;
69+
}
70+
71+
int
72+
Iterator_init(Iterator* self, PyObject* args, PyObject* kwargs)
73+
{
74+
return 0;
75+
}
76+
77+
void
78+
Iterator_dealloc(Iterator* self)
79+
{
80+
if(self->iter != NULL)
81+
{
82+
JS_RemoveRoot(self->cx->cx, &(self->root));
83+
}
84+
85+
Py_XDECREF(self->cx);
86+
}
87+
88+
PyObject*
89+
Iterator_next(Iterator* self)
90+
{
91+
PyObject* ret = NULL;
92+
jsid propid;
93+
jsval propname;
94+
95+
if(!JS_NextProperty(self->cx->cx, self->iter, &propid))
96+
{
97+
PyErr_SetString(PyExc_RuntimeError, "Failed to iterate next JS prop.");
98+
goto done;
99+
}
100+
101+
if(!JS_IdToValue(self->cx->cx, propid, &propname))
102+
{
103+
PyErr_SetString(PyExc_RuntimeError, "Failed to convert property id.");
104+
goto done;
105+
}
106+
107+
if(propname != JSVAL_VOID)
108+
{
109+
ret = js2py(self->cx->cx, propname);
110+
}
111+
112+
// We return NULL with no error to signal completion.
113+
114+
done:
115+
return ret;
116+
}
117+
118+
static PyMemberDef Iterator_members[] = {
119+
{NULL}
120+
};
121+
122+
static PyMethodDef Iterator_methods[] = {
123+
{NULL}
124+
};
125+
126+
PyTypeObject _IteratorType = {
127+
PyObject_HEAD_INIT(NULL)
128+
0, /*ob_size*/
129+
"spidermonkey.Iterator", /*tp_name*/
130+
sizeof(Iterator), /*tp_basicsize*/
131+
0, /*tp_itemsize*/
132+
(destructor)Iterator_dealloc, /*tp_dealloc*/
133+
0, /*tp_print*/
134+
0, /*tp_getattr*/
135+
0, /*tp_setattr*/
136+
0, /*tp_compare*/
137+
0, /*tp_repr*/
138+
0, /*tp_as_number*/
139+
0, /*tp_as_sequence*/
140+
0, /*tp_as_mapping*/
141+
0, /*tp_hash*/
142+
0, /*tp_call*/
143+
0, /*tp_str*/
144+
0, /*tp_getattro*/
145+
0, /*tp_setattro*/
146+
0, /*tp_as_buffer*/
147+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
148+
"JavaScript Iterator", /*tp_doc*/
149+
0, /*tp_traverse*/
150+
0, /*tp_clear*/
151+
0, /*tp_richcompare*/
152+
0, /*tp_weaklistoffset*/
153+
0, /*tp_iter*/
154+
(iternextfunc)Iterator_next, /*tp_iternext*/
155+
Iterator_methods, /*tp_methods*/
156+
Iterator_members, /*tp_members*/
157+
0, /*tp_getset*/
158+
0, /*tp_base*/
159+
0, /*tp_dict*/
160+
0, /*tp_descr_get*/
161+
0, /*tp_descr_set*/
162+
0, /*tp_dictoffset*/
163+
(initproc)Iterator_init, /*tp_init*/
164+
0, /*tp_alloc*/
165+
Iterator_new, /*tp_new*/
166+
};

spidermonkey/jsiterator.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2009 Paul J. Davis <paul.joseph.davis@gmail.com>
3+
*
4+
* This file is part of the python-spidermonkey package released
5+
* under the MIT license.
6+
*
7+
*/
8+
9+
#ifndef PYSM_JSITERATOR_H
10+
#define PYSM_JSITERATOR_H
11+
12+
/*
13+
This is a representation of a JavaScript
14+
object in Python land.
15+
*/
16+
17+
typedef struct {
18+
PyObject_HEAD
19+
Context* cx;
20+
JSObject* iter;
21+
jsval root;
22+
} Iterator;
23+
24+
extern PyTypeObject _IteratorType;
25+
26+
PyObject* Iterator_Wrap(Context* cx, JSObject* obj);
27+
28+
#endif

spidermonkey/jsobject.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,12 @@ Object_rich_cmp(Object* self, PyObject* other, int op)
338338
return ret;
339339
}
340340

341+
PyObject*
342+
Object_iterator(Object* self, PyObject* args, PyObject* kwargs)
343+
{
344+
return Iterator_Wrap(self->cx, self->obj);
345+
}
346+
341347
static PyMemberDef Object_members[] = {
342348
{NULL}
343349
};
@@ -379,7 +385,7 @@ PyTypeObject _ObjectType = {
379385
0, /*tp_clear*/
380386
(richcmpfunc)Object_rich_cmp, /*tp_richcompare*/
381387
0, /*tp_weaklistoffset*/
382-
0, /*tp_iter*/
388+
(getiterfunc)Object_iterator, /*tp_iter*/
383389
0, /*tp_iternext*/
384390
Object_methods, /*tp_methods*/
385391
Object_members, /*tp_members*/

spidermonkey/spidermonkey.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ PyTypeObject* ContextType = NULL;
1818
PyTypeObject* ObjectType = NULL;
1919
PyTypeObject* ArrayType = NULL;
2020
PyTypeObject* FunctionType = NULL;
21+
PyTypeObject* IteratorType = NULL;
2122
PyTypeObject* HashCObjType = NULL;
2223
PyObject* JSError = NULL;
2324

@@ -40,6 +41,8 @@ initspidermonkey(void)
4041
_FunctionType.tp_base = &_ObjectType;
4142
if(PyType_Ready(&_FunctionType) < 0) return;
4243

44+
if(PyType_Ready(&_IteratorType) < 0) return;
45+
4346
if(PyType_Ready(&_HashCObjType) < 0) return;
4447

4548
m = Py_InitModule3("spidermonkey", spidermonkey_methods,
@@ -70,6 +73,10 @@ initspidermonkey(void)
7073
Py_INCREF(FunctionType);
7174
PyModule_AddObject(m, "Function", (PyObject*) FunctionType);
7275

76+
IteratorType = &_IteratorType;
77+
Py_INCREF(IteratorType);
78+
// No module access on purpose.
79+
7380
HashCObjType = &_HashCObjType;
7481
Py_INCREF(HashCObjType);
7582
// Don't add access from the module on purpose.

spidermonkey/spidermonkey.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "jsobject.h"
2828
#include "jsarray.h"
2929
#include "jsfunction.h"
30+
#include "jsiterator.h"
3031

3132
#include "convert.h"
3233
#include "error.h"
@@ -40,6 +41,7 @@ extern PyTypeObject* ClassType;
4041
extern PyTypeObject* ObjectType;
4142
extern PyTypeObject* ArrayType;
4243
extern PyTypeObject* FunctionType;
44+
extern PyTypeObject* IteratorType;
4345
extern PyTypeObject* HashCObjType;
4446
extern PyObject* JSError;
4547

tests/t.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ def lt(a, b):
5353
def gt(a, b):
5454
assert a > b, "%r <= %r" % (a, b)
5555

56+
def isin(a, b):
57+
assert a in b, "%r is not in %r" % (a, b)
58+
59+
def isnotin(a, b):
60+
assert a not in b, "%r is in %r" % (a, b)
61+
5662
def has(a, b):
5763
assert hasattr(a, b), "%r has no attribute %r" % (a, b)
5864

tests/test-iterate.py

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,29 @@
44
# under the MIT license.
55
import t
66

7-
@t.rt()
8-
def test_iter_py(rt):
9-
pairs = [
10-
({"foo": "bar", "baz": "bam"}, ["foo", "baz"]),
11-
(["a", "b", "c"], [0, 1, 2])
12-
]
13-
def check(a, b):
14-
cx = rt.new_context()
15-
cx.add_global("zip", a)
16-
js = """
17-
var ret = [];
18-
for(var v in zip) {ret.push(v);}
19-
ret;
20-
"""
21-
t.eq(cx.execute(js), b)
22-
map(lambda x: check(*x), pairs)
7+
js_script = """
8+
var ret = [];
9+
for(var v in data) {ret.push(v);}
10+
ret;
11+
"""
2312

24-
@t.rt()
25-
def test_iter_js(rt):
26-
pairs = [
27-
('var f = {"foo": 1, "domino": "daily"}; f;', ["domino", "foo"]),
28-
('["foo", 1, "bing"]', [0, 1, 2])
29-
]
30-
def check(a, b):
31-
cx = rt.new_context()
32-
ret = cx.execute(a)
33-
data = [k for k in ret]
34-
data.sort()
35-
t.eq(data, b)
36-
map(lambda x: check(*x), pairs)
13+
@t.glbl("data", {"foo": "bar", "baz": "bam"})
14+
def test_iter_py_map(cx, glbl):
15+
t.eq(cx.execute(js_script), ["foo", "baz"])
16+
17+
@t.glbl("data", ["a", "b", "c"])
18+
def test_iter_py_array(cx, glbl):
19+
t.eq(cx.execute(js_script), [0, 1, 2])
20+
21+
@t.cx()
22+
def test_iter_js_object(cx):
23+
ret = cx.execute('var f = {"foo": 1, "domino": "daily"}; f;')
24+
items = set(["domino", "foo"])
25+
for k in ret:
26+
t.isin(k, items)
27+
items.remove(k)
28+
29+
@t.cx()
30+
def test_iter_js_array(cx):
31+
ret = cx.execute('["foo", 1, "bing", [3, 6]]')
32+
t.eq([k for k in ret], ["foo", 1, "bing", [3, 6]])

0 commit comments

Comments
 (0)