From 211b3a6fdf8bcd57abb41e5561ef90461e59e3ca Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Tue, 11 Nov 2014 09:13:42 +1100 Subject: [PATCH] Implement ES7 Async/Await --- acorn.js | 127 +++++-- test/tests-harmony.js | 769 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 876 insertions(+), 20 deletions(-) diff --git a/acorn.js b/acorn.js index 401b9421c8..0e9b364416 100644 --- a/acorn.js +++ b/acorn.js @@ -164,7 +164,13 @@ comments.push(comment); }; } - isKeyword = options.ecmaVersion >= 6 ? isEcma6Keyword : isEcma5AndLessKeyword; + if (options.ecmaVersion >= 7) { + isKeyword = isEcma7Keyword; + } else if (options.ecmaVersion === 6) { + isKeyword = isEcma6Keyword; + } else { + isKeyword = isEcma5AndLessKeyword; + } } // The `getLineInfo` function is mostly useful when the @@ -289,7 +295,7 @@ // that `break` and `continue` have somewhere to jump to, and // `strict` indicates whether strict mode is on. - var inFunction, inGenerator, labels, strict; + var inFunction, inGenerator, inAsync, labels, strict; // This counter is used for checking that arrow expressions did // not contain nested parentheses in argument list. @@ -305,7 +311,7 @@ function initParserState() { lastStart = lastEnd = tokPos; if (options.locations) lastEndLoc = new Position; - inFunction = inGenerator = strict = false; + inFunction = inGenerator = inAsync = strict = false; labels = []; readToken(); } @@ -368,6 +374,7 @@ var _class = {keyword: "class"}, _extends = {keyword: "extends", beforeExpr: true}; var _export = {keyword: "export"}, _import = {keyword: "import"}; var _yield = {keyword: "yield", beforeExpr: true}; + var _async = {keyword: "async"}, _await = {keyword: "await", beforeExpr: true}; // The keywords that denote values. @@ -394,7 +401,8 @@ "void": {keyword: "void", prefix: true, beforeExpr: true}, "delete": {keyword: "delete", prefix: true, beforeExpr: true}, "class": _class, "extends": _extends, - "export": _export, "import": _import, "yield": _yield}; + "export": _export, "import": _import, "yield": _yield, + "await": _await, "async": _async}; // Punctuation token types. Again, the `type` property is purely for debugging. @@ -517,7 +525,11 @@ var isEcma5AndLessKeyword = makePredicate(ecma5AndLessKeywords); - var isEcma6Keyword = makePredicate(ecma5AndLessKeywords + " let const class extends export import yield"); + var ecma6AndLessKeywords = ecma5AndLessKeywords + " let const class extends export import yield"; + + var isEcma6Keyword = makePredicate(ecma6AndLessKeywords); + + var isEcma7Keyword = makePredicate(ecma6AndLessKeywords + " async await"); var isKeyword = isEcma5AndLessKeyword; @@ -1539,6 +1551,7 @@ case _debugger: return parseDebuggerStatement(node); case _do: return parseDoStatement(node); case _for: return parseForStatement(node); + case _async: return parseAsync(node, true); case _function: return parseFunctionStatement(node); case _class: return parseClass(node, true); case _if: return parseIfStatement(node); @@ -1640,7 +1653,46 @@ function parseFunctionStatement(node) { next(); - return parseFunction(node, true); + return parseFunction(node, true, false); + } + + function parseAsync(node, isStatement) { + if (options.ecmaVersion < 7) { + unexpected(); + } + + next(); + + switch (tokType) { + case _function: + next(); + return parseFunction(node, isStatement, true); + + if (!isStatement) unexpected(); + + case _name: + var id = parseIdent(tokType !== _name); + if (eat(_arrow)) { + return parseArrowExpression(node, [id], true); + } + + case _parenL: + var oldParenL = ++metParenL; + var exprList = []; + next(); + if (tokType !== _parenR) { + var val = parseExpression(); + exprList = val.type === "SequenceExpression" ? val.expressions : [val]; + } + expect(_parenR); + // if '=>' follows '(...)', convert contents to arguments + if (metParenL === oldParenL && eat(_arrow)) { + return parseArrowExpression(node, exprList, true); + } + + default: + unexpected(); + } } function parseIfStatement(node) { @@ -2023,6 +2075,9 @@ case _yield: if (inGenerator) return parseYield(); + case _await: + if (inAsync) return parseAwait(); + case _name: var start = storeCurrentPos(); var id = parseIdent(tokType !== _name); @@ -2104,10 +2159,13 @@ case _braceL: return parseObj(); + case _async: + return parseAsync(startNode(), false); + case _function: var node = startNode(); next(); - return parseFunction(node, false); + return parseFunction(node, false, false); case _class: return parseClass(startNode(), false); @@ -2192,7 +2250,11 @@ if (options.allowTrailingCommas && eat(_braceR)) break; } else first = false; - var prop = startNode(), isGenerator; + var prop = startNode(), isGenerator, isAsync; + if (options.ecmaVersion >= 7) { + isAsync = eat(_async); + if (isAsync && tokType === _star) unexpected(); + } if (options.ecmaVersion >= 6) { prop.method = false; prop.shorthand = false; @@ -2205,13 +2267,13 @@ } else if (options.ecmaVersion >= 6 && tokType === _parenL) { prop.kind = "init"; prop.method = true; - prop.value = parseMethod(isGenerator); + prop.value = parseMethod(isGenerator, isAsync); } else if (options.ecmaVersion >= 5 && !prop.computed && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set")) { - if (isGenerator) unexpected(); + if (isGenerator || isAsync) unexpected(); prop.kind = prop.key.name; parsePropertyName(prop); - prop.value = parseMethod(false); + prop.value = parseMethod(false, false); } else if (options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") { prop.kind = "init"; prop.value = prop.key; @@ -2240,7 +2302,7 @@ // Initialize empty function node. - function initFunction(node) { + function initFunction(node, isAsync) { node.id = null; node.params = []; if (options.ecmaVersion >= 6) { @@ -2248,14 +2310,18 @@ node.rest = null; node.generator = false; } + if (options.ecmaVersion >= 7) { + node.async = isAsync; + } } // Parse a function declaration or literal (depending on the // `isStatement` parameter). - function parseFunction(node, isStatement, allowExpressionBody) { - initFunction(node); + function parseFunction(node, isStatement, isAsync, allowExpressionBody) { + initFunction(node, isAsync); if (options.ecmaVersion >= 6) { + if (isAsync && tokType === _star) unexpected(); node.generator = eat(_star); } if (isStatement || tokType === _name) { @@ -2268,9 +2334,9 @@ // Parse object or class method. - function parseMethod(isGenerator) { + function parseMethod(isGenerator, isAsync) { var node = startNode(); - initFunction(node); + initFunction(node, isAsync); parseFunctionParams(node); var allowExpressionBody; if (options.ecmaVersion >= 6) { @@ -2285,8 +2351,8 @@ // Parse arrow function expression with given parameters. - function parseArrowExpression(node, params) { - initFunction(node); + function parseArrowExpression(node, params, isAsync) { + initFunction(node, isAsync); var defaults = node.defaults, hasDefaults = false; @@ -2355,6 +2421,8 @@ function parseFunctionBody(node, allowExpression) { var isExpression = allowExpression && tokType !== _braceL; + var oldInAsync = inAsync; + inAsync = node.async; if (isExpression) { node.body = parseExpression(true); node.expression = true; @@ -2367,6 +2435,7 @@ node.expression = false; inFunction = oldInFunc; inGenerator = oldInGen; labels = oldLabels; } + inAsync = oldInAsync; // If this is a strict mode function, verify that argument names // are not repeated, and it does not try to bind the words `eval` @@ -2400,17 +2469,22 @@ } else { method['static'] = false; } + var isAsync = false; + if (options.ecmaVersion >= 7) { + isAsync = eat(_async); + if (isAsync && tokType === _star) unexpected(); + } var isGenerator = eat(_star); parsePropertyName(method); if (tokType === _name && !method.computed && method.key.type === "Identifier" && (method.key.name === "get" || method.key.name === "set")) { - if (isGenerator) unexpected(); + if (isGenerator || isAsync) unexpected(); method.kind = method.key.name; parsePropertyName(method); } else { method.kind = ""; } - method.value = parseMethod(isGenerator); + method.value = parseMethod(isGenerator, isAsync); classBody.body.push(finishNode(method, "MethodDefinition")); eat(_semi); } @@ -2615,6 +2689,19 @@ return finishNode(node, "YieldExpression"); } + // Parses await expression inside async function. + + function parseAwait() { + var node = startNode(); + next(); + if (eat(_semi) || canInsertSemicolon()) { + unexpected(); + } + node.delegate = eat(_star); + node.argument = parseExpression(true); + return finishNode(node, "AwaitExpression"); + } + // Parses array and generator comprehensions. function parseComprehension(node, isGenerator) { diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 539418f870..aa10ecec09 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -13405,6 +13405,771 @@ test("/[a-z]/u", { ecmaVersion: 6 }); +// ES7: Async Functions + +test('async function foo(promise) { await promise; }', { + type: "Program", + body: [{ + type: "FunctionDeclaration", + id: { + type: "Identifier", + name: "foo", + loc: { + start: {line: 1, column: 15}, + end: {line: 1, column: 18} + } + }, + params: [{ + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 19}, + end: {line: 1, column: 26} + } + }], + defaults: [], + body: { + type: "BlockStatement", + body: [{ + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 36}, + end: {line: 1, column: 43} + } + }, + loc: { + start: {line: 1, column: 30}, + end: {line: 1, column: 43} + } + }, + loc: { + start: {line: 1, column: 30}, + end: {line: 1, column: 44} + } + }], + loc: { + start: {line: 1, column: 28}, + end: {line: 1, column: 46} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 46} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('(function(x) { async function inner() { await x } })', { + type: "Program", + body: [{ + type: "ExpressionStatement", + expression: { + type: "FunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "x", + loc: { + start: {line: 1, column: 10}, + end: {line: 1, column: 11} + } + } + ], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "FunctionDeclaration", + id: { + type: "Identifier", + name: "inner", + loc: { + start: {line: 1, column: 30}, + end: {line: 1, column: 35} + } + }, + params: [], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "x", + loc: { + start: {line: 1, column: 46}, + end: {line: 1, column: 47} + } + }, + loc: { + start: {line: 1, column: 40}, + end: {line: 1, column: 47} + } + }, + loc: { + start: {line: 1, column: 40}, + end: {line: 1, column: 47} + } + } + ], + loc: { + start: {line: 1, column: 38}, + end: {line: 1, column: 49} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 15}, + end: {line: 1, column: 49} + } + } + ], + loc: { + start: {line: 1, column: 13}, + end: {line: 1, column: 51} + } + }, + rest: null, + generator: false, + expression: false, + loc: { + start: {line: 1, column: 1}, + end: {line: 1, column: 51} + } + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 52} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('var foo = async function(promise) { await promise; }', { + type: "Program", + body: [{ + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + id: { + type: "Identifier", + name: "foo", + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 7} + } + }, + init: { + type: "FunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 25}, + end: {line: 1, column: 32} + } + } + ], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 42}, + end: {line: 1, column: 49} + } + }, + loc: { + start: {line: 1, column: 36}, + end: {line: 1, column: 49} + } + }, + loc: { + start: {line: 1, column: 36}, + end: {line: 1, column: 50} + } + } + ], + loc: { + start: {line: 1, column: 34}, + end: {line: 1, column: 52} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 10}, + end: {line: 1, column: 52} + } + }, + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 52} + } + } + ], + kind: "var", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 52} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('var o = { a: 1, async foo(promise) { await promise } }', { + type: "Program", + body: [{ + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + id: { + type: "Identifier", + name: "o", + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 5} + } + }, + init: { + type: "ObjectExpression", + properties: [ + { + type: "Property", + key: { + type: "Identifier", + name: "a", + loc: { + start: {line: 1, column: 10}, + end: {line: 1, column: 11} + } + }, + value: { + type: "Literal", + value: 1, + loc: { + start: {line: 1, column: 13}, + end: {line: 1, column: 14} + } + }, + kind: "init", + method: false, + shorthand: false, + computed: false, + loc: { + start: {line: 1, column: 10}, + end: {line: 1, column: 14} + } + }, + { + type: "Property", + key: { + type: "Identifier", + name: "foo", + loc: { + start: {line: 1, column: 22}, + end: {line: 1, column: 25} + } + }, + value: { + type: "FunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 26}, + end: {line: 1, column: 33} + } + } + ], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 43}, + end: {line: 1, column: 50} + } + }, + loc: { + start: {line: 1, column: 37}, + end: {line: 1, column: 50} + } + }, + loc: { + start: {line: 1, column: 37}, + end: {line: 1, column: 50} + } + } + ], + loc: { + start: {line: 1, column: 35}, + end: {line: 1, column: 52} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 25}, + end: {line: 1, column: 52} + } + }, + kind: "init", + method: true, + shorthand: false, + computed: false, + loc: { + start: {line: 1, column: 16}, + end: {line: 1, column: 52} + } + } + ], + loc: { + start: {line: 1, column: 8}, + end: {line: 1, column: 54} + } + }, + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 54} + } + } + ], + kind: "var", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 54} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('class Foo { async bar(promise) { await promise } }', { + type: "Program", + body: [{ + type: "ClassDeclaration", + id: { + type: "Identifier", + name: "Foo", + loc: { + start: {line: 1, column: 6}, + end: {line: 1, column: 9} + } + }, + superClass: null, + body: { + type: "ClassBody", + body: [ + { + type: "MethodDefinition", + key: { + type: "Identifier", + name: "bar", + loc: { + start: {line: 1, column: 18}, + end: {line: 1, column: 21} + } + }, + value: { + type: "FunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 22}, + end: {line: 1, column: 29} + } + } + ], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 39}, + end: {line: 1, column: 46} + } + }, + loc: { + start: {line: 1, column: 33}, + end: {line: 1, column: 46} + } + }, + loc: { + start: {line: 1, column: 33}, + end: {line: 1, column: 46} + } + } + ], + loc: { + start: {line: 1, column: 31}, + end: {line: 1, column: 48} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 21}, + end: {line: 1, column: 48} + } + }, + kind: "", + static: false, + loc: { + start: {line: 1, column: 12}, + end: {line: 1, column: 48} + } + } + ], + loc: { + start: {line: 1, column: 10}, + end: {line: 1, column: 50} + } + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 50} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('f(a, async promise => await promise)', { + type: "Program", + body: [{ + type: "ExpressionStatement", + expression: { + type: "CallExpression", + callee: { + type: "Identifier", + name: "f", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 1} + } + }, + arguments: [ + { + type: "Identifier", + name: "a", + loc: { + start: {line: 1, column: 2}, + end: {line: 1, column: 3} + } + }, + { + type: "ArrowFunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 11}, + end: {line: 1, column: 18} + } + } + ], + defaults: [], + body: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 28}, + end: {line: 1, column: 35} + } + }, + loc: { + start: {line: 1, column: 22}, + end: {line: 1, column: 35} + } + }, + rest: null, + generator: false, + expression: true, + async: true, + loc: { + start: {line: 1, column: 5}, + end: {line: 1, column: 35} + } + } + ], + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 36} + } + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 36} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('f(a, async(x, y) => await [x, y], b)', { + type: "Program", + body: [{ + type: "ExpressionStatement", + expression: { + type: "CallExpression", + callee: { + type: "Identifier", + name: "f", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 1} + } + }, + arguments: [ + { + type: "Identifier", + name: "a", + loc: { + start: {line: 1, column: 2}, + end: {line: 1, column: 3} + } + }, + { + type: "ArrowFunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "x", + loc: { + start: {line: 1, column: 11}, + end: {line: 1, column: 12} + } + }, + { + type: "Identifier", + name: "y", + loc: { + start: {line: 1, column: 14}, + end: {line: 1, column: 15} + } + } + ], + defaults: [], + body: { + type: "AwaitExpression", + argument: { + type: "ArrayExpression", + elements: [ + { + type: "Identifier", + name: "x", + loc: { + start: {line: 1, column: 27}, + end: {line: 1, column: 28} + } + }, + { + type: "Identifier", + name: "y", + loc: { + start: {line: 1, column: 30}, + end: {line: 1, column: 31} + } + } + ], + loc: { + start: {line: 1, column: 26}, + end: {line: 1, column: 32} + } + }, + loc: { + start: {line: 1, column: 20}, + end: {line: 1, column: 32} + } + }, + rest: null, + generator: false, + expression: true, + async: true, + loc: { + start: {line: 1, column: 5}, + end: {line: 1, column: 32} + } + }, + { + type: "Identifier", + name: "b", + loc: { + start: {line: 1, column: 34}, + end: {line: 1, column: 35} + } + } + ], + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 36} + } + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 36} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('f(async function(promise) { await promise })', { + type: "Program", + body: [{ + type: "ExpressionStatement", + expression: { + type: "CallExpression", + callee: { + type: "Identifier", + name: "f", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 1} + } + }, + arguments: [ + { + type: "FunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 17}, + end: {line: 1, column: 24} + } + } + ], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 34}, + end: {line: 1, column: 41} + } + }, + loc: { + start: {line: 1, column: 28}, + end: {line: 1, column: 41} + } + }, + loc: { + start: {line: 1, column: 28}, + end: {line: 1, column: 41} + } + } + ], + loc: { + start: {line: 1, column: 26}, + end: {line: 1, column: 43} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 2}, + end: {line: 1, column: 43} + } + } + ], + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 44} + } + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 44} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + // Harmony Invalid syntax testFail("0o", "Expected number in radix 8 (1:2)", {ecmaVersion: 6}); @@ -13517,6 +14282,10 @@ testFail("(10) => 00", "Unexpected token (1:1)", {ecmaVersion: 6}); testFail("(10, 20) => 00", "Unexpected token (1:1)", {ecmaVersion: 6}); +testFail("function foo(promise) { await promise; }", "Unexpected token (1:30)", {ecmaVersion: 7}); + +testFail("async function* foo(promise) { await promise; }", "Unexpected token (1:14)", {ecmaVersion: 7}); + testFail("yield v", "Unexpected token (1:6)", {ecmaVersion: 6}); testFail("yield 10", "Unexpected token (1:6)", {ecmaVersion: 6});