summaryrefslogtreecommitdiff
path: root/Libraries
diff options
context:
space:
mode:
authorMatthew Olsson <matthewcolsson@gmail.com>2020-05-05 22:36:24 -0700
committerAndreas Kling <kling@serenityos.org>2020-05-06 19:19:02 +0200
commit107ca2e4ba8f88f39f58c4a92d238ea642aa0d39 (patch)
tree2fee5756368b75095ed869eca804637d2434f30e /Libraries
parent8fe821fae29e790f0a8fa0bc27d14547a316aaa3 (diff)
downloadserenity-107ca2e4ba8f88f39f58c4a92d238ea642aa0d39.zip
LibJS: Add function call spreading
Adds support for the following syntax: myFunction(...x, ...[1, 2, 3], ...o.foo, ...'abcd')
Diffstat (limited to 'Libraries')
-rw-r--r--Libraries/LibJS/AST.cpp26
-rw-r--r--Libraries/LibJS/AST.h11
-rw-r--r--Libraries/LibJS/Parser.cpp22
-rw-r--r--Libraries/LibJS/Tests/function-spread.js27
4 files changed, 72 insertions, 14 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp
index 7c67f6097f..a3537577e9 100644
--- a/Libraries/LibJS/AST.cpp
+++ b/Libraries/LibJS/AST.cpp
@@ -137,12 +137,28 @@ Value CallExpression::execute(Interpreter& interpreter) const
arguments.values().append(function.bound_arguments());
for (size_t i = 0; i < m_arguments.size(); ++i) {
- auto value = m_arguments[i].execute(interpreter);
- if (interpreter.exception())
- return {};
- arguments.append(value);
+ auto value = m_arguments[i].value->execute(interpreter);
if (interpreter.exception())
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();
@@ -657,7 +673,7 @@ void CallExpression::dump(int indent) const
printf("CallExpression %s\n", is_new_expression() ? "[new]" : "");
m_callee->dump(indent + 1);
for (auto& argument : m_arguments)
- argument.dump(indent + 1);
+ argument.value->dump(indent + 1);
}
void StringLiteral::dump(int indent) const
diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h
index 92308db809..af753de830 100644
--- a/Libraries/LibJS/AST.h
+++ b/Libraries/LibJS/AST.h
@@ -567,7 +567,12 @@ private:
class CallExpression : public Expression {
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_arguments(move(arguments))
{
@@ -586,12 +591,12 @@ private:
ThisAndCallee compute_this_and_callee(Interpreter&) const;
NonnullRefPtr<Expression> m_callee;
- const NonnullRefPtrVector<Expression> m_arguments;
+ const Vector<Argument> m_arguments;
};
class NewExpression final : public CallExpression {
public:
- NewExpression(NonnullRefPtr<Expression> callee, NonnullRefPtrVector<Expression> arguments = {})
+ NewExpression(NonnullRefPtr<Expression> callee, Vector<Argument> arguments = {})
: CallExpression(move(callee), move(arguments))
{
}
diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp
index 309350c7a1..05568e7dd4 100644
--- a/Libraries/LibJS/Parser.cpp
+++ b/Libraries/LibJS/Parser.cpp
@@ -777,10 +777,15 @@ NonnullRefPtr<CallExpression> Parser::parse_call_expression(NonnullRefPtr<Expres
{
consume(TokenType::ParenOpen);
- NonnullRefPtrVector<Expression> arguments;
+ Vector<CallExpression::Argument> arguments;
- while (match_expression()) {
- arguments.append(parse_expression(0));
+ while (match_expression() || match(TokenType::TripleDot)) {
+ if (match(TokenType::TripleDot)) {
+ consume();
+ arguments.append({ parse_expression(0), true });
+ } else {
+ arguments.append({ parse_expression(0), false });
+ }
if (!match(TokenType::Comma))
break;
consume();
@@ -798,12 +803,17 @@ NonnullRefPtr<NewExpression> Parser::parse_new_expression()
// FIXME: Support full expressions as the callee as well.
auto callee = create_ast_node<Identifier>(consume(TokenType::Identifier).value());
- NonnullRefPtrVector<Expression> arguments;
+ Vector<CallExpression::Argument> arguments;
if (match(TokenType::ParenOpen)) {
consume(TokenType::ParenOpen);
- while (match_expression()) {
- arguments.append(parse_expression(0));
+ while (match_expression() || match(TokenType::TripleDot)) {
+ if (match(TokenType::TripleDot)) {
+ consume();
+ arguments.append({ parse_expression(0), true });
+ } else {
+ arguments.append({ parse_expression(0), false });
+ }
if (!match(TokenType::Comma))
break;
consume();
diff --git a/Libraries/LibJS/Tests/function-spread.js b/Libraries/LibJS/Tests/function-spread.js
new file mode 100644
index 0000000000..f7d7d2ea2f
--- /dev/null
+++ b/Libraries/LibJS/Tests/function-spread.js
@@ -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);
+}