diff --git a/lib/nodes.js b/lib/nodes.js index b0c230b7..9dfc8da8 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -47,7 +47,7 @@ return ClosureNode.wrap(this).compile(o); }; BaseNode.prototype.compileReference = function(o, options) { - var compiled, pair, reference; + var compiled, pair, reference, temp; options || (options = {}); pair = (function() { if (!(this.containsType(CallNode) || (this instanceof ValueNode && (!(this.base instanceof LiteralNode) || this.hasProperties())))) { @@ -55,13 +55,13 @@ } else if (this instanceof ValueNode && options.assignment) { return this.cacheIndexes(o); } else { - reference = literal(o.scope.freeVariable('cache')); + reference = literal(temp = o.scope.freeVariable('cache')); compiled = new AssignNode(reference, this); - return [compiled, reference]; + return [compiled, reference, temp]; } }).call(this); if (options.precompile) { - return [pair[0].compile(o), pair[1].compile(o)]; + return [pair[0].compile(o), pair[1].compile(o), pair[2]]; } return pair; }; @@ -1377,18 +1377,23 @@ return [this.first.compile(o), this.operator, this.second.compile(o)].join(' '); }; OpNode.prototype.compileChain = function(o) { - var _cache2, _cache3, first, second, shared; + var _cache2, _cache3, first, js, second, shared, temp; shared = this.first.unwrap().second; if (shared.containsType(CallNode)) { _cache2 = shared.compileReference(o); this.first.second = _cache2[0]; shared = _cache2[1]; + temp = _cache2[2]; } _cache3 = [this.first.compile(o), this.second.compile(o), shared.compile(o)]; first = _cache3[0]; second = _cache3[1]; shared = _cache3[2]; - return "(" + (first) + ") && (" + (shared) + " " + (this.operator) + " " + (second) + ")"; + js = ("(" + (first) + ") && (" + (shared) + " " + (this.operator) + " " + (second) + ")"); + if (temp) { + o.scope.reuse(temp); + } + return js; }; OpNode.prototype.compileAssignment = function(o) { var _cache2, first, firstVar, second; diff --git a/lib/scope.js b/lib/scope.js index 66af2231..c3a34a9b 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -1,6 +1,6 @@ (function() { var Scope; - var __hasProp = Object.prototype.hasOwnProperty; + var __slice = Array.prototype.slice, __hasProp = Object.prototype.hasOwnProperty; if (!(typeof process !== "undefined" && process !== null)) { this.exports = this; } @@ -25,6 +25,16 @@ this.variables[name] = 'var'; return false; }; + Scope.prototype.reuse = function() { + var _cache, _cache2, _index, _result, names, val; + names = __slice.call(arguments, 0); + _result = []; _cache = names; + for (_index = 0, _cache2 = _cache.length; _index < _cache2; _index++) { + val = _cache[_index]; + _result.push(this.variables[val] = 'reuse'); + } + return _result; + }; Scope.prototype.any = function(fn) { var _cache, k, v; _cache = this.variables; @@ -54,7 +64,7 @@ Scope.prototype.freeVariable = function(type) { var index, temp; index = 0; - while (this.check(temp = this.temporary(type, index))) { + while ((this.check(temp = this.temporary(type, index))) && this.variables[temp] !== 'reuse') { index++; } this.variables[temp] = 'var'; @@ -68,7 +78,7 @@ }; Scope.prototype.hasDeclarations = function(body) { return body === this.expressions && this.any(function(k, val) { - return val === 'var'; + return val === 'var' || val === 'reuse'; }); }; Scope.prototype.hasAssignments = function(body) { @@ -83,7 +93,7 @@ for (key in _cache) { if (!__hasProp.call(_cache, key)) continue; val = _cache[key]; - if (val === 'var') { + if (val === 'var' || val === 'reuse') { _result.push(key); } } diff --git a/src/nodes.coffee b/src/nodes.coffee index 6dbd2ed4..6f2c7b03 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -70,10 +70,10 @@ exports.BaseNode = class BaseNode else if this instanceof ValueNode and options.assignment this.cacheIndexes(o) else - reference = literal o.scope.freeVariable 'cache' + reference = literal temp = o.scope.freeVariable 'cache' compiled = new AssignNode reference, this - [compiled, reference] - return [pair[0].compile(o), pair[1].compile(o)] if options.precompile + [compiled, reference, temp] + return [pair[0].compile(o), pair[1].compile(o), pair[2]] if options.precompile pair # Convenience method to grab the current indentation level, plus tabbing in. @@ -1176,9 +1176,11 @@ exports.OpNode = class OpNode extends BaseNode # true compileChain: (o) -> shared = @first.unwrap().second - [@first.second, shared] = shared.compileReference(o) if shared.containsType CallNode + [@first.second, shared, temp] = shared.compileReference(o) if shared.containsType CallNode [first, second, shared] = [@first.compile(o), @second.compile(o), shared.compile(o)] - "(#{first}) && (#{shared} #{@operator} #{second})" + js = "(#{first}) && (#{shared} #{@operator} #{second})" + o.scope.reuse temp if temp + js # When compiling a conditional assignment, take care to ensure that the # operands are only evaluated once, even though we have to reference them diff --git a/src/scope.coffee b/src/scope.coffee index 46d73c1a..bf1d62aa 100644 --- a/src/scope.coffee +++ b/src/scope.coffee @@ -29,6 +29,11 @@ exports.Scope = class Scope @variables[name] = 'var' false + # Erase a variable from scope. This is usually carried when we are done + # working with a list of temporary variables and we want to flag them for reuse. + reuse: (names...) -> + (@variables[val] = 'reuse') for val in names + # Test variables and return true the first time fn(v, k) returns true any: (fn) -> for v, k of @variables when fn(v, k) @@ -55,7 +60,7 @@ exports.Scope = class Scope # compiler-generated variable. `_var`, `_var2`, and so on... freeVariable: (type) -> index = 0 - index++ while @check temp = @temporary type, index + index++ while (@check temp = @temporary type, index) and @variables[temp] isnt 'reuse' @variables[temp] = 'var' temp @@ -67,7 +72,7 @@ exports.Scope = class Scope # Does this scope reference any variables that need to be declared in the # given function body? hasDeclarations: (body) -> - body is @expressions and @any (k, val) -> val is 'var' + body is @expressions and @any (k, val) -> val is 'var' or val is 'reuse' # Does this scope reference any assignments that need to be declared at the # top of the given function body? @@ -76,7 +81,7 @@ exports.Scope = class Scope # Return the list of variables first declared in this scope. declaredVariables: -> - (key for key, val of @variables when val is 'var').sort() + (key for key, val of @variables when val is 'var' or val is 'reuse').sort() # Return the list of assignments that are supposed to be made at the top # of this scope.