mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 06:35:42 +00:00
Bug 666396 - Implemement yield*. r=jorendorff, r=Waldo
This commit is contained in:
parent
dfb080bf26
commit
f70b793cab
@ -273,6 +273,12 @@ EmitJump(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, ptrdiff_t off)
|
||||
return offset;
|
||||
}
|
||||
|
||||
static ptrdiff_t
|
||||
EmitCall(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, uint16_t argc)
|
||||
{
|
||||
return Emit3(cx, bce, op, ARGC_HI(argc), ARGC_LO(argc));
|
||||
}
|
||||
|
||||
/* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */
|
||||
const char js_with_statement_str[] = "with statement";
|
||||
const char js_finally_block_str[] = "finally block";
|
||||
@ -1642,10 +1648,10 @@ CheckSideEffects(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool
|
||||
|
||||
default:
|
||||
/*
|
||||
* All of PNK_INC, PNK_DEC, PNK_THROW, and PNK_YIELD have direct
|
||||
* effects. Of the remaining unary-arity node types, we can't
|
||||
* easily prove that the operand never denotes an object with a
|
||||
* toString or valueOf method.
|
||||
* All of PNK_INC, PNK_DEC, PNK_THROW, PNK_YIELD, and PNK_YIELD_STAR
|
||||
* have direct effects. Of the remaining unary-arity node types, we
|
||||
* can't easily prove that the operand never denotes an object with
|
||||
* a toString or valueOf method.
|
||||
*/
|
||||
*answer = true;
|
||||
return true;
|
||||
@ -4892,6 +4898,157 @@ EmitReturn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
EmitYieldStar(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *iter)
|
||||
{
|
||||
JS_ASSERT(bce->sc->isFunctionBox());
|
||||
JS_ASSERT(bce->sc->asFunctionBox()->isStarGenerator());
|
||||
|
||||
if (!EmitTree(cx, bce, iter)) // ITER
|
||||
return false;
|
||||
|
||||
int depth = bce->stackDepth;
|
||||
JS_ASSERT(depth >= 1);
|
||||
|
||||
// Initial send value is undefined.
|
||||
if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) // ITER RECEIVED
|
||||
return false;
|
||||
ptrdiff_t initialSend = -1;
|
||||
if (EmitBackPatchOp(cx, bce, &initialSend) < 0) // goto initialSend
|
||||
return false;
|
||||
|
||||
// Try prologue. // ITER RESULT
|
||||
StmtInfoBCE stmtInfo(cx);
|
||||
PushStatementBCE(bce, &stmtInfo, STMT_TRY, bce->offset());
|
||||
ptrdiff_t noteIndex = NewSrcNote(cx, bce, SRC_TRY);
|
||||
if (noteIndex < 0 || Emit1(cx, bce, JSOP_TRY) < 0)
|
||||
return false;
|
||||
ptrdiff_t tryStart = bce->offset(); // tryStart:
|
||||
JS_ASSERT(bce->stackDepth == depth + 1);
|
||||
|
||||
// Yield RESULT as-is, without re-boxing.
|
||||
if (Emit1(cx, bce, JSOP_YIELD) < 0) // ITER RECEIVED
|
||||
return false;
|
||||
|
||||
// Try epilogue.
|
||||
if (!SetSrcNoteOffset(cx, bce, noteIndex, 0, bce->offset() - tryStart + JSOP_TRY_LENGTH))
|
||||
return false;
|
||||
if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
|
||||
return false;
|
||||
ptrdiff_t subsequentSend = -1;
|
||||
if (EmitBackPatchOp(cx, bce, &subsequentSend) < 0) // goto subsequentSend
|
||||
return false;
|
||||
ptrdiff_t tryEnd = bce->offset(); // tryEnd:
|
||||
|
||||
// Catch location.
|
||||
// THROW? = 'throw' in ITER // ITER
|
||||
bce->stackDepth = (unsigned) depth;
|
||||
if (Emit1(cx, bce, JSOP_EXCEPTION) < 0) // ITER EXCEPTION
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_SWAP) < 0) // EXCEPTION ITER
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_DUP) < 0) // EXCEPTION ITER ITER
|
||||
return false;
|
||||
if (!EmitAtomOp(cx, cx->names().throw_, JSOP_STRING, bce)) // EXCEPTION ITER ITER "throw"
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_SWAP) < 0) // EXCEPTION ITER "throw" ITER
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_IN) < 0) // EXCEPTION ITER THROW?
|
||||
return false;
|
||||
// if (THROW?) goto delegate
|
||||
ptrdiff_t checkThrow = EmitJump(cx, bce, JSOP_IFNE, 0); // EXCEPTION ITER
|
||||
if (checkThrow < 0)
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_POP) < 0) // EXCEPTION
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_THROW) < 0) // throw EXCEPTION
|
||||
return false;
|
||||
|
||||
SetJumpOffsetAt(bce, checkThrow); // delegate:
|
||||
// RESULT = ITER.throw(EXCEPTION) // EXCEPTION ITER
|
||||
bce->stackDepth = (unsigned) depth + 1;
|
||||
if (Emit1(cx, bce, JSOP_DUP) < 0) // EXCEPTION ITER ITER
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_DUP) < 0) // EXCEPTION ITER ITER ITER
|
||||
return false;
|
||||
if (!EmitAtomOp(cx, cx->names().throw_, JSOP_CALLPROP, bce)) // EXCEPTION ITER ITER THROW
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_SWAP) < 0) // EXCEPTION ITER THROW ITER
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_NOTEARG) < 0) // EXCEPTION ITER THROW ITER
|
||||
return false;
|
||||
if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)3) < 0) // ITER THROW ITER EXCEPTION
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_NOTEARG) < 0) // ITER THROW ITER EXCEPTION
|
||||
return false;
|
||||
if (EmitCall(cx, bce, JSOP_CALL, 1) < 0) // ITER RESULT
|
||||
return false;
|
||||
JS_ASSERT(bce->stackDepth == depth + 1);
|
||||
ptrdiff_t checkResult = -1;
|
||||
if (EmitBackPatchOp(cx, bce, &checkResult) < 0) // goto checkResult
|
||||
return false;
|
||||
|
||||
// Catch epilogue.
|
||||
if (!PopStatementBCE(cx, bce))
|
||||
return false;
|
||||
// This is a peace offering to ReconstructPCStack. See the note in EmitTry.
|
||||
if (Emit1(cx, bce, JSOP_NOP) < 0)
|
||||
return false;
|
||||
if (!bce->tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd))
|
||||
return false;
|
||||
|
||||
// After the try/catch block: send the received value to the iterator.
|
||||
if (!BackPatch(cx, bce, initialSend, bce->code().end(), JSOP_GOTO)) // initialSend:
|
||||
return false;
|
||||
if (!BackPatch(cx, bce, subsequentSend, bce->code().end(), JSOP_GOTO)) // subsequentSend:
|
||||
return false;
|
||||
|
||||
// Send location.
|
||||
// result = iter.next(received) // ITER RECEIVED
|
||||
if (Emit1(cx, bce, JSOP_SWAP) < 0) // RECEIVED ITER
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_DUP) < 0) // RECEIVED ITER ITER
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_DUP) < 0) // RECEIVED ITER ITER ITER
|
||||
return false;
|
||||
if (!EmitAtomOp(cx, cx->names().next, JSOP_CALLPROP, bce)) // RECEIVED ITER ITER NEXT
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_SWAP) < 0) // RECEIVED ITER NEXT ITER
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_NOTEARG) < 0) // RECEIVED ITER NEXT ITER
|
||||
return false;
|
||||
if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)3) < 0) // ITER NEXT ITER RECEIVED
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_NOTEARG) < 0) // ITER NEXT ITER RECEIVED
|
||||
return false;
|
||||
if (EmitCall(cx, bce, JSOP_CALL, 1) < 0) // ITER RESULT
|
||||
return false;
|
||||
JS_ASSERT(bce->stackDepth == depth + 1);
|
||||
|
||||
if (!BackPatch(cx, bce, checkResult, bce->code().end(), JSOP_GOTO)) // checkResult:
|
||||
return false;
|
||||
// if (!result.done) goto tryStart; // ITER RESULT
|
||||
if (Emit1(cx, bce, JSOP_DUP) < 0) // ITER RESULT RESULT
|
||||
return false;
|
||||
if (!EmitAtomOp(cx, cx->names().done, JSOP_GETPROP, bce)) // ITER RESULT DONE
|
||||
return false;
|
||||
// if (!DONE) goto tryStart;
|
||||
if (EmitJump(cx, bce, JSOP_IFEQ, tryStart - bce->offset()) < 0) // ITER RESULT
|
||||
return false;
|
||||
|
||||
// result.value
|
||||
if (Emit1(cx, bce, JSOP_SWAP) < 0) // RESULT ITER
|
||||
return false;
|
||||
if (Emit1(cx, bce, JSOP_POP) < 0) // RESULT
|
||||
return false;
|
||||
if (!EmitAtomOp(cx, cx->names().value, JSOP_GETPROP, bce)) // VALUE
|
||||
return false;
|
||||
|
||||
JS_ASSERT(bce->stackDepth == depth);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
EmitStatementList(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
|
||||
{
|
||||
@ -5194,7 +5351,7 @@ EmitCallOrNew(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
||||
}
|
||||
|
||||
if (!spread) {
|
||||
if (Emit3(cx, bce, pn->getOp(), ARGC_HI(argc), ARGC_LO(argc)) < 0)
|
||||
if (EmitCall(cx, bce, pn->getOp(), argc) < 0)
|
||||
return false;
|
||||
} else {
|
||||
if (Emit1(cx, bce, pn->getOp()) < 0)
|
||||
@ -5889,6 +6046,10 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
||||
ok = EmitReturn(cx, bce, pn);
|
||||
break;
|
||||
|
||||
case PNK_YIELD_STAR:
|
||||
ok = EmitYieldStar(cx, bce, pn->pn_kid);
|
||||
break;
|
||||
|
||||
case PNK_YIELD:
|
||||
JS_ASSERT(bce->sc->isFunctionBox());
|
||||
if (bce->sc->asFunctionBox()->isStarGenerator()) {
|
||||
|
@ -125,6 +125,7 @@ class UpvarCookie
|
||||
F(THROW) \
|
||||
F(DEBUGGER) \
|
||||
F(YIELD) \
|
||||
F(YIELD_STAR) \
|
||||
F(GENEXP) \
|
||||
F(ARRAYCOMP) \
|
||||
F(ARRAYPUSH) \
|
||||
|
@ -4577,16 +4577,14 @@ Parser<ParseHandler>::yieldExpression()
|
||||
|
||||
pc->lastYieldOffset = begin;
|
||||
|
||||
bool isDelegatingYield = tokenStream.matchToken(TOK_MUL);
|
||||
ParseNodeKind kind = tokenStream.matchToken(TOK_MUL) ? PNK_YIELD_STAR : PNK_YIELD;
|
||||
|
||||
// ES6 generators require a value.
|
||||
Node exprNode = assignExpr();
|
||||
if (!exprNode)
|
||||
return null();
|
||||
|
||||
// FIXME: Plumb isDelegatingYield appropriately.
|
||||
(void) isDelegatingYield;
|
||||
return handler.newUnary(PNK_YIELD, JSOP_YIELD, begin, exprNode);
|
||||
return handler.newUnary(kind, JSOP_NOP, begin, exprNode);
|
||||
}
|
||||
|
||||
case NotGenerator:
|
||||
@ -4648,7 +4646,7 @@ Parser<ParseHandler>::yieldExpression()
|
||||
return null();
|
||||
}
|
||||
|
||||
return handler.newUnary(PNK_YIELD, JSOP_YIELD, begin, exprNode);
|
||||
return handler.newUnary(PNK_YIELD, JSOP_NOP, begin, exprNode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6094,7 +6092,7 @@ Parser<FullParseHandler>::generatorExpr(ParseNode *kid)
|
||||
ParseNode *pn = UnaryNode::create(PNK_YIELD, &handler);
|
||||
if (!pn)
|
||||
return null();
|
||||
pn->setOp(JSOP_YIELD);
|
||||
pn->setOp(JSOP_NOP);
|
||||
pn->setInParens(true);
|
||||
pn->pn_pos = kid->pn_pos;
|
||||
pn->pn_kid = kid;
|
||||
|
14
js/src/jit-test/lib/iteration.js
Normal file
14
js/src/jit-test/lib/iteration.js
Normal file
@ -0,0 +1,14 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
load(libdir + "asserts.js");
|
||||
|
||||
if (typeof assertIteratorResult === 'undefined') {
|
||||
var assertIteratorResult = function assertIteratorResult(result, value, done) {
|
||||
assertEq(typeof result, "object");
|
||||
assertDeepEq(result.value, value);
|
||||
assertDeepEq(result.done, done);
|
||||
}
|
||||
}
|
@ -46,3 +46,6 @@ check('m().next();', 'declarative', gw.makeDebuggeeValue(g.m));
|
||||
|
||||
g.eval('function n() { let (x = 1) { debugger; } }');
|
||||
check('n()', 'declarative', null);
|
||||
|
||||
g.eval('function* o() { debugger; yield true; }');
|
||||
check('o().next();', 'declarative', gw.makeDebuggeeValue(g.o));
|
||||
|
@ -5,10 +5,6 @@ var g = newGlobal();
|
||||
var dbg = new Debugger;
|
||||
var gw = dbg.addDebuggee(g);
|
||||
|
||||
g.eval('function f(x) { debugger; yield x; }');
|
||||
g.eval('var g = f(2);');
|
||||
g.eval('var h = f(3);');
|
||||
|
||||
function check(gen, label) {
|
||||
print("check(" + label + ")");
|
||||
var hits;
|
||||
@ -22,5 +18,14 @@ function check(gen, label) {
|
||||
assertEq(hits, 1);
|
||||
}
|
||||
|
||||
g.eval('function f(x) { debugger; yield x; }');
|
||||
g.eval('var g = f(2);');
|
||||
g.eval('var h = f(3);');
|
||||
check(g.g, 'g.g');
|
||||
check(g.h, 'g.h');
|
||||
|
||||
g.eval('function* f(x) { debugger; yield x; }');
|
||||
g.eval('var g = f(2);');
|
||||
g.eval('var h = f(3);');
|
||||
check(g.g, 'g.g');
|
||||
check(g.h, 'g.h');
|
||||
|
@ -29,3 +29,6 @@ test("new function () { eval('debugger'); }", {type: "eval", generator: false, c
|
||||
test("function gen() { debugger; yield 1; debugger; }\n" +
|
||||
"for (var x in gen()) {}\n",
|
||||
{type: "call", generator: true, constructing: false}, 2);
|
||||
test("var iter = (function* stargen() { debugger; yield 1; debugger; })();\n" +
|
||||
"iter.next(); iter.next();",
|
||||
{type: "call", generator: true, constructing: false}, 2);
|
||||
|
12
js/src/jit-test/tests/debug/Frame-eval-18.js
Normal file
12
js/src/jit-test/tests/debug/Frame-eval-18.js
Normal file
@ -0,0 +1,12 @@
|
||||
// yield is not allowed in eval in a star generator.
|
||||
|
||||
load(libdir + 'asserts.js');
|
||||
|
||||
var g = newGlobal();
|
||||
var dbg = new Debugger(g);
|
||||
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
assertThrowsInstanceOf(function() { frame.eval('yield 10;') }, SyntaxError);
|
||||
};
|
||||
|
||||
g.eval("(function*g(){ debugger; })()");
|
@ -0,0 +1,20 @@
|
||||
// Returning {throw:} from an onPop handler when yielding works and
|
||||
// does not close the generator-iterator.
|
||||
|
||||
load(libdir + "iteration.js");
|
||||
|
||||
var g = newGlobal();
|
||||
var dbg = new Debugger;
|
||||
var gw = dbg.addDebuggee(g);
|
||||
dbg.onDebuggerStatement = function handleDebugger(frame) {
|
||||
frame.onPop = function (c) {
|
||||
return {throw: "fit"};
|
||||
};
|
||||
};
|
||||
g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
|
||||
g.eval("var it = g();");
|
||||
var rv = gw.evalInGlobal("it.next();");
|
||||
assertEq(rv.throw, "fit");
|
||||
|
||||
dbg.enabled = false;
|
||||
assertIteratorResult(g.it.next(), 1, false);
|
@ -0,0 +1,19 @@
|
||||
// Throwing an exception from an onPop handler when yielding terminates the debuggee
|
||||
// but does not close the generator-iterator.
|
||||
|
||||
load(libdir + 'iteration.js')
|
||||
|
||||
var g = newGlobal();
|
||||
var dbg = new Debugger;
|
||||
var gw = dbg.addDebuggee(g);
|
||||
dbg.onDebuggerStatement = function handleDebugger(frame) {
|
||||
frame.onPop = function (c) {
|
||||
throw "fit";
|
||||
};
|
||||
};
|
||||
g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
|
||||
g.eval("var it = g();");
|
||||
assertEq(gw.evalInGlobal("it.next();"), null);
|
||||
|
||||
dbg.enabled = false;
|
||||
assertIteratorResult(g.it.next(), 1, false);
|
@ -0,0 +1,42 @@
|
||||
// Each resumption of an ES6 generator gets a fresh frame, whose onPop
|
||||
// handler fires the next time the generator yields. This is not the
|
||||
// behavior the spec requests, but it's what we do for the moment, and
|
||||
// it's good to check that at least we don't crash.
|
||||
|
||||
load(libdir + 'iteration.js');
|
||||
|
||||
var g = newGlobal();
|
||||
var dbg = new Debugger(g);
|
||||
var log;
|
||||
|
||||
var debuggerFrames = [];
|
||||
var poppedFrames = [];
|
||||
dbg.onDebuggerStatement = function handleDebugger(frame) {
|
||||
log += 'd';
|
||||
assertEq(frame.type, "call");
|
||||
|
||||
assertEq(debuggerFrames.indexOf(frame), -1);
|
||||
assertEq(poppedFrames.indexOf(frame), -1);
|
||||
debuggerFrames.push(frame);
|
||||
|
||||
if (frame.eval('i').return % 3 == 0) {
|
||||
frame.onPop = function handlePop(c) {
|
||||
log += ')' + c.return.value;
|
||||
assertEq(debuggerFrames.indexOf(this) != -1, true);
|
||||
assertEq(poppedFrames.indexOf(this), -1);
|
||||
poppedFrames.push(this);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }");
|
||||
log ='';
|
||||
g.eval("var t = 0, iter = g();");
|
||||
for (var j = 0; j < 10; j++)
|
||||
g.eval("t += iter.next().value;");
|
||||
assertIteratorResult(g.eval("iter.next()"), undefined, true);
|
||||
assertEq(g.eval("t"), 45);
|
||||
|
||||
// FIXME: Should equal this, but see bug 917809.
|
||||
// assertEq(log, "d)0ddd)3ddd)6ddd)9");
|
||||
assertEq(log, "d)undefinedddd)undefinedddd)undefinedddd)undefined");
|
@ -38,5 +38,7 @@ test("var obj = {get x() { return 0; }, set x(v) {}}; debugger;",
|
||||
"S[SS]");
|
||||
test("function r(n) { for (var i = 0; i < n; i++) yield i; } debugger;",
|
||||
"S[S]");
|
||||
test("function* qux(n) { for (var i = 0; i < n; i++) yield i; } debugger;",
|
||||
"S[S]");
|
||||
test("var it = (3 for (p in obj)); debugger;",
|
||||
"S[S]");
|
||||
|
40
js/src/jit-test/tests/debug/breakpoint-11.js
Normal file
40
js/src/jit-test/tests/debug/breakpoint-11.js
Normal file
@ -0,0 +1,40 @@
|
||||
// Setting a breakpoint in a generator function works, and we can
|
||||
// traverse the stack and evaluate expressions in the context of older
|
||||
// generator frames.
|
||||
|
||||
var g = newGlobal();
|
||||
var dbg = Debugger(g);
|
||||
dbg.onDebuggerStatement = function (frame) {
|
||||
function hit(frame) {
|
||||
assertEq(frame.generator, true);
|
||||
assertEq(frame.older.generator, true);
|
||||
frame.older.eval("q += 16");
|
||||
}
|
||||
|
||||
var s = frame.script;
|
||||
var offs = s.getLineOffsets(g.line0 + 9);
|
||||
for (var i = 0; i < offs.length; i++)
|
||||
s.setBreakpoint(offs[i], {hit: hit});
|
||||
};
|
||||
|
||||
g.eval("line0 = Error().lineNumber;\n" +
|
||||
"function* g(x) {\n" + // + 1
|
||||
" var q = 10;\n" + // + 2
|
||||
" yield* x;\n" + // + 3
|
||||
" return q;\n" + // + 4
|
||||
"}\n" + // + 5
|
||||
"function* range(n) {\n" + // + 6
|
||||
" debugger;\n" + // + 7
|
||||
" for (var i = 0; i < n; i++)\n" + // + 8
|
||||
" yield i;\n" + // + 9 <-- breakpoint
|
||||
" return;\n" + // so that line 9 only has the yield
|
||||
"}");
|
||||
|
||||
g.eval("var iter = g(range(2))");
|
||||
g.eval("var first = iter.next().value");
|
||||
g.eval("var second = iter.next().value");
|
||||
g.eval("var third = iter.next().value");
|
||||
|
||||
assertEq(g.first, 0);
|
||||
assertEq(g.second, 1);
|
||||
assertEq(g.third, 42);
|
@ -49,6 +49,9 @@ test(function () { g.clone(evaluate("(function(x) { return x + 1; })", {compileA
|
||||
// eval declaring a generator
|
||||
test(function () { g.eval("function r(n) { for (var i=0;i<n;i++) yield i; }"); });
|
||||
|
||||
// eval declaring a star generator
|
||||
test(function () { g.eval("function* sg(n) { for (var i=0;i<n;i++) yield i; }"); });
|
||||
|
||||
// eval with a generator-expression
|
||||
test(function () { g.eval("var it = (obj[p] for (p in obj));"); });
|
||||
|
||||
|
20
js/src/jit-test/tests/debug/resumption-06.js
Normal file
20
js/src/jit-test/tests/debug/resumption-06.js
Normal file
@ -0,0 +1,20 @@
|
||||
// |jit-test| debug
|
||||
// Forced return from a star generator frame.
|
||||
|
||||
load(libdir + 'asserts.js')
|
||||
load(libdir + 'iteration.js')
|
||||
|
||||
var g = newGlobal();
|
||||
g.debuggeeGlobal = this;
|
||||
g.eval("var dbg = new Debugger(debuggeeGlobal);" +
|
||||
"dbg.onDebuggerStatement = function () { return {return: '!'}; };");
|
||||
|
||||
function* gen() {
|
||||
yield '1';
|
||||
debugger; // Force return here. The value is ignored.
|
||||
yield '2';
|
||||
}
|
||||
var iter = gen();
|
||||
assertIteratorResult(iter.next(), '1', false);
|
||||
assertEq(iter.next(), '!');
|
||||
assertThrowsInstanceOf(iter.next.bind(iter), TypeError);
|
@ -1803,12 +1803,12 @@ NativeMethod(JSContext *cx, unsigned argc, Value *vp)
|
||||
}
|
||||
|
||||
#define JSPROP_ROPERM (JSPROP_READONLY | JSPROP_PERMANENT)
|
||||
#define JS_METHOD(name, T, impl, len, perms) JS_FN(name, (NativeMethod<T,impl>), len, perms)
|
||||
#define JS_METHOD(name, T, impl, len, attrs) JS_FN(name, (NativeMethod<T,impl>), len, attrs)
|
||||
|
||||
static const JSFunctionSpec star_generator_methods[] = {
|
||||
JS_FN("iterator", iterator_iterator, 0, 0),
|
||||
JS_METHOD("next", StarGeneratorObject, star_generator_next, 1, JSPROP_ROPERM),
|
||||
JS_METHOD("throw", StarGeneratorObject, star_generator_throw, 1, JSPROP_ROPERM),
|
||||
JS_METHOD("next", StarGeneratorObject, star_generator_next, 1, 0),
|
||||
JS_METHOD("throw", StarGeneratorObject, star_generator_throw, 1, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
@ -96,6 +96,8 @@ static char const * const callbackNames[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
enum YieldKind { Delegating, NotDelegating };
|
||||
|
||||
typedef AutoValueVector NodeVector;
|
||||
|
||||
/*
|
||||
@ -601,7 +603,7 @@ class NodeBuilder
|
||||
|
||||
bool thisExpression(TokenPos *pos, MutableHandleValue dst);
|
||||
|
||||
bool yieldExpression(HandleValue arg, TokenPos *pos, MutableHandleValue dst);
|
||||
bool yieldExpression(HandleValue arg, YieldKind kind, TokenPos *pos, MutableHandleValue dst);
|
||||
|
||||
bool comprehensionBlock(HandleValue patt, HandleValue src, bool isForEach, bool isForOf, TokenPos *pos,
|
||||
MutableHandleValue dst);
|
||||
@ -1242,13 +1244,23 @@ NodeBuilder::thisExpression(TokenPos *pos, MutableHandleValue dst)
|
||||
}
|
||||
|
||||
bool
|
||||
NodeBuilder::yieldExpression(HandleValue arg, TokenPos *pos, MutableHandleValue dst)
|
||||
NodeBuilder::yieldExpression(HandleValue arg, YieldKind kind, TokenPos *pos, MutableHandleValue dst)
|
||||
{
|
||||
RootedValue cb(cx, callbacks[AST_YIELD_EXPR]);
|
||||
if (!cb.isNull())
|
||||
return callback(cb, opt(arg), pos, dst);
|
||||
RootedValue delegateVal(cx);
|
||||
|
||||
return newNode(AST_YIELD_EXPR, pos, "argument", arg, dst);
|
||||
switch (kind) {
|
||||
case Delegating:
|
||||
delegateVal = BooleanValue(true);
|
||||
break;
|
||||
case NotDelegating:
|
||||
delegateVal = BooleanValue(false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!cb.isNull())
|
||||
return callback(cb, opt(arg), delegateVal, pos, dst);
|
||||
return newNode(AST_YIELD_EXPR, pos, "argument", arg, "delegate", delegateVal, dst);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -2597,13 +2609,22 @@ ASTSerializer::expression(ParseNode *pn, MutableHandleValue dst)
|
||||
case PNK_NULL:
|
||||
return literal(pn, dst);
|
||||
|
||||
case PNK_YIELD_STAR:
|
||||
{
|
||||
JS_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos));
|
||||
|
||||
RootedValue arg(cx);
|
||||
return expression(pn->pn_kid, &arg) &&
|
||||
builder.yieldExpression(arg, Delegating, &pn->pn_pos, dst);
|
||||
}
|
||||
|
||||
case PNK_YIELD:
|
||||
{
|
||||
JS_ASSERT_IF(pn->pn_kid, pn->pn_pos.encloses(pn->pn_kid->pn_pos));
|
||||
|
||||
RootedValue arg(cx);
|
||||
return optExpression(pn->pn_kid, &arg) &&
|
||||
builder.yieldExpression(arg, &pn->pn_pos, dst);
|
||||
builder.yieldExpression(arg, NotDelegating, &pn->pn_pos, dst);
|
||||
}
|
||||
|
||||
case PNK_ARRAYCOMP:
|
||||
|
38
js/src/tests/ecma_6/Generators/delegating-yield-1.js
Normal file
38
js/src/tests/ecma_6/Generators/delegating-yield-1.js
Normal file
@ -0,0 +1,38 @@
|
||||
// This file was written by Andy Wingo <wingo@igalia.com> and originally
|
||||
// contributed to V8 as generators-objects.js, available here:
|
||||
//
|
||||
// http://code.google.com/p/v8/source/browse/branches/bleeding_edge/test/mjsunit/harmony/generators-objects.js
|
||||
|
||||
// Test that yield* re-yields received results without re-boxing.
|
||||
|
||||
function results(results) {
|
||||
var i = 0;
|
||||
function next() {
|
||||
return results[i++];
|
||||
}
|
||||
return { next: next }
|
||||
}
|
||||
|
||||
function* yield_results(expected) {
|
||||
return yield* results(expected);
|
||||
}
|
||||
|
||||
function collect_results(iter) {
|
||||
var ret = [];
|
||||
var result;
|
||||
do {
|
||||
result = iter.next();
|
||||
ret.push(result);
|
||||
} while (!result.done);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// We have to put a full result for the end, because the return will re-box.
|
||||
var expected = [{value: 1}, 13, "foo", {value: 34, done: true}];
|
||||
|
||||
// Sanity check.
|
||||
assertDeepEq(expected, collect_results(results(expected)));
|
||||
assertDeepEq(expected, collect_results(yield_results(expected)));
|
||||
|
||||
if (typeof reportCompare == "function")
|
||||
reportCompare(true, true);
|
49
js/src/tests/ecma_6/Generators/delegating-yield-10.js
Normal file
49
js/src/tests/ecma_6/Generators/delegating-yield-10.js
Normal file
@ -0,0 +1,49 @@
|
||||
// Errors accessing next, done, or value don't cause an exception to be
|
||||
// thrown into the iterator of a yield*.
|
||||
|
||||
function* g(n) { for (var i=0; i<n; i++) yield i; }
|
||||
function* delegate(iter) { return yield* iter; }
|
||||
|
||||
var log = "", inner, outer;
|
||||
|
||||
// That var is poisoooooon, p-poison poison...
|
||||
var Poison = new Error;
|
||||
|
||||
function log_calls(method) {
|
||||
return function () {
|
||||
log += "x"
|
||||
return method.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
function poison(receiver, prop) {
|
||||
Object.defineProperty(receiver, prop, { get: function () { throw Poison } });
|
||||
}
|
||||
|
||||
// Poison inner.next.
|
||||
inner = g(10);
|
||||
outer = delegate(inner);
|
||||
inner.throw = log_calls(inner.throw);
|
||||
poison(inner, 'next')
|
||||
assertThrowsValue(outer.next.bind(outer), Poison);
|
||||
assertEq(log, "");
|
||||
|
||||
// Poison result value from inner.
|
||||
inner = g(10);
|
||||
outer = delegate(inner);
|
||||
inner.next = function () { return { done: true, get value() { throw Poison} } };
|
||||
inner.throw = log_calls(inner.throw);
|
||||
assertThrowsValue(outer.next.bind(outer), Poison);
|
||||
assertEq(log, "");
|
||||
|
||||
// Poison result done from inner.
|
||||
inner = g(10);
|
||||
outer = delegate(inner);
|
||||
inner.next = function () { return { get done() { throw Poison }, value: 42 } };
|
||||
inner.throw = log_calls(inner.throw);
|
||||
assertThrowsValue(outer.next.bind(outer), Poison);
|
||||
assertEq(log, "");
|
||||
|
||||
// mischief managed.
|
||||
if (typeof reportCompare == "function")
|
||||
reportCompare(true, true);
|
19
js/src/tests/ecma_6/Generators/delegating-yield-11.js
Normal file
19
js/src/tests/ecma_6/Generators/delegating-yield-11.js
Normal file
@ -0,0 +1,19 @@
|
||||
// The first call to yield* passes one arg to "next".
|
||||
|
||||
function Iter() {
|
||||
function next() {
|
||||
if (arguments.length != 1)
|
||||
throw Error;
|
||||
return { value: 42, done: true }
|
||||
}
|
||||
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
function* delegate(iter) { return yield* iter; }
|
||||
|
||||
var iter = delegate(new Iter());
|
||||
assertDeepEq(iter.next(), {value:42, done:true});
|
||||
|
||||
if (typeof reportCompare == "function")
|
||||
reportCompare(true, true);
|
71
js/src/tests/ecma_6/Generators/delegating-yield-2.js
Normal file
71
js/src/tests/ecma_6/Generators/delegating-yield-2.js
Normal file
@ -0,0 +1,71 @@
|
||||
// Test yield* with iter.throw and monkeypatching.
|
||||
|
||||
function* g1() { return (yield 1); }
|
||||
function* g2() { try { yield 1; } catch (e) { yield e; } }
|
||||
function* delegate(iter) { return yield* iter; }
|
||||
var GeneratorObjectPrototype = Object.getPrototypeOf(g1).prototype;
|
||||
var GeneratorObjectPrototype_throw = GeneratorObjectPrototype.throw;
|
||||
|
||||
// An uncaught delegated throw.
|
||||
var inner = g1();
|
||||
var outer = delegate(inner);
|
||||
assertIteratorResult(1, false, outer.next());
|
||||
assertThrowsValue(function () { outer.throw(42) }, 42);
|
||||
assertThrowsInstanceOf(function () { outer.throw(42) }, TypeError);
|
||||
|
||||
// A caught delegated throw.
|
||||
inner = g2();
|
||||
outer = delegate(inner);
|
||||
assertIteratorResult(1, false, outer.next());
|
||||
assertIteratorResult(42, false, outer.throw(42));
|
||||
assertThrowsValue(function () { outer.throw(42) }, 42);
|
||||
assertThrowsInstanceOf(function () { outer.throw(42) }, TypeError);
|
||||
|
||||
// What would be an uncaught delegated throw, but with a monkeypatched iterator.
|
||||
inner = g1();
|
||||
outer = delegate(inner);
|
||||
assertIteratorResult(1, false, outer.next());
|
||||
inner.throw = function(e) { return e*2; };
|
||||
assertEq(84, outer.throw(42));
|
||||
assertIteratorResult(undefined, true, outer.next());
|
||||
|
||||
// Monkeypatching inner.next.
|
||||
inner = g1();
|
||||
outer = delegate(inner);
|
||||
inner.next = function() { return { value: 13, done: true } };
|
||||
assertIteratorResult(13, true, outer.next());
|
||||
|
||||
// What would be a caught delegated throw, but with a monkeypunched prototype.
|
||||
inner = g2();
|
||||
outer = delegate(inner);
|
||||
assertIteratorResult(1, false, outer.next());
|
||||
delete GeneratorObjectPrototype.throw;
|
||||
var outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42);
|
||||
assertThrowsValue(outer_throw_42, 42);
|
||||
assertThrowsInstanceOf(outer_throw_42, TypeError);
|
||||
|
||||
// Monkeypunch a different throw handler.
|
||||
inner = g2();
|
||||
outer = delegate(inner);
|
||||
outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42);
|
||||
assertIteratorResult(1, false, outer.next());
|
||||
GeneratorObjectPrototype.throw = function(e) { return e*2; }
|
||||
assertEq(84, outer_throw_42());
|
||||
assertEq(84, outer_throw_42());
|
||||
// This continues indefinitely.
|
||||
assertEq(84, outer_throw_42());
|
||||
assertIteratorResult(undefined, true, outer.next());
|
||||
|
||||
// The same, but restoring the original pre-monkey throw.
|
||||
inner = g2();
|
||||
outer = delegate(inner);
|
||||
outer_throw_42 = GeneratorObjectPrototype_throw.bind(outer, 42);
|
||||
assertIteratorResult(1, false, outer.next());
|
||||
assertEq(84, outer_throw_42());
|
||||
assertEq(84, outer_throw_42());
|
||||
GeneratorObjectPrototype.throw = GeneratorObjectPrototype_throw;
|
||||
assertIteratorResult(42, false, outer_throw_42());
|
||||
assertIteratorResult(undefined, true, outer.next());
|
||||
|
||||
if (typeof reportCompare == "function")
|
||||
reportCompare(true, true);
|
40
js/src/tests/ecma_6/Generators/delegating-yield-3.js
Normal file
40
js/src/tests/ecma_6/Generators/delegating-yield-3.js
Normal file
@ -0,0 +1,40 @@
|
||||
// Test yield* with iter.next and monkeypatching.
|
||||
|
||||
function* g(n) { for (var i=0; i<n; i++) yield i; }
|
||||
function* delegate(iter) { return yield* iter; }
|
||||
var GeneratorObjectPrototype = Object.getPrototypeOf(g).prototype;
|
||||
var GeneratorObjectPrototype_next = GeneratorObjectPrototype.next;
|
||||
|
||||
// Monkeypatch next on an iterator.
|
||||
var inner = g(20);
|
||||
var outer = delegate(inner);
|
||||
assertIteratorResult(0, false, outer.next());
|
||||
assertIteratorResult(1, false, outer.next());
|
||||
inner.next = function() { return 0; };
|
||||
// 42 yielded directly without re-boxing.
|
||||
assertEq(0, outer.next());
|
||||
// Outer generator not terminated.
|
||||
assertEq(0, outer.next());
|
||||
// Restore.
|
||||
inner.next = GeneratorObjectPrototype_next;
|
||||
assertIteratorResult(2, false, outer.next());
|
||||
// Repatch.
|
||||
inner.next = function() { return { value: 42, done: true }; };
|
||||
assertIteratorResult(42, true, outer.next());
|
||||
|
||||
// Monkeypunch next on the prototype.
|
||||
var inner = g(20);
|
||||
var outer = delegate(inner);
|
||||
assertIteratorResult(0, false, outer.next());
|
||||
assertIteratorResult(1, false, outer.next());
|
||||
GeneratorObjectPrototype.next = function() { return 0; };
|
||||
// 42 yielded directly without re-boxing.
|
||||
assertEq(0, GeneratorObjectPrototype_next.call(outer));
|
||||
// Outer generator not terminated.
|
||||
assertEq(0, GeneratorObjectPrototype_next.call(outer));
|
||||
// Restore.
|
||||
GeneratorObjectPrototype.next = GeneratorObjectPrototype_next;
|
||||
assertIteratorResult(2, false, outer.next());
|
||||
|
||||
if (typeof reportCompare == "function")
|
||||
reportCompare(true, true);
|
18
js/src/tests/ecma_6/Generators/delegating-yield-4.js
Normal file
18
js/src/tests/ecma_6/Generators/delegating-yield-4.js
Normal file
@ -0,0 +1,18 @@
|
||||
// With yield*, inner and outer iterators can be invoked separately.
|
||||
|
||||
function* g(n) { for (var i=0; i<n; i++) yield i; }
|
||||
function* delegate(iter) { return yield* iter; }
|
||||
|
||||
var inner = g(20);
|
||||
var outer1 = delegate(inner);
|
||||
var outer2 = delegate(inner);
|
||||
|
||||
assertIteratorResult(0, false, outer1.next());
|
||||
assertIteratorResult(1, false, outer2.next());
|
||||
assertIteratorResult(2, false, inner.next());
|
||||
assertIteratorResult(3, false, outer1.next());
|
||||
assertIteratorResult(4, false, outer2.next());
|
||||
assertIteratorResult(5, false, inner.next());
|
||||
|
||||
if (typeof reportCompare == "function")
|
||||
reportCompare(true, true);
|
33
js/src/tests/ecma_6/Generators/delegating-yield-5.js
Normal file
33
js/src/tests/ecma_6/Generators/delegating-yield-5.js
Normal file
@ -0,0 +1,33 @@
|
||||
// Test that a deep yield* chain re-yields received results without
|
||||
// re-boxing.
|
||||
|
||||
function results(results) {
|
||||
var i = 0;
|
||||
function next() {
|
||||
return results[i++];
|
||||
}
|
||||
return { next: next }
|
||||
}
|
||||
|
||||
function* yield_results(expected, n) {
|
||||
return yield* n ? yield_results(expected, n - 1) : results(expected);
|
||||
}
|
||||
|
||||
function collect_results(iter) {
|
||||
var ret = [];
|
||||
var result;
|
||||
do {
|
||||
result = iter.next();
|
||||
ret.push(result);
|
||||
} while (!result.done);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// We have to put a full result for the end, because the return will re-box.
|
||||
var expected = [{value: 1}, 13, "foo", {value: 34, done: true}];
|
||||
|
||||
assertDeepEq(expected, collect_results(results(expected)));
|
||||
assertDeepEq(expected, collect_results(yield_results(expected, 20)));
|
||||
|
||||
if (typeof reportCompare == "function")
|
||||
reportCompare(true, true);
|
49
js/src/tests/ecma_6/Generators/delegating-yield-6.js
Normal file
49
js/src/tests/ecma_6/Generators/delegating-yield-6.js
Normal file
@ -0,0 +1,49 @@
|
||||
// Test that each yield* loop just checks "done", and "value" is only
|
||||
// fetched once at the end.
|
||||
|
||||
var log = "";
|
||||
|
||||
function collect_results(iter) {
|
||||
var ret = [];
|
||||
var result;
|
||||
do {
|
||||
result = iter.next();
|
||||
ret.push(result);
|
||||
} while (!result.done);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function Iter(val, count) {
|
||||
function next() {
|
||||
return {
|
||||
get done() { log += "d"; return count-- == 0; },
|
||||
get value() { log += "v"; return val; }
|
||||
}
|
||||
}
|
||||
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
function* delegate(iter) { return yield* iter; }
|
||||
|
||||
var inner = new Iter(42, 5);
|
||||
var outer = delegate(inner);
|
||||
|
||||
// Five values, and one terminal value.
|
||||
outer.next();
|
||||
outer.next();
|
||||
outer.next();
|
||||
outer.next();
|
||||
outer.next();
|
||||
outer.next();
|
||||
|
||||
assertEq(log, "ddddddv");
|
||||
|
||||
// Outer's dead, man. Outer's dead.
|
||||
assertThrowsInstanceOf(outer.next.bind(outer), TypeError);
|
||||
|
||||
// No more checking the iterator.
|
||||
assertEq(log, "ddddddv");
|
||||
|
||||
if (typeof reportCompare == "function")
|
||||
reportCompare(true, true);
|
33
js/src/tests/ecma_6/Generators/delegating-yield-7.js
Normal file
33
js/src/tests/ecma_6/Generators/delegating-yield-7.js
Normal file
@ -0,0 +1,33 @@
|
||||
// The iteratee of yield* can be a proxy.
|
||||
|
||||
function results(results) {
|
||||
var i = 0;
|
||||
function next() {
|
||||
return results[i++];
|
||||
}
|
||||
return { next: next }
|
||||
}
|
||||
|
||||
function* yield_results(expected) {
|
||||
return yield* Proxy(results(expected), {});
|
||||
}
|
||||
|
||||
function collect_results(iter) {
|
||||
var ret = [];
|
||||
var result;
|
||||
do {
|
||||
result = iter.next();
|
||||
ret.push(result);
|
||||
} while (!result.done);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// We have to put a full result for the end, because the return will re-box.
|
||||
var expected = [{value: 1}, 13, "foo", {value: 34, done: true}];
|
||||
|
||||
// Sanity check.
|
||||
assertDeepEq(expected, collect_results(results(expected)));
|
||||
assertDeepEq(expected, collect_results(yield_results(expected)));
|
||||
|
||||
if (typeof reportCompare == "function")
|
||||
reportCompare(true, true);
|
44
js/src/tests/ecma_6/Generators/delegating-yield-8.js
Normal file
44
js/src/tests/ecma_6/Generators/delegating-yield-8.js
Normal file
@ -0,0 +1,44 @@
|
||||
// Test that yield* can appear in a loop, and alongside yield.
|
||||
|
||||
function* countdown(n) {
|
||||
while (n > 0) {
|
||||
yield n;
|
||||
yield* countdown(--n);
|
||||
}
|
||||
return 34;
|
||||
}
|
||||
|
||||
function collect_results(iter) {
|
||||
var ret = [];
|
||||
var result;
|
||||
do {
|
||||
result = iter.next();
|
||||
ret.push(result);
|
||||
} while (!result.done);
|
||||
return ret;
|
||||
}
|
||||
|
||||
var expected = [
|
||||
// yield in countdown(3), n == 3
|
||||
{value: 3, done: false},
|
||||
// yield in yield* countdown(2), n == 2
|
||||
{value: 2, done: false},
|
||||
// yield in nested yield* countdown(1), n == 1
|
||||
{value: 1, done: false},
|
||||
// countdown(0) yields no values
|
||||
// second go-through of countdown(2) loop, n == 1
|
||||
{value: 1, done: false},
|
||||
// second go-through of countdown(3) loop, n == 2
|
||||
{value: 2, done: false},
|
||||
// yield in yield* countdown(1), n == 1
|
||||
{value: 1, done: false},
|
||||
// third go-through of countdown(3) loop, n == 1
|
||||
{value: 1, done: false},
|
||||
// done
|
||||
{value: 34, done: true}
|
||||
];
|
||||
|
||||
assertDeepEq(expected, collect_results(countdown(3)));
|
||||
|
||||
if (typeof reportCompare == "function")
|
||||
reportCompare(true, true);
|
37
js/src/tests/ecma_6/Generators/delegating-yield-9.js
Normal file
37
js/src/tests/ecma_6/Generators/delegating-yield-9.js
Normal file
@ -0,0 +1,37 @@
|
||||
// Test that yield* can appear in a loop, and inside yield.
|
||||
|
||||
function* countdown(n) {
|
||||
while (n > 0) {
|
||||
yield (yield* countdown(--n));
|
||||
}
|
||||
return 34;
|
||||
}
|
||||
|
||||
function collect_results(iter) {
|
||||
var ret = [];
|
||||
var result;
|
||||
do {
|
||||
result = iter.next();
|
||||
ret.push(result);
|
||||
} while (!result.done);
|
||||
return ret;
|
||||
}
|
||||
|
||||
var expected = [
|
||||
// Only 34 yielded from the "yield" and the last return make it out.
|
||||
// Three yields in countdown(3), two in countdown(2), and one in
|
||||
// countdown(1) (called twice).
|
||||
{value: 34, done: false},
|
||||
{value: 34, done: false},
|
||||
{value: 34, done: false},
|
||||
{value: 34, done: false},
|
||||
{value: 34, done: false},
|
||||
{value: 34, done: false},
|
||||
{value: 34, done: false},
|
||||
{value: 34, done: true}, // final
|
||||
];
|
||||
|
||||
assertDeepEq(collect_results(countdown(3)), expected);
|
||||
|
||||
if (typeof reportCompare == "function")
|
||||
reportCompare(true, true);
|
@ -58,11 +58,9 @@ function TestGenerator(g, expected_values_for_next,
|
||||
testSend(g);
|
||||
testThrow(g);
|
||||
|
||||
// FIXME: Implement yield*. Bug 907738.
|
||||
//
|
||||
// testNext(function*() { return yield* g(); });
|
||||
// testSend(function*() { return yield* g(); });
|
||||
// testThrow(function*() { return yield* g(); });
|
||||
testNext(function*() { return yield* g(); });
|
||||
testSend(function*() { return yield* g(); });
|
||||
testThrow(function*() { return yield* g(); });
|
||||
|
||||
if (g instanceof GeneratorFunction) {
|
||||
testNext(function() { return new g(); });
|
||||
@ -332,39 +330,6 @@ TestGenerator(
|
||||
"foo",
|
||||
[42, undefined]);
|
||||
|
||||
/* FIXME: Implement yield*. Bug 907738.
|
||||
|
||||
// Test that yield* re-yields received results without re-boxing.
|
||||
function TestDelegatingYield() {
|
||||
function results(results) {
|
||||
var i = 0;
|
||||
function next() {
|
||||
return results[i++];
|
||||
}
|
||||
return { next: next }
|
||||
}
|
||||
function* yield_results(expected) {
|
||||
return yield* results(expected);
|
||||
}
|
||||
function collect_results(iter) {
|
||||
var ret = [];
|
||||
var result;
|
||||
do {
|
||||
result = iter.next();
|
||||
ret.push(result);
|
||||
} while (!result.done);
|
||||
return ret;
|
||||
}
|
||||
// We have to put a full result for the end, because the return will re-box.
|
||||
var expected = [{value: 1}, 13, "foo", {value: 34, done: true}];
|
||||
|
||||
// Sanity check.
|
||||
assertDeepEq(expected, collect_results(results(expected)));
|
||||
assertDeepEq(expected, collect_results(yield_results(expected)));
|
||||
}
|
||||
TestDelegatingYield();
|
||||
*/
|
||||
|
||||
function TestTryCatch(instantiate) {
|
||||
function* g() { yield 1; try { yield 2; } catch (e) { yield e; } yield 3; }
|
||||
function Sentinel() {}
|
||||
@ -425,8 +390,7 @@ function TestTryCatch(instantiate) {
|
||||
Test6(instantiate(g));
|
||||
}
|
||||
TestTryCatch(function (g) { return g(); });
|
||||
// FIXME: Implement yield*. Bug 907738.
|
||||
// TestTryCatch(function* (g) { return yield* g(); });
|
||||
TestTryCatch(function* (g) { return yield* g(); });
|
||||
|
||||
function TestTryFinally(instantiate) {
|
||||
function* g() { yield 1; try { yield 2; } finally { yield 3; } yield 4; }
|
||||
@ -495,8 +459,7 @@ function TestTryFinally(instantiate) {
|
||||
Test7(instantiate(g));
|
||||
}
|
||||
TestTryFinally(function (g) { return g(); });
|
||||
// FIXME: Implement yield*. Bug 907738.
|
||||
// TestTryFinally(function* (g) { return yield* g(); });
|
||||
TestTryFinally(function* (g) { return yield* g(); });
|
||||
|
||||
function TestNestedTry(instantiate) {
|
||||
function* g() {
|
||||
@ -586,8 +549,7 @@ function TestNestedTry(instantiate) {
|
||||
// That's probably enough.
|
||||
}
|
||||
TestNestedTry(function (g) { return g(); });
|
||||
// FIXME: Implement yield*. Bug 907738.
|
||||
// TestNestedTry(function* (g) { return yield* g(); });
|
||||
TestNestedTry(function* (g) { return yield* g(); });
|
||||
|
||||
function TestRecursion() {
|
||||
function TestNextRecursion() {
|
||||
|
@ -3352,7 +3352,7 @@ default:
|
||||
|
||||
switch (tn->kind) {
|
||||
case JSTRY_CATCH:
|
||||
JS_ASSERT(*regs.pc == JSOP_ENTERBLOCK);
|
||||
JS_ASSERT(*regs.pc == JSOP_ENTERBLOCK || *regs.pc == JSOP_EXCEPTION);
|
||||
|
||||
/* Catch cannot intercept the closing of a generator. */
|
||||
if (JS_UNLIKELY(cx->getPendingException().isMagic(JS_GENERATOR_CLOSING)))
|
||||
|
Loading…
Reference in New Issue
Block a user