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:
parent
96f076983e
commit
5a34f53689
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
66
lib/nodes.js
66
lib/nodes.js
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: /^['"]/
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue