From 5552e866f99a83ca05a9cd7ba654c25ecc7475be Mon Sep 17 00:00:00 2001 From: Brandon Mills Date: Wed, 13 Nov 2013 14:25:38 -0500 Subject: [PATCH] Support rest parameters http://wiki.ecmascript.org/doku.php?id=harmony:rest_parameters The final parameter to a function is a rest parameter if it is prefixed by "...". FunctionExpression and FunctionDeclaration nodes have a new "rest" property that is null if there is no rest parameter, or contains an Identifer for the parameter. https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API#Functions Implemented by adding a new token, `_ellipsis`, which consists of three dots. Modified the body of parseFunction to allow a single rest parameter at the end of an argument list. Both the token and the rest parameter require `options.ecmaVersion` >= 6, otherwise three dots are tokenized as three dots. --- acorn.js | 37 ++++-- test/tests.js | 323 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 350 insertions(+), 10 deletions(-) diff --git a/acorn.js b/acorn.js index 31935f41a5..d84e3b3897 100644 --- a/acorn.js +++ b/acorn.js @@ -308,7 +308,7 @@ var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true}; var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; - var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; + var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _ellipsis = {type: "..."}, _question = {type: "?", beforeExpr: true}; // Operators. These carry several kinds of properties to help the // parser use them properly (the presence of these properties is @@ -345,8 +345,8 @@ exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR, parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon, - dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof, - num: _num, regexp: _regexp, string: _string}; + dot: _dot, ellipsis: _ellipsis, question: _question, slash: _slash, eq: _eq, + name: _name, eof: _eof, num: _num, regexp: _regexp, string: _string}; for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw]; // This is a trick taken from Esprima. It turns out that, on @@ -582,8 +582,14 @@ function readToken_dot() { var next = input.charCodeAt(tokPos + 1); if (next >= 48 && next <= 57) return readNumber(true); - ++tokPos; - return finishToken(_dot); + var next2 = input.charCodeAt(tokPos + 2); + if (options.ecmaVersion >= 6 && next === 46 && next2 === 46) { // 46 = dot '.' + tokPos += 3; + return finishToken(_ellipsis); + } else { + ++tokPos; + return finishToken(_dot); + } } function readToken_slash() { // '/' @@ -659,7 +665,7 @@ function getTokenFromCode(code) { switch(code) { // The interpretation of a dot depends on whether it is followed - // by a digit. + // by a digit or another two dots. case 46: // '.' return readToken_dot(); @@ -1706,11 +1712,22 @@ else if (isStatement) unexpected(); else node.id = null; node.params = []; - var first = true; + node.rest = null; expect(_parenL); - while (!eat(_parenR)) { - if (!first) expect(_comma); else first = false; - node.params.push(parseIdent()); + for (;;) { + if (eat(_parenR)) { + break; + } else if (options.ecmaVersion >= 6 && eat(_ellipsis)) { + node.rest = parseIdent(); + expect(_parenR); + break; + } else { + node.params.push(parseIdent()); + if (!eat(_comma)) { + expect(_parenR); + break; + } + } } // Start a new scope with regard to labels and the `inFunction` diff --git a/test/tests.js b/test/tests.js index 9cb04321fb..4bc5b4d65d 100644 --- a/test/tests.js +++ b/test/tests.js @@ -23572,6 +23572,171 @@ test("function hello(a, b) { sayHi(); }", { } }); +test("function hello(...rest) { }", { + type: "Program", + body: [ + { + type: "FunctionDeclaration", + id: { + type: "Identifier", + name: "hello", + loc: { + start: { + line: 1, + column: 9 + }, + end: { + line: 1, + column: 14 + } + } + }, + params: [], + rest: { + type: "Identifier", + name: "rest", + loc: { + start: { + line: 1, + column: 18 + }, + end: { + line: 1, + column: 22 + } + } + }, + body: { + type: "BlockStatement", + body: [], + loc: { + start: { + line: 1, + column: 24 + }, + end: { + line: 1, + column: 27 + } + } + }, + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 27 + } + } + } + ], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 27 + } + } +}, { + ecmaVersion: 6, + locations: true +}); + +test("function hello(a, ...rest) { }", { + type: "Program", + body: [ + { + type: "FunctionDeclaration", + id: { + type: "Identifier", + name: "hello", + loc: { + start: { + line: 1, + column: 9 + }, + end: { + line: 1, + column: 14 + } + } + }, + params: [ + { + type: "Identifier", + name: "a", + loc: { + start: { + line: 1, + column: 15 + }, + end: { + line: 1, + column: 16 + } + } + } + ], + rest: { + type: "Identifier", + name: "rest", + loc: { + start: { + line: 1, + column: 21 + }, + end: { + line: 1, + column: 25 + } + } + }, + body: { + type: "BlockStatement", + body: [], + loc: { + start: { + line: 1, + column: 27 + }, + end: { + line: 1, + column: 30 + } + } + }, + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 30 + } + } + } + ], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 30 + } + } +}, { + ecmaVersion: 6, + locations: true +}); + test("var hi = function() { sayHi() };", { type: "Program", body: [ @@ -23702,6 +23867,153 @@ test("var hi = function() { sayHi() };", { } }); +test("var hi = function (...r) { sayHi() };", { + type: "Program", + body: [ + { + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + id: { + type: "Identifier", + name: "hi", + loc: { + start: { + line: 1, + column: 4 + }, + end: { + line: 1, + column: 6 + } + } + }, + init: { + type: "FunctionExpression", + id: null, + params: [], + rest: { + type: "Identifier", + name: "r", + loc: { + start: { + line: 1, + column: 22 + }, + end: { + line: 1, + column: 23 + } + } + }, + body: { + type: "BlockStatement", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "CallExpression", + callee: { + type: "Identifier", + name: "sayHi", + loc: { + start: { + line: 1, + column: 27 + }, + end: { + line: 1, + column: 32 + } + } + }, + arguments: [], + loc: { + start: { + line: 1, + column: 27 + }, + end: { + line: 1, + column: 34 + } + } + }, + loc: { + start: { + line: 1, + column: 27 + }, + end: { + line: 1, + column: 34 + } + } + } + ], + loc: { + start: { + line: 1, + column: 25 + }, + end: { + line: 1, + column: 36 + } + } + }, + loc: { + start: { + line: 1, + column: 9 + }, + end: { + line: 1, + column: 36 + } + } + }, + loc: { + start: { + line: 1, + column: 4 + }, + end: { + line: 1, + column: 36 + } + } + } + ], + kind: "var", + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 37 + } + } + } + ], + loc: { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 37 + } + } +}, { + ecmaVersion: 6, + locations: true +}); + test("var hi = function eval() { };", { type: "Program", body: [ @@ -26551,6 +26863,17 @@ testFail("({ get i() { }, get i() { } })", testFail("({ set i(x) { }, set i(x) { } })", "Redefinition of property (1:21)"); +testFail("function t(...) { }", + "Unexpected token (1:11)"); + +testFail("function t(...) { }", + "Unexpected token (1:14)", + { ecmaVersion: 6 }); + +testFail("function t(...rest, b) { }", + "Unexpected token (1:18)", + { ecmaVersion: 6 }); + testFail("function t(if) { }", "Unexpected token (1:11)");