From 454c1e6bbe4298965cc3a2ebb8dd5bc359683923 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Mon, 4 May 2020 16:05:13 +0100 Subject: [PATCH] LibJS: Implement rest parameters --- Libraries/LibJS/AST.cpp | 5 +- Libraries/LibJS/AST.h | 1 + Libraries/LibJS/Parser.cpp | 27 +++++++-- Libraries/LibJS/Runtime/ScriptFunction.cpp | 18 ++++-- Libraries/LibJS/Tests/function-rest-params.js | 57 +++++++++++++++++++ 5 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 Libraries/LibJS/Tests/function-rest-params.js diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 0544b23321..a153f7b70d 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -695,10 +695,11 @@ void FunctionNode::dump(int indent, const char* class_name) const for (auto& parameter : m_parameters) { print_indent(indent + 2); + if (parameter.is_rest) + printf("..."); printf("%s\n", parameter.name.characters()); - if (parameter.default_value) { + if (parameter.default_value) parameter.default_value->dump(indent + 3); - } } } if (!m_variables.is_empty()) { diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index 571b035c76..64262a5740 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -158,6 +158,7 @@ public: struct Parameter { FlyString name; RefPtr default_value; + bool is_rest { false }; }; const FlyString& name() const { return m_name; } diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index 4ff34318ed..e9d258bd53 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -282,8 +282,13 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe Vector parameters; bool parse_failed = false; + bool has_rest_parameter = false; while (true) { if (match(TokenType::Comma)) { + if (has_rest_parameter) { + parse_failed = true; + break; + } consume(TokenType::Comma); } else if (match(TokenType::Identifier)) { auto parameter_name = consume(TokenType::Identifier).value(); @@ -293,6 +298,15 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe default_value = parse_expression(0); } parameters.append({ parameter_name, default_value }); + } else if (match(TokenType::TripleDot)) { + consume(); + if (has_rest_parameter) { + parse_failed = true; + break; + } + has_rest_parameter = true; + auto parameter_name = consume(TokenType::Identifier).value(); + parameters.append({ parameter_name, nullptr, true }); } else if (match(TokenType::ParenClose)) { if (expect_parens) { consume(TokenType::ParenClose); @@ -359,7 +373,7 @@ NonnullRefPtr Parser::parse_primary_expression() switch (m_parser_state.m_current_token.type()) { case TokenType::ParenOpen: { consume(TokenType::ParenOpen); - if (match(TokenType::ParenClose) || match(TokenType::Identifier)) { + if (match(TokenType::ParenClose) || match(TokenType::Identifier) || match(TokenType::TripleDot)) { auto arrow_function_result = try_parse_arrow_function_expression(true); if (!arrow_function_result.is_null()) { return arrow_function_result.release_nonnull(); @@ -818,7 +832,13 @@ NonnullRefPtr Parser::parse_function_node(bool needs_function_ } consume(TokenType::ParenOpen); Vector parameters; - while (match(TokenType::Identifier)) { + while (match(TokenType::Identifier) || match(TokenType::TripleDot)) { + if (match(TokenType::TripleDot)) { + consume(); + auto parameter_name = consume(TokenType::Identifier).value(); + parameters.append({ parameter_name, nullptr, true }); + break; + } auto parameter_name = consume(TokenType::Identifier).value(); RefPtr default_value; if (match(TokenType::Equals)) { @@ -826,9 +846,8 @@ NonnullRefPtr Parser::parse_function_node(bool needs_function_ default_value = parse_expression(0); } parameters.append({ parameter_name, default_value }); - if (match(TokenType::ParenClose)) { + if (match(TokenType::ParenClose)) break; - } consume(TokenType::Comma); } consume(TokenType::ParenClose); diff --git a/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Libraries/LibJS/Runtime/ScriptFunction.cpp index f138f3b71d..32a1741dc0 100644 --- a/Libraries/LibJS/Runtime/ScriptFunction.cpp +++ b/Libraries/LibJS/Runtime/ScriptFunction.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -99,10 +100,19 @@ Value ScriptFunction::call(Interpreter& interpreter) for (size_t i = 0; i < m_parameters.size(); ++i) { auto parameter = parameters()[i]; auto value = js_undefined(); - if (i < argument_values.size() && !argument_values[i].is_undefined()) { - value = argument_values[i]; - } else if (parameter.default_value) { - value = parameter.default_value->execute(interpreter); + if (parameter.is_rest) { + auto* array = Array::create(interpreter.global_object()); + for (size_t rest_index = i; rest_index < argument_values.size(); ++rest_index) + array->elements().append(argument_values[rest_index]); + value = Value(array); + } else { + if (i < argument_values.size() && !argument_values[i].is_undefined()) { + value = argument_values[i]; + } else if (parameter.default_value) { + value = parameter.default_value->execute(interpreter); + if (interpreter.exception()) + return {}; + } } arguments.append({ parameter.name, value }); interpreter.current_environment()->set(parameter.name, { value, DeclarationKind::Var }); diff --git a/Libraries/LibJS/Tests/function-rest-params.js b/Libraries/LibJS/Tests/function-rest-params.js new file mode 100644 index 0000000000..c1bc9fd422 --- /dev/null +++ b/Libraries/LibJS/Tests/function-rest-params.js @@ -0,0 +1,57 @@ +load("test-common.js"); + +try { + function foo(...a) { + assert(a instanceof Array); + assert(a.length === 0); + } + foo(); + + function foo(...a) { + assert(a instanceof Array); + assert(a.length === 4); + assert(a[0] === "foo"); + assert(a[1] === 123); + assert(a[2] === undefined); + assert(a[3].foo === "bar"); + } + foo("foo", 123, undefined, { foo: "bar" }); + + function foo(a, b, ...c) { + assert(a === "foo"); + assert(b === 123); + assert(c instanceof Array); + assert(c.length === 0); + } + foo("foo", 123); + + function foo(a, b, ...c) { + assert(a === "foo"); + assert(b === 123); + assert(c instanceof Array); + assert(c.length === 2); + assert(c[0] === undefined); + assert(c[1].foo === "bar"); + } + foo("foo", 123, undefined, { foo: "bar" }); + + var foo = (...a) => { + assert(a instanceof Array); + assert(a.length === 0); + }; + foo(); + + var foo = (a, b, ...c) => { + assert(a === "foo"); + assert(b === 123); + assert(c instanceof Array); + assert(c.length === 2); + assert(c[0] === undefined); + assert(c[1].foo === "bar"); + }; + foo("foo", 123, undefined, { foo: "bar" }); + + console.log("PASS"); +} catch (e) { + console.log("FAIL: " + e); +}