Source/JavaScriptCore: Implement ES6 spread operator
authoroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 17 Oct 2013 01:02:34 +0000 (01:02 +0000)
committeroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 17 Oct 2013 01:02:34 +0000 (01:02 +0000)
https://bugs.webkit.org/show_bug.cgi?id=122911

Reviewed by Michael Saboff.

Implement the ES6 spread operator

This has a little bit of refactoring to move the enumeration logic out ForOfNode
and into BytecodeGenerator, and then adds the logic to make it nicely callback
driven.

The rest of the logic is just the addition of the SpreadExpressionNode, the parsing,
and actually handling the spread.

* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitNewArray):
(JSC::BytecodeGenerator::emitCall):
(JSC::BytecodeGenerator::emitEnumeration):
* bytecompiler/BytecodeGenerator.h:
* bytecompiler/NodesCodegen.cpp:
(JSC::ArrayNode::emitBytecode):
(JSC::ForOfNode::emitBytecode):
(JSC::SpreadExpressionNode::emitBytecode):
* parser/ASTBuilder.h:
(JSC::ASTBuilder::createSpreadExpression):
* parser/Lexer.cpp:
(JSC::::lex):
* parser/NodeConstructors.h:
(JSC::SpreadExpressionNode::SpreadExpressionNode):
* parser/Nodes.h:
(JSC::ExpressionNode::isSpreadExpression):
(JSC::SpreadExpressionNode::expression):
* parser/Parser.cpp:
(JSC::::parseArrayLiteral):
(JSC::::parseArguments):
(JSC::::parseMemberExpression):
* parser/Parser.h:
(JSC::Parser::getTokenName):
(JSC::Parser::updateErrorMessageSpecialCase):
* parser/ParserTokens.h:
* parser/SyntaxChecker.h:
(JSC::SyntaxChecker::createSpreadExpression):

LayoutTests: Implement spread
https://bugs.webkit.org/show_bug.cgi?id=122911

Reviewed by Michael Saboff.

Add testcases

* js/basic-spread-expected.txt: Added.
* js/basic-spread.html: Added.
* js/parser-syntax-check-expected.txt:
* js/script-tests/basic-spread.js: Added.
(f):
(o.f.o.f.o.f.o.f.h.eval.o.h.o.h.o.h.o.h.g):
* js/script-tests/parser-syntax-check.js:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@157545 268f45cc-cd09-0410-ab3c-d52691b4dbfc

19 files changed:
LayoutTests/ChangeLog
LayoutTests/js/basic-spread-expected.txt [new file with mode: 0644]
LayoutTests/js/basic-spread.html [new file with mode: 0644]
LayoutTests/js/parser-syntax-check-expected.txt
LayoutTests/js/script-tests/basic-spread.js [new file with mode: 0644]
LayoutTests/js/script-tests/parser-syntax-check.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/Configurations/JavaScriptCore.xcconfig
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Source/JavaScriptCore/parser/ASTBuilder.h
Source/JavaScriptCore/parser/Lexer.cpp
Source/JavaScriptCore/parser/NodeConstructors.h
Source/JavaScriptCore/parser/Nodes.h
Source/JavaScriptCore/parser/Parser.cpp
Source/JavaScriptCore/parser/Parser.h
Source/JavaScriptCore/parser/ParserTokens.h
Source/JavaScriptCore/parser/SyntaxChecker.h

index f7822c1..360bc1b 100644 (file)
@@ -1,3 +1,20 @@
+2013-10-16  Oliver Hunt  <oliver@apple.com>
+
+        Implement spread
+        https://bugs.webkit.org/show_bug.cgi?id=122911
+
+        Reviewed by Michael Saboff.
+
+        Add testcases
+
+        * js/basic-spread-expected.txt: Added.
+        * js/basic-spread.html: Added.
+        * js/parser-syntax-check-expected.txt:
+        * js/script-tests/basic-spread.js: Added.
+        (f):
+        (o.f.o.f.o.f.o.f.h.eval.o.h.o.h.o.h.o.h.g):
+        * js/script-tests/parser-syntax-check.js:
+
 2013-10-16  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r157529.
diff --git a/LayoutTests/js/basic-spread-expected.txt b/LayoutTests/js/basic-spread-expected.txt
new file mode 100644 (file)
index 0000000..98b774c
--- /dev/null
@@ -0,0 +1,99 @@
+This test checks the behavior of the spread construct.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS passedThis is o
+PASS args[0] is 1
+PASS args[1] is undefined
+PASS args[2] is null
+PASS args[3] is 4
+PASS a is [1,2,3]
+PASS [...a] is [1,2,3]
+PASS [...a] is [1,2,3]
+PASS [...a,...[...a]] is [1,2,3,1,2,3]
+PASS [,,,...a] is [,,,1,2,3]
+PASS [...a,,,].join('|') is [1,2,3,,,].join('|')
+PASS [,...a,4] is [,1,2,3,4]
+PASS [,...a,,5] is [,1,2,3,,5]
+PASS [...a.keys()] is [0,1,2]
+PASS [...a.entries()].join('|') is [[0,1],[1,2],[2,3]].join('|')
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/basic-spread.html b/LayoutTests/js/basic-spread.html
new file mode 100644 (file)
index 0000000..3b42ca3
--- /dev/null
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src="../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script src="script-tests/basic-spread.js"></script>
+<script src="../resources/js-test-post.js"></script>
+</body>
+</html>
index 7c1be54..0bcc1ea 100644 (file)
@@ -631,6 +631,53 @@ PASS Invalid: "for (of in){}"
 PASS Invalid: "function f() { for (of in){} }"
 PASS Invalid: "for (var of in){}"
 PASS Invalid: "function f() { for (var of in){} }"
