LibJS: Implement rest parameters

This commit is contained in:
Linus Groh 2020-05-04 16:05:13 +01:00 committed by Andreas Kling
parent 9df71afdb3
commit 454c1e6bbe
Notes: sideshowbarker 2024-07-19 06:59:12 +09:00
5 changed files with 98 additions and 10 deletions

View file

@ -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()) {

View file

@ -158,6 +158,7 @@ public:
struct Parameter {
FlyString name;
RefPtr<Expression> default_value;
bool is_rest { false };
};
const FlyString& name() const { return m_name; }

View file

@ -282,8 +282,13 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
Vector<FunctionNode::Parameter> 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<FunctionExpression> 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<Expression> 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<FunctionNodeType> Parser::parse_function_node(bool needs_function_
}
consume(TokenType::ParenOpen);
Vector<FunctionNode::Parameter> 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<Expression> default_value;
if (match(TokenType::Equals)) {
@ -826,9 +846,8 @@ NonnullRefPtr<FunctionNodeType> 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);

View file

@ -27,6 +27,7 @@
#include <AK/Function.h>
#include <LibJS/AST.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/ScriptFunction.h>
@ -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 });

View file

@ -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);
}