1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00

closes #1669; loop results are now collected in the same way as auto-returns are generated

This commit is contained in:
satyr 2011-09-07 12:13:23 +09:00
parent 2ea51b02cc
commit ad1bc1e0b8
4 changed files with 95 additions and 96 deletions

View file

@ -1,5 +1,5 @@
(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, Push, 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 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; }
@ -74,8 +74,14 @@
}
return [src, tmp];
};
Base.prototype.makeReturn = function() {
return new Return(this);
Base.prototype.makeReturn = function(res) {
var me;
me = this.unwrapAll();
if (res) {
return new Call(new Literal("" + res + ".push"), [me]);
} else {
return new Return(me);
}
};
Base.prototype.contains = function(pred) {
var contains;
@ -200,13 +206,13 @@
if (exp.jumps(o)) return exp;
}
};
Block.prototype.makeReturn = function() {
Block.prototype.makeReturn = function(res) {
var expr, len;
len = this.expressions.length;
while (len--) {
expr = this.expressions[len];
if (!(expr instanceof Comment)) {
this.expressions[len] = expr.makeReturn();
this.expressions[len] = expr.makeReturn(res);
if (expr instanceof Return && !expr.expression) {
this.expressions.splice(len, 1);
}
@ -310,7 +316,7 @@
if (this.isStatement()) {
return this;
} else {
return new Return(this);
return Literal.__super__.makeReturn.apply(this, arguments);
}
};
Literal.prototype.isAssignable = function() {
@ -423,13 +429,6 @@
Value.prototype.isSplice = function() {
return last(this.properties) instanceof Slice;
};
Value.prototype.makeReturn = function() {
if (this.properties.length) {
return Value.__super__.makeReturn.call(this);
} else {
return this.base.makeReturn();
}
};
Value.prototype.unwrap = function() {
if (this.properties.length) {
return this;
@ -817,13 +816,7 @@
Obj.prototype.compileNode = function(o) {
var i, idt, indent, join, lastNoncom, node, obj, prop, props, _i, _len;
props = this.properties;
if (!props.length) {
if (this.front) {
return '({})';
} else {
return '{}';
}
}
if (!props.length) return (this.front ? '({})' : '{}');
if (this.generated) {
for (_i = 0, _len = props.length; _i < _len; _i++) {
node = props[_i];
@ -1415,9 +1408,13 @@
}
While.prototype.children = ['condition', 'guard', 'body'];
While.prototype.isStatement = YES;
While.prototype.makeReturn = function() {
this.returns = true;
return this;
While.prototype.makeReturn = function(res) {
if (res) {
return While.__super__.makeReturn.apply(this, arguments);
} else {
this.returns = true;
return this;
}
};
While.prototype.addBody = function(body) {
this.body = body;
@ -1443,10 +1440,9 @@
if (body.isEmpty()) {
body = '';
} else {
if (o.level > LEVEL_TOP || this.returns) {
rvar = o.scope.freeVariable('results');
if (this.returns) {
body.makeReturn(rvar = o.scope.freeVariable('results'));
set = "" + this.tab + rvar + " = [];\n";
if (body) body = Push.wrap(rvar, body);
}
if (this.guard) {
if (body.expressions.length > 1) {
@ -1670,9 +1666,9 @@
var _ref2;
return this.attempt.jumps(o) || ((_ref2 = this.recovery) != null ? _ref2.jumps(o) : void 0);
};
Try.prototype.makeReturn = function() {
if (this.attempt) this.attempt = this.attempt.makeReturn();
if (this.recovery) this.recovery = this.recovery.makeReturn();
Try.prototype.makeReturn = function(res) {
if (this.attempt) this.attempt = this.attempt.makeReturn(res);
if (this.recovery) this.recovery = this.recovery.makeReturn(res);
return this;
};
Try.prototype.compileNode = function(o) {
@ -1737,9 +1733,6 @@
Parens.prototype.isComplex = function() {
return this.body.isComplex();
};
Parens.prototype.makeReturn = function() {
return this.body.makeReturn();
};
Parens.prototype.compileNode = function(o) {
var bare, code, expr;
expr = this.body.unwrap();
@ -1758,7 +1751,7 @@
return Parens;
})();
exports.For = For = (function() {
__extends(For, Base);
__extends(For, While);
function For(body, source) {
var _ref2;
this.source = source.source, this.guard = source.guard, this.step = source.step, this.name = source.name, this.index = source.index;
@ -1782,12 +1775,6 @@
this.returns = false;
}
For.prototype.children = ['body', 'source', 'guard', 'step'];
For.prototype.isStatement = YES;
For.prototype.jumps = While.prototype.jumps;
For.prototype.makeReturn = function() {
this.returns = true;
return this;
};
For.prototype.compileNode = function(o) {
var body, defPart, forPart, forVarPart, guardPart, idt1, index, ivar, lastJumps, lvar, name, namePart, ref, resultPart, returnResult, rvar, scope, source, stepPart, stepvar, svar, varPart, _ref2;
body = Block.wrap([this.body]);
@ -1839,7 +1826,7 @@
if (this.returns) {
resultPart = "" + this.tab + rvar + " = [];\n";
returnResult = "\n" + this.tab + "return " + rvar + ";";
body = Push.wrap(rvar, body);
body.makeReturn(rvar);
}
if (this.guard) {
if (body.expressions.length > 1) {
@ -1911,14 +1898,17 @@
}
return (_ref4 = this.otherwise) != null ? _ref4.jumps(o) : void 0;
};
Switch.prototype.makeReturn = function() {
Switch.prototype.makeReturn = function(res) {
var pair, _i, _len, _ref2, _ref3;
_ref2 = this.cases;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
pair = _ref2[_i];
pair[1].makeReturn();
pair[1].makeReturn(res);
}
if ((_ref3 = this.otherwise) != null) _ref3.makeReturn();
if (res) {
this.otherwise || (this.otherwise = new Block([new Literal('void 0')]));
}
if ((_ref3 = this.otherwise) != null) _ref3.makeReturn(res);
return this;
};
Switch.prototype.compileNode = function(o) {
@ -1993,9 +1983,12 @@
return this.compileExpression(o);
}
};
If.prototype.makeReturn = function() {
this.body && (this.body = new Block([this.body.makeReturn()]));
this.elseBody && (this.elseBody = new Block([this.elseBody.makeReturn()]));
If.prototype.makeReturn = function(res) {
if (res) {
this.elseBody || (this.elseBody = new Block([new Literal('void 0')]));
}
this.body && (this.body = new Block([this.body.makeReturn(res)]));
this.elseBody && (this.elseBody = new Block([this.elseBody.makeReturn(res)]));
return this;
};
If.prototype.ensureBlock = function(node) {
@ -2044,12 +2037,6 @@
};
return If;
})();
Push = {
wrap: function(name, exps) {
if (exps.isEmpty() || last(exps.expressions).jumps()) return exps;
return exps.push(new Call(new Value(new Literal(name), [new Access(new Literal('push'))]), [exps.pop()]));
}
};
Closure = {
wrap: function(expressions, statement, noReturn) {
var args, call, func, mentionsArgs, meth;

View file

@ -76,8 +76,12 @@ exports.Base = class Base
# Construct a node that returns the current node's result.
# Note that this is overridden for smarter behavior for
# many statement nodes (e.g. If, For)...
makeReturn: ->
new Return this
makeReturn: (res) ->
me = @unwrapAll()
if res
new Call new Literal("#{res}.push"), [me]
else
new Return me
# Does this node, or any of its children, contain a node of a certain kind?
# Recursively traverses down the *children* of the nodes, yielding to a block
@ -191,12 +195,12 @@ exports.Block = class Block extends Base
# A Block node does not return its entire body, rather it
# ensures that the final expression is returned.
makeReturn: ->
makeReturn: (res) ->
len = @expressions.length
while len--
expr = @expressions[len]
if expr not instanceof Comment
@expressions[len] = expr.makeReturn()
@expressions[len] = expr.makeReturn res
@expressions.splice(len, 1) if expr instanceof Return and not expr.expression
break
this
@ -281,7 +285,7 @@ exports.Literal = class Literal extends Base
constructor: (@value) ->
makeReturn: ->
if @isStatement() then this else new Return this
if @isStatement() then this else super
isAssignable: ->
IDENTIFIER.test @value
@ -374,9 +378,6 @@ exports.Value = class Value extends Base
isSplice: ->
last(@properties) instanceof Slice
makeReturn: ->
if @properties.length then super() else @base.makeReturn()
# The value can be unwrapped as its inner node, if there are no attached
# properties.
unwrap: ->
@ -1210,9 +1211,12 @@ exports.While = class While extends Base
isStatement: YES
makeReturn: ->
@returns = yes
this
makeReturn: (res) ->
if res
super
else
@returns = yes
this
addBody: (@body) ->
this
@ -1234,10 +1238,9 @@ exports.While = class While extends Base
if body.isEmpty()
body = ''
else
if o.level > LEVEL_TOP or @returns
rvar = o.scope.freeVariable 'results'
if @returns
body.makeReturn rvar = o.scope.freeVariable 'results'
set = "#{@tab}#{rvar} = [];\n"
body = Push.wrap rvar, body if body
if @guard
if body.expressions.length > 1
body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
@ -1419,9 +1422,9 @@ exports.Try = class Try extends Base
jumps: (o) -> @attempt.jumps(o) or @recovery?.jumps(o)
makeReturn: ->
@attempt = @attempt .makeReturn() if @attempt
@recovery = @recovery.makeReturn() if @recovery
makeReturn: (res) ->
@attempt = @attempt .makeReturn res if @attempt
@recovery = @recovery.makeReturn res if @recovery
this
# Compilation is more or less as you would expect -- the *finally* clause
@ -1497,7 +1500,6 @@ exports.Parens = class Parens extends Base
unwrap : -> @body
isComplex : -> @body.isComplex()
makeReturn: -> @body.makeReturn()
compileNode: (o) ->
expr = @body.unwrap()
@ -1518,7 +1520,7 @@ exports.Parens = class Parens extends Base
# Unlike Python array comprehensions, they can be multi-line, and you can pass
# the current index of the loop as a second parameter. Unlike Ruby blocks,
# you can map and filter in a single pass.
exports.For = class For extends Base
exports.For = class For extends While
constructor: (body, source) ->
{@source, @guard, @step, @name, @index} = source
@body = Block.wrap [body]
@ -1534,14 +1536,6 @@ exports.For = class For extends Base
children: ['body', 'source', 'guard', 'step']
isStatement: YES
jumps: While::jumps
makeReturn: ->
@returns = yes
this
# Welcome to the hairiest method in all of CoffeeScript. Handles the inner
# loop, filtering, stepping, and result saving for array, object, and range
# comprehensions. Some of the generated code can be shared in common, and
@ -1582,7 +1576,7 @@ exports.For = class For extends Base
if @returns
resultPart = "#{@tab}#{rvar} = [];\n"
returnResult = "\n#{@tab}return #{rvar};"
body = Push.wrap rvar, body
body.makeReturn rvar
if @guard
if body.expressions.length > 1
body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
@ -1636,9 +1630,10 @@ exports.Switch = class Switch extends Base
return block if block.jumps o
@otherwise?.jumps o
makeReturn: ->
pair[1].makeReturn() for pair in @cases
@otherwise?.makeReturn()
makeReturn: (res) ->
pair[1].makeReturn res for pair in @cases
@otherwise or= new Block [new Literal 'void 0'] if res
@otherwise?.makeReturn res
this
compileNode: (o) ->
@ -1696,9 +1691,10 @@ exports.If = class If extends Base
compileNode: (o) ->
if @isStatement o then @compileStatement o else @compileExpression o
makeReturn: ->
@body and= new Block [@body.makeReturn()]
@elseBody and= new Block [@elseBody.makeReturn()]
makeReturn: (res) ->
@elseBody or= new Block [new Literal 'void 0'] if res
@body and= new Block [@body.makeReturn res]
@elseBody and= new Block [@elseBody.makeReturn res]
this
ensureBlock: (node) ->
@ -1752,15 +1748,6 @@ exports.If = class If extends Base
# Faux-nodes are never created by the grammar, but are used during code
# generation to generate other combinations of nodes.
#### Push
# The **Push** creates the tree for `array.push(value)`,
# which is helpful for recording the result arrays from comprehensions.
Push =
wrap: (name, exps) ->
return exps if exps.isEmpty() or last(exps.expressions).jumps()
exps.push new Call new Value(new Literal(name), [new Access new Literal 'push']), [exps.pop()]
#### Closure
# A faux-node used to wrap an expressions body in a closure.

View file

@ -428,3 +428,28 @@ test "#1326: `by` value is uncached", ->
arrayEq a, rangeCompileSimple
#exercises Range.compile
eq "#{i for i in [0..2] by h()}", '0,1,2'
test "#1669: break/continue should skip the result only for that branch", ->
ns = for n in [0..99]
if n > 9
break
else if n & 1
continue
else
n
eq "#{ns}", '0,2,4,6,8'
# `else undefined` is implied.
ns = for n in [1..9]
if n % 2
continue unless n % 5
n
eq "#{ns}", "1,,3,,,7,,9"
# Ditto.
ns = for n in [1..9]
switch
when n % 2
continue unless n % 5
n
eq "#{ns}", "1,,3,,,7,,9"

View file

@ -404,7 +404,7 @@ test "Switch with break as the return value of a loop.", ->
when 1 then i
when 0 then break
eq results.join(', '), '9, , 7, , 5, , 3, , 1, '
eq results.join(', '), '9, 7, 5, 3, 1'
test "Issue #997. Switch doesn't fallthrough.", ->