+spread operator
+PASS Valid:   "foo(...bar)"
+PASS Valid:   "function f() { foo(...bar) }"
+PASS Valid:   "o.foo(...bar)"
+PASS Valid:   "function f() { o.foo(...bar) }"
+PASS Valid:   "o[foo](...bar)"
+PASS Valid:   "function f() { o[foo](...bar) }"
+PASS Invalid: "new foo(...bar)"
+PASS Invalid: "function f() { new foo(...bar) }"
+PASS Invalid: "new o.foo(...bar)"
+PASS Invalid: "function f() { new o.foo(...bar) }"
+PASS Invalid: "new o[foo](...bar)"
+PASS Invalid: "function f() { new o[foo](...bar) }"
+PASS Invalid: "foo(...)"
+PASS Invalid: "function f() { foo(...) }"
+PASS Invalid: "o.foo(...)"
+PASS Invalid: "function f() { o.foo(...) }"
+PASS Invalid: "o[foo](...)"
+PASS Invalid: "function f() { o[foo](...) }"
+PASS Invalid: "foo(bar...)"
+PASS Invalid: "function f() { foo(bar...) }"
+PASS Invalid: "o.foo(bar...)"
+PASS Invalid: "function f() { o.foo(bar...) }"
+PASS Invalid: "o[foo](bar...)"
+PASS Invalid: "function f() { o[foo](bar...) }"
+PASS Invalid: "foo(a,...bar)"
+PASS Invalid: "function f() { foo(a,...bar) }"
+PASS Invalid: "o.foo(a,...bar)"
+PASS Invalid: "function f() { o.foo(a,...bar) }"
+PASS Invalid: "o[foo](a,...bar)"
+PASS Invalid: "function f() { o[foo](a,...bar) }"
+PASS Invalid: "foo(...bar, a)"
+PASS Invalid: "function f() { foo(...bar, a) }"
+PASS Invalid: "o.foo(...bar, a)"
+PASS Invalid: "function f() { o.foo(...bar, a) }"
+PASS Invalid: "o[foo](...bar, a)"
+PASS Invalid: "function f() { o[foo](...bar, a) }"
+PASS Valid:   "[...bar]"
+PASS Valid:   "function f() { [...bar] }"
+PASS Valid:   "[a, ...bar]"
+PASS Valid:   "function f() { [a, ...bar] }"
+PASS Valid:   "[...bar, a]"
+PASS Valid:   "function f() { [...bar, a] }"
+PASS Valid:   "[...bar,,,,]"
+PASS Valid:   "function f() { [...bar,,,,] }"
+PASS Valid:   "[,,,,...bar]"
+PASS Valid:   "function f() { [,,,,...bar] }"
 PASS e.line is 1
 PASS foo is 'PASS'
 PASS bar is 'PASS'
diff --git a/LayoutTests/js/script-tests/basic-spread.js b/LayoutTests/js/script-tests/basic-spread.js
new file mode 100644 (file)
index 0000000..19e1062
--- /dev/null
@@ -0,0 +1,61 @@
+description(
+"This test checks the behavior of the spread construct."
+);
+
+function f(a,b,c,d)
+{
+    args = arguments;
+    passedThis = this;
+    shouldBe("passedThis", "o")
+    shouldBe("args[0]", "1")
+    shouldBe("args[1]", "undefined")
+    shouldBe("args[2]", "null")
+    shouldBe("args[3]", "4")
+}
+
+var o = {}
+o.f = f;
+var test1 = [1, undefined, null, 4]
+var test2 = [1, , null, 4]
+var test3 = {length: 4, 0: 1, 2: null, 3: 4}
+var test4 = {length: 4, 0: 1, 1: undefined, 2: null, 3: 4}
+o.f(...test1)
+o.f(...test2)
+o.f(...test3)
+o.f(...test4)
+
+var h=eval('"f"')
+o[h](...test1)
+o[h](...test2)
+o[h](...test3)
+o[h](...test4)
+
+function g()
+{
+    o.f(...arguments)
+}
+
+g.apply(null, test1)
+g.apply(null, test2)
+g.apply(null, test3)
+g.apply(null, test4)
+
+g(...test1)
+g(...test2)
+g(...test3)
+g(...test4)
+
+var a=[1,2,3]
+
+shouldBe("a", "[1,2,3]")
+shouldBe("[...a]", "[1,2,3]")
+a=[...a]
+shouldBe("[...a]", "[1,2,3]")
+shouldBe("[...a,...[...a]]", "[1,2,3,1,2,3]")
+shouldBe("[,,,...a]", "[,,,1,2,3]")
+shouldBe("[...a,,,].join('|')", "[1,2,3,,,].join('|')")
+shouldBe("[,...a,4]", "[,1,2,3,4]")
+shouldBe("[,...a,,5]", "[,1,2,3,,5]")
+shouldBe("[...a.keys()]", "[0,1,2]")
+shouldBe("[...a.entries()].join('|')", "[[0,1],[1,2],[2,3]].join('|')")
+
index 36cf9e9..8d309c3 100644 (file)
@@ -398,6 +398,32 @@ invalid("for (of of in of){}")
 invalid("for (of in){}")
 invalid("for (var of in){}")
 
