diff --git a/lib/nodes.js b/lib/nodes.js index dc01b64e..9fa106c5 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -70,13 +70,15 @@ Base.prototype.makeReturn = function() { return new Return(this); }; - Base.prototype.contains = function(block, arg) { + Base.prototype.contains = function(pred) { var contains; contains = false; - this.traverseChildren(false, function(node, arg) { - var rearg; - return (rearg = block(node, arg)) === true ? !(contains = true) : arg != null ? rearg : void 0; - }, arg); + this.traverseChildren(false, function(node) { + if (pred(node)) { + contains = true; + return false; + } + }); return contains; }; Base.prototype.containsType = function(type) { @@ -85,11 +87,7 @@ }); }; Base.prototype.containsPureStatement = function() { - return this.isPureStatement() || this.contains(function(node, func) { - return func(node) || (node instanceof While || node instanceof For ? function(node) { - return node instanceof Return; - } : func); - }, function(node) { + return this.isPureStatement() || this.contains(function(node) { return node.isPureStatement(); }); }; @@ -136,12 +134,12 @@ }); return nodes; }; - Base.prototype.traverseChildren = function(crossScope, func, arg) { + Base.prototype.traverseChildren = function(crossScope, func) { return this.eachChild(function(child) { - if ((arg = func(child, arg)) === false) { + if (func(child) === false) { return false; } - return child.traverseChildren(crossScope, func, arg); + return child.traverseChildren(crossScope, func); }); }; Base.prototype.invert = function() { @@ -1199,14 +1197,31 @@ __extends(While, Base); While.prototype.children = ['condition', 'guard', 'body']; While.prototype.isStatement = YES; - While.prototype.addBody = function(body) { - this.body = body; + While.prototype.addBody = function(_arg) { + this.body = _arg; return this; }; While.prototype.makeReturn = function() { this.returns = true; return this; }; + While.prototype.containsPureStatement = function() { + var _ref2, expressions, i, ret; + expressions = this.body.expressions; + i = expressions.length; + if ((_ref2 = expressions[--i]) != null ? _ref2.containsPureStatement() : void 0) { + return true; + } + ret = function(node) { + return node instanceof Return; + }; + while (i--) { + if (expressions[i].contains(ret)) { + return true; + } + } + return false; + }; While.prototype.compileNode = function(o) { var body, code, rvar, set; o.indent = this.idt(1); @@ -1488,12 +1503,12 @@ })(); exports.For = (function() { For = (function() { - function For(_arg, head) { - this.body = _arg; + function For(body, head) { if (head.index instanceof Value) { throw SyntaxError('index cannot be a pattern matching expression'); } extend(this, head); + this.body = Expressions.wrap([body]); if (!this.object) { this.step || (this.step = new Literal(1)); } @@ -1510,6 +1525,7 @@ this.returns = true; return this; }; + For.prototype.containsPureStatement = While.prototype.containsPureStatement; For.prototype.compileReturnValue = function(val, o) { if (this.returns) { return '\n' + new Return(new Literal(val)).compile(o); @@ -1522,11 +1538,11 @@ For.prototype.compileNode = function(o) { var _ref2, _ref3, _ref4, _ref5, _ref6, body, code, cond, defPart, forPart, guardPart, idt, index, ivar, lvar, name, namePart, pvar, retPart, rvar, scope, sourcePart, step, svar, tail, tvar, varPart, vars; scope = o.scope; + body = this.body; name = !this.pattern && ((_ref2 = this.name) != null ? _ref2.compile(o) : void 0); index = (_ref3 = this.index) != null ? _ref3.compile(o) : void 0; ivar = !index ? scope.freeVariable('i') : index; varPart = guardPart = defPart = retPart = ''; - body = Expressions.wrap([this.body]); idt = this.idt(1); if (name) { scope.find(name, { diff --git a/src/nodes.coffee b/src/nodes.coffee index 99139808..623f5889 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -85,11 +85,12 @@ exports.Base = class Base # Recursively traverses down the *children* of the nodes, yielding to a block # and returning true when the block finds a match. `contains` does not cross # scope boundaries. - contains: (block, arg) -> + contains: (pred) -> contains = no - @traverseChildren false, (node, arg) -> - if (rearg = block node, arg) is true then not contains = true else if arg? then rearg - , arg + @traverseChildren no, (node) -> + if pred node + contains = yes + return no contains # Is this node of a certain type, or does it contain the type? @@ -99,11 +100,7 @@ exports.Base = class Base # Convenience for the most common use of contains. Does the node contain # a pure statement? containsPureStatement: -> - @isPureStatement() or @contains (node, func) -> - func(node) or if node instanceof While or node instanceof For - (node) -> node instanceof Return - else func - , (node) -> node.isPureStatement() + @isPureStatement() or @contains (node) -> node.isPureStatement() # `toString` representation of the node, for inspecting the parse tree. # This is what `coffee --nodes` prints out. @@ -125,10 +122,10 @@ exports.Base = class Base @eachChild (node) -> nodes.push node nodes - traverseChildren: (crossScope, func, arg) -> + traverseChildren: (crossScope, func) -> @eachChild (child) -> - return false if (arg = func child, arg) is false - child.traverseChildren crossScope, func, arg + return false if func(child) is false + child.traverseChildren crossScope, func invert: -> new Op '!', this @@ -996,14 +993,21 @@ exports.While = class While extends Base @condition = if options?.invert then condition.invert() else condition @guard = options?.guard - addBody: (body) -> - @body = body + addBody: (@body) -> this makeReturn: -> @returns = true this + containsPureStatement: -> + {expressions} = @body + i = expressions.length + return true if expressions[--i]?.containsPureStatement() + ret = (node) -> node instanceof Return + return true while i-- when expressions[i].contains ret + false + # The main difference from a JavaScript *while* is that the CoffeeScript # *while* can be used as a part of a larger expression -- while loops may # return an array containing the computed result of each iteration. @@ -1259,10 +1263,11 @@ exports.For = class For extends Base isStatement: YES - constructor: (@body, head) -> + constructor: (body, head) -> if head.index instanceof Value throw SyntaxError 'index cannot be a pattern matching expression' extend this, head + @body = Expressions.wrap [body] @step or= new Literal 1 unless @object @pattern = @name instanceof Value @returns = false @@ -1271,6 +1276,8 @@ exports.For = class For extends Base @returns = true this + containsPureStatement: While::containsPureStatement + compileReturnValue: (val, o) -> return '\n' + new Return(new Literal val).compile o if @returns return '\n' + val if val @@ -1282,11 +1289,11 @@ exports.For = class For extends Base # some cannot. compileNode: (o) -> {scope} = o + {body} = this name = not @pattern and @name?.compile o index = @index?.compile o ivar = if not index then scope.freeVariable 'i' else index varPart = guardPart = defPart = retPart = '' - body = Expressions.wrap [@body] idt = @idt 1 scope.find(name, immediate: yes) if name scope.find(index, immediate: yes) if index diff --git a/test/test_comprehensions.coffee b/test/test_comprehensions.coffee index e155d0de..981c4169 100644 --- a/test/test_comprehensions.coffee +++ b/test/test_comprehensions.coffee @@ -114,6 +114,14 @@ ok val[0] is i # Comprehensions only wrap their last line in a closure, allowing other lines # to have pure expressions in them. func = -> for i in [1] - return if false + break if i is 2 j for j in [1] + ok func()[0][0] is 1 + +i = 6 +odds = while i-- + continue unless i & 1 + i + +ok odds.join(', ') is '5, 3, 1'