Scoped comprehensions are back out, Do is back in.

This commit is contained in:
Jeremy Ashkenas 2010-12-24 08:59:30 -08:00
parent 97f8e9ce1c
commit 094b876a38
10 changed files with 219 additions and 287 deletions

View File

@ -36,10 +36,7 @@
Block: [
o('INDENT OUTDENT', function() {
return new Expressions;
}), o('FullBlock')
],
FullBlock: [
o('INDENT Body OUTDENT', function() {
}), o('INDENT Body OUTDENT', function() {
return $2;
})
],
@ -376,9 +373,6 @@
return new For($1, $2);
}), o('ForBody Block', function() {
return new For($2, $1);
}), o('ForBody FuncGlyph FullBlock', function() {
$1.scoped = true;
return new For($3, $1);
})
],
ForBody: [
@ -554,7 +548,7 @@
})
]
};
operators = [['left', '.', '?.', '::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', 'LOGIC'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS'], ['right', 'POST_IF']];
operators = [['left', '.', '?.', '::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', 'LOGIC'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'DO', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS'], ['right', 'POST_IF']];
tokens = [];
for (name in grammar) {
alternatives = grammar[name];

View File

@ -58,6 +58,7 @@
} else if (__indexOf.call(RELATION, tag) >= 0) {
if (tag !== 'INSTANCEOF' && this.seenFor) {
tag = 'FOR' + tag;
this.seenFor = false;
} else {
tag = 'RELATION';
if (this.value() === '!') {
@ -251,7 +252,6 @@
if (!(match = MULTI_DENT.exec(this.chunk))) {
return 0;
}
this.seenFor = false;
indent = match[0];
this.line += count(indent, '\n');
prev = last(this.tokens, 1);
@ -340,7 +340,7 @@
var match, prev, tag, value, _ref, _ref2, _ref3, _ref4;
if (match = OPERATOR.exec(this.chunk)) {
value = match[0];
if (!this.seenFor && CODE.test(value)) {
if (CODE.test(value)) {
this.tagParameters();
}
} else {
@ -580,7 +580,7 @@
};
return Lexer;
})();
JS_KEYWORDS = ['true', 'false', 'null', 'this', 'new', 'delete', 'typeof', 'in', 'instanceof', 'return', 'throw', 'break', 'continue', 'debugger', 'if', 'else', 'switch', 'for', 'while', 'try', 'catch', 'finally', 'class', 'extends', 'super'];
JS_KEYWORDS = ['true', 'false', 'null', 'this', 'new', 'delete', 'typeof', 'in', 'instanceof', 'return', 'throw', 'break', 'continue', 'debugger', 'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally', 'class', 'extends', 'super'];
COFFEE_KEYWORDS = ['undefined', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when'];
for (op in COFFEE_ALIASES = {
and: '&&',
@ -595,7 +595,7 @@
}) {
COFFEE_KEYWORDS.push(op);
}
RESERVED = ['case', 'default', 'function', 'var', 'void', 'with', 'do', 'const', 'let', 'enum', 'export', 'import', 'native', '__hasProp', '__extends', '__slice', '__bind', '__indexOf'];
RESERVED = ['case', 'default', 'function', 'var', 'void', 'with', 'const', 'let', 'enum', 'export', 'import', 'native', '__hasProp', '__extends', '__slice', '__bind', '__indexOf'];
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED);
exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS);
IDENTIFIER = /^([$A-Za-z_][$\w]*)([^\n\S]*:(?!:))?/;
@ -618,7 +618,7 @@
TRAILING_SPACES = /\s+$/;
NO_NEWLINE = /^(?:[-+*&|\/%=<>!.\\][<>=&|]*|and|or|is(?:nt)?|n(?:ot|ew)|delete|typeof|instanceof)$/;
COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='];
UNARY = ['!', '~', 'NEW', 'TYPEOF', 'DELETE'];
UNARY = ['!', '~', 'NEW', 'TYPEOF', 'DELETE', 'DO'];
LOGIC = ['&&', '||', '&', '|', '^'];
SHIFT = ['<<', '>>', '>>>'];
COMPARE = ['==', '!=', '<', '>', '<=', '>='];

View File

@ -1505,6 +1505,9 @@
if (op === 'in') {
return new In(first, second);
}
if (op === 'do') {
return new Call(first, first.params || []);
}
if (op === 'new') {
if (first instanceof Call) {
return first.newInstance();
@ -1784,7 +1787,7 @@
__extends(For, Base);
function For(body, source) {
var _ref;
this.source = source.source, this.guard = source.guard, this.step = source.step, this.name = source.name, this.index = source.index, this.scoped = source.scoped;
this.source = source.source, this.guard = source.guard, this.step = source.step, this.name = source.name, this.index = source.index;
this.body = Expressions.wrap([body]);
this.own = !!source.own;
this.object = !!source.object;
@ -1843,9 +1846,6 @@
guardPart = '';
defPart = '';
idt1 = this.tab + TAB;
if (this.scoped && this.jumps()) {
throw SyntaxError('cannot use a pure statement in a scoped loop.');
}
if (this.range) {
forPart = source.compile(merge(o, {
index: ivar,
@ -1877,10 +1877,7 @@
if (this.pattern) {
body.expressions.unshift(new Assign(this.name, new Literal("" + svar + "[" + ivar + "]")));
}
if (this.scoped) {
body = Closure.wrap(body, true, !this.returns);
}
defPart += this.pluckDirectCall(o, body, name, index);
defPart += this.pluckDirectCall(o, body);
if (namePart) {
varPart = "\n" + idt1 + namePart + ";";
}
@ -1898,8 +1895,8 @@
}
return "" + defPart + (resultPart || '') + this.tab + "for (" + forPart + ") {" + guardPart + varPart + body + this.tab + "}" + (returnResult || '');
};
For.prototype.pluckDirectCall = function(o, body, name, index) {
var arg, args, base, defs, expr, fn, i, idx, ref, val, _len, _len2, _ref, _ref2, _ref3, _ref4, _ref5, _ref6;
For.prototype.pluckDirectCall = function(o, body) {
var base, defs, expr, fn, idx, ref, val, _len, _ref, _ref2, _ref3, _ref4, _ref5, _ref6;
defs = '';
_ref = body.expressions;
for (idx = 0, _len = _ref.length; idx < _len; idx++) {
@ -1915,19 +1912,11 @@
fn = ((_ref5 = val.base) != null ? _ref5.unwrapAll() : void 0) || val;
ref = new Literal(o.scope.freeVariable('fn'));
base = new Value(ref);
args = compact([name, index]);
if (this.object) {
args.reverse();
}
for (i = 0, _len2 = args.length; i < _len2; i++) {
arg = args[i];
fn.params.push(new Param(args[i] = new Literal(arg)));
}
if (val.base) {
_ref6 = [base, val], val.base = _ref6[0], base = _ref6[1];
args.unshift(new Literal('this'));
}
body.expressions[idx] = new Call(base, args);
body.expressions[idx] = new Call(base, expr.args);
defs += this.tab + new Assign(ref, fn).compile(o, LEVEL_TOP) + ';\n';
}
return defs;

File diff suppressed because one or more lines are too long

View File

@ -51,31 +51,6 @@
}
return i - 1;
};
Rewriter.prototype.scanLineBack = function(i, condition) {
var levels, token, tokens, _ref, _ref2, _ref3;
tokens = this.tokens;
levels = 0;
while (token = tokens[i--]) {
if ((_ref = token[0], __indexOf.call(LINEBREAKS, _ref) >= 0) && !token.generated) {
return false;
}
if (_ref2 = token[0], __indexOf.call(EXPRESSION_START, _ref2) >= 0) {
levels -= 1;
} else if (_ref3 = token[0], __indexOf.call(EXPRESSION_END, _ref3) >= 0) {
levels += 1;
}
if (levels > 0) {
continue;
}
if (levels < 0) {
return false;
}
if (condition.call(this, token, i)) {
return true;
}
}
return false;
};
Rewriter.prototype.removeLeadingNewlines = function() {
var i, tag, _len, _ref;
_ref = this.tokens;
@ -201,11 +176,6 @@
if (!(callObject || (prev != null ? prev.spaced : void 0) && (prev.call || (_ref3 = prev[0], __indexOf.call(IMPLICIT_FUNC, _ref3) >= 0)) && (__indexOf.call(IMPLICIT_CALL, tag) >= 0 || !(token.spaced || token.newLine) && __indexOf.call(IMPLICIT_UNSPACED_CALL, tag) >= 0))) {
return 1;
}
if ((tag === '->' || tag === '=>') && this.scanLineBack(i, function(token, i) {
return token[0] === 'FOR';
})) {
return 1;
}
tokens.splice(i, 0, ['CALL_START', '(', token[2]]);
this.detectEnd(i + 1, function(token, i) {
var post, _ref;

View File

@ -105,10 +105,6 @@ grammar =
# token stream.
Block: [
o 'INDENT OUTDENT', -> new Expressions
o 'FullBlock'
]
FullBlock: [
o 'INDENT Body OUTDENT', -> $2
]
@ -422,7 +418,6 @@ grammar =
o 'Statement ForBody', -> new For $1, $2
o 'Expression ForBody', -> new For $1, $2
o 'ForBody Block', -> new For $2, $1
o 'ForBody FuncGlyph FullBlock', -> $1.scoped = yes; new For $3, $1
]
ForBody: [
@ -565,7 +560,7 @@ operators = [
['nonassoc', 'INDENT', 'OUTDENT']
['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS']
['right', 'FORIN', 'FOROF', 'BY', 'WHEN']
['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS']
['right', 'IF', 'ELSE', 'FOR', 'DO', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS']
['right', 'POST_IF']
]

View File

@ -96,6 +96,7 @@ exports.Lexer = class Lexer
else if tag in RELATION
if tag isnt 'INSTANCEOF' and @seenFor
tag = 'FOR' + tag
@seenFor = no
else
tag = 'RELATION'
if @value() is '!'
@ -231,7 +232,6 @@ exports.Lexer = class Lexer
# can close multiple indents, so we need to know how far in we happen to be.
lineToken: ->
return 0 unless match = MULTI_DENT.exec @chunk
@seenFor = no
indent = match[0]
@line += count indent, '\n'
prev = last @tokens, 1
@ -305,7 +305,7 @@ exports.Lexer = class Lexer
literalToken: ->
if match = OPERATOR.exec @chunk
[value] = match
@tagParameters() if not @seenFor and CODE.test value
@tagParameters() if CODE.test value
else
value = @chunk.charAt 0
tag = value
@ -500,7 +500,7 @@ JS_KEYWORDS = [
'true', 'false', 'null', 'this'
'new', 'delete', 'typeof', 'in', 'instanceof'
'return', 'throw', 'break', 'continue', 'debugger'
'if', 'else', 'switch', 'for', 'while', 'try', 'catch', 'finally'
'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally'
'class', 'extends', 'super'
]
@ -521,7 +521,7 @@ COFFEE_KEYWORDS.push op for op of COFFEE_ALIASES =
# used by CoffeeScript internally. We throw an error when these are encountered,
# to avoid having a JavaScript error at runtime.
RESERVED = [
'case', 'default', 'function', 'var', 'void', 'with', 'do'
'case', 'default', 'function', 'var', 'void', 'with'
'const', 'let', 'enum', 'export', 'import', 'native'
'__hasProp', '__extends', '__slice', '__bind', '__indexOf'
]
@ -609,7 +609,7 @@ COMPOUND_ASSIGN = [
]
# Unary tokens.
UNARY = ['!', '~', 'NEW', 'TYPEOF', 'DELETE']
UNARY = ['!', '~', 'NEW', 'TYPEOF', 'DELETE', 'DO']
# Logical tokens.
LOGIC = ['&&', '||', '&', '|', '^']

View File

@ -1177,6 +1177,7 @@ exports.While = class While extends Base
exports.Op = class Op extends Base
constructor: (op, first, second, flip) ->
return new In first, second if op is 'in'
return new Call first, first.params or [] if op is 'do'
if op is 'new'
return first.newInstance() if first instanceof Call
first = new Parens first if first instanceof Code and first.bound
@ -1423,7 +1424,7 @@ exports.Parens = class Parens extends Base
# you can map and filter in a single pass.
exports.For = class For extends Base
constructor: (body, source) ->
{@source, @guard, @step, @name, @index, @scoped} = source
{@source, @guard, @step, @name, @index} = source
@body = Expressions.wrap [body]
@own = !!source.own
@object = !!source.object
@ -1466,8 +1467,6 @@ exports.For = class For extends Base
guardPart = ''
defPart = ''
idt1 = @tab + TAB
if @scoped and @jumps()
throw SyntaxError 'cannot use a pure statement in a scoped loop.'
if @range
forPart = source.compile merge(o, {index: ivar, @step})
else
@ -1489,9 +1488,7 @@ exports.For = class For extends Base
body = Expressions.wrap [new If @guard, body]
if @pattern
body.expressions.unshift new Assign @name, new Literal "#{svar}[#{ivar}]"
if @scoped
body = Closure.wrap body, true, not @returns
defPart += @pluckDirectCall o, body, name, index
defPart += @pluckDirectCall o, body
varPart = "\n#{idt1}#{namePart};" if namePart
if @object
forPart = "#{ivar} in #{svar}"
@ -1502,7 +1499,7 @@ exports.For = class For extends Base
#{defPart}#{resultPart or ''}#{@tab}for (#{forPart}) {#{guardPart}#{varPart}#{body}#{@tab}}#{returnResult or ''}
"""
pluckDirectCall: (o, body, name, index) ->
pluckDirectCall: (o, body) ->
defs = ''
for expr, idx in body.expressions
expr = expr.unwrapAll()
@ -1516,14 +1513,10 @@ exports.For = class For extends Base
fn = val.base?.unwrapAll() or val
ref = new Literal o.scope.freeVariable 'fn'
base = new Value ref
args = compact [name, index]
args.reverse() if @object
for arg, i in args
fn.params.push new Param args[i] = new Literal arg
if val.base
[val.base, base] = [base, val]
args.unshift new Literal 'this'
body.expressions[idx] = new Call base, args
body.expressions[idx] = new Call base, expr.args
defs += @tab + new Assign(ref, fn).compile(o, LEVEL_TOP) + ';\n'
defs

View File

@ -54,20 +54,6 @@ class exports.Rewriter
i += 1
i - 1
scanLineBack: (i, condition) ->
{tokens} = this
levels = 0
while token = tokens[i--]
return no if token[0] in LINEBREAKS and not token.generated
if token[0] in EXPRESSION_START
levels -= 1
else if token[0] in EXPRESSION_END
levels += 1
continue if levels > 0
return no if levels < 0
return yes if condition.call this, token, i
no
# Leading newlines would introduce an ambiguity in the grammar, so we
# dispatch them here.
removeLeadingNewlines: ->
@ -160,8 +146,6 @@ class exports.Rewriter
return 1 unless callObject or
prev?.spaced and (prev.call or prev[0] in IMPLICIT_FUNC) and
(tag in IMPLICIT_CALL or not (token.spaced or token.newLine) and tag in IMPLICIT_UNSPACED_CALL)
if tag in ['->', '=>'] and @scanLineBack(i, (token, i) -> token[0] is 'FOR')
return 1
tokens.splice i, 0, ['CALL_START', '(', token[2]]
@detectEnd i + 1, (token, i) ->
[tag] = token

View File

@ -76,9 +76,10 @@ all = 1
# Ensure that the closure wrapper preserves local variables.
obj = {}
for method in ['one', 'two', 'three'] ->
obj[method] = ->
"I'm " + method
for method in ['one', 'two', 'three']
do (method) ->
obj[method] = ->
"I'm " + method
ok obj.one() is "I'm one"
ok obj.two() is "I'm two"
@ -94,8 +95,9 @@ ok i is 4
# Ensure that local variables are closed over for range comprehensions.
funcs = for i in [1..3] ->
-> -i
funcs = for i in [1..3]
do (i) ->
-> -i
eq (func() for func in funcs).join(' '), '-1 -2 -3'
ok i is 4
@ -104,8 +106,9 @@ ok i is 4
# Even when referenced in the filter.
list = ['one', 'two', 'three']
methods = for num, i in list when num isnt 'two' and i isnt 1 ->
-> num + ' ' + i
methods = for num, i in list when num isnt 'two' and i isnt 1
do (num, i) ->
-> num + ' ' + i
ok methods.length is 2
ok methods[0]() is 'one 0'
@ -115,19 +118,21 @@ ok methods[1]() is 'three 2'
# Even a convoluted one.
funcs = []
for i in [1..3] ->
x = i * 2
((z)->
funcs.push -> z + ' ' + i
)(x)
for i in [1..3]
do (i) ->
x = i * 2
((z)->
funcs.push -> z + ' ' + i
)(x)
ok (func() for func in funcs).join(', ') is '2 1, 4 2, 6 3'
funcs = []
results = for i in [1..3] ->
z = (x * 3 for x in [1..i])
((a, b, c) -> [a, b, c].join(' ')).apply this, z
results = for i in [1..3]
do (i) ->
z = (x * 3 for x in [1..i])
((a, b, c) -> [a, b, c].join(' ')).apply this, z
ok results.join(', ') is '3 , 3 6 , 3 6 9'
@ -139,9 +144,11 @@ ok(num % 2 is 0 for num in array by 2)
# Nested shared scopes.
foo = ->
for i in [0..7] =>
for j in [0..7] =>
-> i + j
for i in [0..7]
do (i) ->
for j in [0..7]
do (j) ->
-> i + j
eq foo()[3][4](), 7
@ -150,7 +157,9 @@ eq foo()[3][4](), 7
a = [[0], [1]]
funcs = []
for [v] in a -> funcs.push -> v
for [v] in a
do (v) ->
funcs.push -> v
eq funcs[0](), 0
eq funcs[1](), 1
@ -268,9 +277,10 @@ funcs = []
list = ->
[1, 2, 3]
for y in list() ->
z = y
funcs.push -> "y is #{y} and z is #{z}"
for y in list()
do (y) ->
z = y
funcs.push -> "y is #{y} and z is #{z}"
eq funcs[1](), "y is 2 and z is 2"
@ -293,13 +303,17 @@ arrayEq (break for [1..10]), []
# Comprehensions over function literals.
a = 0
for f in [-> a = 1] -> f()
for f in [-> a = 1]
do (f) ->
do f
eq a, 1
# Comprehensions that mention arguments.
list = [arguments: 10]
args = for f in list -> f.arguments
args = for f in list
do (f) ->
f.arguments
eq args[0], 10