diff options
author | Matthew Olsson <matthewcolsson@gmail.com> | 2020-05-05 22:36:24 -0700 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-05-06 19:19:02 +0200 |
commit | 107ca2e4ba8f88f39f58c4a92d238ea642aa0d39 (patch) | |
tree | 2fee5756368b75095ed869eca804637d2434f30e | |
parent | 8fe821fae29e790f0a8fa0bc27d14547a316aaa3 (diff) | |
download | serenity-107ca2e4ba8f88f39f58c4a92d238ea642aa0d39.zip |
LibJS: Add function call spreading
Adds support for the following syntax:
myFunction(...x, ...[1, 2, 3], ...o.foo, ...'abcd')
-rw-r--r-- | Libraries/LibJS/AST.cpp | 26 | ||||
-rw-r--r-- | Libraries/LibJS/AST.h | 11 | ||||
-rw-r--r-- | Libraries/LibJS/Parser.cpp | 22 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/function-spread.js | 27 |
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); +} |