From 981db17b8f718be9437d20ced9b0dd2408ae57ed Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 25 Sep 2011 21:44:23 -0400 Subject: [PATCH] Adopting coco-style efficient bound functions for the common case ... but not for class/prototypes. --- lib/coffee-script/nodes.js | 42 ++++++++++++++++++++++---------------- src/nodes.coffee | 12 ++++++++--- test/functions.coffee | 15 ++++++++++++++ 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 7bcd6f81..322c1d23 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1,6 +1,6 @@ (function() { var Access, Arr, Assign, Base, Block, Call, Class, Closure, Code, Comment, Existence, Extends, For, IDENTIFIER, IDENTIFIER_STR, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, compact, del, ends, extend, flatten, last, merge, multident, starts, unfoldSoak, utility, _ref; - var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (__hasProp.call(this, i) && this[i] === item) return i; } return -1; }; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }, __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (__hasProp.call(this, i) && this[i] === item) return i; } return -1; }; Scope = require('./scope').Scope; @@ -397,8 +397,8 @@ }; Literal.prototype.compileNode = function(o) { - var code, _ref2; - code = this.isUndefined ? o.level >= LEVEL_ACCESS ? '(void 0)' : 'void 0' : this.value.reserved && ((_ref2 = "" + this.value) !== 'eval' && _ref2 !== 'arguments') ? "\"" + this.value + "\"" : this.value; + var code, _ref2, _ref3; + code = this.isUndefined ? o.level >= LEVEL_ACCESS ? '(void 0)' : 'void 0' : this.value === 'this' ? ((_ref2 = o.scope.method) != null ? _ref2.bound : void 0) ? o.scope.method.context : this.value : this.value.reserved && ((_ref3 = "" + this.value) !== 'eval' && _ref3 !== 'arguments') ? "\"" + this.value + "\"" : this.value; if (this.isStatement()) { return "" + this.tab + code + ";"; } else { @@ -563,20 +563,21 @@ Value.prototype.unfoldSoak = function(o) { var result; + var _this = this; if (this.unfoldedSoak != null) return this.unfoldedSoak; - result = __bind(function() { + result = (function() { var fst, i, ifn, prop, ref, snd, _len, _ref2; - if (ifn = this.base.unfoldSoak(o)) { - Array.prototype.push.apply(ifn.body.properties, this.properties); + if (ifn = _this.base.unfoldSoak(o)) { + Array.prototype.push.apply(ifn.body.properties, _this.properties); return ifn; } - _ref2 = this.properties; + _ref2 = _this.properties; for (i = 0, _len = _ref2.length; i < _len; i++) { prop = _ref2[i]; if (!prop.soak) continue; prop.soak = false; - fst = new Value(this.base, this.properties.slice(0, i)); - snd = new Value(this.base, this.properties.slice(i)); + fst = new Value(_this.base, _this.properties.slice(0, i)); + snd = new Value(_this.base, _this.properties.slice(i)); if (fst.isComplex()) { ref = new Literal(o.scope.freeVariable('ref')); fst = new Parens(new Assign(ref, fst)); @@ -587,7 +588,7 @@ }); } return null; - }, this)(); + })(); return this.unfoldedSoak = result || false; }; @@ -1166,7 +1167,8 @@ }; Class.prototype.walkBody = function(name, o) { - return this.traverseChildren(false, __bind(function(child) { + var _this = this; + return this.traverseChildren(false, function(child) { var exps, i, node, _len, _ref2; if (child instanceof Class) return false; if (child instanceof Block) { @@ -1174,12 +1176,12 @@ for (i = 0, _len = _ref2.length; i < _len; i++) { node = _ref2[i]; if (node instanceof Value && node.isObject(true)) { - exps[i] = this.addProperties(node, name, o); + exps[i] = _this.addProperties(node, name, o); } } return child.expressions = exps = flatten(exps); } - }, this)); + }); }; Class.prototype.ensureConstructor = function(name) { @@ -1429,7 +1431,7 @@ this.params = params || []; this.body = body || new Block; this.bound = tag === 'boundfunc'; - if (this.bound) this.context = 'this'; + if (this.bound) this.context = '_this'; } Code.prototype.children = ['params', 'body']; @@ -1441,7 +1443,7 @@ Code.prototype.jumps = NO; Code.prototype.compileNode = function(o) { - var code, exprs, i, idt, lit, p, param, ref, splats, v, val, vars, wasEmpty, _i, _j, _k, _len, _len2, _len3, _len4, _ref2, _ref3, _ref4, _ref5; + var code, exprs, i, idt, lit, p, param, ref, splats, v, val, vars, wasEmpty, _i, _j, _k, _len, _len2, _len3, _len4, _ref2, _ref3, _ref4, _ref5, _ref6; o.scope = new Scope(o.scope, this.body, this); o.scope.shared = del(o, 'sharedScope'); o.indent += TAB; @@ -1500,6 +1502,13 @@ } } if (!(wasEmpty || this.noReturn)) this.body.makeReturn(); + if (this.bound) { + if ((_ref6 = o.scope.parent.method) != null ? _ref6.bound : void 0) { + this.bound = o.scope.parent.method.context; + } else { + o.scope.parent.assign('_this', 'this'); + } + } idt = o.indent; code = 'function'; if (this.ctor) code += ' ' + this.name; @@ -1509,9 +1518,6 @@ } code += '}'; if (this.ctor) return this.tab + code; - if (this.bound) { - return utility('bind') + ("(" + code + ", " + this.context + ")"); - } if (this.front || (o.level >= LEVEL_ACCESS)) { return "(" + code + ")"; } else { diff --git a/src/nodes.coffee b/src/nodes.coffee index a813c6cd..07ceb8d3 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -310,6 +310,8 @@ exports.Literal = class Literal extends Base compileNode: (o) -> code = if @isUndefined if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0' + else if @value is 'this' + if o.scope.method?.bound then o.scope.method.context else @value else if @value.reserved and "#{@value}" not in ['eval', 'arguments'] "\"#{@value}\"" else @@ -1080,7 +1082,7 @@ exports.Code = class Code extends Base @params = params or [] @body = body or new Block @bound = tag is 'boundfunc' - @context = 'this' if @bound + @context = '_this' if @bound children: ['params', 'body'] @@ -1095,7 +1097,7 @@ exports.Code = class Code extends Base # a closure. compileNode: (o) -> o.scope = new Scope o.scope, @body, this - o.scope.shared = del o, 'sharedScope' + o.scope.shared = del(o, 'sharedScope') o.indent += TAB delete o.bare vars = [] @@ -1122,6 +1124,11 @@ exports.Code = class Code extends Base @body.expressions.unshift exprs... if exprs.length o.scope.parameter vars[i] = v.compile o for v, i in vars unless splats @body.makeReturn() unless wasEmpty or @noReturn + if @bound + if o.scope.parent.method?.bound + @bound = o.scope.parent.method.context + else + o.scope.parent.assign '_this', 'this' idt = o.indent code = 'function' code += ' ' + @name if @ctor @@ -1129,7 +1136,6 @@ exports.Code = class Code extends Base code += "\n#{ @body.compileWithDeclarations o }\n#{@tab}" unless @body.isEmpty() code += '}' return @tab + code if @ctor - return utility('bind') + "(#{code}, #{@context})" if @bound if @front or (o.level >= LEVEL_ACCESS) then "(#{code})" else code # Short-circuit `traverseChildren` method to prevent it from crossing scope boundaries diff --git a/test/functions.coffee b/test/functions.coffee index 2d6b6f5f..5276b63f 100644 --- a/test/functions.coffee +++ b/test/functions.coffee @@ -64,6 +64,21 @@ ok obj isnt obj.unbound() eq obj, obj.nested() +test "even more fancy bound functions", -> + obj = + one: -> + do => + return this.two() + two: -> + do => + do => + do => + return this.three + three: 3 + + eq obj.one(), 3 + + test "self-referencing functions", -> changeMe = -> changeMe = 2