Commits

masklinn  committed 9f89391

Fix creation of lists & tuples to ensure there are no raw JS objects (or primitives) in them, only py.object

  • Participants
  • Parent commits 12c1b7f

Comments (0)

Files changed (5)

File doc/utility.rst

     :returns: ``Number``
     :raises: ``TypeError`` if the object doesn't have a length
 
+.. function:: py.PY_getItem(o, key)
+
+    Returns the element of ``o`` corresponding to the object
+    ``key``. This is equivalent to ``o[key]``.
+
+    :param o: :class:`py.object`
+    :param key: :class:`py.object`
+    :returns: :class:`py.object`
+    :raises: ``TypeError`` if ``o`` does not support the operation, if
+             ``key`` or the return value is not a :class:`py.object`
+
+.. function:: py.PY_setItem(o, key, v)
+
+    Maps the object ``key`` to the value ``v`` in ``o``. Equivalent to
+    ``o[key] = v``.
+
+    :param o: :class:`py.object`
+    :param key: :class:`py.object`
+    :param v: :class:`py.object`
+    :raises: ``TypeError`` if ``o`` does not support the operation, or
+             if ``key`` or ``v`` are not :class:`py.object`
+
 Number Protocol
 ---------------
 
             }
             return o;
         case Array:
-            var a = py.PY_call(py.list);
-            a._values = val;
-            return a;
+            return py.list.fromJSON(val);
         }
 
         throw new Error("Could not convert " + val + " to a pyval");
         }
         return v;
     };
+    py.PY_getItem = function (o, key) {
+        if (!('__getitem__' in o)) {
+            throw new Error(
+                "TypeError: '" + typename(o) +
+                    "' object is unsubscriptable")
+        }
+        if (!py.PY_isInstance(key, py.object)) {
+            throw new Error(
+                "TypeError: '" + typename(key) +
+                    "' is not a py.js object");
+        }
+        var res = o.__getitem__(key);
+        if (!py.PY_isInstance(key, py.object)) {
+            throw new Error(
+                "TypeError: __getitem__ must return a py.js object, got "
+                    + typename(res));
+        }
+        return res;
+    };
+    py.PY_setItem = function (o, key, v) {
+        if (!('__setitem__' in o)) {
+            throw new Error(
+                "TypeError: '" + typename(o) +
+                    "' object does not support item assignment");
+        }
+        if (!py.PY_isInstance(key, py.object)) {
+            throw new Error(
+                "TypeError: '" + typename(key) +
+                    "' is not a py.js object");
+        }
+        if (!py.PY_isInstance(v, py.object)) {
+            throw new Error(
+                "TypeError: '" + typename(v) +
+                    "' is not a py.js object");
+        }
+        o.__setitem__(key, v);
+    };
+
     py.PY_add = function (o1, o2) {
         return PY_op(o1, o2, '+');
     };
         },
         __contains__: function (value) {
             for(var i=0, len=this._values.length; i<len; ++i) {
-                if (this._values[i].__eq__(value) === py.True) {
+                if (py.PY_isTrue(this._values[i].__eq__(value))) {
                     return py.True;
                 }
             }
             return py.False;
         },
         __getitem__: function (index) {
-            return PY_ensurepy(this._values[index.toJSON()]);
+            return this._values[index.toJSON()];
         },
         toJSON: function () {
             var out = [];
                 out.push(this._values[i].toJSON());
             }
             return out;
+        },
+        fromJSON: function (ar) {
+            if (!(ar instanceof Array)) {
+                throw new Error("Can only create a py.tuple from an Array");
+            }
+            var t = py.PY_call(py.tuple);
+            for(var i=0; i<ar.length; ++i) {
+                t._values.push(PY_ensurepy(ar[i]));
+            }
+            return t;
         }
     });
     py.list = py.tuple;
         }
         throw new Error(
             "TypeError: unsupported operand type(s) for " + op + ": '"
-                + o1.__class__.__name__ + "' and '"
-                + o2.__class__.__name__ + "'");
+                + typename(o1) + "' and '" + typename(o2) + "'");
     };
 
     var PY_builtins = {
                 tuple_values.push(py.evaluate(
                     tuple_exprs[j], context));
             }
-            var t = py.PY_call(py.tuple);
-            t._values = tuple_values;
-            return t;
+            return py.tuple.fromJSON(tuple_values);
         case '[':
             if (expr.second) {
-                return py.evaluate(expr.first, context)
-                    .__getitem__(py.evaluate(expr.second, context));
+                return py.PY_getItem(
+                    py.evaluate(expr.first, context),
+                    py.evaluate(expr.second, context));
             }
             var list_exprs = expr.first, list_values = [];
             for (var k=0; k<list_exprs.length; ++k) {
                 list_values.push(py.evaluate(
                     list_exprs[k], context));
             }
-            var l = py.PY_call(py.list);
-            l._values = list_values;
-            return l;
+            return py.list.fromJSON(list_values);
         case '{':
             var dict_exprs = expr.first, dict = py.PY_call(py.dict);
             for(var l=0; l<dict_exprs.length; ++l) {
-                dict.__setitem__(
+                py.PY_setItem(dict,
                     py.evaluate(dict_exprs[l][0], context),
                     py.evaluate(dict_exprs[l][1], context));
             }
         }).to.throwException(/^TypeError/);
     });
 });
