Ticket #423. When functions are generated within comprehensions ... the comprehensions should close over the element instead of sharing it.

This commit is contained in:
Jeremy Ashkenas
2010-06-13 21:21:30 -04:00
parent 6f91331626
commit b0a45e5b93
4 changed files with 89 additions and 67 deletions

View File

@@ -58,41 +58,44 @@
// compile them. If a directory is passed, recursively compile all // compile them. If a directory is passed, recursively compile all
// '.coffee' extension source files in it and all subdirectories. // '.coffee' extension source files in it and all subdirectories.
compileScripts = function() { compileScripts = function() {
var _b, _c, _d, _e, base, compile, source; var _b, _c, _d, _e;
_b = []; _d = sources; _b = []; _d = sources;
for (_c = 0, _e = _d.length; _c < _e; _c++) { for (_c = 0, _e = _d.length; _c < _e; _c++) {
source = _d[_c]; (function() {
_b.push((function() { var base, compile;
base = source; var source = _d[_c];
compile = function(source, topLevel) { return _b.push((function() {
return path.exists(source, function(exists) { base = source;
if (!(exists)) { compile = function(source, topLevel) {
throw new Error(("File not found: " + source)); return path.exists(source, function(exists) {
} if (!(exists)) {
return fs.stat(source, function(err, stats) { throw new Error(("File not found: " + source));
if (stats.isDirectory()) {
return fs.readdir(source, function(err, files) {
var _f, _g, _h, _i, file;
_f = []; _h = files;
for (_g = 0, _i = _h.length; _g < _i; _g++) {
file = _h[_g];
_f.push(compile(path.join(source, file)));
}
return _f;
});
} else if (topLevel || path.extname(source) === '.coffee') {
fs.readFile(source, function(err, code) {
return compileScript(source, code.toString(), base);
});
if (options.watch) {
return watch(source, base);
}
} }
return fs.stat(source, function(err, stats) {
if (stats.isDirectory()) {
return fs.readdir(source, function(err, files) {
var _f, _g, _h, _i, file;
_f = []; _h = files;
for (_g = 0, _i = _h.length; _g < _i; _g++) {
file = _h[_g];
_f.push(compile(path.join(source, file)));
}
return _f;
});
} else if (topLevel || path.extname(source) === '.coffee') {
fs.readFile(source, function(err, code) {
return compileScript(source, code.toString(), base);
});
if (options.watch) {
return watch(source, base);
}
}
});
}); });
}); };
}; return compile(source, true);
return compile(source, true); })());
})()); })();
} }
return _b; return _b;
}; };

View File

