/**
* 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.iteration;
import static com.github.anba.es6draft.runtime.AbstractOperations.CreateIterResultObject;
import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;
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.Messages;
import com.github.anba.es6draft.runtime.internal.ResumptionPoint;
import com.github.anba.es6draft.runtime.internal.RuntimeInfo;
import com.github.anba.es6draft.runtime.internal.ScriptException;
import com.github.anba.es6draft.runtime.types.ScriptObject;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject;
/**
* <h1>25 Control Abstraction Objects</h1><br>
* <h2>25.3 Generator Objects</h2>
* <ul>
* <li>25.3.2 Properties of Generator Instances
* </ul>
*/
public final class GeneratorObject extends OrdinaryObject {
/**
* [[GeneratorState]]
*/
public enum GeneratorState {
SuspendedStart, SuspendedYield, Executing, Completed
}
/** [[GeneratorState]] */
private GeneratorState state;
/** [[Code]] */
private RuntimeInfo.Function code;
/** [[GeneratorContext]] */
private ExecutionContext context;
/** [[LastYieldValue]] */
private Object lastYieldValue;
// internal generator implementation
private Continuation<ScriptObject> continuation;
/**
* Constructs a new Generator object.
*
* @param realm
* the realm object
*/
public GeneratorObject(Realm realm) {
super(realm);
}
/**
* [[GeneratorState]]
*
* @return the generator state
*/
public GeneratorState getState() {
return state;
}
/**
* [[LastYieldValue]]
*
* @return the last yield value
*/
public Object getLastYieldValue() {
return lastYieldValue;
}
/**
* Returns {@code true} for legacy generator objects.
*
* @return {@code true} if legacy generator object
*/
public boolean isLegacyGenerator() {
return code != null && code.is(RuntimeInfo.FunctionFlags.LegacyGenerator);
}
@Override
public String toString() {
return String.format("%s, state=%s, lastYieldValue=%s", super.toString(), state, lastYieldValue);
}
/**
* Proceeds to the "suspendedYield" generator state.
*/
private void suspend() {
assert state == GeneratorState.Executing : "suspend from: " + state;
this.state = GeneratorState.SuspendedYield;
this.lastYieldValue = UNDEFINED;
}
/**
* Proceeds to the "completed" generator state and releases internal resources.
*/
private void close() {
assert state == GeneratorState.Executing || state == GeneratorState.SuspendedStart : "close from: " + state;
this.state = GeneratorState.Completed;
this.lastYieldValue = null;
this.context = null;
this.code = null;
this.continuation = null;
}
/**
* Starts generator execution and sets {@link #state} to its initial value {@link GeneratorState#SuspendedStart}.
*
* @param cx
* the execution context
* @param code
* the runtime function code
* @see GeneratorAbstractOperations#GeneratorStart(ExecutionContext, GeneratorObject, RuntimeInfo.Function)
*/
void start(ExecutionContext cx, RuntimeInfo.Function code) {
assert state == null;
this.context = cx;
this.code = code;
this.state = GeneratorState.SuspendedStart;
this.lastYieldValue = UNDEFINED;
this.context.setCurrentGenerator(this);
this.continuation = new CodeContinuation<>(new GeneratorHandler(this));
}
/**
* Resumes generator execution.
*
* @param cx
* the execution context
* @param value
* the resumption value
* @return the iterator result object
* @see GeneratorAbstractOperations#GeneratorResume(ExecutionContext, Object, Object)
*/
ScriptObject resume(ExecutionContext cx, Object value) {
switch (state) {
case Executing:
throw newTypeError(cx, Messages.Key.GeneratorExecuting);
case Completed:
return CreateIterResultObject(cx, UNDEFINED, true);
case SuspendedStart:
this.state = GeneratorState.Executing;
this.lastYieldValue = value;
return continuation.start(cx);
case SuspendedYield:
this.state = GeneratorState.Executing;
this.lastYieldValue = value;
return continuation.resume(cx, value);
default:
throw new AssertionError();
}
}
/**
* Stops generator execution with a {@code return} event.
*
* @param cx
* the execution context
* @param value
* the return value
* @return the iterator result object
* @see GeneratorPrototype.Properties#_return(ExecutionContext, Object, Object)
*/
ScriptObject _return(ExecutionContext cx, Object value) {
switch (state) {
case Executing:
throw newTypeError(cx, Messages.Key.GeneratorExecuting);
case SuspendedStart:
close();
// fall-through
case Completed:
return CreateIterResultObject(cx, value, true);
case SuspendedYield:
this.state = GeneratorState.Executing;
return continuation._return(cx, value);
default:
throw new AssertionError();
}
}
/**
* Stops generator execution with a {@code throw} event.
*
* @param cx
* the execution context
* @param value
* the exception value
* @return the iterator result object
* @see GeneratorPrototype.Properties#_throw(ExecutionContext, Object, Object)
*/
ScriptObject _throw(ExecutionContext cx, Object value) {
switch (state) {
case Executing:
throw newTypeError(cx, Messages.Key.GeneratorExecuting);
case SuspendedStart:
close();
// fall-through
case Completed:
throw ScriptException.create(value);
case SuspendedYield:
this.state = GeneratorState.Executing;
return continuation._throw(cx, ScriptException.create(value));
default:
throw new AssertionError();
}
}
private static final class GeneratorHandler implements Continuation.Handler<ScriptObject> {
private final GeneratorObject generatorObject;
GeneratorHandler(GeneratorObject generatorObject) {
this.generatorObject = generatorObject;
}
@Override
public Object evaluate(ResumptionPoint resumptionPoint) throws Throwable {
GeneratorObject genObj = generatorObject;
return genObj.code.handle().invokeExact(genObj.context, resumptionPoint);
}
@Override
public void close() {
generatorObject.close();
}
@Override
public ScriptObject suspendWith(ExecutionContext cx, Object value) {
generatorObject.suspend();
// The iteration result object was already constructed at the call site.
return (ScriptObject) value;
}
@Override
public ScriptObject returnWith(ExecutionContext cx, Object result) {
return CreateIterResultObject(cx, result, true);
}
@Override
public ScriptObject returnWith(ExecutionContext cx, ScriptException exception) {
throw exception;
}
}
}