+describe('PY_getItem', function () {
+    it('should error if there is no __getitem__', function () {
+        var o = py.PY_call(py.type('t', null, {}));
+        expect(function () {
+            py.PY_getItem(o, 'foo');
+        }).to.throwException(/^TypeError: 't' object is unsubscriptable$/);
+    });
+    it('should error if it gets non-py arguments', function () {
+        var o = py.PY_call(py.type('t', null, {
+            __getitem__: function () {}
+        }));
+        expect(function () {
+            py.PY_getItem(o, 'foo');
+        }).to.throwException(/^TypeError/);
+    });
+    it('should use __getitem__ to get its value', function () {
+        var called = false;
+        var o = py.PY_call(py.type('t', null, {
+            __getitem__: function (arg) {
+                called = true;
+                return py.None;
+            }
+        }));
+        expect(py.PY_getItem(o, py.str.fromJSON('foo'))).to.be(py.None);
+        expect(called).to.be(true);
+    });
+    it('should error out if __getitem__ returns a non-py type', function () {
+        var o = py.PY_call(py.type('t', null, {
+            __getitem__: function () {
+                return 42;
+            }
+        }));
+        expect(function () {
+            py.PY_getItem(o, 'foo');
+        }).to.throwException(/^TypeError/);
+    });
+});
+describe('PY_setItem', function () {
+    it('should error if there is no __setitem__', function () {
+        var o = py.PY_call(py.type('t', null, {}));
+        expect(function () {
+            py.PY_setItem(o);
+        }).to.throwException(/^TypeError: 't' object does not support item assignment$/);
+    });
+    it('should error if it gets non-py arguments', function () {
+        var o = py.PY_call(py.type('t', null, {
+            __setitem__: function () {}
+        }));
+        expect(function () {
+            py.PY_setItem(o, 'foo', 1);
+        }).to.throwException(/^TypeError/);
+        expect(function () {
+            py.PY_setItem(o, py.str.fromJSON('foo'), 1);
+        }).to.throwException(/^TypeError/);
+    });
+    it('should use __setitem__ to set its value', function () {
+        var called = false;
+        var o = py.PY_call(py.type('t', null, {
+            __getitem__: function (k) {
+                if (called) {
+                    return py.str.fromJSON('bar')
+                }
+            },
+            __setitem__: function (a1, a2) {
+                expect(a1.toJSON()).to.be('foo');
+                expect(a2.toJSON()).to.be('bar');
+                called = true;
+            }
+        }));
+        py.PY_setItem(o, py.str.fromJSON('foo'), py.str.fromJSON('bar'));
+        expect(py.PY_getItem(o, py.str.fromJSON('foo')).toJSON())
+            .to.be('bar');
+    });
+});
 
 describe('PY_add', function () {
 });

File test/builtins/list.js

+var py = require('../../lib/py.js'),
+    expect = require('expect.js');
+var ev = function (str, context) {
+    return py.evaluate(py.parse(py.tokenize(str)), context);
+};
+
+describe('py.tuple', function () {
+    it('shoud have the right type', function () {
+        expect(ev('()').__class__).to.be(py.tuple);
+    });
+    it('should map to a JS array', function () {
+        expect(py.eval('()')).to.eql([]);
+        expect(py.eval('(1, 2, 3)')).to.eql([1, 2, 3]);
+    });
+    it('should parse from a JS array', function () {
+        expect(py.tuple.fromJSON([1, 2, 3]).toJSON())
+            .to.eql([1, 2, 3]);
+    });
+    describe('context conversion', function () {
+        it('should convert arrays to lists', function () {
+            expect(py.eval('foo[3]', {foo: [9, 8, 7, 6, 5]}))
+                .to.be(6);
+        });
+        it('should round-trip', function () {
+            expect(py.eval('a', {a: [1, 2, 3]}))
+                .to.eql([1, 2, 3]);
+        });
+    });
+});
+describe('py.list', function () {
+    it('shoud have the right type', function () {
+        expect(ev('[]').__class__).to.be(py.list);
+    });
+    it('should map to a JS array', function () {
+        expect(py.eval('[]')).to.eql([]);
+        expect(py.eval('[1, 2, 3]')).to.eql([1, 2, 3]);
+    });
+});

File test/test.js

             expect(py.eval('None')).to.be(null);
         });
     });
-    describe('Tuple', function () {
-        it('shoud have the right type', function () {
-            expect(ev('()').__class__).to.be(py.tuple);
-        });
-        it('should map to a JS array', function () {
-            expect(py.eval('()')).to.eql([]);
-            expect(py.eval('(1, 2, 3)')).to.eql([1, 2, 3]);
-        });
-    });
-    describe('List', function () {
-        it('shoud have the right type', function () {
-            expect(ev('[]').__class__).to.be(py.list);
-        });
-        it('should map to a JS array', function () {
-            expect(py.eval('[]')).to.eql([]);
-            expect(py.eval('[1, 2, 3]')).to.eql([1, 2, 3]);
-        });
-    });
     describe('Dict', function () {
         it('shoud have the right type', function () {
             expect(ev('{}').__class__).to.be(py.dict);
     it('should convert bare objects to objects', function () {
         expect(py.eval('foo.bar', {foo: {bar: 3}})).to.be(3);
     });
-    it('should convert arrays to lists', function () {
-        expect(py.eval('foo[3]', {foo: [9, 8, 7, 6, 5]}))
-            .to.be(6);
-    });
 });