Commits

masklinn committed ff1af87

Fully implement (untested) binary numerical operators

Remove duplications in binary operators:
* map {operators: (forward, reflected, fallback|null)}
* only provide operands and operator to PY_op, it can fetch the rest
itself
* use switch fallthrough to only have two PY_op callsites: boolean
operators and numerical binary operators, simpler to grok and less
bug-prone (especially for numerical ops as they don't get provided
with pre-evaluated operands)

Also, fully implements (also mostly untested) conversion rules of
``float()`` and ``str()``.

Comments (0)

Files changed (1)

     booleans_initialized = true;
     py.float = py.type(function float(value) {
         value = (value instanceof Array) ? value[0] : value;
-        this._value = value;
+        if (value === undefined) { this._value = 0; return; }
+        if (value instanceof py.float) { return value; }
+        if (typeof value === 'number' || value instanceof Number) {
+            this._value = value;
+            return;
+        }
+        if (typeof value === 'string' || value instanceof String) {
+            this._value = parseFloat(value);
+            return;
+        }
+        if (value instanceof py.object && '__float__' in value) {
+            var res = value.__float__();
+            if (res instanceof py.float) {
+                return res;
+            }
+            throw new Error('TypeError: __float__ returned non-float (type ' +
+                            res.constructor.name + ')');
+        }
+        throw new Error('TypeError: float() argument must be a string or a number');
     }, py.object, {
         __eq__: function (other) {
             return this._value === other._value ? py.True : py.False;
     });
     py.str = py.type(function str(s) {
         s = (s instanceof Array) ? s[0] : s;
-        this._value = s;
+        if (s === undefined) { this._value = ''; return; }
+        if (s instanceof py.str) { return s; }
+        if (typeof s === 'string' || s instanceof String) {
+            this._value = s;
+            return;
+        }
+        var v = s.__str__();
+        if (v instanceof py.str) { return v; }
+        throw new Error('TypeError: __str__ returned non-string (type ' +
+                        v.constructor.name + ')');
     }, py.object, {
         __eq__: function (other) {
             if (other instanceof py.str && this._value === other._value) {
             : py.False;
     });
 
+
+    // All binary operators with fallbacks, so they can be applied generically
+    var PY_operators = {
+        '==': ['eq', 'eq', function (a, b) { return a === b; }],
+        '!=': ['ne', 'ne', function (a, b) { return a !== b; }],
+        '<': ['lt', 'gt', function (a, b) {return a.constructor.name < b.constructor.name;}],
+        '<=': ['le', 'ge', function (a, b) {return a.constructor.name <= b.constructor.name;}],
+        '>': ['gt', 'lt', function (a, b) {return a.constructor.name > b.constructor.name;}],
+        '>=': ['ge', 'le', function (a, b) {return a.constructor.name >= b.constructor.name;}],
+
+        '+': ['add', 'radd'],
+        '-': ['sub', 'rsub'],
+        '*': ['mul', 'rmul'],
+        '/': ['div', 'rdiv'],
+        '//': ['floordiv', 'rfloordiv'],
+        '%': ['mod', 'rmod'],
+        '**': ['pow', 'rpow'],
+        '<<': ['lshift', 'rlshift'],
+        '>>': ['rshift', 'rrshift'],
+        '&': ['and', 'rand'],
+        '^': ['xor', 'rxor'],
+        '|': ['or', 'ror']
+    };
     /**
       * Implements operator fallback/reflection.
       *
       *
       * Third argument is the actual operator.
       *
-      * The next two arguments are the normal and reflected names for
-      * the dunder-method corresponding to the operator. The last argument
-      * is an optional handler in case both method calls "fail".
-      *
-      * First we try to call o1.__$m1__(o2), if o1.__$m1__ does not
-      * exist or if it returns py.NotImplemented *and* the operands
-      * are of different types we try o2.__$m2__(o1). If that does not
-      * work either (for the same reason), we try the handler. If
-      * there is no fallback handler, raise a TypeError.
-      *
       * If the operator methods raise exceptions, those exceptions are
       * not intercepted.
       */
-    var PY_op = function (o1, o2, op, m1, m2, otherwise) {
+    var PY_op = function (o1, o2, op) {
         var r;
-        var forward = '__' + m1 + '__', reverse = '__' + m2 + '__';
+        var methods = PY_operators[op];
+        var forward = '__' + methods[0] + '__', reverse = '__' + methods[1] + '__';
+        var otherwise = methods[2];
 
         if (forward in o1 && (r = o1[forward](o2)) !== py.NotImplemented) {
             return r;
     var evaluate_operator = function (operator, a, b) {
         var v;
         switch (operator) {
-        case '==':
-            return PY_op(a, b, '==', 'eq', 'eq', function (a, b) {
-                return a === b;
-            });
         case 'is': return a === b ? py.True : py.False;
-        case '!=':
-            return PY_op(a, b, '!=', 'ne', 'ne', function (a, b) {
-                return a !== b;
-            });
         case 'is not': return a !== b ? py.True : py.False;
-        case '<':
-            return PY_op(a, b, '<', 'lt', 'gt', function (a, b) {
-                return a.constructor.name < b.constructor.name;
-            });
-        case '<=':
-            return PY_op(a, b, '<=', 'le', 'ge', function (a, b) {
-                return a.constructor.name <= b.constructor.name;
-            });
-        case '>':
-            return PY_op(a, b, '>', 'gt', 'lt', function (a, b) {
-                return a.constructor.name > b.constructor.name;
-            });
-        case '>=':
-            return PY_op(a, b, '>=', 'ge', 'le', function (a, b) {
-                return a.constructor.name >= b.constructor.name;
-            });
         case 'in':
             return b.__contains__(a);
         case 'not in':
             return b.__contains__(a) === py.True ? py.False : py.True;
+        case '==': case '!=':
+        case '<': case '<=':
+        case '>': case '>=':
+            return PY_op(a, b, operator);
         }
         throw new Error('SyntaxError: unknown comparator [[' + operator + ']]');
     };
                 .__getattribute__(expr.second.value);
         // numerical operators
         case '+':
-            if (expr.second) {
-                return PY_op(
-                    py.evaluate(expr.first, context),
-                    py.evaluate(expr.second, context),
-                    '+',
-                    'add', 'radd');
+            if (!expr.second) {
+                return (py.evaluate(expr.first, context)).__pos__();
             }
-            return (py.evaluate(expr.first, context)).__pos__();
         case '-':
-            if (expr.second) {
-                return PY_op(
-                    py.evaluate(expr.first, context),
-                    py.evaluate(expr.second, context),
-                    '-',
-                    'sub', 'rsub');
+            if (!expr.second) {
+                return (py.evaluate(expr.first, context)).__neg__();
             }
-            return (py.evaluate(expr.first, context)).__neg__();
-        case '*':
+        case '*': case '/': case '//':
+        case '%':
+        case '**':
+        case '<<': case '>>':
+        case '&': case '^': case '|':
             return PY_op(
                 py.evaluate(expr.first, context),
                 py.evaluate(expr.second, context),
-                '*',
-                'mul', 'rmul');
-        case '/':
-            return PY_op(
-                py.evaluate(expr.first, context),
-                py.evaluate(expr.second, context),
-                '/',
-                'div', 'rdiv');
+                expr.id);
+
         default:
             throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
         }