LibJS: Implement rest parameters
This commit is contained in:
parent
9df71afdb3
commit
454c1e6bbe
Notes:
sideshowbarker
2024-07-19 06:59:12 +09:00
Author: https://github.com/linusg Commit: https://github.com/SerenityOS/serenity/commit/454c1e6bbe4 Pull-request: https://github.com/SerenityOS/serenity/pull/2099 Reviewed-by: https://github.com/awesomekling
5 changed files with 98 additions and 10 deletions
|
@ -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()) {
|
||||
|
|
|
@ -158,6 +158,7 @@ public:
|
|||
struct Parameter {
|
||||
FlyString name;
|
||||
RefPtr<Expression> default_value;
|
||||
bool is_rest { false };
|
||||
};
|
||||
|
||||
const FlyString& name() const { return m_name; }
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 });
|
||||
|
|
57
Libraries/LibJS/Tests/function-rest-params.js
Normal file
57
Libraries/LibJS/Tests/function-rest-params.js
Normal 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);
|
||||
}
|
Loading…
Add table
Reference in a new issue