/** * Copyright (c) 2012-2016 André Bargull * Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms. * * <https://github.com/anba/es6draft> */ package com.github.anba.es6draft.runtime.objects.async.iteration; import static com.github.anba.es6draft.runtime.AbstractOperations.CreateIterResultObject; import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED; import java.util.ArrayList; import java.util.List; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.Realm; import com.github.anba.es6draft.runtime.internal.CodeContinuation; import com.github.anba.es6draft.runtime.internal.Continuation; import com.github.anba.es6draft.runtime.internal.ResumptionPoint; import com.github.anba.es6draft.runtime.internal.ReturnValue; import com.github.anba.es6draft.runtime.internal.RuntimeInfo; import com.github.anba.es6draft.runtime.internal.ScriptException; import com.github.anba.es6draft.runtime.objects.async.Async; import com.github.anba.es6draft.runtime.objects.promise.PromiseAbstractOperations; import com.github.anba.es6draft.runtime.objects.promise.PromiseCapability; import com.github.anba.es6draft.runtime.objects.promise.PromiseObject; import com.github.anba.es6draft.runtime.types.ScriptObject; import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject; /** * <h1>Async Generator Functions</h1><br> * <h2>AsyncGenerator Objects</h2> * <ul> * <li>Properties of AsyncGenerator Instances * </ul> */ public final class AsyncGeneratorObject extends OrdinaryObject implements Async { /** * [[AsyncGeneratorState]] */ public enum AsyncGeneratorState { // NB: Additional 'SuspendedAwait' state to ease debugging of async generator execution. SuspendedStart, SuspendedYield, SuspendedAwait, Executing, Completed } /** [[AsyncGeneratorState]] */ private AsyncGeneratorState state; /** [[Code]] */ private RuntimeInfo.Function code; /** [[AsyncGeneratorContext]] */ private ExecutionContext context; /** [[AsyncGeneratorQueue]] */ private List<AsyncGeneratorRequest> queue; // internal generator implementation private Continuation<Void> continuation; /** * Constructs a new AsyncGenerator object. * * @param realm * the realm object */ public AsyncGeneratorObject(Realm realm) { super(realm); } /** * [[AsyncGeneratorState]] * * @return the async generator state */ public AsyncGeneratorState getState() { return state; } /** * [[AsyncGeneratorQueue]] * * @return the list of generator requests */ public List<?> getQueue() { return queue; } @Override public String toString() { return String.format("%s, state=%s, queue=%s", super.toString(), state, queue); } /** * Proceeds to either the "suspendedAwait" or the "suspendedYield" generator state. */ private void suspend(AsyncGeneratorState suspendState) { assert suspendState == AsyncGeneratorState.SuspendedYield || suspendState == AsyncGeneratorState.SuspendedAwait; assert state == AsyncGeneratorState.Executing : "suspend from: " + state; this.state = suspendState; } /** * Proceeds to the "completed" generator state and releases internal resources. */ private void close() { assert state == AsyncGeneratorState.Executing || state == AsyncGeneratorState.SuspendedStart : "close from: " + state; this.state = AsyncGeneratorState.Completed; this.context = null; this.code = null; this.continuation = null; } /** * Starts generator execution and sets {@link #state} to its initial value * {@link AsyncGeneratorState#SuspendedStart}. * * @param cx * the execution context * @param code * the runtime function code * @see AsyncGeneratorAbstractOperations#AsyncGeneratorStart(ExecutionContext, AsyncGeneratorObject, * com.github.anba.es6draft.runtime.internal.RuntimeInfo.Function) */ void start(ExecutionContext cx, RuntimeInfo.Function code) { assert state == null; this.context = cx; this.code = code; this.state = AsyncGeneratorState.SuspendedStart; this.queue = new ArrayList<>(); this.context.setCurrentAsync(this); this.continuation = new CodeContinuation<>(new AsyncGeneratorHandler(this)); } @Override public void resume(ExecutionContext cx, Object value) { switch (state) { case SuspendedStart: case SuspendedYield: case Executing: case Completed: throw new AssertionError(); case SuspendedAwait: state = AsyncGeneratorState.Executing; continuation.resume(cx, value); return; default: throw new AssertionError(); } } @Override public void _throw(ExecutionContext cx, Object value) { switch (state) { case SuspendedStart: case SuspendedYield: case Executing: case Completed: throw new AssertionError(); case SuspendedAwait: state = AsyncGeneratorState.Executing; continuation._throw(cx, ScriptException.create(value)); return; default: throw new AssertionError(); } } /** * AsyncGeneratorFulfill ( generator, iteratorResult ) * * @param cx * the execution context * @param iteratorResult * the iterator result object */ void fulfill(ExecutionContext cx, ScriptObject iteratorResult) { /* step 1 (implicit) */ /* step 2 (not applicable) */ /* step 3 */ List<AsyncGeneratorRequest> queue = this.queue; /* step 4 */ assert !queue.isEmpty(); AsyncGeneratorRequest next = queue.remove(0); /* step 5 */ PromiseCapability<PromiseObject> capability = next.getCapability(); /* step 6 */ capability.getResolve().call(cx, UNDEFINED, iteratorResult); /* step 7 */ resumeNext(cx); /* step 8 (return) */ } /** * AsyncGeneratorReject ( generator, exception ) * * @param cx * the execution context * @param exception * the exception */ void reject(ExecutionContext cx, ScriptException exception) { /* step 1 (implicit) */ /* step 2 (not applicable) */ /* step 3 */ List<AsyncGeneratorRequest> queue = this.queue; /* step 4 */ assert !queue.isEmpty(); AsyncGeneratorRequest next = queue.remove(0); /* step 5 */ PromiseCapability<PromiseObject> capability = next.getCapability(); /* step 6 */ capability.getReject().call(cx, UNDEFINED, exception.getValue()); /* step 7 */ resumeNext(cx); /* step 8 (return) */ } /** * AsyncGeneratorResumeNext ( generator ) * * @param cx * the execution context */ void resumeNext(ExecutionContext cx) { /* step 1 (implicit) */ /* steps 2-3 */ assert !(state == AsyncGeneratorState.Executing || state == AsyncGeneratorState.SuspendedAwait); /* step 4 */ List<AsyncGeneratorRequest> queue = this.queue; /* step 5 */ if (queue.isEmpty()) { return; } /* step 6 */ AsyncGeneratorRequest request = queue.get(0); /* step 7 (implicit) */ /* steps 8-20 */ // System.out.printf("request=%s, queue=%s%n", request, queue); switch (request.getCompletionType()) { case Normal: { /* step 8 */ Object completion = request.getCompletion(); /* step 9 (not applicable) */ switch (state) { case Completed: /* step 10 */ fulfill(cx, CreateIterResultObject(cx, UNDEFINED, true)); return; case SuspendedStart: /* steps 11-19 */ state = AsyncGeneratorState.Executing; continuation.start(cx); /* step 20 */ return; case SuspendedYield: /* steps 11-19 */ state = AsyncGeneratorState.Executing; continuation.resume(cx, completion); /* step 20 */ return; case Executing: case SuspendedAwait: default: throw new AssertionError(); } } case Return: { /* step 8 */ ReturnValue completion = (ReturnValue) request.getCompletion(); /* steps 9-11 */ switch (state) { case SuspendedStart: /* step 9.a */ close(); // fall-through case Completed: /* step 9.b.i */ ScriptObject result = CreateIterResultObject(cx, completion.getValue(), true); fulfill(cx, result); return; case SuspendedYield: /* step 10 (not applicable) */ /* steps 11-19 */ state = AsyncGeneratorState.Executing; continuation._return(cx, completion.getValue()); /* step 20 */ return; case Executing: case SuspendedAwait: default: throw new AssertionError(); } } case Throw: { /* step 8 */ ScriptException completion = (ScriptException) request.getCompletion(); /* steps 9-11 */ switch (state) { case SuspendedStart: /* step 9.a */ close(); // fall-through case Completed: /* step 9.b */ reject(cx, completion); return; case SuspendedYield: /* step 10 (not applicable) */ /* steps 11-19 */ state = AsyncGeneratorState.Executing; continuation._throw(cx, completion); /* step 20 */ return; case Executing: case SuspendedAwait: default: throw new AssertionError(); } } default: throw new AssertionError(); } } /** * AsyncGeneratorEnqueue ( generator, completion ) * * @param cx * the execution context * @param completionType * the completion type * @param completion * the completion value * @return the promise object */ PromiseObject enqueue(ExecutionContext cx, AsyncGeneratorRequest.CompletionType completionType, Object completion) { /* steps 1-3 (not applicable) */ /* step 4 */ List<AsyncGeneratorRequest> queue = this.queue; /* step 5 */ PromiseCapability<PromiseObject> capability = PromiseAbstractOperations.PromiseBuiltinCapability(cx); /* step 6 */ AsyncGeneratorRequest request = new AsyncGeneratorRequest(completionType, completion, capability); /* step 7 */ queue.add(request); /* step 8 */ AsyncGeneratorState state = this.state; /* step 9 */ if (!(state == AsyncGeneratorState.Executing || state == AsyncGeneratorState.SuspendedAwait)) { resumeNext(cx); } return capability.getPromise(); } private static final class AsyncGeneratorHandler implements Continuation.Handler<Void> { private final AsyncGeneratorObject generatorObject; AsyncGeneratorHandler(AsyncGeneratorObject generatorObject) { this.generatorObject = generatorObject; } @Override public Object evaluate(ResumptionPoint resumptionPoint) throws Throwable { AsyncGeneratorObject genObj = generatorObject; return genObj.code.handle().invokeExact(genObj.context, resumptionPoint); } @Override public void close() { generatorObject.close(); } @Override public Void suspendWith(ExecutionContext cx, Object value) { if (value != null) { generatorObject.suspend(AsyncGeneratorState.SuspendedYield); // The iteration result object was already constructed at the call site. generatorObject.fulfill(cx, (ScriptObject) value); } else { generatorObject.suspend(AsyncGeneratorState.SuspendedAwait); } return null; } @Override public Void returnWith(ExecutionContext cx, Object result) { // AsyncGeneratorStart - step 5.g generatorObject.fulfill(cx, CreateIterResultObject(cx, result, true)); return null; } @Override public Void returnWith(ExecutionContext cx, ScriptException exception) { // AsyncGeneratorStart - step 5.f generatorObject.reject(cx, exception); return null; } } }