package php.runtime.lang; import php.runtime.Memory; import php.runtime.env.CallStack; import php.runtime.env.CallStackItem; import php.runtime.env.Environment; import php.runtime.env.TraceInfo; import php.runtime.exceptions.CriticalException; import php.runtime.exceptions.FinallyException; import php.runtime.exceptions.support.ErrorType; import php.runtime.lang.spl.iterator.Iterator; import php.runtime.lang.support.IManualDestructable; import php.runtime.memory.KeyValueMemory; import php.runtime.memory.ReferenceMemory; import php.runtime.reflection.ClassEntity; import php.runtime.reflection.helper.GeneratorEntity; import php.runtime.util.generator.YieldAdapterIterator; import java.util.NoSuchElementException; import static php.runtime.annotation.Reflection.*; @Name("Generator") @Final abstract public class Generator extends BaseObject implements Iterator, IManualDestructable { private enum ClosedType { DEFAULT, MANUAL } protected Memory self; protected final Memory[] uses; protected boolean isInit = false; protected int counter = 0; protected boolean valid = true; protected final YieldAdapterIterator<Bucket> iterator; protected final php.runtime.util.generator.Generator<Bucket> gen; protected final static ThreadLocal<Generator> currentGenerator = new ThreadLocal<Generator>(); protected CallStackItem callStackItem; protected CallStack callStack; protected Throwable lastThrowable = null; protected RuntimeException newThrow = null; protected boolean busy; protected ClosedType closed; public Generator(final Environment env, ClassEntity generator, Memory self, Memory[] uses) { super(env, generator); if (generator == null) { throw new CriticalException("Unable to create generator"); } env.registerObjectInGC(this); this.self = self; this.uses = uses; CallStackItem stackItem = env.peekCall(0); this.callStack = env.getCallStack(); this.callStackItem = stackItem == null ? null : new CallStackItem(stackItem); gen = new php.runtime.util.generator.Generator<Bucket>() { @Override protected void run(YieldAdapterIterator<Bucket> yieldAdapter) { try { currentGenerator.set(Generator.this); env.__replaceCallStack(Generator.this.callStack); Generator.this._run(env); } catch (Throwable e) { lastThrowable = e; Generator.this.setCurrent(Memory.NULL); } } }; iterator = gen.iterator(); } abstract protected Memory _run(Environment env, Memory... args); protected Memory _next(Environment env) { if (busy) { env.error(env.trace(), "Cannot resume an already running generator"); } boolean x2 = false; if (callStackItem != null) { env.pushCall(new CallStackItem(callStackItem)); x2 = true; } try { counter += 1; busy = true; return iterator.next().getValue(); } catch (NoSuchElementException e) { valid = false; callStackItem = null; } finally { if (x2) env.popCall(); busy = false; checkThrow(); } return null; } protected void checkNewThrow() { if (newThrow != null) { try { throw newThrow; } finally { this.newThrow = null; } } } protected void checkThrow() { if (lastThrowable != null) { try { if (lastThrowable instanceof RuntimeException) { throw (RuntimeException)lastThrowable; } throw new RuntimeException(lastThrowable); } finally { this.lastThrowable = null; } } } public boolean isReturnReferences() { return ((GeneratorEntity)getReflection()).isReturnReference(); } @Override public void onManualDestruct(Environment env) { closed = ClosedType.MANUAL; } @Signature public Memory __destruct(Environment env, Memory... args) { if (isInit) { if (closed == null) { closed = ClosedType.DEFAULT; } newThrow = new FinallyException(); _next(env); } return Memory.NULL; } @Override @Signature public Memory next(Environment env, Memory... args) { _next(env); return Memory.NULL; } @Signature(@Arg("value")) synchronized public Memory send(Environment env, Memory... args) { if (!isInit) { rewind(env); } Bucket current = iterator.getCurrentValue(); if (current == null) { iterator.setCurrentValue(new Bucket(args[0])); } else { current.pushValue(args[0]); } return _next(env); } @Name("throw") @Signature(@Arg(value = "throwable", nativeType = BaseException.class)) synchronized public Memory _throw(Environment env, Memory... args) { if (valid) { newThrow = args[0].toObject(BaseException.class); ((BaseException)newThrow).setTraceInfo(env, env.trace()); return _next(env); } else { env.__throwException(args[0].toObject(BaseException.class)); } return Memory.NULL; } @Signature @Override public Memory rewind(Environment env, Memory... args) { if (!valid) { env.exception("Cannot traverse an already closed generator"); } if (counter > 1) { env.exception("Cannot rewind a generator that was already run"); } if (!isInit) { counter = 0; _next(env); isInit = true; } return Memory.NULL; } @Signature @Override public Memory valid(Environment env, Memory... args) { return valid ? Memory.TRUE : Memory.FALSE; } @Signature @Override public Memory current(Environment env, Memory... args) { if (!isInit) { rewind(env); } if (iterator.getCurrentValue() == null) { return Memory.NULL; } return iterator.getCurrentValue().getValue(); } protected Memory __current() { Bucket current = iterator.getCurrentValue(); return current == null ? Memory.NULL : current.getValue(); } @Signature @Override public Memory key(Environment env, Memory... args) { if (!isInit) { rewind(env); } if (iterator.getCurrentValue() == null) { return Memory.NULL; } return iterator.getCurrentValue().getKey(); } @Signature final public Memory __clone(Environment env, Memory... args) { env.error(ErrorType.E_ERROR, "Trying to clone an uncloneable object of class " + getReflection().getName()); return Memory.NULL; } @Signature public Memory __wakeup(Environment env, Memory... args) { env.exception("Unserialization of 'Generator' is not allowed"); return Memory.NULL; } @Signature private Memory __sleep(Environment env, Memory... args) { env.exception(env.trace(), "Serialization of 'Generator' is not allowed"); return Memory.NULL; } public CallStackItem getCallStackItem() { return callStackItem; } protected void _setValid(boolean valid) { checkNewThrow(); this.valid = valid; if (!valid) { callStackItem = null; } } protected Memory yield(Environment env, TraceInfo trace) { return yield(env, trace, Memory.NULL); } protected Bucket setCurrent(Memory value) { boolean returnRef = (((GeneratorEntity)getReflection()).isReturnReference()); Bucket current = iterator.getCurrentValue(); if (value instanceof KeyValueMemory) { if (current != null) { current.setKey(((KeyValueMemory) value).key); current.setValue(returnRef ? new ReferenceMemory(value) : value.toValue()); } else { current = new Bucket(((KeyValueMemory) value).key, returnRef ? new ReferenceMemory(value) : value.toValue()); } } else { if (returnRef) { value = new ReferenceMemory(value); } if (current != null) { current.pushValue(value); } else { current = new Bucket(Memory.CONST_INT_0, value); } } return current; } protected Memory yield(Environment env, TraceInfo trace, Memory value) { if (closed == ClosedType.MANUAL) { env.error(trace, "Cannot yield from finally in a force-closed generator"); } checkNewThrow(); Bucket current = setCurrent(value); gen.yield(current); checkNewThrow(); return current.getValue(); } protected Memory yield(Memory key, Memory value) { return gen.yield(new Bucket(key, value)).getValue(); } public static Generator current() { return currentGenerator.get(); } private static class Bucket { private Memory key; private Memory value; private Bucket(Memory value) { this.key = Memory.CONST_INT_0; this.value = value; } private Bucket(Memory key, Memory value) { this.key = key; this.value = value; } public Memory getKey() { return key; } public void setKey(Memory key) { this.key = key; } public Memory getValue() { return value; } public void setValue(Memory value) { this.value = value; } public void pushValue(Memory value) { key = key.inc(); this.value = value; } } }