Skip to content

Commit a770bff

Browse files
committed
Support async generators
1 parent 4e3b259 commit a770bff

29 files changed

+1818
-6062
lines changed

src/messages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Error messages should be identical to V8.
22
export const Messages = {
3+
AsyncFunctionInSingleStatementContext: 'Async functions can only be declared at the top level or inside a block.',
34
BadImportCallArity: 'Unexpected token',
45
BadGetterArity: 'Getter must not have any formal parameters',
56
BadSetterArity: 'Setter must have exactly one formal parameter',
@@ -12,6 +13,7 @@ export const Messages = {
1213
DefaultRestProperty: 'Unexpected token =',
1314
DuplicateBinding: 'Duplicate binding %0',
1415
DuplicateConstructor: 'A class may only have one constructor',
16+
DuplicateParameter: 'Duplicate parameter name not allowed in this context',
1517
DuplicateProtoProperty: 'Duplicate __proto__ fields are not allowed in object literals',
1618
ForInOfLoopInitializer: '%0 loop variable declaration may not have an initializer',
1719
GeneratorInLegacyContext: 'Generator declarations are not allowed in legacy contexts',

src/nodes.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,12 @@ export class AsyncFunctionDeclaration {
115115
readonly generator: boolean;
116116
readonly expression: boolean;
117117
readonly async: boolean;
118-
constructor(id: Identifier | null, params: FunctionParameter[], body: BlockStatement) {
118+
constructor(id: Identifier | null, params: FunctionParameter[], body: BlockStatement, generator: boolean) {
119119
this.type = Syntax.FunctionDeclaration;
120120
this.id = id;
121121
this.params = params;
122122
this.body = body;
123-
this.generator = false;
123+
this.generator = generator;
124124
this.expression = false;
125125
this.async = true;
126126
}
@@ -134,12 +134,12 @@ export class AsyncFunctionExpression {
134134
readonly generator: boolean;
135135
readonly expression: boolean;
136136
readonly async: boolean;
137-
constructor(id: Identifier | null, params: FunctionParameter[], body: BlockStatement) {
137+
constructor(id: Identifier | null, params: FunctionParameter[], body: BlockStatement, generator: boolean) {
138138
this.type = Syntax.FunctionExpression;
139139
this.id = id;
140140
this.params = params;
141141
this.body = body;
142-
this.generator = false;
142+
this.generator = generator;
143143
this.expression = false;
144144
this.async = true;
145145
}

src/parser.ts

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,12 @@ export class Parser {
256256
this.errorHandler.tolerate(this.unexpectedTokenError(token, message));
257257
}
258258

259+
tolerateInvalidLoopStatement() {
260+
if (this.matchKeyword("class") || this.matchKeyword("function")) {
261+
this.tolerateError(Messages.UnexpectedToken, this.lookahead);
262+
}
263+
}
264+
259265
collectComments() {
260266
if (!this.config.comment) {
261267
this.scanner.scanComments();
@@ -772,8 +778,7 @@ export class Parser {
772778
return body;
773779
}
774780

775-
parsePropertyMethodFunction(): Node.FunctionExpression {
776-
const isGenerator = false;
781+
parsePropertyMethodFunction(isGenerator: boolean): Node.FunctionExpression {
777782
const node = this.createNode();
778783

779784
const previousAllowYield = this.context.allowYield;
@@ -785,19 +790,24 @@ export class Parser {
785790
return this.finalize(node, new Node.FunctionExpression(null, params.params, method, isGenerator));
786791
}
787792

788-
parsePropertyMethodAsyncFunction(): Node.FunctionExpression {
793+
parsePropertyMethodAsyncFunction(isGenerator: boolean): Node.FunctionExpression {
789794
const node = this.createNode();
790795

791796
const previousAllowYield = this.context.allowYield;
792797
const previousAwait = this.context.await;
793798
this.context.allowYield = false;
794799
this.context.await = true;
800+
795801
const params = this.parseFormalParameters();
802+
if (params.message === Messages.StrictParamDupe) {
803+
this.throwError(Messages.DuplicateParameter);
804+
}
805+
796806
const method = this.parsePropertyMethod(params);
797807
this.context.allowYield = previousAllowYield;
798808
this.context.await = previousAwait;
799809

800-
return this.finalize(node, new Node.AsyncFunctionExpression(null, params.params, method));
810+
return this.finalize(node, new Node.AsyncFunctionExpression(null, params.params, method, isGenerator));
801811
}
802812

803813
parseObjectPropertyKey(): Node.PropertyKey {
@@ -855,13 +865,18 @@ export class Parser {
855865
let method = false;
856866
let shorthand = false;
857867
let isAsync = false;
868+
let isGenerator = false;
858869

859870
if (token.type === Token.Identifier) {
860871
const id = token.value;
861872
this.nextToken();
862873
computed = this.match('[');
863874
isAsync = !this.hasLineTerminator && (id === 'async') &&
864-
!this.match(':') && !this.match('(') && !this.match('*') && !this.match(',');
875+
!this.match(':') && !this.match('(') && !this.match(',');
876+
isGenerator = this.match('*');
877+
if (isGenerator) {
878+
this.nextToken();
879+
}
865880
key = isAsync ? this.parseObjectPropertyKey() : this.finalize(node, new Node.Identifier(id));
866881
} else if (this.match('*')) {
867882
this.nextToken();
@@ -908,7 +923,7 @@ export class Parser {
908923
value = this.inheritCoverGrammar(this.parseAssignmentExpression);
909924

910925
} else if (this.match('(')) {
911-
value = isAsync ? this.parsePropertyMethodAsyncFunction() : this.parsePropertyMethodFunction();
926+
value = isAsync ? this.parsePropertyMethodAsyncFunction(isGenerator) : this.parsePropertyMethodFunction(isGenerator);
912927
method = true;
913928

914929
} else if (token.type === Token.Identifier) {
@@ -2125,7 +2140,7 @@ export class Parser {
21252140
return this.finalize(node, new Node.Property('init', key, computed, value, method, shorthand));
21262141
}
21272142

2128-
parseRestProperty(params, kind): Node.RestElement {
2143+
parseRestProperty(params): Node.RestElement {
21292144
const node = this.createNode();
21302145
this.expect('...');
21312146
const arg = this.parsePattern(params);
@@ -2144,7 +2159,7 @@ export class Parser {
21442159

21452160
this.expect('{');
21462161
while (!this.match('}')) {
2147-
properties.push(this.match('...') ? this.parseRestProperty(params, kind) : this.parsePropertyPattern(params, kind));
2162+
properties.push(this.match('...') ? this.parseRestProperty(params) : this.parsePropertyPattern(params, kind));
21482163
if (!this.match('}')) {
21492164
this.expect(',');
21502165
}
@@ -2316,6 +2331,8 @@ export class Parser {
23162331
const node = this.createNode();
23172332
this.expectKeyword('do');
23182333

2334+
this.tolerateInvalidLoopStatement();
2335+
23192336
const previousInIteration = this.context.inIteration;
23202337
this.context.inIteration = true;
23212338
const body = this.parseStatement();
@@ -2519,6 +2536,7 @@ export class Parser {
25192536
body = this.finalize(this.createNode(), new Node.EmptyStatement());
25202537
} else {
25212538
this.expect(')');
2539+
this.tolerateInvalidLoopStatement();
25222540

25232541
const previousInIteration = this.context.inIteration;
25242542
this.context.inIteration = true;
@@ -3046,12 +3064,15 @@ export class Parser {
30463064

30473065
const isAsync = this.matchContextualKeyword('async');
30483066
if (isAsync) {
3067+
if (this.context.inIteration) {
3068+
this.tolerateError(Messages.AsyncFunctionInSingleStatementContext);
3069+
}
30493070
this.nextToken();
30503071
}
30513072

30523073
this.expectKeyword('function');
30533074

3054-
const isGenerator = isAsync ? false : this.match('*');
3075+
const isGenerator = this.match('*');
30553076
if (isGenerator) {
30563077
this.nextToken();
30573078
}
@@ -3084,6 +3105,10 @@ export class Parser {
30843105
this.context.allowYield = !isGenerator;
30853106

30863107
const formalParameters = this.parseFormalParameters(firstRestricted);
3108+
if (isGenerator && formalParameters.message === Messages.StrictParamDupe) {
3109+
this.throwError(Messages.DuplicateParameter);
3110+
}
3111+
30873112
const params = formalParameters.params;
30883113
const stricted = formalParameters.stricted;
30893114
firstRestricted = formalParameters.firstRestricted;
@@ -3107,8 +3132,9 @@ export class Parser {
31073132
this.context.await = previousAllowAwait;
31083133
this.context.allowYield = previousAllowYield;
31093134

3110-
return isAsync ? this.finalize(node, new Node.AsyncFunctionDeclaration(id, params, body)) :
3111-
this.finalize(node, new Node.FunctionDeclaration(id, params, body, isGenerator));
3135+
return isAsync
3136+
? this.finalize(node, new Node.AsyncFunctionDeclaration(id, params, body, isGenerator))
3137+
: this.finalize(node, new Node.FunctionDeclaration(id, params, body, isGenerator));
31123138
}
31133139

31143140
parseFunctionExpression(): Node.AsyncFunctionExpression | Node.FunctionExpression {
@@ -3121,7 +3147,7 @@ export class Parser {
31213147

31223148
this.expectKeyword('function');
31233149

3124-
const isGenerator = isAsync ? false : this.match('*');
3150+
const isGenerator = this.match('*');
31253151
if (isGenerator) {
31263152
this.nextToken();
31273153
}
@@ -3154,6 +3180,12 @@ export class Parser {
31543180
}
31553181

31563182
const formalParameters = this.parseFormalParameters(firstRestricted);
3183+
if (formalParameters.message === Messages.StrictParamDupe) {
3184+
if (isGenerator || isAsync) {
3185+
this.throwError(Messages.DuplicateParameter);
3186+
}
3187+
}
3188+
31573189
const params = formalParameters.params;
31583190
const stricted = formalParameters.stricted;
31593191
firstRestricted = formalParameters.firstRestricted;
@@ -3176,8 +3208,9 @@ export class Parser {
31763208
this.context.await = previousAllowAwait;
31773209
this.context.allowYield = previousAllowYield;
31783210

3179-
return isAsync ? this.finalize(node, new Node.AsyncFunctionExpression(id, params, body)) :
3180-
this.finalize(node, new Node.FunctionExpression(id, params, body, isGenerator));
3211+
return isAsync
3212+
? this.finalize(node, new Node.AsyncFunctionExpression(id, params, body, isGenerator))
3213+
: this.finalize(node, new Node.FunctionExpression(id, params, body, isGenerator));
31813214
}
31823215

31833216
// https://tc39.github.io/ecma262/#sec-directive-prologues-and-the-use-strict-directive
@@ -3360,6 +3393,7 @@ export class Parser {
33603393
let method = false;
33613394
let isStatic = false;
33623395
let isAsync = false;
3396+
let isGenerator = false;
33633397

33643398
if (this.match('*')) {
33653399
this.nextToken();
@@ -3379,8 +3413,12 @@ export class Parser {
33793413
}
33803414
if ((token.type === Token.Identifier) && !this.hasLineTerminator && (token.value === 'async')) {
33813415
const punctuator = this.lookahead.value;
3382-
if (punctuator !== ':' && punctuator !== '(' && punctuator !== '*') {
3416+
if (punctuator !== ':' && punctuator !== '(') {
33833417
isAsync = true;
3418+
isGenerator = this.match("*");
3419+
if (isGenerator) {
3420+
this.nextToken();
3421+
}
33843422
token = this.lookahead;
33853423
computed = this.match('[');
33863424
key = this.parseObjectPropertyKey();
@@ -3415,7 +3453,7 @@ export class Parser {
34153453

34163454
if (!kind && key && this.match('(')) {
34173455
kind = 'init';
3418-
value = isAsync ? this.parsePropertyMethodAsyncFunction() : this.parsePropertyMethodFunction();
3456+
value = isAsync ? this.parsePropertyMethodAsyncFunction(isGenerator) : this.parsePropertyMethodFunction(isGenerator);
34193457
method = true;
34203458
}
34213459

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"index":17,"lineNumber":1,"column":18,"message":"Error: Line 1: Unexpected token [object Object]","description":"Unexpected token [object Object]"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
for (var x of []) class C {}

0 commit comments

Comments
 (0)