mirror of
https://github.com/RGBCube/serenity
synced 2025-05-23 17:35:10 +00:00
LibJS: Add function call spreading
Adds support for the following syntax: myFunction(...x, ...[1, 2, 3], ...o.foo, ...'abcd')
This commit is contained in:
parent
8fe821fae2
commit
107ca2e4ba
4 changed files with 72 additions and 14 deletions
|
@ -137,12 +137,28 @@ Value CallExpression::execute(Interpreter& interpreter) const
|
||||||
arguments.values().append(function.bound_arguments());
|
arguments.values().append(function.bound_arguments());
|
||||||
|
|
||||||
for (size_t i = 0; i < m_arguments.size(); ++i) {
|
for (size_t i = 0; i < m_arguments.size(); ++i) {
|
||||||
auto value = m_arguments[i].execute(interpreter);
|
auto value = m_arguments[i].value->execute(interpreter);
|
||||||
if (interpreter.exception())
|
|
||||||
return {};
|
|
||||||
arguments.append(value);
|
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
|
if (m_arguments[i].is_spread) {
|
||||||
|
// FIXME: Support generic iterables
|
||||||
|
Vector<Value> iterables;
|
||||||
|
if (value.is_string()) {
|
||||||
|
for (auto ch : value.as_string().string())
|
||||||
|
iterables.append(Value(js_string(interpreter, String::format("%c", ch))));
|
||||||
|
} else if (value.is_object() && value.as_object().is_array()) {
|
||||||
|
iterables = static_cast<const Array&>(value.as_object()).elements();
|
||||||
|
} else if (value.is_object() && value.as_object().is_string_object()) {
|
||||||
|
for (auto ch : static_cast<const StringObject&>(value.as_object()).primitive_string().string())
|
||||||
|
iterables.append(Value(js_string(interpreter, String::format("%c", ch))));
|
||||||
|
} else {
|
||||||
|
interpreter.throw_exception<TypeError>(String::format("%s is not iterable", value.to_string()));
|
||||||
|
}
|
||||||
|
for (auto& value : iterables)
|
||||||
|
arguments.append(value);
|
||||||
|
} else {
|
||||||
|
arguments.append(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& call_frame = interpreter.push_call_frame();
|
auto& call_frame = interpreter.push_call_frame();
|
||||||
|
@ -657,7 +673,7 @@ void CallExpression::dump(int indent) const
|
||||||
printf("CallExpression %s\n", is_new_expression() ? "[new]" : "");
|
printf("CallExpression %s\n", is_new_expression() ? "[new]" : "");
|
||||||
m_callee->dump(indent + 1);
|
m_callee->dump(indent + 1);
|
||||||
for (auto& argument : m_arguments)
|
for (auto& argument : m_arguments)
|
||||||
argument.dump(indent + 1);
|
argument.value->dump(indent + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StringLiteral::dump(int indent) const
|
void StringLiteral::dump(int indent) const
|
||||||
|
|
|
@ -567,7 +567,12 @@ private:
|
||||||
|
|
||||||
class CallExpression : public Expression {
|
class CallExpression : public Expression {
|
||||||
public:
|
public:
|
||||||
CallExpression(NonnullRefPtr<Expression> callee, NonnullRefPtrVector<Expression> arguments = {})
|
struct Argument {
|
||||||
|
NonnullRefPtr<Expression> value;
|
||||||
|
bool is_spread;
|
||||||
|
};
|
||||||
|
|
||||||
|
CallExpression(NonnullRefPtr<Expression> callee, Vector<Argument> arguments = {})
|
||||||
: m_callee(move(callee))
|
: m_callee(move(callee))
|
||||||
, m_arguments(move(arguments))
|
, m_arguments(move(arguments))
|
||||||
{
|
{
|
||||||
|
@ -586,12 +591,12 @@ private:
|
||||||
ThisAndCallee compute_this_and_callee(Interpreter&) const;
|
ThisAndCallee compute_this_and_callee(Interpreter&) const;
|
||||||
|
|
||||||
NonnullRefPtr<Expression> m_callee;
|
NonnullRefPtr<Expression> m_callee;
|
||||||
const NonnullRefPtrVector<Expression> m_arguments;
|
const Vector<Argument> m_arguments;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NewExpression final : public CallExpression {
|
class NewExpression final : public CallExpression {
|
||||||
public:
|
public:
|
||||||
NewExpression(NonnullRefPtr<Expression> callee, NonnullRefPtrVector<Expression> arguments = {})
|
NewExpression(NonnullRefPtr<Expression> callee, Vector<Argument> arguments = {})
|
||||||
: CallExpression(move(callee), move(arguments))
|
: CallExpression(move(callee), move(arguments))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -777,10 +777,15 @@ NonnullRefPtr<CallExpression> Parser::parse_call_expression(NonnullRefPtr<Expres
|
||||||
{
|
{
|
||||||
consume(TokenType::ParenOpen);
|
consume(TokenType::ParenOpen);
|
||||||
|
|
||||||
NonnullRefPtrVector<Expression> arguments;
|
Vector<CallExpression::Argument> arguments;
|
||||||
|
|
||||||
while (match_expression()) {
|
while (match_expression() || match(TokenType::TripleDot)) {
|
||||||
arguments.append(parse_expression(0));
|
if (match(TokenType::TripleDot)) {
|
||||||
|
consume();
|
||||||
|
arguments.append({ parse_expression(0), true });
|
||||||
|
} else {
|
||||||
|
arguments.append({ parse_expression(0), false });
|
||||||
|
}
|
||||||
if (!match(TokenType::Comma))
|
if (!match(TokenType::Comma))
|
||||||
break;
|
break;
|
||||||
consume();
|
consume();
|
||||||
|
@ -798,12 +803,17 @@ NonnullRefPtr<NewExpression> Parser::parse_new_expression()
|
||||||
// FIXME: Support full expressions as the callee as well.
|
// FIXME: Support full expressions as the callee as well.
|
||||||
auto callee = create_ast_node<Identifier>(consume(TokenType::Identifier).value());
|
auto callee = create_ast_node<Identifier>(consume(TokenType::Identifier).value());
|
||||||
|
|
||||||
NonnullRefPtrVector<Expression> arguments;
|
Vector<CallExpression::Argument> arguments;
|
||||||
|
|
||||||
if (match(TokenType::ParenOpen)) {
|
if (match(TokenType::ParenOpen)) {
|
||||||
consume(TokenType::ParenOpen);
|
consume(TokenType::ParenOpen);
|
||||||
while (match_expression()) {
|
while (match_expression() || match(TokenType::TripleDot)) {
|
||||||
arguments.append(parse_expression(0));
|
if (match(TokenType::TripleDot)) {
|
||||||
|
consume();
|
||||||
|
arguments.append({ parse_expression(0), true });
|
||||||
|
} else {
|
||||||
|
arguments.append({ parse_expression(0), false });
|
||||||
|
}
|
||||||
if (!match(TokenType::Comma))
|
if (!match(TokenType::Comma))
|
||||||
break;
|
break;
|
||||||
consume();
|
consume();
|
||||||
|
|
27
Libraries/LibJS/Tests/function-spread.js
Normal file
27
Libraries/LibJS/Tests/function-spread.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sum = (a, b, c) => a + b + c;
|
||||||
|
const a = [1, 2, 3];
|
||||||
|
|
||||||
|
assert(sum(...a) === 6);
|
||||||
|
assert(sum(1, ...a) === 4);
|
||||||
|
assert(sum(...a, 10) === 6);
|
||||||
|
|
||||||
|
const foo = (a, b, c) => c;
|
||||||
|
|
||||||
|
const o = { bar: [1, 2, 3] };
|
||||||
|
assert(foo(...o.bar) === 3);
|
||||||
|
assert(foo(..."abc") === "c");
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
[...1];
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "1 is not iterable",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue