Selaa lähdekoodia

LibJS: Add function call spreading

Adds support for the following syntax:

    myFunction(...x, ...[1, 2, 3], ...o.foo, ...'abcd')
Matthew Olsson 5 vuotta sitten
vanhempi
commit
107ca2e4ba

+ 21 - 5
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

+ 8 - 3
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))
     {
     }

+ 16 - 6
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();

+ 27 - 0
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);
+}