@@ -1590,20 +1590,22 @@
// comprehensions. Some of the generated code can be shared in common, and // comprehensions. Some of the generated code can be shared in common, and
// some cannot. // some cannot.
ForNode.prototype.compileNode = function(o) { ForNode.prototype.compileNode = function(o) {
var body, bodyDent, close, forPart, index, ivar, lvar, name, range, returnResult, rvar, scope, source, sourcePart, stepPart, svar, topLevel, varPart, vars; var body, close, codeInBody, forPart, index, ivar, lvar, name, namePart, range, returnResult, rvar, scope, source, sourcePart, stepPart, svar, topLevel, varPart, vars;
topLevel = del(o, 'top') && !this.returns; topLevel = del(o, 'top') && !this.returns;
range = this.source instanceof ValueNode && this.source.base instanceof RangeNode && !this.source.properties.length; range = this.source instanceof ValueNode && this.source.base instanceof RangeNode && !this.source.properties.length;
source = range ? this.source.base : this.source; source = range ? this.source.base : this.source;
codeInBody = this.body.contains(function(n) {
return n instanceof CodeNode;
});
scope = o.scope; scope = o.scope;
name = this.name && this.name.compile(o); name = this.name && this.name.compile(o);
index = this.index && this.index.compile(o); index = this.index && this.index.compile(o);
if (name && !this.pattern) { if (name && !this.pattern && !codeInBody) {
scope.find(name); scope.find(name);
} }
if (index) { if (index && !codeInBody) {
scope.find(index); scope.find(index);
} }
bodyDent = this.idt(1);
if (!(topLevel)) { if (!(topLevel)) {
rvar = scope.freeVariable(); rvar = scope.freeVariable();
} }
@@ -1620,13 +1622,13 @@
svar = scope.freeVariable(); svar = scope.freeVariable();
sourcePart = ("" + svar + " = " + (this.source.compile(o)) + ";"); sourcePart = ("" + svar + " = " + (this.source.compile(o)) + ";");
if (this.pattern) { if (this.pattern) {
varPart = new AssignNode(this.name, literal(("" + svar + "[" + ivar + "]"))).compile(merge(o, { namePart = new AssignNode(this.name, literal(("" + svar + "[" + ivar + "]"))).compile(merge(o, {
indent: this.idt(1), indent: this.idt(1),
top: true top: true
})) + "\n"; })) + "\n";
} else { } else {
if (name) { if (name) {
varPart = ("" + bodyDent + name + " = " + svar + "[" + ivar + "];\n"); namePart = ("" + name + " = " + svar + "[" + ivar + "]");
} }
} }
if (!(this.object)) { if (!(this.object)) {
@@ -1638,18 +1640,23 @@
sourcePart = (rvar ? ("" + rvar + " = []; ") : '') + sourcePart; sourcePart = (rvar ? ("" + rvar + " = []; ") : '') + sourcePart;
sourcePart = sourcePart ? ("" + this.tab + sourcePart + "\n" + this.tab) : this.tab; sourcePart = sourcePart ? ("" + this.tab + sourcePart + "\n" + this.tab) : this.tab;
returnResult = this.compileReturnValue(rvar, o); returnResult = this.compileReturnValue(rvar, o);
if (topLevel && body.contains(function(n) {
return n instanceof CodeNode;
})) {
body = ClosureNode.wrap(body, true);
}
if (!(topLevel)) { if (!(topLevel)) {
body = PushNode.wrap(rvar, body); body = PushNode.wrap(rvar, body);
} }
this.guard ? (body = Expressions.wrap([new IfNode(this.guard, body)])) : null; this.guard ? (body = Expressions.wrap([new IfNode(this.guard, body)])) : null;
if (codeInBody) {
if (namePart) {
body.unshift(literal(("var " + namePart)));
}
body = ClosureNode.wrap(body, true);
} else {
if (namePart) {
varPart = ("" + (this.idt(1)) + namePart + ";\n");
}
}
this.object ? (forPart = ("" + ivar + " in " + svar + ") { if (" + (utility('hasProp')) + ".call(" + svar + ", " + ivar + ")")) : null; this.object ? (forPart = ("" + ivar + " in " + svar + ") { if (" + (utility('hasProp')) + ".call(" + svar + ", " + ivar + ")")) : null;
body = body.compile(merge(o, { body = body.compile(merge(o, {
indent: bodyDent, indent: this.idt(1),
top: true top: true
})); }));
vars = range ? name : ("" + name + ", " + ivar); vars = range ? name : ("" + name + ", " + ivar);

View File

@@ -1188,44 +1188,48 @@ exports.ForNode: class ForNode extends BaseNode
# comprehensions. Some of the generated code can be shared in common, and # comprehensions. Some of the generated code can be shared in common, and
# some cannot. # some cannot.
compileNode: (o) -> compileNode: (o) ->
topLevel: del(o, 'top') and not @returns topLevel: del(o, 'top') and not @returns
range: @source instanceof ValueNode and @source.base instanceof RangeNode and not @source.properties.length range: @source instanceof ValueNode and @source.base instanceof RangeNode and not @source.properties.length
source: if range then @source.base else @source source: if range then @source.base else @source
codeInBody: @body.contains (n) -> n instanceof CodeNode
scope: o.scope scope: o.scope
name: @name and @name.compile o name: @name and @name.compile o
index: @index and @index.compile o index: @index and @index.compile o
scope.find name if name and not @pattern scope.find name if name and not @pattern and not codeInBody
scope.find index if index scope.find index if index and not codeInBody
bodyDent: @idt 1
rvar: scope.freeVariable() unless topLevel rvar: scope.freeVariable() unless topLevel
ivar: if range then name else index or scope.freeVariable() ivar: if range then name else index or scope.freeVariable()
varPart: '' varPart: ''
body: Expressions.wrap([@body]) body: Expressions.wrap([@body])
if range if range
sourcePart: source.compileVariables o sourcePart: source.compileVariables o
forPart: source.compile merge o, {index: ivar, step: @step} forPart: source.compile merge o, {index: ivar, step: @step}
else else
svar: scope.freeVariable() svar: scope.freeVariable()
sourcePart: "$svar = ${ @source.compile(o) };" sourcePart: "$svar = ${ @source.compile(o) };"
if @pattern if @pattern
varPart: new AssignNode(@name, literal("$svar[$ivar]")).compile(merge o, {indent: @idt(1), top: true}) + "\n" namePart: new AssignNode(@name, literal("$svar[$ivar]")).compile(merge o, {indent: @idt(1), top: true}) + "\n"
else else
varPart: "$bodyDent$name = $svar[$ivar];\n" if name namePart: "$name = $svar[$ivar]" if name
unless @object unless @object
lvar: scope.freeVariable() lvar: scope.freeVariable()
stepPart: if @step then "$ivar += ${ @step.compile(o) }" else "$ivar++" stepPart: if @step then "$ivar += ${ @step.compile(o) }" else "$ivar++"
forPart: "$ivar = 0, $lvar = ${svar}.length; $ivar < $lvar; $stepPart" forPart: "$ivar = 0, $lvar = ${svar}.length; $ivar < $lvar; $stepPart"
sourcePart: (if rvar then "$rvar = []; " else '') + sourcePart sourcePart: (if rvar then "$rvar = []; " else '') + sourcePart
sourcePart: if sourcePart then "$@tab$sourcePart\n$@tab" else @tab sourcePart: if sourcePart then "$@tab$sourcePart\n$@tab" else @tab
returnResult: @compileReturnValue(rvar, o) returnResult: @compileReturnValue(rvar, o)
body: ClosureNode.wrap(body, true) if topLevel and body.contains (n) -> n instanceof CodeNode
body: PushNode.wrap(rvar, body) unless topLevel body: PushNode.wrap(rvar, body) unless topLevel
if @guard if @guard
body: Expressions.wrap([new IfNode(@guard, body)]) body: Expressions.wrap([new IfNode(@guard, body)])
if codeInBody
body.unshift literal "var $namePart" if namePart
body: ClosureNode.wrap(body, true)
else
varPart: "${@idt(1)}$namePart;\n" if namePart
if @object if @object
forPart: "$ivar in $svar) { if (${utility('hasProp')}.call($svar, $ivar)" forPart: "$ivar in $svar) { if (${utility('hasProp')}.call($svar, $ivar)"
body: body.compile(merge(o, {indent: bodyDent, top: true})) body: body.compile(merge(o, {indent: @idt(1), top: true}))
vars: if range then name else "$name, $ivar" vars: if range then name else "$name, $ivar"
close: if @object then '}}' else '}' close: if @object then '}}' else '}'
"${sourcePart}for ($forPart) {\n$varPart$body\n$@tab$close$returnResult" "${sourcePart}for ($forPart) {\n$varPart$body\n$@tab$close$returnResult"

View File

@@ -62,18 +62,26 @@ ok 2 in evens
# Ensure that the closure wrapper preserves local variables. # Ensure that the closure wrapper preserves local variables.
obj: {} obj: {}
methods: ['one', 'two', 'three'] for method in ['one', 'two', 'three']
obj[method]: ->
for method in methods "I'm " + method
name: method
obj[name]: ->
"I'm " + name
ok obj.one() is "I'm one" ok obj.one() is "I'm one"
ok obj.two() is "I'm two" ok obj.two() is "I'm two"
ok obj.three() is "I'm three" ok obj.three() is "I'm three"
# Even when referenced in the filter.
list: ['one', 'two', 'three']
methods: for num in list when num isnt 'two'
-> num
ok methods.length is 2
ok methods[0]() is 'one'
ok methods[1]() is 'three'
# Naked ranges are expanded into arrays. # Naked ranges are expanded into arrays.
array: [0..10] array: [0..10]
ok(num % 2 is 0 for num in array by 2) ok(num % 2 is 0 for num in array by 2)