Commits

masklinn  committed 69e92f2

Added a bunch of JSAPI functions, fixes #13 (kinda, incomplete)

  • Participants
  • Parent commits 32ed378
  • Tags 0.7

Comments (0)

Files changed (4)

File doc/builtins.rst

 
 .. class:: py.dict()
 
+.. function:: py.len(object)
+
 .. function:: py.isinstance(object, type)
 
 .. function:: py.issubclass(type, other_type)

File doc/utility.rst

 
 They are prefixed with ``PY_``.
 
-.. function:: py.PY_call(callable[, args][, kwargs])
-
-    Call an arbitrary python-level callable from javascript.
-
-    :param callable: A ``py.js`` callable object (broadly speaking,
-                     either a class or an object with a ``__call__``
-                     method)
-
-    :param args: javascript Array of :class:`py.object`, used as
-                 positional arguments to ``callable``
-
-    :param kwargs: javascript Object mapping names to
-                   :class:`py.object`, used as named arguments to
-                   ``callable``
-
-    :returns: nothing or :class:`py.object`
-
 .. function:: py.PY_parseArgs(arguments, format)
 
     Arguments parser converting from the :ref:`user-defined calling
     :param Function fn: the javascript function to wrap
     :returns: a callable py.js object
 
+Object Protocol
+---------------
+
+.. function:: py.PY_hasAttr(o, attr_name)
+
+    Returns ``true`` if ``o`` has the attribute ``attr_name``,
+    otherwise returns ``false``. Equivalent to Python's ``hasattr(o,
+    attr_name)``
+
+    :param o: A :class:`py.object`
+    :param attr_name: a javascript ``String``
+    :rtype: ``Boolean``
+
+.. function:: py.PY_getAttr(o, attr_name)
+
+    Retrieve an attribute ``attr_name`` from the object ``o``. Returns
+    the attribute value on success, raises ``AttributeError`` on
+    failure. Equivalent to the python expression ``o.attr_name``.
+
+    :param o: A :class:`py.object`
+    :param attr_name: a javascript ``String``
+    :returns: A :class:`py.object`
+    :raises: ``AttributeError``
+
+.. function:: py.PY_str(o)
+
+    Computes a string representation of ``o``, returns the string
+    representation. Equivalent to ``str(o)``
+
+    :param o: A :class:`py.object`
+    :returns: :class:`py.str`
+
+.. function:: py.PY_isInstance(inst, cls)
+
+    Returns ``true`` if ``inst`` is an instance of ``cls``, ``false``
+    otherwise.
+
+.. function:: py.PY_isSubclass(derived, cls)
+
+    Returns ``true`` if ``derived`` is ``cls`` or a subclass thereof.
+
+.. function:: py.PY_call(callable[, args][, kwargs])
+
+    Call an arbitrary python-level callable from javascript.
+
+    :param callable: A ``py.js`` callable object (broadly speaking,
+                     either a class or an object with a ``__call__``
+                     method)
+
+    :param args: javascript Array of :class:`py.object`, used as
+                 positional arguments to ``callable``
+
+    :param kwargs: javascript Object mapping names to
+                   :class:`py.object`, used as named arguments to
+                   ``callable``
+
+    :returns: nothing or :class:`py.object`
+
+.. function:: py.PY_isTrue(o)
+
+    Returns ``true`` if the object is considered truthy, ``false``
+    otherwise. Equivalent to ``bool(o)``.
+
+    :param o: A :class:`py.object`
+    :rtype: Boolean
+
+.. function:: py.PY_not(o)
+
+    Inverse of :func:`py.PY_isTrue`.
+
+.. function:: py.PY_size(o)
+
+    If ``o`` is a sequence or mapping, returns its length. Otherwise,
+    raises ``TypeError``.
+
+    :param o: A :class:`py.object`
+    :returns: ``Number``
+    :raises: ``TypeError`` if the object doesn't have a length
+
+Number Protocol
+---------------
+
+.. function:: py.PY_add(o1, o2)
+
+    Returns the result of adding ``o1`` and ``o2``, equivalent to
+    ``o1 + o2``.
+
+    :param o1: :class:`py.object`
+    :param o2: :class:`py.object`
+    :returns: :class:`py.object`
+
+.. function:: py.PY_subtract(o1, o2)
+
+    Returns the result of subtracting ``o2`` from ``o1``, equivalent
+    to ``o1 - o2``.
+
+    :param o1: :class:`py.object`
+    :param o2: :class:`py.object`
+    :returns: :class:`py.object`
+
+.. function:: py.PY_multiply(o1, o2)
+
+    Returns the result of multiplying ``o1`` by ``o2``, equivalent to
+    ``o1 * o2``.
+
+    :param o1: :class:`py.object`
+    :param o2: :class:`py.object`
+    :returns: :class:`py.object`
+
+.. function:: py.PY_divide(o1, o2)
+
+    Returns the result of dividing ``o1`` by ``o2``, equivalent to
+    ``o1 / o2``.
+
+    :param o1: :class:`py.object`
+    :param o2: :class:`py.object`
+    :returns: :class:`py.object`
+
+.. function:: py.PY_negative(o)
+
+    Returns the negation of ``o``, equivalent to ``-o``.
+
+    :param o: :class:`py.object`
+    :returns: :class:`py.object`
+
+.. function:: py.PY_positive(o)
+
+    Returns the "positive" of ``o``, equivalent to ``+o``.
+
+    :param o: :class:`py.object`
+    :returns: :class:`py.object`
+
 .. [#kwonly] Python 2, which py.js currently implements, does not
              support Python-level keyword-only parameters (it can be
              done through the C-API), but it seemed neat and easy
 
         var fn = function () {}
         fn.prototype = py.object;
-        if (py.PY_call(py.isinstance, [val, py.object]) === py.True
-            || py.PY_call(py.issubclass, [val, py.object]) === py.True) {
+        if (py.PY_isInstance(val, py.object)
+            || py.PY_isSubclass(val, py.object)) {
             return val;
         }
 
         return out;
     };
 
+    py.PY_hasAttr = function (o, attr_name) {
+        try {
+            py.PY_getAttr(o, attr_name);
+            return true;
+        } catch (e) {
+            return false;
+        }
+    };
+    py.PY_getAttr = function (o, attr_name) {
+        return PY_ensurepy(o.__getattribute__(attr_name));
+    };
+    py.PY_str = function (o) {
+        var v = o.__str__();
+        if (py.PY_isInstance(v, py.str)) {
+            return v;
+        }
+        var typename;
+        if (v.__class__) { // py type
+            typename = v.__class__.__name__;
+        } else if(typeof v !== 'object') { // JS primitive
+            typename = typeof v;
+        } else { // JS object
+            typename = v.constructor.name;
+        }
+        throw new Error(
+            'TypeError: __str__ returned non-string (type '+typename+')');
+    };
+    py.PY_isInstance = function (inst, cls) {
+        var fn = function () {};
+        fn.prototype = cls;
+        return inst instanceof fn;
+    };
+    py.PY_isSubclass = function (derived, cls) {
+        var fn = function () {};
+        fn.prototype = cls;
+        return derived === cls || derived instanceof fn;
+    };
     py.PY_call = function (callable, args, kwargs) {
         if (!args) {
             args = []; kwargs = {};
         }
         return callable.__call__(args, kwargs);
     };
+    py.PY_isTrue = function (o) {
+        var res = o.__nonzero__();
+        if (res === py.True) {
+            return true;
+        }
+        if (res === py.False) {
+            return false;
+        }
+        throw new Error(
+            "TypeError: __nonzero__ should return bool, returned "
+                + res.__class__.__name__);
+    };
+    py.PY_not = function (o) {
+        return !py.PY_isTrue(o);
+    };
+    py.PY_size = function (o) {
+        if (!o.__len__) {
+            throw new Error(
+                "TypeError: object of type '" +
+                    o.__class__.__name__ +
+                    "' has no len()");
+        }
+        var v = o.__len__();
+        if (typeof v !== 'number') {
+            throw new Error("TypeError: a number is required");
+        }
+        return v;
+    };
+    py.PY_add = function (o1, o2) {
+        return PY_op(o1, o2, '+');
+    };
+    py.PY_subtract = function (o1, o2) {
+        return PY_op(o1, o2, '-');
+    };
+    py.PY_multiply = function (o1, o2) {
+        return PY_op(o1, o2, '*');
+    };
+    py.PY_divide = function (o1, o2) {
+        return PY_op(o1, o2, '/');
+    };
+    py.PY_negative = function (o) {
+        if (!o.__neg__) {
+            throw new Error(
+                "TypeError: bad operand for unary -: '"
+                    + o.__class__.__name
+                    + "'");
+        }
+        return o.__neg__();
+    };
+    py.PY_positive = function (o) {
+        if (!o.__pos__) {
+            throw new Error(
+                "TypeError: bad operand for unary +: '"
+                    + o.__class__.__name
+                    + "'");
+        }
+        return o.__pos__();
+    };
+
     // Builtins
     py.type = function type(name, bases, dict) {
         var proto;
                     // val is a method from the class
                     return PY_instancemethod.fromJSON(val, this);
                 }
-                return PY_ensurepy(val);
+                return val;
             }
             if ('__getattr__' in this) {
                 return this.__getattr__(name);
             if (args.value === ph) {
                 return py.False;
             }
-            return args.value.__nonzero__() === py.True ? py.True : py.False;
+            return py.PY_isTrue(args.value) ? py.True : py.False;
         },
         __str__: function () {
             return py.str.fromJSON((this === py.True) ? "True" : "False");
             if (value === placeholder) {
                 this._value = 0; return;
             }
-            if (py.PY_call(py.isinstance, [value, py.float]) === py.True) {
+            if (py.PYisInstance(value, py.float)) {
                 this._value = value._value;
             }
-            if (py.PY_call(py.isinstance, [value, py.object]) === py.True
-                    && '__float__' in value) {
+            if (py.PY_isInstance(value, py.object) && '__float__' in value) {
                 var res = value.__float__();
-                if (py.PY_call(py.isinstance, [res, py.float]) === py.True) {
+                if (py.PY_isInstance(res, py.float)) {
                     this._value = res._value;
                     return;
                 }
             return this._value === other._value ? py.True : py.False;
         },
         __lt__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.float]) !== py.True) {
+            if (!py.PY_isInstance(other, py.float)) {
                 return py.NotImplemented;
             }
             return this._value < other._value ? py.True : py.False;
         },
         __le__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.float]) !== py.True) {
+            if (!py.PY_isInstance(other, py.float)) {
                 return py.NotImplemented;
             }
             return this._value <= other._value ? py.True : py.False;
         },
         __gt__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.float]) !== py.True) {
+            if (!py.PY_isInstance(other, py.float)) {
                 return py.NotImplemented;
             }
             return this._value > other._value ? py.True : py.False;
         },
         __ge__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.float]) !== py.True) {
+            if (!py.PY_isInstance(other, py.float)) {
                 return py.NotImplemented;
             }
             return this._value >= other._value ? py.True : py.False;
         },
         __add__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.float]) !== py.True) {
+            if (!py.PY_isInstance(other, py.float)) {
                 return py.NotImplemented;
             }
             return py.float.fromJSON(this._value + other._value);
             return py.float.fromJSON(-this._value);
         },
         __sub__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.float]) !== py.True) {
+            if (!py.PY_isInstance(other, py.float)) {
                 return py.NotImplemented;
             }
             return py.float.fromJSON(this._value - other._value);
         },
         __mul__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.float]) !== py.True) {
+            if (!py.PY_isInstance(other, py.float)) {
                 return py.NotImplemented;
             }
             return py.float.fromJSON(this._value * other._value);
         },
         __div__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.float]) !== py.True) {
+            if (!py.PY_isInstance(other, py.float)) {
                 return py.NotImplemented;
             }
             return py.float.fromJSON(this._value / other._value);
             var args = py.PY_parseArgs(arguments, [['value', placeholder]]);
             var s = args.value;
             if (s === placeholder) { this._value = ''; return; }
-            var v = s.__str__();
-            if (py.PY_call(py.isinstance, [v, py.str]) === py.True) {
-                this._value = v._value;
-                return;
-            }
-            var typename;
-            if (v.__class__) { // py type
-                typename = v.__class__.__name__;
-            } else if(typeof v !== 'object') { // JS primitive
-                typename = typeof v;
-            } else { // JS object
-                typename = v.constructor.name;
-            }
-            throw new Error(
-                'TypeError: __str__ returned non-string (type '+typename+')');
+            this._value = py.PY_str(s)._value;
         },
         __hash__: function () {
             return '\1\0\1' + this._value;
             return this;
         },
         __eq__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.str]) === py.True
+            if (py.PY_isInstance(other, py.str)
                     && this._value === other._value) {
                 return py.True;
             }
             return py.False;
         },
         __lt__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.str]) !== py.True) {
+            if (py.PY_not(py.PY_call(py.isinstance, [other, py.str]))) {
                 return py.NotImplemented;
             }
             return this._value < other._value ? py.True : py.False;
         },
         __le__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.str]) !== py.True) {
+            if (!py.PY_isInstance(other, py.str)) {
                 return py.NotImplemented;
             }
             return this._value <= other._value ? py.True : py.False;
         },
         __gt__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.str]) !== py.True) {
+            if (!py.PY_isInstance(other, py.str)) {
                 return py.NotImplemented;
             }
             return this._value > other._value ? py.True : py.False;
         },
         __ge__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.str]) !== py.True) {
+            if (!py.PY_isInstance(other, py.str)) {
                 return py.NotImplemented;
             }
             return this._value >= other._value ? py.True : py.False;
         },
         __add__: function (other) {
-            if (py.PY_call(py.isinstance, [other, py.str]) !== py.True) {
+            if (!py.PY_isInstance(other, py.str)) {
                 return py.NotImplemented;
             }
             return py.str.fromJSON(this._value + other._value);
         }
     });
 
+    py.len = new py.PY_def.fromJSON(function len() {
+        var args = py.PY_parseArgs(arguments, ['object']);
+        return py.float.fromJSON(py.PY_size(args.object));
+    });
     py.isinstance = new py.PY_def.fromJSON(function isinstance() {
         var args = py.PY_parseArgs(arguments, ['object', 'class']);
-        var fn = function () {};
-        fn.prototype = args['class'];
-        return (args.object instanceof fn) ? py.True : py.False;
+        return py.PY_isInstance(args.object, args['class'])
+            ? py.True : py.False;
     });
     py.issubclass = new py.PY_def.fromJSON(function issubclass() {
         var args = py.PY_parseArgs(arguments, ['C', 'B']);
-        var fn = function () {};
-        fn.prototype = args.B;
-        if (args.C === args.B || args.C instanceof fn) {
-            return py.True;
-        }
-        return py.False;
+        return py.PY_isSubclass(args.C, args.B)
+            ? py.True : py.False;
     });
 
 
         list: py.list,
         dict: py.dict,
 
+        len: py.len,
         isinstance: py.isinstance,
         issubclass: py.issubclass,
         classmethod: py.classmethod,
             }
             return py.True;
         case 'not':
-            return py.evaluate(expr.first, context).__nonzero__() === py.True
-                ? py.False
-                : py.True;
+            return py.PY_isTrue(py.evaluate(expr.first, context)) ? py.False : py.True;
         case 'and':
             var and_first = py.evaluate(expr.first, context);
             if (and_first.__nonzero__() === py.True) {
             if (expr.second.id !== '(name)') {
                 throw new Error('SyntaxError: ' + expr);
             }
-            return py.evaluate(expr.first, context)
-                .__getattribute__(expr.second.value);
+            return py.PY_getAttr(py.evaluate(expr.first, context),
+                                 expr.second.value);
         // numerical operators
         case '~':
             return (py.evaluate(expr.first, context)).__invert__();
         case '+':
             if (!expr.second) {
-                return (py.evaluate(expr.first, context)).__pos__();
+                return py.PY_positive(py.evaluate(expr.first, context));
             }
         case '-':
             if (!expr.second) {
-                return (py.evaluate(expr.first, context)).__neg__();
+                return py.PY_negative(py.evaluate(expr.first, context));
             }
         case '*': case '/': case '//':
         case '%':
 var py = require('../lib/py.js'),
     expect = require('expect.js');
-
+var fail = function () { expect(true).to.be(false); };
 var ev = function (str, context) {
     return py.evaluate(py.parse(py.tokenize(str)), context);
 };
 
+describe('PY_hasAttr', function () {
+    it('should OK instance attributes', function () {
+        var o = py.PY_call(py.type('t', null, {
+            __init__: function () {
+                this.attr = 'ok';
+            }
+        }));
+        expect(py.PY_hasAttr(o, 'attr')).to.be(true);
+    });
+    it('should OK class attributes', function () {
+        var o = py.PY_call(py.type('t', null, {
+            attr: 'ok'
+        }));
+        expect(py.PY_hasAttr(o, 'attr')).to.be(true);
+    });
+    it('should NOK on no attribute', function () {
+        var o = py.PY_call(py.type('t', null, {}));
+        expect(py.PY_hasAttr(o, 'attr')).to.be(false);
+    });
+    it('should NOK on overridden __getattr__ throwing', function () {
+        var o1 = py.PY_call(py.type('t', null, {
+            __getattr__: function () {
+                return null;
+            }
+        }));
+        expect(py.PY_hasAttr(o1, 'attr')).to.be(true);
+        var o2 = py.PY_call(py.type('t', null, {
+            __getattr__: function () {
+                throw new Error();
+            }
+        }));
+        expect(py.PY_hasAttr(o2, 'attr')).to.be(false);
+    });
+    it('should NOK on overridden __getattribute__ throwing', function () {
+        var o1 = py.PY_call(py.type('t', null, {
+            __getattribute__: function () {
+                return null;
+            }
+        }));
+        expect(py.PY_hasAttr(o1, 'attr')).to.be(true);
+        var o2 = py.PY_call(py.type('t', null, {
+            __getattribute__: function () {
+                throw new Error();
+            }
+        }));
+        expect(py.PY_hasAttr(o2, 'attr')).to.be(false);
+    });
+});
+describe('PY_getAttr', function () {
+    it('should return instance attributes', function () {
+        var o = py.PY_call(py.type('t', null, {
+            __init__: function () {
+                this.attr = 'ok';
+            }
+        }));
+        expect(py.PY_getAttr(o, 'attr').toJSON()).to.be('ok');
+    });
+    it('should return class attributes', function () {
+        var o = py.PY_call(py.type('t', null, {
+            attr: 'ok'
+        }));
+        expect(py.PY_getAttr(o, 'attr').toJSON()).to.be('ok');
+    });
+    it('should raise on no attribute', function () {
+        var o = py.PY_call(py.type('t', null, {}));
+        expect(function () {
+            py.PY_getAttr(o, 'attr');
+        }).to.throwException(/^AttributeError/);
+    });
+    it('should NOK on overridden __getattr__ throwing', function () {
+        var o1 = py.PY_call(py.type('t', null, {
+            __getattr__: function () {
+                return 'ok'
+            }
+        }));
+        expect(py.PY_getAttr(o1, 'attr').toJSON()).to.be('ok');
+        var o2 = py.PY_call(py.type('t', null, {
+            __getattr__: function () {
+                throw new Error();
+            }
+        }));
+        expect(function () {
+            py.PY_getAttr(o2, 'attr');
+        }).to.throwException();
+    });
+    it('should NOK on overridden __getattribute__ throwing', function () {
+        var o1 = py.PY_call(py.type('t', null, {
+            __getattribute__: function () {
+                return 'ok';
+            }
+        }));
+        expect(py.PY_getAttr(o1, 'attr').toJSON()).to.be('ok');
+        var o2 = py.PY_call(py.type('t', null, {
+            __getattribute__: function () {
+                throw new Error();
+            }
+        }));
+        expect(function () {
+            py.PY_getAttr(o2, 'attr');
+        }).to.throwException();
+    });
+});
+describe('PY_str', function () {
+    it('should just return a py.str', function () {
+        var o = py.PY_str(py.PY_call(py.type('f', null, {})));
+        expect(o.__class__).to.be(py.str);
+        expect(o.toJSON()).to.be('<f object>');
+    });
+});
+describe('PY_isInstance', function () {
+});
+describe('PY_isSubclass', function () {
+});
 describe('PY_call', function () {
     it('should call a function', function () {
         var called = false;
         expect(called).to.be(true);
     });
 });
+describe('PY_isTrue', function () {
+    it('should work on basic booleans', function () {
+        expect(py.PY_isTrue(py.True)).to.be(true);
+        expect(py.PY_isTrue(py.False)).to.be(false);
+    });
+    it('should default to true', function () {
+        var o = py.PY_call(py.type('t', null, {}));
+        expect(py.PY_isTrue(o)).to.be(true);
+    });
+    it('should use nonzero', function () {
+        var o = py.PY_call(py.type('t', null, {
+            __nonzero__: function () {
+                return py.False;
+            }
+        }));
+        expect(py.PY_isTrue(o)).to.be(false);
+    });
+    it('should refuse non-bool results', function () {
+        var o = py.PY_call(py.type('t', null, {
+            __nonzero__: function () {
+                return py.str.fromJSON("ok");
+            }
+        }));
+        expect(function () {
+            py.PY_isTrue(o)
+        }).to.throwException(/^TypeError/);
+    });
+});
+describe('PY_not', function () {
+    it('should return the reverse of PY_isTrue', function () {
+        expect(py.PY_not(py.True)).to.be(false);
+        expect(py.PY_not(py.False)).to.be(true);
+    });
+});
+describe('PY_size', function () {
+    it('should error if no __len__', function () {
+        var o = py.PY_call(py.type('t', null, {}));
+        expect(function () {
+            py.PY_size(o);
+        }).to.throwException(/^TypeError/);
+    });
+    it('should use __len__ to get its value', function () {
+        var o = py.PY_call(py.type('t', null, {
+            __len__: function () {
+                return 42;
+            }
+        }));
+        expect(py.PY_size(o)).to.be(42);
+    });
+    it('should TypeError if __len__ does not return a number', function () {
+        var o = py.PY_call(py.type('t', null, {
+            __len__: function () {
+                return "foo";
+            }
+        }));
+        expect(function () {
+            py.PY_size(o);
+        }).to.throwException(/^TypeError/);
+    });
+});
+
+describe('PY_add', function () {
+});
+describe('PY_subtract', function () {
+});
+describe('PY_multiply', function () {
+});
+describe('PY_divide', function () {
+});
+describe('PY_negative', function () {
+});
+describe('PY_positive', function () {
+});