+debug("spread operator")
+valid("foo(...bar)")
+valid("o.foo(...bar)")
+valid("o[foo](...bar)")
+invalid("new foo(...bar)")
+invalid("new o.foo(...bar)")
+invalid("new o[foo](...bar)")
+invalid("foo(...)")
+invalid("o.foo(...)")
+invalid("o[foo](...)")
+invalid("foo(bar...)")
+invalid("o.foo(bar...)")
+invalid("o[foo](bar...)")
+invalid("foo(a,...bar)")
+invalid("o.foo(a,...bar)")
+invalid("o[foo](a,...bar)")
+invalid("foo(...bar, a)")
+invalid("o.foo(...bar, a)")
+invalid("o[foo](...bar, a)")
+valid("[...bar]")
+valid("[a, ...bar]")
+valid("[...bar, a]")
+valid("[...bar,,,,]")
+valid("[,,,,...bar]")
+
+
 
 
 try { eval("a.b.c = {};"); } catch(e1) { e=e1; shouldBe("e.line", "1") }
index 3c0f2e6..aa1d755 100644 (file)
@@ -1,3 +1,48 @@
+2013-10-16  Oliver Hunt  <oliver@apple.com>
+
+        Implement ES6 spread operator
+        https://bugs.webkit.org/show_bug.cgi?id=122911
+
+        Reviewed by Michael Saboff.
+
+        Implement the ES6 spread operator
+
+        This has a little bit of refactoring to move the enumeration logic out ForOfNode
+        and into BytecodeGenerator, and then adds the logic to make it nicely callback
+        driven.
+
+        The rest of the logic is just the addition of the SpreadExpressionNode, the parsing,
+        and actually handling the spread.
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::emitNewArray):
+        (JSC::BytecodeGenerator::emitCall):
+        (JSC::BytecodeGenerator::emitEnumeration):
+        * bytecompiler/BytecodeGenerator.h:
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::ArrayNode::emitBytecode):
+        (JSC::ForOfNode::emitBytecode):
+        (JSC::SpreadExpressionNode::emitBytecode):
+        * parser/ASTBuilder.h:
+        (JSC::ASTBuilder::createSpreadExpression):
+        * parser/Lexer.cpp:
+        (JSC::::lex):
+        * parser/NodeConstructors.h:
+        (JSC::SpreadExpressionNode::SpreadExpressionNode):
+        * parser/Nodes.h:
+        (JSC::ExpressionNode::isSpreadExpression):
+        (JSC::SpreadExpressionNode::expression):
+        * parser/Parser.cpp:
+        (JSC::::parseArrayLiteral):
+        (JSC::::parseArguments):
+        (JSC::::parseMemberExpression):
+        * parser/Parser.h:
+        (JSC::Parser::getTokenName):
+        (JSC::Parser::updateErrorMessageSpecialCase):
+        * parser/ParserTokens.h:
+        * parser/SyntaxChecker.h:
+        (JSC::SyntaxChecker::createSpreadExpression):
+
 2013-10-16  Filip Pizlo  <fpizlo@apple.com>
 
         Add a useLLInt option to jsc
index aef1910..7c12eec 100644 (file)
@@ -34,7 +34,7 @@ JSVALUE_MODEL_ppc = 32_64;
 JSVALUE_MODEL_x86_64 = 64;
 
 // Prevent C++ standard library operator new, delete and their related exception types from being exported as weak symbols.
-OTHER_LDFLAGS_HIDE_SYMBOLS = -Wl,-unexported_symbol -Wl,__ZTISt9bad_alloc -Wl,-unexported_symbol -Wl,__ZTISt9exception -Wl,-unexported_symbol -Wl,__ZTSSt9bad_alloc -Wl,-unexported_symbol -Wl,__ZTSSt9exception -Wl,-unexported_symbol -Wl,__ZdlPvS_ -Wl,-unexported_symbol -Wl,__ZnwmPv -Wl,-unexported_symbol -Wl,__ZNKSt3__18functionIFvvEEclEv -Wl,-unexported_symbol -Wl,__ZNSt3__18functionIFvvEEC1EOS2_ -Wl,-unexported_symbol -Wl,__ZNSt3__18functionIFvvEEC2EOS2_ -Wl,-unexported_symbol -Wl,__ZNSt3__18functionIFvvEED1Ev -Wl,-unexported_symbol -Wl,__ZNSt3__18functionIFvvEED2Ev -Wl,-all_load;
+OTHER_LDFLAGS_HIDE_SYMBOLS = -Wl,-unexported_symbol -Wl,__ZTISt9bad_alloc -Wl,-unexported_symbol -Wl,__ZTISt9exception -Wl,-unexported_symbol -Wl,__ZTSSt9bad_alloc -Wl,-unexported_symbol -Wl,__ZTSSt9exception -Wl,-unexported_symbol -Wl,__ZdlPvS_ -Wl,-unexported_symbol -Wl,__ZnwmPv -Wl,-unexported_symbol -Wl,__ZNKSt3__18functionIFvvEEclEv -Wl,-unexported_symbol -Wl,__ZNSt3__18functionIFvvEEC1EOS2_ -Wl,-unexported_symbol -Wl,__ZNSt3__18functionIFvvEEC2EOS2_ -Wl,-unexported_symbol -Wl,__ZNKSt3__18functionIFvRN3JSC17BytecodeGeneratorEPNS1_10RegisterIDEEEclES3_S5_ -Wl,-unexported_symbol -Wl,__ZNSt3__18functionIFvRN3JSC17BytecodeGeneratorEPNS1_10RegisterIDEEED1Ev -Wl,-unexported_symbol -Wl,__ZNSt3__18functionIFvRN3JSC17BytecodeGeneratorEPNS1_10RegisterIDEEED2Ev -Wl,-unexported_symbol -Wl,__ZNSt3__18functionIFvvEED1Ev -Wl,-unexported_symbol -Wl,__ZNSt3__18functionIFvvEED2Ev -Wl,-all_load;
 
 OTHER_LDFLAGS_BASE = -lobjc -Wl,-Y,3 $(OTHER_LDFLAGS_HIDE_SYMBOLS);
 OTHER_LDFLAGS = $(inherited) $(OTHER_LDFLAGS_$(PLATFORM_NAME));
index b4a55e7..ad4c2cf 100644 (file)
@@ -1515,13 +1515,16 @@ RegisterID* BytecodeGenerator::emitNewArray(RegisterID* dst, ElementNode* elemen
 
     Vector<RefPtr<RegisterID>, 16, UnsafeVectorOverflow> argv;
     for (ElementNode* n = elements; n; n = n->next()) {
-        if (n->elision())
+        if (!length)
             break;
+        length--;
+        ASSERT(!n->value()->isSpreadExpression());
         argv.append(newTemporary());
         // op_new_array requires the initial values to be a sequential range of registers
         ASSERT(argv.size() == 1 || argv[argv.size() - 1]->index() == argv[argv.size() - 2]->index() - 1);
         emitNode(argv.last().get(), n->value());
     }
+    ASSERT(!length);
     emitOpcode(op_new_array);
     instructions().append(dst->index());
     instructions().append(argv.size() ? argv[0]->index() : 0); // argv
@@ -1694,7 +1697,14 @@ RegisterID* BytecodeGenerator::emitCall(OpcodeID opcodeID, RegisterID* dst, Regi
     // Generate code for arguments.
     unsigned argument = 0;
     if (callArguments.argumentsNode()) {
-        for (ArgumentListNode* n = callArguments.argumentsNode()->m_listNode; n; n = n->m_next)
+        ArgumentListNode* n = callArguments.argumentsNode()->m_listNode;
+        if (n && n->m_expr->isSpreadExpression()) {
+            RELEASE_ASSERT(!n->m_next);
+            auto expression = static_cast<SpreadExpressionNode*>(n->m_expr)->expression();
+            expression->emitBytecode(*this, callArguments.argumentRegister(0));
+            return emitCallVarargs(dst, func, callArguments.thisRegister(), callArguments.argumentRegister(0), newTemporary(), callArguments.profileHookRegister(), divot, divotStart, divotEnd);
+        }
+        for (; n; n = n->m_next)
             emitNode(callArguments.argumentRegister(argument++), n);
     }
     
@@ -2371,5 +2381,36 @@ void BytecodeGenerator::emitReadOnlyExceptionIfNeeded()
     instructions().append(addConstantValue(addStringConstant(Identifier(m_vm, StrictModeReadonlyPropertyWriteError)))->index());
     instructions().append(false);
 }
+    
+void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, ExpressionNode* subjectNode, const std::function<void(BytecodeGenerator&, RegisterID*)>& callBack)
+{
+    LabelScopePtr scope = newLabelScope(LabelScope::Loop);
+    RefPtr<RegisterID> subject = newTemporary();
+    emitNode(subject.get(), subjectNode);
+    RefPtr<RegisterID> iterator = emitGetById(newTemporary(), subject.get(), propertyNames().iteratorPrivateName);
+    {
+        CallArguments args(*this, 0);
+        emitMove(args.thisRegister(), subject.get());
+        emitCall(iterator.get(), iterator.get(), NoExpectedFunction, args, node->divot(), node->divotStart(), node->divotEnd());
+    }
+    RefPtr<RegisterID> iteratorNext = emitGetById(newTemporary(), iterator.get(), propertyNames().iteratorNextPrivateName);
+    RefPtr<RegisterID> value = newTemporary();
+    emitLoad(value.get(), jsUndefined());
+    
+    emitJump(scope->continueTarget());
+    
+    RefPtr<Label> loopStart = newLabel();
+    emitLabel(loopStart.get());
+    emitLoopHint();
+    callBack(*this, value.get());
+    emitLabel(scope->continueTarget());
+    CallArguments nextArguments(*this, 0, 1);
+    emitMove(nextArguments.thisRegister(), iterator.get());
+    emitMove(nextArguments.argumentRegister(0), value.get());
+    emitCall(value.get(), iteratorNext.get(), NoExpectedFunction, nextArguments, node->divot(), node->divotStart(), node->divotEnd());
+    RefPtr<RegisterID> result = newTemporary();
+    emitJumpIfFalse(emitEqualityOp(op_stricteq, result.get(), value.get(), emitLoad(0, JSValue(vm()->iterationTerminator.get()))), loopStart.get());
+    emitLabel(scope->breakTarget());
+}
 
 } // namespace JSC
index 38b9294..b0e9bd4 100644 (file)
 #include "StaticPropertyAnalyzer.h"
 #include "UnlinkedCodeBlock.h"
 #include "VMStackBounds.h"
+
+#include <functional>
+
 #include <wtf/PassRefPtr.h>
 #include <wtf/SegmentedVector.h>
 #include <wtf/Vector.h>
 
+
 namespace JSC {
 
     class Identifier;
@@ -362,8 +366,9 @@ namespace JSC {
         RegisterID* emitCall(RegisterID* dst, RegisterID* func, ExpectedFunction, CallArguments&, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);
         RegisterID* emitCallEval(RegisterID* dst, RegisterID* func, CallArguments&, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);
         RegisterID* emitCallVarargs(RegisterID* dst, RegisterID* func, RegisterID* thisRegister, RegisterID* arguments, RegisterID* firstFreeRegister, RegisterID* profileHookRegister, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);
-        RegisterID* emitLoadVarargs(RegisterID* argCountDst, RegisterID* thisRegister, RegisterID* args);
 
+        void emitEnumeration(ThrowableExpressionData* enumerationNode, ExpressionNode* subjectNode, const std::function<void(BytecodeGenerator&, RegisterID*)>& callBack);
+        
         RegisterID* emitReturn(RegisterID* src);
         RegisterID* emitEnd(RegisterID* src) { return emitUnaryNoDstOp(op_end, src); }
 
index e8010c1..dd37178 100644 (file)
@@ -168,7 +168,7 @@ RegisterID* ArrayNode::emitBytecode(BytecodeGenerator& generator, RegisterID* ds
     unsigned length = 0;
     ElementNode* firstPutElement;
     for (firstPutElement = m_element; firstPutElement; firstPutElement = firstPutElement->next()) {
-        if (firstPutElement->elision())
+        if (firstPutElement->elision() || firstPutElement->value()->isSpreadExpression())
             break;
         ++length;
     }
@@ -177,8 +177,10 @@ RegisterID* ArrayNode::emitBytecode(BytecodeGenerator& generator, RegisterID* ds
         return generator.emitNewArray(generator.finalDestination(dst), m_element, length);
 
     RefPtr<RegisterID> array = generator.emitNewArray(generator.tempDestination(dst), m_element, length);
-
-    for (ElementNode* n = firstPutElement; n; n = n->next()) {
+    ElementNode* n = firstPutElement;
+    for (; n; n = n->next()) {
+        if (n->value()->isSpreadExpression())
+            goto handleSpread;
         RegisterID* value = generator.emitNode(n->value());
         length += n->elision();
         generator.emitPutByIndex(array.get(), length++, value);
@@ -190,6 +192,31 @@ RegisterID* ArrayNode::emitBytecode(BytecodeGenerator& generator, RegisterID* ds
     }
 
     return generator.moveToDestinationIfNeeded(dst, array.get());
+    
+handleSpread:
+    RefPtr<RegisterID> index = generator.emitLoad(generator.newTemporary(), jsNumber(length));
+    auto spreader = [this, array, index](BytecodeGenerator& generator, RegisterID* value)
+    {
+        generator.emitPutByVal(array.get(), index.get(), value);
+        generator.emitInc(index.get());
+    };
+    for (; n; n = n->next()) {
+        if (n->elision())
+            generator.emitBinaryOp(op_add, index.get(), index.get(), generator.emitLoad(0, jsNumber(n->elision())), OperandTypes(ResultType::numberTypeIsInt32(), ResultType::numberTypeIsInt32()));
+        if (n->value()->isSpreadExpression()) {
+            SpreadExpressionNode* spread = static_cast<SpreadExpressionNode*>(n->value());
+            generator.emitEnumeration(spread, spread->expression(), spreader);
+        } else {
+            generator.emitPutByVal(array.get(), index.get(), generator.emitNode(n->value()));
+            generator.emitInc(index.get());
+        }
+    }
+    
+    if (m_elision) {
+        generator.emitBinaryOp(op_add, index.get(), index.get(), generator.emitLoad(0, jsNumber(m_elision)), OperandTypes(ResultType::numberTypeIsInt32(), ResultType::numberTypeIsInt32()));
+        generator.emitPutById(array.get(), generator.propertyNames().length, index.get());
+    }
+    return generator.moveToDestinationIfNeeded(dst, array.get());
 }
 
 bool ArrayNode::isSimpleArray() const
@@ -379,7 +406,7 @@ RegisterID* NewExprNode::emitBytecode(BytecodeGenerator& generator, RegisterID*
     return generator.emitConstruct(returnValue.get(), func.get(), expectedFunction, callArguments, divot(), divotStart(), divotEnd());
 }
 
-inline CallArguments::CallArguments(BytecodeGenerator& generator, ArgumentsNode* argumentsNode, unsigned additionalArguments)
+CallArguments::CallArguments(BytecodeGenerator& generator, ArgumentsNode* argumentsNode, unsigned additionalArguments)
     : m_argumentsNode(argumentsNode)
 {
     if (generator.shouldEmitProfileHooks())
@@ -1760,77 +1787,51 @@ void ForInNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
 }
 
 // ------------------------------ ForOfNode ------------------------------------
-
 void ForOfNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
 {
-    LabelScopePtr scope = generator.newLabelScope(LabelScope::Loop);
-    
     if (!m_lexpr->isLocation()) {
         emitThrowReferenceError(generator, "Left side of for-of statement is not a reference.");
         return;
     }
     
+    LabelScopePtr scope = generator.newLabelScope(LabelScope::Loop);
+    
     generator.emitDebugHook(WillExecuteStatement, firstLine(), startOffset(), lineStartOffset());
-    RefPtr<RegisterID> subject = generator.emitNode(m_expr);
-    RefPtr<RegisterID> iterator = generator.emitGetById(generator.newTemporary(), subject.get(), generator.propertyNames().iteratorPrivateName);
+    auto extractor = [this, dst](BytecodeGenerator& generator, RegisterID* value)
     {
-        CallArguments args(generator, 0);
-        generator.emitMove(args.thisRegister(), subject.get());
-        generator.emitCall(iterator.get(), iterator.get(), NoExpectedFunction, args, divot(), divotStart(), divotEnd());
-    }
-    RefPtr<RegisterID> iteratorNext = generator.emitGetById(generator.newTemporary(), iterator.get(), generator.propertyNames().iteratorNextPrivateName);
-    RefPtr<RegisterID> value = generator.newTemporary();
-    generator.emitLoad(value.get(), jsUndefined());
-    
-    generator.emitJump(scope->continueTarget());
-    
-    RefPtr<Label> loopStart = generator.newLabel();
-    generator.emitLabel(loopStart.get());
-    generator.emitLoopHint();
-    
-    if (m_lexpr->isResolveNode()) {
-        const Identifier& ident = static_cast<ResolveNode*>(m_lexpr)->identifier();
-        if (Local local = generator.local(ident))
-            generator.emitMove(local.get(), value.get());
-        else {
-            if (generator.isStrictMode())
+        if (m_lexpr->isResolveNode()) {
+            const Identifier& ident = static_cast<ResolveNode*>(m_lexpr)->identifier();
+            if (Local local = generator.local(ident))
+                generator.emitMove(local.get(), value);
+            else {
+                if (generator.isStrictMode())
+                    generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
+                RegisterID* scope = generator.emitResolveScope(generator.newTemporary(), ident);
                 generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
-            RegisterID* scope = generator.emitResolveScope(generator.newTemporary(), ident);
-            generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
-            generator.emitPutToScope(scope, ident, value.get(), generator.isStrictMode() ? ThrowIfNotFound : DoNotThrowIfNotFound);
+                generator.emitPutToScope(scope, ident, value, generator.isStrictMode() ? ThrowIfNotFound : DoNotThrowIfNotFound);
+            }
+        } else if (m_lexpr->isDotAccessorNode()) {
+            DotAccessorNode* assignNode = static_cast<DotAccessorNode*>(m_lexpr);
+            const Identifier& ident = assignNode->identifier();
+            RefPtr<RegisterID> base = generator.emitNode(assignNode->base());
+            
+            generator.emitExpressionInfo(assignNode->divot(), assignNode->divotStart(), assignNode->divotEnd());
+            generator.emitPutById(base.get(), ident, value);
+        } else if (m_lexpr->isBracketAccessorNode()) {
+            BracketAccessorNode* assignNode = static_cast<BracketAccessorNode*>(m_lexpr);
+            RefPtr<RegisterID> base = generator.emitNode(assignNode->base());
+            RegisterID* subscript = generator.emitNode(assignNode->subscript());
+            
+            generator.emitExpressionInfo(assignNode->divot(), assignNode->divotStart(), assignNode->divotEnd());
+            generator.emitPutByVal(base.get(), subscript, value);
+        } else {
+            ASSERT(m_lexpr->isDeconstructionNode());
+            DeconstructingAssignmentNode* assignNode = static_cast<DeconstructingAssignmentNode*>(m_lexpr);
+            assignNode->bindings()->emitBytecode(generator, value);
         }
-    } else if (m_lexpr->isDotAccessorNode()) {
-        DotAccessorNode* assignNode = static_cast<DotAccessorNode*>(m_lexpr);
-        const Identifier& ident = assignNode->identifier();
-        RefPtr<RegisterID> base = generator.emitNode(assignNode->base());
-        
-        generator.emitExpressionInfo(assignNode->divot(), assignNode->divotStart(), assignNode->divotEnd());
-        generator.emitPutById(base.get(), ident, value.get());
-    } else if (m_lexpr->isBracketAccessorNode()) {
-        BracketAccessorNode* assignNode = static_cast<BracketAccessorNode*>(m_lexpr);
-        RefPtr<RegisterID> base = generator.emitNode(assignNode->base());
-        RegisterID* subscript = generator.emitNode(assignNode->subscript());
-        
-        generator.emitExpressionInfo(assignNode->divot(), assignNode->divotStart(), assignNode->divotEnd());
-        generator.emitPutByVal(base.get(), subscript, value.get());
-    } else {
-        ASSERT(m_lexpr->isDeconstructionNode());
-        DeconstructingAssignmentNode* assignNode = static_cast<DeconstructingAssignmentNode*>(m_lexpr);
-        assignNode->bindings()->emitBytecode(generator, value.get());
-    }
-    
-    generator.emitNode(dst, m_statement);
-    
-    generator.emitLabel(scope->continueTarget());
-    CallArguments nextArguments(generator, 0, 1);
-    generator.emitMove(nextArguments.thisRegister(), iterator.get());
-    generator.emitMove(nextArguments.argumentRegister(0), value.get());
-    generator.emitCall(value.get(), iteratorNext.get(), NoExpectedFunction, nextArguments, divot(), divotStart(), divotEnd());
-    RefPtr<RegisterID> result = generator.emitLoad(generator.newTemporary(), JSValue(generator.vm()->iterationTerminator.get()));
-    generator.emitJumpIfFalse(generator.emitEqualityOp(op_stricteq, result.get(), value.get(), result.get()), loopStart.get());
-    generator.emitLoad(value.get(), jsUndefined());
-    generator.emitDebugHook(WillExecuteStatement, firstLine(), startOffset(), lineStartOffset());
-    generator.emitLabel(scope->breakTarget());
+        generator.emitNode(dst, m_statement);
+    };
+    generator.emitEnumeration(this, m_expr, extractor);
 }
 
 // ------------------------------ ContinueNode ---------------------------------
@@ -2428,5 +2429,11 @@ void BindingNode::collectBoundIdentifiers(Vector<Identifier>& identifiers) const
 {
     identifiers.append(m_boundProperty);
 }
+    
+RegisterID* SpreadExpressionNode::emitBytecode(BytecodeGenerator&, RegisterID*)
+{
+    RELEASE_ASSERT_NOT_REACHED();
+    return 0;
+}
 
 } // namespace JSC
index 48a83ec..cfbd6d6 100644 (file)
@@ -230,6 +230,13 @@ public:
         return node;
     }
 
+    ExpressionNode* createSpreadExpression(const JSTokenLocation& location, ExpressionNode* expression, const JSTextPosition& start, const JSTextPosition& divot, const JSTextPosition& end)
+    {
+        auto node = new (m_vm) SpreadExpressionNode(location, expression);
+        setExceptionLocation(node, start, divot, end);
+        return node;
+    }
+
     ExpressionNode* createRegExp(const JSTokenLocation& location, const Identifier& pattern, const Identifier& flags, const JSTextPosition& start)
     {
         if (Yarr::checkSyntax(pattern.string()))
index fb62b97..2c43159 100644 (file)
@@ -1578,6 +1578,12 @@ start:
     case CharacterDot:
         shift();
         if (!isASCIIDigit(m_current)) {
+            if (UNLIKELY((m_current == '.') && (peek(1) == '.'))) {
+                shift();
+                shift();
+                token = DOTDOTDOT;
+                break;
+            }
             token = DOT;
             break;
         }
index 863aa9f..6c116f8 100644 (file)
@@ -202,6 +202,13 @@ inline ResolveNode::ResolveNode(const JSTokenLocation& location, const Identifie
         , m_ident(ident)
     {
     }
+    
+    
+    inline SpreadExpressionNode::SpreadExpressionNode(const JSTokenLocation& location, ExpressionNode* expression)
+        : ExpressionNode(location)
+        , m_expression(expression)
+    {
+    }
 
     inline ArgumentListNode::ArgumentListNode(const JSTokenLocation& location, ExpressionNode* expr)
         : ExpressionNode(location)
index 9fe2261..13a6c8a 100644 (file)
@@ -161,6 +161,7 @@ namespace JSC {
         virtual bool isAdd() const { return false; }
         virtual bool isSubtract() const { return false; }
         virtual bool isBoolean() const { return false; }
+        virtual bool isSpreadExpression() const { return false; }
 
         virtual void emitBytecodeInConditionContext(BytecodeGenerator&, Label*, Label*, FallThroughMode);
 
@@ -550,6 +551,19 @@ namespace JSC {
         const Identifier& m_ident;
     };
 
+    class SpreadExpressionNode : public ExpressionNode, public ThrowableExpressionData {
+    public:
+        SpreadExpressionNode(const JSTokenLocation&, ExpressionNode*);
+        
+        ExpressionNode* expression() const { return m_expression; }
+        
+    private:
+        virtual RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) OVERRIDE;
+        
+        virtual bool isSpreadExpression() const OVERRIDE { return true; }
+        ExpressionNode* m_expression;
+    };
+
     class ArgumentListNode : public ExpressionNode {
     public:
         ArgumentListNode(const JSTokenLocation&, ExpressionNode*);
index 4af2aa2..3347ff3 100644 (file)
@@ -1576,7 +1576,17 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseArrayLiteral
         return context.createArray(location, elisions);
     }
     
-    TreeExpression elem = parseAssignmentExpression(context);
+    TreeExpression elem;
+    if (UNLIKELY(match(DOTDOTDOT))) {
+        auto spreadLocation = m_token.m_location;
+        auto start = m_token.m_startPosition;
+        auto divot = m_token.m_endPosition;
+        next();
+        auto spreadExpr = parseAssignmentExpression(context);
+        failIfFalse(spreadExpr);
+        elem = context.createSpreadExpression(spreadLocation, spreadExpr, start, divot, m_lastTokenEndPosition);
+    } else
+        elem = parseAssignmentExpression(context);
     failIfFalse(elem);
     typename TreeBuilder::ElementList elementList = context.createElementList(elisions, elem);
     typename TreeBuilder::ElementList tail = elementList;
@@ -1595,6 +1605,17 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseArrayLiteral
             next(TreeBuilder::DontBuildStrings);
             return context.createArray(location, elisions, elementList);
         }
+        if (UNLIKELY(match(DOTDOTDOT))) {
+            auto spreadLocation = m_token.m_location;
+            auto start = m_token.m_startPosition;
+            auto divot = m_token.m_endPosition;
+            next();
+            TreeExpression elem = parseAssignmentExpression(context);
+            failIfFalse(elem);
+            auto spread = context.createSpreadExpression(spreadLocation, elem, start, divot, m_lastTokenEndPosition);
+            tail = context.createElementList(tail, elisions, spread);
+            continue;
+        }
         TreeExpression elem = parseAssignmentExpression(context);
         failIfFalse(elem);
         tail = context.createElementList(tail, elisions, elem);
@@ -1695,7 +1716,7 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parsePrimaryExpre
 }
 
 template <typename LexerType>
-template <class TreeBuilder> TreeArguments Parser<LexerType>::parseArguments(TreeBuilder& context)
+template <class TreeBuilder> TreeArguments Parser<LexerType>::parseArguments(TreeBuilder& context, SpreadMode mode)
 {
     consumeOrFailWithFlags(OPENPAREN, TreeBuilder::DontBuildStrings);
     JSTokenLocation location(tokenLocation());
@@ -1703,6 +1724,20 @@ template <class TreeBuilder> TreeArguments Parser<LexerType>::parseArguments(Tre
         next(TreeBuilder::DontBuildStrings);
         return context.createArguments();
     }
+    if (match(DOTDOTDOT) && mode == AllowSpread) {
+        JSTokenLocation spreadLocation(tokenLocation());
+        auto start = m_token.m_startPosition;
+        auto divot = m_token.m_endPosition;
+        next();
+        auto spreadExpr = parseAssignmentExpression(context);
+        auto end = m_lastTokenEndPosition;
+        if (!spreadExpr)
+            failWithMessage("Invalid spread expression.");
+        consumeOrFail(CLOSEPAREN);
+        auto spread = context.createSpreadExpression(spreadLocation, spreadExpr, start, divot, end);
+        TreeArgumentsList argList = context.createArgumentsList(location, spread);
+        return context.createArguments(argList);
+    }
     TreeExpression firstArg = parseAssignmentExpression(context);
     failIfFalse(firstArg);
     
@@ -1769,12 +1804,12 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseMemberExpres
             if (newCount) {
                 newCount--;
                 JSTextPosition expressionEnd = lastTokenEndPosition();
-                TreeArguments arguments = parseArguments(context);
+                TreeArguments arguments = parseArguments(context, DontAllowSpread);
                 failIfFalse(arguments);
                 base = context.createNewExpr(location, base, arguments, expressionStart, expressionEnd, lastTokenEndPosition());
             } else {
                 JSTextPosition expressionEnd = lastTokenEndPosition();
-                TreeArguments arguments = parseArguments(context);
+                TreeArguments arguments = parseArguments(context, AllowSpread);
                 failIfFalse(arguments);
                 base = context.makeFunctionCallNode(location, base, arguments, expressionStart, expressionEnd, lastTokenEndPosition());
             }
index 1aad225..56ec2f8 100644 (file)
@@ -754,7 +754,9 @@ private:
             return "/";
         case MOD: 
             return "%";
-        case RETURN: 
+        case DOTDOTDOT:
+            return "...";
+        case RETURN:
         case RESERVED_IF_STRICT:
         case RESERVED: 
         case NUMBER:
@@ -836,6 +838,9 @@ private:
         case RETURN:
             m_errorMessage = ASCIILiteral("Return statements are only valid inside functions");
             return;
+        case DOTDOTDOT:
+            m_errorMessage = ASCIILiteral("Spread operator is not supported in this context");
+            return;
         default:
             RELEASE_ASSERT_NOT_REACHED();
             m_errorMessage = ASCIILiteral("internal error");
@@ -954,7 +959,8 @@ private:
     template <class TreeBuilder> ALWAYS_INLINE TreeExpression parseArrayLiteral(TreeBuilder&);
     template <class TreeBuilder> ALWAYS_INLINE TreeExpression parseObjectLiteral(TreeBuilder&);
     template <class TreeBuilder> ALWAYS_INLINE TreeExpression parseStrictObjectLiteral(TreeBuilder&);
-    template <class TreeBuilder> ALWAYS_INLINE TreeArguments parseArguments(TreeBuilder&);
+    enum SpreadMode { AllowSpread, DontAllowSpread };
+    template <class TreeBuilder> ALWAYS_INLINE TreeArguments parseArguments(TreeBuilder&, SpreadMode);
     template <bool strict, class TreeBuilder> ALWAYS_INLINE TreeProperty parseProperty(TreeBuilder&);
     template <class TreeBuilder> ALWAYS_INLINE TreeFunctionBody parseFunctionBody(TreeBuilder&);
     template <class TreeBuilder> ALWAYS_INLINE TreeFormalParameterList parseFormalParameters(TreeBuilder&);
index 898fe62..82a2663 100644 (file)
@@ -102,6 +102,7 @@ enum JSTokenType {
     MODEQUAL,
     XOREQUAL,
     OREQUAL,
+    DOTDOTDOT,
     LastUntaggedToken,
 
     // Begin tagged tokens
index 2d05ce7..12bf21e 100644 (file)
@@ -157,6 +157,7 @@ public:
     void setFunctionStart(int, int) { }
     int createArguments() { return 1; }
     int createArguments(int) { return 1; }
+    ExpressionType createSpreadExpression(const JSTokenLocation&, ExpressionType, int, int, int) { return 1; }
     int createArgumentsList(const JSTokenLocation&, int) { return 1; }
     int createArgumentsList(const JSTokenLocation&, int, int) { return 1; }
     template <bool complete> Property createProperty(const Identifier* name, int, PropertyNode::Type type)