enhancements to range comprehensions, back to being safe -- and usable downwards without a 'by' clause, and optimized when working with integer literals.

This commit is contained in:
Jeremy Ashkenas 2010-07-17 18:45:29 -04:00
parent 96f076983e
commit 5a34f53689
7 changed files with 88 additions and 53 deletions

View File

@ -1,4 +1,4 @@
countdown: num for num in [10..1] by -1
countdown: num for num in [10..1]
deliverEggs: ->
for i in [0...eggs.length] by 12

View File

@ -66,7 +66,7 @@
spaces = 20 - name.length;
spaces = spaces > 0 ? (function() {
_b = [];
for (i = 0; i <= spaces; i += 1) {
for (i = 0; (0 <= spaces ? i <= spaces : i >= spaces); (0 <= spaces ? i += 1 : i -= 1)) {
_b.push(' ');
}
return _b;

View File

@ -1,5 +1,5 @@
(function(){
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, DOUBLE_PARENS, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, compact, del, ends, flatten, helpers, include, indexOf, literal, merge, starts, utility;
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, DOUBLE_PARENS, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SIMPLENUM, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, compact, del, ends, flatten, helpers, include, indexOf, literal, merge, starts, utility;
var __extends = function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
@ -558,55 +558,79 @@
this.from = from;
this.to = to;
this.exclusive = !!exclusive;
this.equals = this.exclusive ? '' : '=';
return this;
};
__extends(RangeNode, BaseNode);
RangeNode.prototype['class'] = 'RangeNode';
RangeNode.prototype.children = ['from', 'to'];
RangeNode.prototype.compileVariables = function(o) {
var _b, _c, parts;
_b = this.from.compileReference(o);
var _b, _c, _d, parts;
_b = this.from.compileReference(o, {
precompile: true
});
this.from = _b[0];
this.fromVar = _b[1];
_c = this.to.compileReference(o);
_c = this.to.compileReference(o, {
precompile: true
});
this.to = _c[0];
this.toVar = _c[1];
_d = [this.fromVar.match(SIMPLENUM), this.toVar.match(SIMPLENUM)];
this.fromNum = _d[0];
this.toNum = _d[1];
parts = [];
if (this.from !== this.fromVar) {
parts.push(this.from.compile(o));
parts.push(this.from);
}
if (this.to !== this.toVar) {
parts.push(this.to.compile(o));
parts.push(this.to);
}
return parts.length ? ("" + (parts.join('; ')) + ";") : '';
return parts.length ? ("" + (parts.join('; ')) + "; ") : '';
};
RangeNode.prototype.compileNode = function(o) {
var equals, idx, op, step, vars;
var compare, idx, incr, intro, step, stepPart, vars;
if (!(o.index)) {
return this.compileArray(o);
}
if (this.fromNum && this.toNum) {
return this.compileSimple(o);
}
idx = del(o, 'index');
step = del(o, 'step');
vars = ("" + idx + " = " + (this.fromVar.compile(o)));
step = step ? step.compile(o) : '1';
equals = this.exclusive ? '' : '=';
op = starts(step, '-') ? (">" + equals) : ("<" + equals);
return "" + vars + "; " + (idx) + " " + op + " " + (this.toVar.compile(o)) + "; " + idx + " += " + step;
vars = ("" + idx + " = " + this.fromVar);
intro = ("(" + this.fromVar + " <= " + this.toVar + " ? " + idx);
compare = ("" + intro + " <" + this.equals + " " + this.toVar + " : " + idx + " >" + this.equals + " " + this.toVar + ")");
stepPart = step ? step.compile(o) : '1';
incr = step ? ("" + idx + " += " + stepPart) : ("" + intro + " += " + stepPart + " : " + idx + " -= " + stepPart + ")");
return "" + vars + "; " + compare + "; " + incr;
};
RangeNode.prototype.compileSimple = function(o) {
var _b, from, idx, step, to;
_b = [parseInt(this.fromNum, 10), parseInt(this.toNum, 10)];
from = _b[0];
to = _b[1];
idx = del(o, 'index');
step = del(o, 'step');
step = step && ("" + idx + " += " + (step.compile(o)));
return from <= to ? ("" + idx + " = " + from + "; " + idx + " <" + this.equals + " " + to + "; " + (step || ("" + idx + "++"))) : ("" + idx + " = " + from + "; " + idx + " >" + this.equals + " " + to + "; " + (step || ("" + idx + "--")));
};
RangeNode.prototype.compileArray = function(o) {
var body, clause, equals, from, i, idt, post, pre, result, to, vars;
var body, clause, i, idt, post, pre, result, vars;
idt = this.idt(1);
vars = this.compileVariables(merge(o, {
indent: idt
}));
equals = this.exclusive ? '' : '=';
from = this.fromVar.compile(o);
to = this.toVar.compile(o);
result = o.scope.freeVariable();
i = o.scope.freeVariable();
clause = ("" + from + " <= " + to + " ?");
pre = ("\n" + (idt) + (result) + " = []; " + (vars));
body = ("var " + i + " = " + from + "; " + clause + " " + i + " <" + equals + " " + to + " : " + i + " >" + equals + " " + to + "; " + clause + " " + i + " += 1 : " + i + " -= 1");
if (this.fromNum && this.toNum) {
o.index = i;
body = this.compileSimple(o);
} else {
clause = ("" + this.fromVar + " <= " + this.toVar + " ?");
body = ("var " + i + " = " + this.fromVar + "; " + clause + " " + i + " <" + this.equals + " " + this.toVar + " : " + i + " >" + this.equals + " " + this.toVar + "; " + clause + " " + i + " += 1 : " + i + " -= 1");
}
post = ("{ " + (result) + ".push(" + i + ") };\n" + (idt) + "return " + result + ";\n" + o.indent);
return "(function(){" + (pre) + "\n" + (idt) + "for (" + body + ")" + post + "}).call(this)";
};
@ -1426,9 +1450,6 @@
body = Expressions.wrap([this.body]);
if (range) {
sourcePart = source.compileVariables(o);
if (sourcePart) {
sourcePart += ("\n" + o.indent);
}
forPart = source.compile(merge(o, {
index: ivar,
step: this.step
@ -1656,6 +1677,7 @@
DOUBLE_PARENS = /\(\(([^\(\)\n]*)\)\)/g;
IDENTIFIER = /^[a-zA-Z\$_](\w|\$)*$/;
NUMBER = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b$/i;
SIMPLENUM = /^-?\d+/;
IS_STRING = /^['"]/;
literal = function(name) {
return new LiteralNode(name);

View File

@ -48,7 +48,7 @@
spaces = 15 - rule.longFlag.length;
spaces = spaces > 0 ? (function() {
_d = [];
for (i = 0; i <= spaces; i += 1) {
for (i = 0; (0 <= spaces ? i <= spaces : i >= spaces); (0 <= spaces ? i += 1 : i -= 1)) {
_d.push(' ');
}
return _d;

View File

@ -131,8 +131,7 @@
var __func = function(i) {
var _c, size, tmp;
(_c = stack[stack.length - 1]);
for (tmp = 0; tmp < _c; tmp += 1) {
for (tmp = 0; (0 <= _c ? tmp < _c : tmp > _c); (0 <= _c ? tmp += 1 : tmp -= 1)) {
this.tokens.splice(i, 0, ['CALL_END', ')', this.tokens[i][2]]);
}
size = stack[stack.length - 1] + 1;

View File

@ -531,41 +531,57 @@ exports.RangeNode: class RangeNode extends BaseNode
@from: from
@to: to
@exclusive: !!exclusive
@equals: if @exclusive then '' else '='
# Compiles the range's source variables -- where it starts and where it ends.
# But only if they need to be cached to avoid double evaluation.
compileVariables: (o) ->
[@from, @fromVar]: @from.compileReference o
[@to, @toVar]: @to.compileReference o
[@from, @fromVar]: @from.compileReference o, {precompile: yes}
[@to, @toVar]: @to.compileReference o, {precompile: yes}
[@fromNum, @toNum]: [@fromVar.match(SIMPLENUM), @toVar.match(SIMPLENUM)]
parts: []
parts.push @from.compile o if @from isnt @fromVar
parts.push @to.compile o if @to isnt @toVar
if parts.length then "${parts.join('; ')};" else ''
parts.push @from if @from isnt @fromVar
parts.push @to if @to isnt @toVar
if parts.length then "${parts.join('; ')}; " else ''
# When compiled normally, the range returns the contents of the *for loop*
# needed to iterate over the values in the range. Used by comprehensions.
compileNode: (o) ->
return @compileArray(o) unless o.index
return @compileArray(o) unless o.index
return @compileSimple(o) if @fromNum and @toNum
idx: del o, 'index'
step: del o, 'step'
vars: "$idx = ${@fromVar.compile(o)}"
step: if step then step.compile(o) else '1'
equals: if @exclusive then '' else '='
op: if starts(step, '-') then ">$equals" else "<$equals"
"$vars; ${idx} $op ${@toVar.compile(o)}; $idx += $step"
vars: "$idx = $@fromVar"
intro: "($@fromVar <= $@toVar ? $idx"
compare: "$intro <$@equals $@toVar : $idx >$@equals $@toVar)"
stepPart: if step then step.compile(o) else '1'
incr: if step then "$idx += $stepPart" else "$intro += $stepPart : $idx -= $stepPart)"
"$vars; $compare; $incr"
# Compile a simple range comprehension, with integers.
compileSimple: (o) ->
[from, to]: [parseInt(@fromNum, 10), parseInt(@toNum, 10)]
idx: del o, 'index'
step: del o, 'step'
step: and "$idx += ${step.compile(o)}"
if from <= to
"$idx = $from; $idx <$@equals $to; ${step or "$idx++"}"
else
"$idx = $from; $idx >$@equals $to; ${step or "$idx--"}"
# When used as a value, expand the range into the equivalent array.
compileArray: (o) ->
idt: @idt 1
vars: @compileVariables(merge(o, {indent: idt}))
equals: if @exclusive then '' else '='
from: @fromVar.compile o
to: @toVar.compile o
result: o.scope.freeVariable()
i: o.scope.freeVariable()
clause: "$from <= $to ?"
pre: "\n${idt}${result} = []; ${vars}"
body: "var $i = $from; $clause $i <$equals $to : $i >$equals $to; $clause $i += 1 : $i -= 1"
if @fromNum and @toNum
o.index: i
body: @compileSimple o
else
clause: "$@fromVar <= $@toVar ?"
body: "var $i = $@fromVar; $clause $i <$@equals $@toVar : $i >$@equals $@toVar; $clause $i += 1 : $i -= 1"
post: "{ ${result}.push($i) };\n${idt}return $result;\n$o.indent"
"(function(){${pre}\n${idt}for ($body)$post}).call(this)"
@ -1266,7 +1282,6 @@ exports.ForNode: class ForNode extends BaseNode
body: Expressions.wrap([@body])
if range
sourcePart: source.compileVariables(o)
sourcePart: + "\n$o.indent" if sourcePart
forPart: source.compile merge o, {index: ivar, step: @step}
else
svar: scope.freeVariable()
@ -1492,6 +1507,7 @@ DOUBLE_PARENS: /\(\(([^\(\)\n]*)\)\)/g
# Keep these identifier regexes in sync with the Lexer.
IDENTIFIER: /^[a-zA-Z\$_](\w|\$)*$/
NUMBER : /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b$/i
SIMPLENUM : /^-?\d+/
# Is a literal value a string?
IS_STRING: /^['"]/

View File

@ -25,14 +25,6 @@ result: nums.concat(negs).join(', ')
ok result is '3, 6, 9, -20, -19, -18'
# Ensure that ranges are safe. This used to infinite loop:
j = 5
result: for j in [j..(j+3)]
j
ok result.join(' ') is '5 6 7 8'
# With range comprehensions, you can loop in steps.
results: x for x in [0..25] by 5
@ -40,11 +32,17 @@ ok results.join(' ') is '0 5 10 15 20 25'
# And can loop downwards, with a negative step.
results: x for x in [5..1] by -1
results: x for x in [5..1]
ok results.join(' ') is '5 4 3 2 1'
ok results.join(' ') is [(10-5)..(-2+3)].join(' ')
results: x for x in [10..1]
ok results.join(' ') is [10..1].join(' ')
results: x for x in [10...0] by -2
ok results.join(' ') is [10, 8, 6, 4, 2].join(' ')
# Multiline array comprehension with filter.
evens: for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0