Skip to content

Commit

Permalink
Support async generators
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma committed Aug 21, 2021
1 parent 4e3b259 commit a770bff
Show file tree
Hide file tree
Showing 29 changed files with 1,818 additions and 6,062 deletions.
2 changes: 2 additions & 0 deletions src/messages.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Error messages should be identical to V8.
export const Messages = {
AsyncFunctionInSingleStatementContext: 'Async functions can only be declared at the top level or inside a block.',
BadImportCallArity: 'Unexpected token',
BadGetterArity: 'Getter must not have any formal parameters',
BadSetterArity: 'Setter must have exactly one formal parameter',
Expand All @@ -12,6 +13,7 @@ export const Messages = {
DefaultRestProperty: 'Unexpected token =',
DuplicateBinding: 'Duplicate binding %0',
DuplicateConstructor: 'A class may only have one constructor',
DuplicateParameter: 'Duplicate parameter name not allowed in this context',
DuplicateProtoProperty: 'Duplicate __proto__ fields are not allowed in object literals',
ForInOfLoopInitializer: '%0 loop variable declaration may not have an initializer',
GeneratorInLegacyContext: 'Generator declarations are not allowed in legacy contexts',
Expand Down
8 changes: 4 additions & 4 deletions src/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,12 @@ export class AsyncFunctionDeclaration {
readonly generator: boolean;
readonly expression: boolean;
readonly async: boolean;
constructor(id: Identifier | null, params: FunctionParameter[], body: BlockStatement) {
constructor(id: Identifier | null, params: FunctionParameter[], body: BlockStatement, generator: boolean) {
this.type = Syntax.FunctionDeclaration;
this.id = id;
this.params = params;
this.body = body;
this.generator = false;
this.generator = generator;
this.expression = false;
this.async = true;
}
Expand All @@ -134,12 +134,12 @@ export class AsyncFunctionExpression {
readonly generator: boolean;
readonly expression: boolean;
readonly async: boolean;
constructor(id: Identifier | null, params: FunctionParameter[], body: BlockStatement) {
constructor(id: Identifier | null, params: FunctionParameter[], body: BlockStatement, generator: boolean) {
this.type = Syntax.FunctionExpression;
this.id = id;
this.params = params;
this.body = body;
this.generator = false;
this.generator = generator;
this.expression = false;
this.async = true;
}
Expand Down
70 changes: 54 additions & 16 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ export class Parser {
this.errorHandler.tolerate(this.unexpectedTokenError(token, message));
}

tolerateInvalidLoopStatement() {
if (this.matchKeyword("class") || this.matchKeyword("function")) {
this.tolerateError(Messages.UnexpectedToken, this.lookahead);
}
}

collectComments() {
if (!this.config.comment) {
this.scanner.scanComments();
Expand Down Expand Up @@ -772,8 +778,7 @@ export class Parser {
return body;
}

parsePropertyMethodFunction(): Node.FunctionExpression {
const isGenerator = false;
parsePropertyMethodFunction(isGenerator: boolean): Node.FunctionExpression {
const node = this.createNode();

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

parsePropertyMethodAsyncFunction(): Node.FunctionExpression {
parsePropertyMethodAsyncFunction(isGenerator: boolean): Node.FunctionExpression {
const node = this.createNode();

const previousAllowYield = this.context.allowYield;
const previousAwait = this.context.await;
this.context.allowYield = false;
this.context.await = true;

const params = this.parseFormalParameters();
if (params.message === Messages.StrictParamDupe) {
this.throwError(Messages.DuplicateParameter);
}

const method = this.parsePropertyMethod(params);
this.context.allowYield = previousAllowYield;
this.context.await = previousAwait;

return this.finalize(node, new Node.AsyncFunctionExpression(null, params.params, method));
return this.finalize(node, new Node.AsyncFunctionExpression(null, params.params, method, isGenerator));
}

parseObjectPropertyKey(): Node.PropertyKey {
Expand Down Expand Up @@ -855,13 +865,18 @@ export class Parser {
let method = false;
let shorthand = false;
let isAsync = false;
let isGenerator = false;

if (token.type === Token.Identifier) {
const id = token.value;
this.nextToken();
computed = this.match('[');
isAsync = !this.hasLineTerminator && (id === 'async') &&
!this.match(':') && !this.match('(') && !this.match('*') && !this.match(',');
!this.match(':') && !this.match('(') && !this.match(',');
isGenerator = this.match('*');
if (isGenerator) {
this.nextToken();
}
key = isAsync ? this.parseObjectPropertyKey() : this.finalize(node, new Node.Identifier(id));
} else if (this.match('*')) {
this.nextToken();
Expand Down Expand Up @@ -908,7 +923,7 @@ export class Parser {
value = this.inheritCoverGrammar(this.parseAssignmentExpression);

} else if (this.match('(')) {
value = isAsync ? this.parsePropertyMethodAsyncFunction() : this.parsePropertyMethodFunction();
value = isAsync ? this.parsePropertyMethodAsyncFunction(isGenerator) : this.parsePropertyMethodFunction(isGenerator);
method = true;

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

parseRestProperty(params, kind): Node.RestElement {
parseRestProperty(params): Node.RestElement {
const node = this.createNode();
this.expect('...');
const arg = this.parsePattern(params);
Expand All @@ -2144,7 +2159,7 @@ export class Parser {

this.expect('{');
while (!this.match('}')) {
properties.push(this.match('...') ? this.parseRestProperty(params, kind) : this.parsePropertyPattern(params, kind));
properties.push(this.match('...') ? this.parseRestProperty(params) : this.parsePropertyPattern(params, kind));
if (!this.match('}')) {
this.expect(',');
}
Expand Down Expand Up @@ -2316,6 +2331,8 @@ export class Parser {
const node = this.createNode();
this.expectKeyword('do');

this.tolerateInvalidLoopStatement();

const previousInIteration = this.context.inIteration;
this.context.inIteration = true;
const body = this.parseStatement();
Expand Down Expand Up @@ -2519,6 +2536,7 @@ export class Parser {
body = this.finalize(this.createNode(), new Node.EmptyStatement());
} else {
this.expect(')');
this.tolerateInvalidLoopStatement();

const previousInIteration = this.context.inIteration;
this.context.inIteration = true;
Expand Down Expand Up @@ -3046,12 +3064,15 @@ export class Parser {

const isAsync = this.matchContextualKeyword('async');
if (isAsync) {
if (this.context.inIteration) {
this.tolerateError(Messages.AsyncFunctionInSingleStatementContext);
}
this.nextToken();
}

this.expectKeyword('function');

const isGenerator = isAsync ? false : this.match('*');
const isGenerator = this.match('*');
if (isGenerator) {
this.nextToken();
}
Expand Down Expand Up @@ -3084,6 +3105,10 @@ export class Parser {
this.context.allowYield = !isGenerator;

const formalParameters = this.parseFormalParameters(firstRestricted);
if (isGenerator && formalParameters.message === Messages.StrictParamDupe) {
this.throwError(Messages.DuplicateParameter);
}

const params = formalParameters.params;
const stricted = formalParameters.stricted;
firstRestricted = formalParameters.firstRestricted;
Expand All @@ -3107,8 +3132,9 @@ export class Parser {
this.context.await = previousAllowAwait;
this.context.allowYield = previousAllowYield;

return isAsync ? this.finalize(node, new Node.AsyncFunctionDeclaration(id, params, body)) :
this.finalize(node, new Node.FunctionDeclaration(id, params, body, isGenerator));
return isAsync
? this.finalize(node, new Node.AsyncFunctionDeclaration(id, params, body, isGenerator))
: this.finalize(node, new Node.FunctionDeclaration(id, params, body, isGenerator));
}

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

this.expectKeyword('function');

const isGenerator = isAsync ? false : this.match('*');
const isGenerator = this.match('*');
if (isGenerator) {
this.nextToken();
}
Expand Down Expand Up @@ -3154,6 +3180,12 @@ export class Parser {
}

const formalParameters = this.parseFormalParameters(firstRestricted);
if (formalParameters.message === Messages.StrictParamDupe) {
if (isGenerator || isAsync) {
this.throwError(Messages.DuplicateParameter);
}
}

const params = formalParameters.params;
const stricted = formalParameters.stricted;
firstRestricted = formalParameters.firstRestricted;
Expand All @@ -3176,8 +3208,9 @@ export class Parser {
this.context.await = previousAllowAwait;
this.context.allowYield = previousAllowYield;

return isAsync ? this.finalize(node, new Node.AsyncFunctionExpression(id, params, body)) :
this.finalize(node, new Node.FunctionExpression(id, params, body, isGenerator));
return isAsync
? this.finalize(node, new Node.AsyncFunctionExpression(id, params, body, isGenerator))
: this.finalize(node, new Node.FunctionExpression(id, params, body, isGenerator));
}

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

if (this.match('*')) {
this.nextToken();
Expand All @@ -3379,8 +3413,12 @@ export class Parser {
}
if ((token.type === Token.Identifier) && !this.hasLineTerminator && (token.value === 'async')) {
const punctuator = this.lookahead.value;
if (punctuator !== ':' && punctuator !== '(' && punctuator !== '*') {
if (punctuator !== ':' && punctuator !== '(') {
isAsync = true;
isGenerator = this.match("*");
if (isGenerator) {
this.nextToken();
}
token = this.lookahead;
computed = this.match('[');
key = this.parseObjectPropertyKey();
Expand Down Expand Up @@ -3415,7 +3453,7 @@ export class Parser {

if (!kind && key && this.match('(')) {
kind = 'init';
value = isAsync ? this.parsePropertyMethodAsyncFunction() : this.parsePropertyMethodFunction();
value = isAsync ? this.parsePropertyMethodAsyncFunction(isGenerator) : this.parsePropertyMethodFunction(isGenerator);
method = true;
}

Expand Down
1 change: 1 addition & 0 deletions test/fixtures/ES6/for-of/invalid-decl-cls.failure.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"index":17,"lineNumber":1,"column":18,"message":"Error: Line 1: Unexpected token [object Object]","description":"Unexpected token [object Object]"}
1 change: 1 addition & 0 deletions test/fixtures/ES6/for-of/invalid-decl-cls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
for (var x of []) class C {}
Loading

0 comments on commit a770bff

Please sign in to comment.