diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 2633cb6da4..7b1b25e4a2 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -736,6 +736,17 @@ void Identifier::dump(int indent) const printf("Identifier \"%s\"\n", m_string.characters()); } +void SpreadExpression::dump(int indent) const +{ + ASTNode::dump(indent); + m_target->dump(indent + 1); +} + +Value SpreadExpression::execute(Interpreter& interpreter) const +{ + return m_target->execute(interpreter); +} + Value ThisExpression::execute(Interpreter& interpreter) const { return interpreter.this_value(); @@ -1085,8 +1096,27 @@ Value ArrayExpression::execute(Interpreter& interpreter) const auto value = Value(); if (element) { value = element->execute(interpreter); + if (interpreter.exception()) return {}; + + if (element->is_spread_expression()) { + if (!value.is_array()) { + interpreter.throw_exception(String::format("%s is not iterable", value.to_string().characters())); + return {}; + } + + auto& array_to_spread = static_cast(value.as_object()); + for (auto& it : array_to_spread.elements()) { + if (it.is_empty()) { + array->elements().append(js_undefined()); + } else { + array->elements().append(it); + } + } + + continue; + } } array->elements().append(value); } diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index bec3f2910a..54d6152a5d 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -55,6 +55,7 @@ public: virtual Value execute(Interpreter&) const = 0; virtual void dump(int indent) const; virtual bool is_identifier() const { return false; } + virtual bool is_spread_expression() const { return false; } virtual bool is_member_expression() const { return false; } virtual bool is_scope_node() const { return false; } virtual bool is_program() const { return false; } @@ -520,6 +521,23 @@ private: FlyString m_string; }; +class SpreadExpression final : public Expression { +public: + explicit SpreadExpression(NonnullRefPtr target) + : m_target(target) + { + } + + virtual Value execute(Interpreter&) const override; + virtual void dump(int indent) const override; + virtual bool is_spread_expression() const override { return true; } + +private: + virtual const char* class_name() const override { return "SpreadExpression"; } + + NonnullRefPtr m_target; +}; + class ThisExpression final : public Expression { public: virtual Value execute(Interpreter&) const override; diff --git a/Libraries/LibJS/Lexer.cpp b/Libraries/LibJS/Lexer.cpp index 79eadc4bbf..c59a8cd17e 100644 --- a/Libraries/LibJS/Lexer.cpp +++ b/Libraries/LibJS/Lexer.cpp @@ -84,6 +84,7 @@ Lexer::Lexer(StringView source) s_three_char_tokens.set("<<=", TokenType::ShiftLeftEquals); s_three_char_tokens.set(">>=", TokenType::ShiftRightEquals); s_three_char_tokens.set(">>>", TokenType::UnsignedShiftRight); + s_three_char_tokens.set("...", TokenType::TripleDot); } if (s_two_char_tokens.is_empty()) { diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index caf0e7da75..63e0a5bd95 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -492,10 +492,16 @@ NonnullRefPtr Parser::parse_array_expression() consume(TokenType::BracketOpen); Vector> elements; - while (match_expression() || match(TokenType::Comma)) { + while (match_expression() || match(TokenType::TripleDot) || match(TokenType::Comma)) { RefPtr expression; - if (match_expression()) + + if (match(TokenType::TripleDot)) { + consume(TokenType::TripleDot); + expression = create_ast_node(parse_expression(0)); + } else if (match_expression()) { expression = parse_expression(0); + } + elements.append(expression); if (!match(TokenType::Comma)) break; diff --git a/Libraries/LibJS/Tests/array-spread.js b/Libraries/LibJS/Tests/array-spread.js new file mode 100644 index 0000000000..470b8cb09d --- /dev/null +++ b/Libraries/LibJS/Tests/array-spread.js @@ -0,0 +1,43 @@ +load("test-common.js"); + +function testArray(arr) { + return arr.length === 4 && + arr[0] === 0 && + arr[1] === 1 && + arr[2] === 2 && + arr[3] === 3; +} + +try { + let arr = [0, ...[1, 2], 3]; + assert(testArray(arr)); + + let a = [1, 2]; + arr = [0, ...a, 3]; + assert(testArray(arr)); + + let obj = { a: [1, 2] }; + arr = [0, ...obj.a, 3]; + assert(testArray(arr)); + + arr = [...[], ...[...[0, 1, 2]], 3]; + assert(testArray(arr)); + + assertThrowsError(() => { + [...1]; + }, { + error: TypeError, + message: "1 is not iterable", + }); + + assertThrowsError(() => { + [...{}]; + }, { + error: TypeError, + message: "[object Object] is not iterable", + }); + + console.log("PASS"); +} catch (e) { + console.log("FAIL: " + e); +} diff --git a/Libraries/LibJS/Token.h b/Libraries/LibJS/Token.h index b301332649..f60a288ce1 100644 --- a/Libraries/LibJS/Token.h +++ b/Libraries/LibJS/Token.h @@ -115,6 +115,7 @@ namespace JS { __ENUMERATE_JS_TOKEN(This) \ __ENUMERATE_JS_TOKEN(Throw) \ __ENUMERATE_JS_TOKEN(Tilde) \ + __ENUMERATE_JS_TOKEN(TripleDot) \ __ENUMERATE_JS_TOKEN(Try) \ __ENUMERATE_JS_TOKEN(Typeof) \ __ENUMERATE_JS_TOKEN(UnsignedShiftRight) \