Commits

masklinn  committed aca5ec7

Add basic arithmetics operator

todo: rest of the numerical ops, test that reflected ops are called and work, conversions

  • Participants
  • Parent commits 23e294c

Comments (0)

Files changed (2)

             if (!(other instanceof py.float)) { return py.NotImplemented; }
             return this._value >= other._value ? py.True : py.False;
         },
+        __add__: function (other) {
+            if (!(other instanceof py.float)) { return py.NotImplemented; }
+            return new py.float(this._value + other._value);
+        },
         __neg__: function () {
             return new py.float(-this._value);
         },
+        __sub__: function (other) {
+            if (!(other instanceof py.float)) { return py.NotImplemented; }
+            return new py.float(this._value - other._value);
+        },
+        __mul__: function (other) {
+            if (!(other instanceof py.float)) { return py.NotImplemented; }
+            return new py.float(this._value * other._value);
+        },
+        __div__: function (other) {
+            if (!(other instanceof py.float)) { return py.NotImplemented; }
+            return new py.float(this._value / other._value);
+        },
         __nonzero__: function () {
             return this._value ? py.True : py.False;
         },
             : py.False;
     });
 
+    /**
+      * Implements operator fallback/reflection.
+      *
+      * First two arguments are the objects to apply the operator on,
+      * in their actual order (ltr).
+      *
+      * 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 r;
+        var forward = '__' + m1 + '__', reverse = '__' + m2 + '__';
+
+        if (forward in o1 && (r = o1[forward](o2)) !== py.NotImplemented) {
+            return r;
+        }
+        if (reverse in o2 && (r = o2[reverse](o1)) !== py.NotImplemented) {
+            return r;
+        }
+        if (otherwise) {
+            return PY_ensurepy(otherwise(o1, o2));
+        }
+        throw new Error(
+            "TypeError: unsupported operand type(s) for " + op + ": '"
+                + o1.constructor.name + "' and '"
+                + o2.constructor.name + "'");
+    }
+
     var PY_builtins = {
         type: py.type,
 
     var evaluate_operator = function (operator, a, b) {
         var v;
         switch (operator) {
-        case '==': return a.__eq__(b);
+        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 a.__ne__(b);
+        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 '<':
-            v = a.__lt__(b);
-            if (v !== py.NotImplemented) { return v; }
-            return PY_ensurepy(a.constructor.name < b.constructor.name);
+            return PY_op(a, b, '<', 'lt', 'gt', function (a, b) {
+                return a.constructor.name < b.constructor.name;
+            });
         case '<=':
-            v = a.__le__(b);
-            if (v !== py.NotImplemented) { return v; }
-            return PY_ensurepy(a.constructor.name <= b.constructor.name);
+            return PY_op(a, b, '<=', 'le', 'ge', function (a, b) {
+                return a.constructor.name <= b.constructor.name;
+            });
         case '>':
-            v = a.__gt__(b);
-            if (v !== py.NotImplemented) { return v; }
-            return PY_ensurepy(a.constructor.name > b.constructor.name);
+            return PY_op(a, b, '>', 'gt', 'lt', function (a, b) {
+                return a.constructor.name > b.constructor.name;
+            });
         case '>=':
-            v = a.__ge__(b);
-            if (v !== py.NotImplemented) { return v; }
-            return PY_ensurepy(a.constructor.name >= b.constructor.name);
+            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':
                 if (result === py.False) { return py.False; }
             }
             return py.True;
-        case '-':
-            if (expr.second) {
-                throw new Error('SyntaxError: binary [-] not implemented yet');
-            }
-            return (py.evaluate(expr.first, context)).__neg__();
         case 'not':
             return py.evaluate(expr.first, context).__nonzero__() === py.True
                 ? py.False
             }
             return py.evaluate(expr.first, context)
                 .__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');
+            }
+            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');
+            }
+            return (py.evaluate(expr.first, context)).__neg__();
+        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');
         default:
             throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
         }

File test/test.js

     it('should aways be available', function () {
         expect(py.eval('bool("foo")')).to.be(true);
     });
+});
+
+describe('numerical protocols', function () {
+    describe('True numbers (float)', function () {
+        describe('Basic arithmetic', function () {
+            it('can be added', function () {
+                expect(py.eval('1 + 1')).to.be(2);
+                expect(py.eval('1.5 + 2')).to.be(3.5);
+                expect(py.eval('1 + -1')).to.be(0);
+            });
+            it('can be subtracted', function () {
+                expect(py.eval('1 - 1')).to.be(0);
+                expect(py.eval('1.5 - 2')).to.be(-0.5);
+                expect(py.eval('2 - 1.5')).to.be(0.5);
+            });
+            it('can be multiplied', function () {
+                expect(py.eval('1 * 3')).to.be(3);
+                expect(py.eval('0 * 5')).to.be(0);
+                expect(py.eval('42 * -2')).to.be(-84);
+            });
+            it('can be divided', function () {
+                expect(py.eval('1 / 2')).to.be(0.5);
+                expect(py.eval('2 / 1')).to.be(2);
+            });
+        });
+    });
 });