bebraw
5/16/2011 - 10:58 AM

Operator overloading for JavaScript. Blog post resource files. Post: http://nixtu.blogspot.com/2011/05/using-jsshaper-to-provide-operator.ht

Operator overloading for JavaScript. Blog post resource files. Post: http://nixtu.blogspot.com/2011/05/using-jsshaper-to-provide-operator.html

var opOverTest = function() {
    var a = new Point(4, 6);
    var b = new Point(2, 9);
    
    return {
        add: a + b,
        sub: a - b,
        div: a / b,
        mul: a * b,
        combo: a + (b - a) * a / b
    };
};
var opOverTest = function() {
    var a = new Point(4, 6);
    var b = new Point(2, 9);
    
    return {
        add: ops.add(a, b),
        sub: ops.sub(a, b),
        div: ops.div(a, b),
        mul: ops.mul(a, b),
        combo: ops.add(a, ops.div(ops.mul((ops.sub(b, a)), a), b))
    };
};
var ops = function() {
    var ret = {};
    var customTypes = [Point];
    var defaults = {
        add: function(a, b) {return a + b;},
        sub: function(a, b) {return a - b;},
        div: function(a, b) {return a / b;},
        mul: function(a, b) {return a * b;}
    };
    
    var addOp = function(op, func) {
        ret[op] = function(a, b) {
            for(var i = 0; i < customTypes.length; i++) {
                var type = customTypes[i];
                
                if(a instanceof type) {
                    return a[op](b);
                }
            }
            
            return func(a, b);
        };
    };
    
    for(var op in defaults) {
        var func = defaults[op];
        
        addOp(op, func);
    }
    
    return ret;
}();
"use strict"; "use restrict";

var Shaper = require("shaper.js");

Shaper("op-overloading", function(root) {
    var prefix = 'ops.';
    var ops = {
        '+': 'add',
        '-': 'sub',
        '/': 'div',
        '*': 'mul'
    };
    // cache expressions here
    // Note that "call" may not be cached since it is mutable.
    var exprs = function() {
        var ret = [];
        
        for(var op in ops) {
            var func = ops[op];
            
            ret.push({
                from: Shaper.parseExpression('$ ' + op + ' $'),
                func: func
            });
        }
        
        return ret;
    }();
    
    return Shaper.traverseTree(root, {
        pre: function(node, ref) {
            for(var i = 0; i < exprs.length; i++) {
                var expr = exprs[i];
                
                if(Shaper.match(expr.from, node)) {
                    var callExpr = prefix + expr.func + '($, $)';
                    var call = Shaper.parseExpression(callExpr);

                    Shaper.replace(call, node.children[0], node.children[1]);
                    Shaper.cloneComments(call, node);

                    return ref.set(call);
                }
            }       
        }
    });
});
var a = new Point(3, 5);
var b = new Point(2, 23);
var result = a.mul(a).sub(b.mul(b)); // a * a - b * b
var isNumber = function() {
    // borrowed from RightJS
    return typeof(val) === 'number';
};

function Point(x, y) {
    this.init(x, y);
}
Point.prototype = {
    init: function(x, y) {
        this.x = x? x: 0;
        this.y = y? y: 0;
    },
    add: function(other) {
        return this._opTemplate(other, function(a, b) {return a + b});
    },
    sub: function(other) {
        return this._opTemplate(other, function(a, b) {return a - b});
    },
    mul: function(other) {
        return this._opTemplate(other, function(a, b) {return a * b});
    },
    div: function(other) {
        return this._opTemplate(other, function(a, b) {return a / b});
    },
    floor: function() {
        return this._opTemplate(null, function(a) {return Math.floor(a)});
    },
    round: function() {
        return this._opTemplate(null, function(a) {return Math.round(a)});
    },
    _opTemplate: function(other, op) {
        if(isNumber(other)) {
            return new Point(op(this.x, other), op(this.y, other));
        }

        if(other == null) {
            return new Point(op(this.x), op(this.y));
        }

        return new Point(op(this.x, other.x), op(this.y, other.y));
    },
    toDist: function() {
        return Math.sqrt(this.toDistSquared());
    },
    toDistSquared: function() {
        return this.x * this.x + this.y * this.y;
    },
    toAngle: function() {
        // returns vec as angle (clockwise) in rad [0, 2*PI] starting from (1, 0)
        var ret = Math.atan2(this.y, this.x);

        ret += ret < 0? 2 * Math.PI: 0;

        ret = Math.min(Math.PI * 2, ret);

        return ret - Math.PI / 2;
    },
    normalize: function() {
        return this.div(this.toDist());
    },
    invert: function() {
        return new Point(-this.x, -this.y);
    },
    toString: function() {
        return 'x: ' + this.x + ', y: ' + this.y;
    }
};