Skip to content

Commit 9b3ad04

Browse files
lahmaariya
authored andcommitted
Support async generators
1 parent 4e3b259 commit 9b3ad04

31 files changed

+2868
-7152
lines changed

src/messages.ts

+3
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',
@@ -60,6 +62,7 @@ export const Messages = {
6062
UnexpectedNumber: 'Unexpected number',
6163
UnexpectedReserved: 'Unexpected reserved word',
6264
UnexpectedString: 'Unexpected string',
65+
UnexpectedSuper: '\'super\' keyword unexpected here',
6366
UnexpectedTemplate: 'Unexpected quasi %0',
6467
UnexpectedToken: 'Unexpected token %0',
6568
UnexpectedTokenIllegal: 'Unexpected token ILLEGAL',

src/nodes.ts

+4-4
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

+70-19
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface Context {
2727
inFunctionBody: boolean;
2828
inIteration: boolean;
2929
inSwitch: boolean;
30+
inClassConstructor: boolean;
3031
labelSet: any;
3132
strict: boolean;
3233
}
@@ -153,6 +154,7 @@ export class Parser {
153154
inFunctionBody: false,
154155
inIteration: false,
155156
inSwitch: false,
157+
inClassConstructor: false,
156158
labelSet: {},
157159
strict: false
158160
};
@@ -256,6 +258,12 @@ export class Parser {
256258
this.errorHandler.tolerate(this.unexpectedTokenError(token, message));
257259
}
258260

261+
tolerateInvalidLoopStatement() {
262+
if (this.matchKeyword("class") || this.matchKeyword("function")) {
263+
this.tolerateError(Messages.UnexpectedToken, this.lookahead);
264+
}
265+
}
266+
259267
collectComments() {
260268
if (!this.config.comment) {
261269
this.scanner.scanComments();
@@ -772,8 +780,7 @@ export class Parser {
772780
return body;
773781
}
774782

775-
parsePropertyMethodFunction(): Node.FunctionExpression {
776-
const isGenerator = false;
783+
parsePropertyMethodFunction(isGenerator: boolean): Node.FunctionExpression {
777784
const node = this.createNode();
778785

779786
const previousAllowYield = this.context.allowYield;
@@ -785,19 +792,24 @@ export class Parser {
785792
return this.finalize(node, new Node.FunctionExpression(null, params.params, method, isGenerator));
786793
}
787794

788-
parsePropertyMethodAsyncFunction(): Node.FunctionExpression {
795+
parsePropertyMethodAsyncFunction(isGenerator: boolean): Node.FunctionExpression {
789796
const node = this.createNode();
790797

791798
const previousAllowYield = this.context.allowYield;
792799
const previousAwait = this.context.await;
793800
this.context.allowYield = false;
794801
this.context.await = true;
802+
795803
const params = this.parseFormalParameters();
804+
if (params.message === Messages.StrictParamDupe) {
805+
this.throwError(Messages.DuplicateParameter);
806+
}
807+
796808
const method = this.parsePropertyMethod(params);
797809
this.context.allowYield = previousAllowYield;
798810
this.context.await = previousAwait;
799811

800-
return this.finalize(node, new Node.AsyncFunctionExpression(null, params.params, method));
812+
return this.finalize(node, new Node.AsyncFunctionExpression(null, params.params, method, isGenerator));
801813
}
802814

803815
parseObjectPropertyKey(): Node.PropertyKey {
@@ -855,13 +867,18 @@ export class Parser {
855867
let method = false;
856868
let shorthand = false;
857869
let isAsync = false;
870+
let isGenerator = false;
858871

859872
if (token.type === Token.Identifier) {
860873
const id = token.value;
861874
this.nextToken();
862875
computed = this.match('[');
863876
isAsync = !this.hasLineTerminator && (id === 'async') &&
864-
!this.match(':') && !this.match('(') && !this.match('*') && !this.match(',');
877+
!this.match(':') && !this.match('(') && !this.match(',');
878+
isGenerator = this.match('*');
879+
if (isGenerator) {
880+
this.nextToken();
881+
}
865882
key = isAsync ? this.parseObjectPropertyKey() : this.finalize(node, new Node.Identifier(id));
866883
} else if (this.match('*')) {
867884
this.nextToken();
@@ -908,7 +925,7 @@ export class Parser {
908925
value = this.inheritCoverGrammar(this.parseAssignmentExpression);
909926

910927
} else if (this.match('(')) {
911-
value = isAsync ? this.parsePropertyMethodAsyncFunction() : this.parsePropertyMethodFunction();
928+
value = isAsync ? this.parsePropertyMethodAsyncFunction(isGenerator) : this.parsePropertyMethodFunction(isGenerator);
912929
method = true;
913930

914931
} else if (token.type === Token.Identifier) {
@@ -1331,7 +1348,8 @@ export class Parser {
13311348
this.context.allowIn = true;
13321349

13331350
let expr;
1334-
if (this.matchKeyword('super') && this.context.inFunctionBody) {
1351+
const isSuper = this.matchKeyword('super');
1352+
if (isSuper && this.context.inFunctionBody) {
13351353
expr = this.createNode();
13361354
this.nextToken();
13371355
expr = this.finalize(expr, new Node.Super());
@@ -1342,6 +1360,10 @@ export class Parser {
13421360
expr = this.inheritCoverGrammar(this.matchKeyword('new') ? this.parseNewExpression : this.parsePrimaryExpression);
13431361
}
13441362

1363+
if (isSuper && this.match('(') && !this.context.inClassConstructor) {
1364+
this.tolerateError(Messages.UnexpectedSuper);
1365+
}
1366+
13451367
let hasOptional = false;
13461368
while (true) {
13471369
let optional = false;
@@ -1426,8 +1448,11 @@ export class Parser {
14261448
assert(this.context.allowIn, 'callee of new expression always allow in keyword.');
14271449

14281450
const node = this.startNode(this.lookahead);
1429-
let expr = (this.matchKeyword('super') && this.context.inFunctionBody) ? this.parseSuper() :
1430-
this.inheritCoverGrammar(this.matchKeyword('new') ? this.parseNewExpression : this.parsePrimaryExpression);
1451+
let expr = (this.matchKeyword('super') && this.context.inFunctionBody)
1452+
? this.parseSuper()
1453+
: this.inheritCoverGrammar(this.matchKeyword('new')
1454+
? this.parseNewExpression
1455+
: this.parsePrimaryExpression);
14311456

14321457
let hasOptional = false;
14331458
while (true) {
@@ -2125,7 +2150,7 @@ export class Parser {
21252150
return this.finalize(node, new Node.Property('init', key, computed, value, method, shorthand));
21262151
}
21272152

2128-
parseRestProperty(params, kind): Node.RestElement {
2153+
parseRestProperty(params): Node.RestElement {
21292154
const node = this.createNode();
21302155
this.expect('...');
21312156
const arg = this.parsePattern(params);
@@ -2144,7 +2169,7 @@ export class Parser {
21442169

21452170
this.expect('{');
21462171
while (!this.match('}')) {
2147-
properties.push(this.match('...') ? this.parseRestProperty(params, kind) : this.parsePropertyPattern(params, kind));
2172+
properties.push(this.match('...') ? this.parseRestProperty(params) : this.parsePropertyPattern(params, kind));
21482173
if (!this.match('}')) {
21492174
this.expect(',');
21502175
}
@@ -2316,6 +2341,8 @@ export class Parser {
23162341
const node = this.createNode();
23172342
this.expectKeyword('do');
23182343

2344+
this.tolerateInvalidLoopStatement();
2345+
23192346
const previousInIteration = this.context.inIteration;
23202347
this.context.inIteration = true;
23212348
const body = this.parseStatement();
@@ -2519,6 +2546,7 @@ export class Parser {
25192546
body = this.finalize(this.createNode(), new Node.EmptyStatement());
25202547
} else {
25212548
this.expect(')');
2549+
this.tolerateInvalidLoopStatement();
25222550

25232551
const previousInIteration = this.context.inIteration;
25242552
this.context.inIteration = true;
@@ -3046,12 +3074,15 @@ export class Parser {
30463074

30473075
const isAsync = this.matchContextualKeyword('async');
30483076
if (isAsync) {
3077+
if (this.context.inIteration) {
3078+
this.tolerateError(Messages.AsyncFunctionInSingleStatementContext);
3079+
}
30493080
this.nextToken();
30503081
}
30513082

30523083
this.expectKeyword('function');
30533084

3054-
const isGenerator = isAsync ? false : this.match('*');
3085+
const isGenerator = this.match('*');
30553086
if (isGenerator) {
30563087
this.nextToken();
30573088
}
@@ -3084,6 +3115,10 @@ export class Parser {
30843115
this.context.allowYield = !isGenerator;
30853116

30863117
const formalParameters = this.parseFormalParameters(firstRestricted);
3118+
if (isGenerator && formalParameters.message === Messages.StrictParamDupe) {
3119+
this.throwError(Messages.DuplicateParameter);
3120+
}
3121+
30873122
const params = formalParameters.params;
30883123
const stricted = formalParameters.stricted;
30893124
firstRestricted = formalParameters.firstRestricted;
@@ -3107,8 +3142,9 @@ export class Parser {
31073142
this.context.await = previousAllowAwait;
31083143
this.context.allowYield = previousAllowYield;
31093144

3110-
return isAsync ? this.finalize(node, new Node.AsyncFunctionDeclaration(id, params, body)) :
3111-
this.finalize(node, new Node.FunctionDeclaration(id, params, body, isGenerator));
3145+
return isAsync
3146+
? this.finalize(node, new Node.AsyncFunctionDeclaration(id, params, body, isGenerator))
3147+
: this.finalize(node, new Node.FunctionDeclaration(id, params, body, isGenerator));
31123148
}
31133149

31143150
parseFunctionExpression(): Node.AsyncFunctionExpression | Node.FunctionExpression {
@@ -3121,7 +3157,7 @@ export class Parser {
31213157

31223158
this.expectKeyword('function');
31233159

3124-
const isGenerator = isAsync ? false : this.match('*');
3160+
const isGenerator = this.match('*');
31253161
if (isGenerator) {
31263162
this.nextToken();
31273163
}
@@ -3154,6 +3190,12 @@ export class Parser {
31543190
}
31553191

31563192
const formalParameters = this.parseFormalParameters(firstRestricted);
3193+
if (formalParameters.message === Messages.StrictParamDupe) {
3194+
if (isGenerator || isAsync) {
3195+
this.throwError(Messages.DuplicateParameter);
3196+
}
3197+
}
3198+
31573199
const params = formalParameters.params;
31583200
const stricted = formalParameters.stricted;
31593201
firstRestricted = formalParameters.firstRestricted;
@@ -3176,8 +3218,9 @@ export class Parser {
31763218
this.context.await = previousAllowAwait;
31773219
this.context.allowYield = previousAllowYield;
31783220

3179-
return isAsync ? this.finalize(node, new Node.AsyncFunctionExpression(id, params, body)) :
3180-
this.finalize(node, new Node.FunctionExpression(id, params, body, isGenerator));
3221+
return isAsync
3222+
? this.finalize(node, new Node.AsyncFunctionExpression(id, params, body, isGenerator))
3223+
: this.finalize(node, new Node.FunctionExpression(id, params, body, isGenerator));
31813224
}
31823225

31833226
// https://tc39.github.io/ecma262/#sec-directive-prologues-and-the-use-strict-directive
@@ -3360,6 +3403,7 @@ export class Parser {
33603403
let method = false;
33613404
let isStatic = false;
33623405
let isAsync = false;
3406+
let isGenerator = false;
33633407

33643408
if (this.match('*')) {
33653409
this.nextToken();
@@ -3379,8 +3423,12 @@ export class Parser {
33793423
}
33803424
if ((token.type === Token.Identifier) && !this.hasLineTerminator && (token.value === 'async')) {
33813425
const punctuator = this.lookahead.value;
3382-
if (punctuator !== ':' && punctuator !== '(' && punctuator !== '*') {
3426+
if (punctuator !== ':' && punctuator !== '(') {
33833427
isAsync = true;
3428+
isGenerator = this.match("*");
3429+
if (isGenerator) {
3430+
this.nextToken();
3431+
}
33843432
token = this.lookahead;
33853433
computed = this.match('[');
33863434
key = this.parseObjectPropertyKey();
@@ -3414,8 +3462,11 @@ export class Parser {
34143462
}
34153463

34163464
if (!kind && key && this.match('(')) {
3465+
const previousInClassConstructor = this.context.inClassConstructor;
3466+
this.context.inClassConstructor = token.value === 'constructor';
34173467
kind = 'init';
3418-
value = isAsync ? this.parsePropertyMethodAsyncFunction() : this.parsePropertyMethodFunction();
3468+
value = isAsync ? this.parsePropertyMethodAsyncFunction(isGenerator) : this.parsePropertyMethodFunction(isGenerator);
3469+
this.context.inClassConstructor = previousInClassConstructor;
34193470
method = true;
34203471
}
34213472

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]"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
for (var x of []) class C {}

0 commit comments

Comments
 (0)