mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
* async iterators * tests; refactor 'For' grammar rules * async iterator tests * formatting
This commit is contained in:
parent
47c491ffa1
commit
1f9cd4eaf7
7 changed files with 263 additions and 166 deletions
|
@ -1350,75 +1350,92 @@
|
|||
For: [
|
||||
o('Statement ForBody',
|
||||
function() {
|
||||
return new For($1,
|
||||
$2);
|
||||
return $2.addBody($1);
|
||||
}),
|
||||
o('Expression ForBody',
|
||||
function() {
|
||||
return new For($1,
|
||||
$2);
|
||||
return $2.addBody($1);
|
||||
}),
|
||||
o('ForBody Block',
|
||||
function() {
|
||||
return new For($2,
|
||||
$1);
|
||||
return $1.addBody($2);
|
||||
}),
|
||||
o('ForLineBody Block',
|
||||
function() {
|
||||
return new For($2,
|
||||
$1);
|
||||
return $1.addBody($2);
|
||||
})
|
||||
],
|
||||
ForBody: [
|
||||
o('FOR Range',
|
||||
function() {
|
||||
return {
|
||||
return new For([],
|
||||
{
|
||||
source: LOC(2)(new Value($2))
|
||||
};
|
||||
});
|
||||
}),
|
||||
o('FOR Range BY Expression',
|
||||
function() {
|
||||
return {
|
||||
return new For([],
|
||||
{
|
||||
source: LOC(2)(new Value($2)),
|
||||
step: $4
|
||||
};
|
||||
});
|
||||
}),
|
||||
o('ForStart ForSource',
|
||||
function() {
|
||||
$2.own = $1.own;
|
||||
$2.ownTag = $1.ownTag;
|
||||
$2.name = $1[0];
|
||||
$2.index = $1[1];
|
||||
return $2;
|
||||
return $1.addSource($2);
|
||||
})
|
||||
],
|
||||
ForLineBody: [
|
||||
o('FOR Range BY ExpressionLine',
|
||||
function() {
|
||||
return {
|
||||
return new For([],
|
||||
{
|
||||
source: LOC(2)(new Value($2)),
|
||||
step: $4
|
||||
};
|
||||
});
|
||||
}),
|
||||
o('ForStart ForLineSource',
|
||||
function() {
|
||||
$2.own = $1.own;
|
||||
$2.ownTag = $1.ownTag;
|
||||
$2.name = $1[0];
|
||||
$2.index = $1[1];
|
||||
return $2;
|
||||
return $1.addSource($2);
|
||||
})
|
||||
],
|
||||
ForStart: [
|
||||
o('FOR ForVariables',
|
||||
function() {
|
||||
return $2;
|
||||
return new For([],
|
||||
{
|
||||
name: $2[0],
|
||||
index: $2[1]
|
||||
});
|
||||
}),
|
||||
o('FOR AWAIT ForVariables',
|
||||
function() {
|
||||
var index,
|
||||
name;
|
||||
[name,
|
||||
index] = $3;
|
||||
return new For([],
|
||||
{
|
||||
name,
|
||||
index,
|
||||
await: true,
|
||||
awaitTag: LOC(2)(new Literal($2))
|
||||
});
|
||||
}),
|
||||
o('FOR OWN ForVariables',
|
||||
function() {
|
||||
$3.own = true;
|
||||
$3.ownTag = LOC(2)(new Literal($2));
|
||||
return $3;
|
||||
var index,
|
||||
name;
|
||||
[name,
|
||||
index] = $3;
|
||||
return new For([],
|
||||
{
|
||||
name,
|
||||
index,
|
||||
own: true,
|
||||
ownTag: LOC(2)(new Literal($2))
|
||||
});
|
||||
})
|
||||
],
|
||||
// An array of all accepted values for a variable inside the loop.
|
||||
|
|
|
@ -3889,8 +3889,8 @@
|
|||
if ((node instanceof Op && node.isAwait()) || node instanceof AwaitReturn) {
|
||||
this.isAsync = true;
|
||||
}
|
||||
if (this.isGenerator && this.isAsync) {
|
||||
return node.error("function can't contain both yield and await");
|
||||
if (node instanceof For && node.isAwait()) {
|
||||
return this.isAsync = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -5361,25 +5361,47 @@
|
|||
exports.For = For = (function() {
|
||||
class For extends While {
|
||||
constructor(body, source) {
|
||||
var attribute, j, len1, ref1, ref2, ref3;
|
||||
super();
|
||||
({source: this.source, guard: this.guard, step: this.step, name: this.name, index: this.index} = source);
|
||||
this.addBody(body);
|
||||
this.addSource(source);
|
||||
}
|
||||
|
||||
isAwait() {
|
||||
var ref1;
|
||||
return (ref1 = this.await) != null ? ref1 : false;
|
||||
}
|
||||
|
||||
addBody(body) {
|
||||
this.body = Block.wrap([body]);
|
||||
this.own = source.own != null;
|
||||
this.object = source.object != null;
|
||||
this.from = source.from != null;
|
||||
return this;
|
||||
}
|
||||
|
||||
addSource(source) {
|
||||
var attr, attribs, attribute, j, k, len1, len2, ref1, ref2, ref3, ref4;
|
||||
({source: this.source = false} = source);
|
||||
attribs = ["name", "index", "guard", "step", "own", "ownTag", "await", "awaitTag", "object", "from"];
|
||||
for (j = 0, len1 = attribs.length; j < len1; j++) {
|
||||
attr = attribs[j];
|
||||
this[attr] = (ref1 = source[attr]) != null ? ref1 : this[attr];
|
||||
}
|
||||
if (!this.source) {
|
||||
return this;
|
||||
}
|
||||
if (this.from && this.index) {
|
||||
this.index.error('cannot use index with for-from');
|
||||
}
|
||||
if (this.own && !this.object) {
|
||||
source.ownTag.error(`cannot use own with for-${(this.from ? 'from' : 'in')}`);
|
||||
this.ownTag.error(`cannot use own with for-${(this.from ? 'from' : 'in')}`);
|
||||
}
|
||||
if (this.object) {
|
||||
[this.name, this.index] = [this.index, this.name];
|
||||
}
|
||||
if (((ref1 = this.index) != null ? typeof ref1.isArray === "function" ? ref1.isArray() : void 0 : void 0) || ((ref2 = this.index) != null ? typeof ref2.isObject === "function" ? ref2.isObject() : void 0 : void 0)) {
|
||||
if (((ref2 = this.index) != null ? typeof ref2.isArray === "function" ? ref2.isArray() : void 0 : void 0) || ((ref3 = this.index) != null ? typeof ref3.isObject === "function" ? ref3.isObject() : void 0 : void 0)) {
|
||||
this.index.error('index cannot be a pattern matching expression');
|
||||
}
|
||||
if (this.await && !this.from) {
|
||||
this.awaitTag.error('await must be used with for-from');
|
||||
}
|
||||
this.range = this.source instanceof Value && this.source.base instanceof Range && !this.source.properties.length && !this.from;
|
||||
this.pattern = this.name instanceof Value;
|
||||
if (this.range && this.index) {
|
||||
|
@ -5389,21 +5411,21 @@
|
|||
this.name.error('cannot pattern match over range loops');
|
||||
}
|
||||
this.returns = false;
|
||||
ref3 = ['source', 'guard', 'step', 'name', 'index'];
|
||||
ref4 = ['source', 'guard', 'step', 'name', 'index'];
|
||||
// Move up any comments in the “`for` line”, i.e. the line of code with `for`,
|
||||
// from any child nodes of that line up to the `for` node itself so that these
|
||||
// comments get output, and get output above the `for` loop.
|
||||
for (j = 0, len1 = ref3.length; j < len1; j++) {
|
||||
attribute = ref3[j];
|
||||
for (k = 0, len2 = ref4.length; k < len2; k++) {
|
||||
attribute = ref4[k];
|
||||
if (!this[attribute]) {
|
||||
continue;
|
||||
}
|
||||
this[attribute].traverseChildren(true, (node) => {
|
||||
var comment, k, len2, ref4;
|
||||
var comment, l, len3, ref5;
|
||||
if (node.comments) {
|
||||
ref4 = node.comments;
|
||||
for (k = 0, len2 = ref4.length; k < len2; k++) {
|
||||
comment = ref4[k];
|
||||
ref5 = node.comments;
|
||||
for (l = 0, len3 = ref5.length; l < len3; l++) {
|
||||
comment = ref5[l];
|
||||
// These comments are buried pretty deeply, so if they happen to be
|
||||
// trailing comments the line they trail will be unrecognizable when
|
||||
// we’re done compiling this `for` loop; so just shift them up to
|
||||
|
@ -5415,6 +5437,7 @@
|
|||
});
|
||||
moveComments(this[attribute], this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// Welcome to the hairiest method in all of CoffeeScript. Handles the inner
|
||||
|
@ -5422,7 +5445,7 @@
|
|||
// comprehensions. Some of the generated code can be shared in common, and
|
||||
// some cannot.
|
||||
compileNode(o) {
|
||||
var body, bodyFragments, compare, compareDown, declare, declareDown, defPart, down, forPartFragments, fragments, guardPart, idt1, increment, index, ivar, kvar, kvarAssign, last, lvar, name, namePart, ref, ref1, resultPart, returnResult, rvar, scope, source, step, stepNum, stepVar, svar, varPart;
|
||||
var body, bodyFragments, compare, compareDown, declare, declareDown, defPart, down, forClose, forCode, forPartFragments, fragments, guardPart, idt1, increment, index, ivar, kvar, kvarAssign, last, lvar, name, namePart, ref, ref1, resultPart, returnResult, rvar, scope, source, step, stepNum, stepVar, svar, varPart;
|
||||
body = Block.wrap([this.body]);
|
||||
ref1 = body.expressions, [last] = slice1.call(ref1, -1);
|
||||
if ((last != null ? last.jumps() : void 0) instanceof Return) {
|
||||
|
@ -5540,8 +5563,13 @@
|
|||
guardPart = `\n${idt1}if (!${utility('hasProp', o)}.call(${svar}, ${kvar})) continue;`;
|
||||
}
|
||||
} else if (this.from) {
|
||||
if (this.await) {
|
||||
forPartFragments = new Op('await', new Parens(new Literal(`${kvar} of ${svar}`)));
|
||||
forPartFragments = forPartFragments.compileToFragments(o, LEVEL_TOP);
|
||||
} else {
|
||||
forPartFragments = [this.makeCode(`${kvar} of ${svar}`)];
|
||||
}
|
||||
}
|
||||
bodyFragments = body.compileToFragments(merge(o, {
|
||||
indent: idt1
|
||||
}), LEVEL_TOP);
|
||||
|
@ -5552,7 +5580,9 @@
|
|||
if (resultPart) {
|
||||
fragments.push(this.makeCode(resultPart));
|
||||
}
|
||||
fragments = fragments.concat(this.makeCode(this.tab), this.makeCode('for ('), forPartFragments, this.makeCode(`) {${guardPart}${varPart}`), bodyFragments, this.makeCode(this.tab), this.makeCode('}'));
|
||||
forCode = this.await ? 'for ' : 'for (';
|
||||
forClose = this.await ? '' : ')';
|
||||
fragments = fragments.concat(this.makeCode(this.tab), this.makeCode(forCode), forPartFragments, this.makeCode(`${forClose} {${guardPart}${varPart}`), bodyFragments, this.makeCode(this.tab), this.makeCode('}'));
|
||||
if (returnResult) {
|
||||
fragments.push(this.makeCode(returnResult));
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -660,26 +660,31 @@ grammar =
|
|||
# Comprehensions can either be normal, with a block of expressions to execute,
|
||||
# or postfix, with a single expression.
|
||||
For: [
|
||||
o 'Statement ForBody', -> new For $1, $2
|
||||
o 'Expression ForBody', -> new For $1, $2
|
||||
o 'ForBody Block', -> new For $2, $1
|
||||
o 'ForLineBody Block', -> new For $2, $1
|
||||
o 'Statement ForBody', -> $2.addBody $1
|
||||
o 'Expression ForBody', -> $2.addBody $1
|
||||
o 'ForBody Block', -> $1.addBody $2
|
||||
o 'ForLineBody Block', -> $1.addBody $2
|
||||
]
|
||||
|
||||
ForBody: [
|
||||
o 'FOR Range', -> source: (LOC(2) new Value($2))
|
||||
o 'FOR Range BY Expression', -> source: (LOC(2) new Value($2)), step: $4
|
||||
o 'ForStart ForSource', -> $2.own = $1.own; $2.ownTag = $1.ownTag; $2.name = $1[0]; $2.index = $1[1]; $2
|
||||
o 'FOR Range', -> new For [], source: (LOC(2) new Value($2))
|
||||
o 'FOR Range BY Expression', -> new For [], source: (LOC(2) new Value($2)), step: $4
|
||||
o 'ForStart ForSource', -> $1.addSource $2
|
||||
]
|
||||
|
||||
ForLineBody: [
|
||||
o 'FOR Range BY ExpressionLine', -> source: (LOC(2) new Value($2)), step: $4
|
||||
o 'ForStart ForLineSource', -> $2.own = $1.own; $2.ownTag = $1.ownTag; $2.name = $1[0]; $2.index = $1[1]; $2
|
||||
o 'FOR Range BY ExpressionLine', -> new For [], source: (LOC(2) new Value($2)), step: $4
|
||||
o 'ForStart ForLineSource', -> $1.addSource $2
|
||||
]
|
||||
|
||||
ForStart: [
|
||||
o 'FOR ForVariables', -> $2
|
||||
o 'FOR OWN ForVariables', -> $3.own = yes; $3.ownTag = (LOC(2) new Literal($2)); $3
|
||||
o 'FOR ForVariables', -> new For [], name: $2[0], index: $2[1]
|
||||
o 'FOR AWAIT ForVariables', ->
|
||||
[name, index] = $3
|
||||
new For [], {name, index, await: yes, awaitTag: (LOC(2) new Literal($2))}
|
||||
o 'FOR OWN ForVariables', ->
|
||||
[name, index] = $3
|
||||
new For [], {name, index, own: yes, ownTag: (LOC(2) new Literal($2))}
|
||||
]
|
||||
|
||||
# An array of all accepted values for a variable inside the loop.
|
||||
|
|
|
@ -2610,8 +2610,8 @@ exports.Code = class Code extends Base
|
|||
@isGenerator = yes
|
||||
if (node instanceof Op and node.isAwait()) or node instanceof AwaitReturn
|
||||
@isAsync = yes
|
||||
if @isGenerator and @isAsync
|
||||
node.error "function can't contain both yield and await"
|
||||
if node instanceof For and node.isAwait()
|
||||
@isAsync = yes
|
||||
|
||||
children: ['params', 'body']
|
||||
|
||||
|
@ -3615,15 +3615,27 @@ exports.StringWithInterpolations = class StringWithInterpolations extends Base
|
|||
exports.For = class For extends While
|
||||
constructor: (body, source) ->
|
||||
super()
|
||||
{@source, @guard, @step, @name, @index} = source
|
||||
@addBody body
|
||||
@addSource source
|
||||
|
||||
children: ['body', 'source', 'guard', 'step']
|
||||
|
||||
isAwait: -> @await ? no
|
||||
|
||||
addBody: (body) ->
|
||||
@body = Block.wrap [body]
|
||||
@own = source.own?
|
||||
@object = source.object?
|
||||
@from = source.from?
|
||||
this
|
||||
|
||||
addSource: (source) ->
|
||||
{@source = no} = source
|
||||
attribs = ["name", "index", "guard", "step", "own", "ownTag", "await", "awaitTag", "object", "from"]
|
||||
@[attr] = source[attr] ? @[attr] for attr in attribs
|
||||
return this unless @source
|
||||
@index.error 'cannot use index with for-from' if @from and @index
|
||||
source.ownTag.error "cannot use own with for-#{if @from then 'from' else 'in'}" if @own and not @object
|
||||
@ownTag.error "cannot use own with for-#{if @from then 'from' else 'in'}" if @own and not @object
|
||||
[@name, @index] = [@index, @name] if @object
|
||||
@index.error 'index cannot be a pattern matching expression' if @index?.isArray?() or @index?.isObject?()
|
||||
@awaitTag.error 'await must be used with for-from' if @await and not @from
|
||||
@range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length and not @from
|
||||
@pattern = @name instanceof Value
|
||||
@index.error 'indexes do not apply to range loops' if @range and @index
|
||||
|
@ -3642,8 +3654,7 @@ exports.For = class For extends While
|
|||
comment.newLine = comment.unshift = yes for comment in node.comments
|
||||
moveComments node, @[attribute]
|
||||
moveComments @[attribute], @
|
||||
|
||||
children: ['body', 'source', 'guard', 'step']
|
||||
this
|
||||
|
||||
# Welcome to the hairiest method in all of CoffeeScript. Handles the inner
|
||||
# loop, filtering, stepping, and result saving for array, object, and range
|
||||
|
@ -3721,6 +3732,10 @@ exports.For = class For extends While
|
|||
forPartFragments = [@makeCode("#{kvar} in #{svar}")]
|
||||
guardPart = "\n#{idt1}if (!#{utility 'hasProp', o}.call(#{svar}, #{kvar})) continue;" if @own
|
||||
else if @from
|
||||
if @await
|
||||
forPartFragments = new Op 'await', new Parens new Literal "#{kvar} of #{svar}"
|
||||
forPartFragments = forPartFragments.compileToFragments o, LEVEL_TOP
|
||||
else
|
||||
forPartFragments = [@makeCode("#{kvar} of #{svar}")]
|
||||
bodyFragments = body.compileToFragments merge(o, indent: idt1), LEVEL_TOP
|
||||
if bodyFragments and bodyFragments.length > 0
|
||||
|
@ -3728,8 +3743,10 @@ exports.For = class For extends While
|
|||
|
||||
fragments = [@makeCode(defPart)]
|
||||
fragments.push @makeCode(resultPart) if resultPart
|
||||
fragments = fragments.concat @makeCode(@tab), @makeCode( 'for ('),
|
||||
forPartFragments, @makeCode(") {#{guardPart}#{varPart}"), bodyFragments,
|
||||
forCode = if @await then 'for ' else 'for ('
|
||||
forClose = if @await then '' else ')'
|
||||
fragments = fragments.concat @makeCode(@tab), @makeCode( forCode),
|
||||
forPartFragments, @makeCode("#{forClose} {#{guardPart}#{varPart}"), bodyFragments,
|
||||
@makeCode(@tab), @makeCode('}')
|
||||
fragments.push @makeCode(returnResult) if returnResult
|
||||
fragments
|
||||
|
|
32
test/async_iterators.coffee
Normal file
32
test/async_iterators.coffee
Normal file
|
@ -0,0 +1,32 @@
|
|||
# This is always fulfilled.
|
||||
winLater = (val, ms) ->
|
||||
new Promise (resolve) -> setTimeout (-> resolve val), ms
|
||||
|
||||
# This is always rejected.
|
||||
failLater = (val, ms) ->
|
||||
new Promise (resolve, reject) -> setTimeout (-> reject new Error val), ms
|
||||
|
||||
createAsyncIterable = (syncIterable) ->
|
||||
for elem in syncIterable
|
||||
yield await winLater elem, 50
|
||||
|
||||
test "async iteration", ->
|
||||
foo = (x for await x from createAsyncIterable [1,2,3])
|
||||
arrayEq foo, [1, 2, 3]
|
||||
|
||||
test "async generator functions", ->
|
||||
foo = (val) ->
|
||||
yield await winLater val + 1, 50
|
||||
|
||||
bar = (val) ->
|
||||
yield await failLater val - 1, 50
|
||||
|
||||
a = await foo(41).next()
|
||||
eq a.value, 42
|
||||
|
||||
try
|
||||
b = do -> await bar(41).next()
|
||||
b.catch (err) ->
|
||||
eq "40", err.message
|
||||
catch err
|
||||
ok no
|
|
@ -1230,28 +1230,6 @@ test "CoffeeScript keywords cannot be used as local names in import list aliases
|
|||
^^^^^^
|
||||
'''
|
||||
|
||||
test "function cannot contain both `await` and `yield`", ->
|
||||
assertErrorFormat '''
|
||||
f = () ->
|
||||
yield 5
|
||||
await a
|
||||
''', '''
|
||||
[stdin]:3:3: error: function can't contain both yield and await
|
||||
await a
|
||||
^^^^^^^
|
||||
'''
|
||||
|
||||
test "function cannot contain both `await` and `yield from`", ->
|
||||
assertErrorFormat '''
|
||||
f = () ->
|
||||
yield from a
|
||||
await b
|
||||
''', '''
|
||||
[stdin]:3:3: error: function can't contain both yield and await
|
||||
await b
|
||||
^^^^^^^
|
||||
'''
|
||||
|
||||
test "cannot have `await` outside a function", ->
|
||||
assertErrorFormat '''
|
||||
await 1
|
||||
|
|
Loading…
Reference in a new issue