Enclosure.java

package Hoot.Runtime.Blocks;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import Hoot.Runtime.Faces.*;
import Hoot.Runtime.Functions.*;
import Hoot.Runtime.Values.Frame;
import Hoot.Runtime.Values.Cacheable;
import Hoot.Runtime.Values.CachedStack;
import Hoot.Runtime.Exceptions.ExceptionBase;
import Hoot.Runtime.Exceptions.ExceptionContext;
import Hoot.Runtime.Exceptions.HandledException;
import Hoot.Runtime.Exceptions.UnhandledException;
import static Hoot.Runtime.Functions.Utils.*;
import static Hoot.Runtime.Functions.Exceptional.*;
import static Hoot.Runtime.Functions.Exceptional.Result.*;
import static Hoot.Runtime.Names.Primitive.normalPriority;
import java.util.function.Predicate;

/**
 * A block enclosure.
 *
 * <h4>Enclosure Responsibilities:</h4>
 * <ul>
 * <li>knows a block</li>
 * <li>knows a block argument count</li>
 * <li>evaluates a block to produce a result</li>
 * </ul>
 *
 * @author nik <nikboyd@sonic.net>
 * @see "Copyright 2010,2021 Nikolas S Boyd."
 * @see "Permission is granted to copy this work provided this copyright statement is retained in all copies."
 */
public class Enclosure implements NiladicValuable, MonadicValuable, DyadicValuable, Cacheable<Enclosure> {

    static final Consumer<Frame> DoNothing = (Frame f) -> {};
    static final Enclosure DefaultContinuation = Enclosure.withBlock(DoNothing);
    public static Enclosure defaultContinuation() { return DefaultContinuation; }

    // convert closures to stack-based! --nik
    static final CachedStack<Enclosure> ClosureStack = new CachedStack<>();
    static { ClosureStack.defaultIfEmpty(defaultContinuation()); }

    @Override public CachedStack<Enclosure> itemStack() { return closureStack(); }
    protected static CachedStack<Enclosure> closureStack() { return ClosureStack; }

    protected static Stack<Enclosure> closures() { return closureStack().cachedStack(); }
    static public Enclosure currentClosure() { return closureStack().top(); }

    private int stackIndex = 0;
    @Override public int stackIndex() { return this.stackIndex; }
    @Override public void stackIndex(int value) { this.stackIndex = value; }

    protected Enclosure(Function<Frame,?> block) { this.block = block; }

    public static Enclosure withBlock(Supplier<?> block) {
        if (block == null) throw new IllegalArgumentException("block missing");
        return withBlock(f -> { return block.get(); }); }

    public static Enclosure withBlock(Consumer<Frame> block) {
        if (block == null) throw new IllegalArgumentException("block missing");
        return new Enclosure(f -> { block.accept(f); return null; }); }

    public static Enclosure withBlock(Function<Frame,?> block) {
        if (block == null) throw new IllegalArgumentException("block missing");
        return new Enclosure(block); }

    public static Enclosure withQuiet(Argued<Frame,?> block) {
        if (block == null) throw new IllegalArgumentException("block missing");
        return new Enclosure(f -> Exceptional.nullOrTryQuietly(block, f)); }

    public static Enclosure withQuieted(Runner block) {
        if (block == null) throw new IllegalArgumentException("block missing");
        return new Enclosure(f -> { Exceptional.runQuietly(block); return null; }); }

    @SuppressWarnings("unchecked")
    public <V> Function<Frame,V> block() { return (Function<Frame,V>)this.block; }
    private final Function<Frame,?> block;

    private Frame frame = new Frame();
    public Frame frame() { return this.frame; }
    public Enclosure withFrame(Frame frame) { this.frame = frame; return this; }
    @Override public IntegerValue argumentCount() { return IntegerValue.with(frame().countArguments()); }

    public Enclosure runQuiet() { Exceptional.runQuietly(() -> value()); return this; }
    public Enclosure runLoud()  { Exceptional.runLoudly(() -> value()); return this; }

    /**
     * @return a value resulting from the base block, after the termination block is also evaluated
     * @param terminationBlock always evaluated (finally)
     */
    public Valued ensure(Valuable terminationBlock) { unwindBlock(terminationBlock); return value(); }

    public Enclosure $catch(MonadicValuable terminationBlock) {
        runLoudly(() -> value(), caughtBy(terminationBlock)); return this; }

    private Handler<Throwable> caughtBy(MonadicValuable terminationBlock) {
        return (Throwable ex) -> { terminationBlock.value(ex); }; }

    public Valued defaultIfCurtailed(Valued defaultValue) {
        return defaultOrTrySurely(() -> value(), DebugHandler, defaultValue); }

    /**
     * @return a value resulting either from the base block, or the termination block if the base fails
     * @param terminationBlock evaluated only if an exception occurs (catch)
     */
    public Valued ifCurtailed(Valuable terminationBlock) {
        return this.on_do(ExceptionBase.type(), curtailed(terminationBlock)); }

    private MonadicValuable curtailed(Valuable terminationBlock) {
        return new MonadicValuable(){
            @Override public <V, R> R value(V value) { return terminationBlock.value(); }
            @Override public IntegerValue argumentCount() { return IntegerValue.with(1); }
        };
    }

    private Handler<Throwable> curtailment(Valuable terminationBlock) {
        return (Throwable ex) -> { terminationBlock.value(); }; }

    public <V> boolean testWithEach(Set<V> values) { return values.stream().anyMatch(each -> value(each)); }
    public <V> Set<?>  evaluateWithEach(Set<V> values) { return mapSet(values, each -> value(each)); }
//    public <V> List<?> evaluateWithEach(V ... values) { return evaluateWithEach(wrap(values)); }
    public <V> List<?> evaluateWithEach(List<V> values) { return map(values, each -> value(each)); }
    public <V,R> List<R> evaluateEach(List<V> values, Class<R> itemType) { return map(values, each -> value(each)); }
    public <V> List<String> collectStringsFrom(List<V> items) {
        return collectWith(emptyList(String.class), items, (list,each) -> { value_value(list, each); }); }

    @Override public <R> R value() { return $return(evaluated()); }
    @Override public <V, R> R value(V value) { return valueWith(value); }
    @Override public <A, B, R> R value_value(A a, B b) { return valueWith(a, b); }

    static final Object Placeholder = new Object();
    public Enclosure valueNames(String... valueNames) { return valueNames(wrap(valueNames)); }
    public Enclosure valueNames(List<String> valueNames) {
        valueNames.forEach(vn -> frame().bind(vn, Placeholder)); return this; }

    public <R> R valueWith(Object... values) {
        return nullOrTryLoudly(
                () -> { frame().withAll(values); return value(); },
                () -> { frame().purge(); }); }

    public Thread fork() { return forkAt(normalPriority()); }
    public Thread forkAt(IntegerValue priority) { return forkAt(priority.intValue()); }
    public Thread forkAt(int priority) {
        Thread result = new Thread(() -> value());
        result.setPriority(priority);
        result.start();
        return result; }

    private Enclosure unwindBlock = DefaultContinuation;
    public Enclosure unwindBlock() { return this.unwindBlock; }
    protected void activateUnwind() { if (hasAny(unwindBlock())) unwindBlock().value(); }
    protected Enclosure unwindBlock(Valuable unwindBlock) {
        this.unwindBlock = (Enclosure)unwindBlock; return unwindBlock(); }

    private void makeCurrent() { closureStack().push(this); }

    @SuppressWarnings("unchecked")
    private <R> R evaluated() {
        try {
            makeCurrent();
            return (R)block().apply(frame());
        }
        finally {
            unwindIfStacked();
        }
    }

    private void popIfStacked() { closureStack().popIfTop(this); }
    protected void unwindIfStacked() { if (onStack()) { activateUnwind(); popIfStacked(); } }
    protected void unwindTo(Frame target) {
        if (hasNo(target)) return;
        if (target.knowsMethod() && !frame().matches(target)) {
            whisper("skipping " + frame().describe());
            if (hasPrior()) {
                Enclosure parent = priorItem();
                unwindIfStacked();
                parent.unwindTo(target);
            }
            else {
                unwindIfStacked();
            }
        }
    }

    public <R> R exitMethod(Frame methodFrame, R result) {
        whisper("seeking " + methodFrame.describe());
        unwindTo(methodFrame);
        return result;
    }

    // knows which kinds of exceptions it handles, where kind = exception type primitive class
    private final Set<Class<?>> handledExceptions = new HashSet<>();
    private void clearExceptions() { handledExceptions().clear(); }
    private Set<Class<?>> handledExceptions() { return this.handledExceptions; }
    public Set<Class<?>> coveredExceptions() { return copySet(handledExceptions()); }
    private boolean handlesAny() { return !handledExceptions().isEmpty(); }
    private boolean handlesNone() { return handledExceptions().isEmpty(); }
    private boolean handledMatch(Predicate<Class<?>> p) { return matchAny(handledExceptions(), p); }
    private boolean under(Class<?> type) { return handledMatch((ht) -> type.isAssignableFrom(ht)); }
    private boolean covers(Class<?> type) { return handledMatch((ht) -> ht.isAssignableFrom(type)); }
    private Class<?> covered(Class<?> type) { return findFirst(handledExceptions(), (ht) -> type.isAssignableFrom(ht)); }

    // only adopt distinct exception types which have no heritage relationship with others
    // if there is a relationship, adopt only the most general type over those collected
    public Enclosure asHandlerOf(HandledException.Metatype ... exceptionTypes) {
        List<HandledException.Metatype> types = wrap(exceptionTypes);
        if (types.isEmpty()) return this;
        types.forEach((type) -> {
            if (hasAny(type)) {
                Class<?> typeClass = type.outerClass();
                if (handlesNone()) {
                    handledExceptions().add(typeClass);
                    enableAsHandler();
                }
                else {
                    while (under(typeClass)) { // clean out any covered types
                        handledExceptions().remove(covered(typeClass));
                    }
                    handledExceptions().add(typeClass);
                    enableAsHandler();
                }
            }
        });
        return this; }

    public boolean handles(HandledException ex) { return handles(ex.valueType()); }
    public boolean handles(Class type) { return hasAny(type) && covers(type) && isActive(); }

    private boolean activeAsHandler = false;
    public boolean isActive() { return this.activeAsHandler; }
    public void disableAsHandler() { this.activeAsHandler = false; }
    public void enableAsHandler() { this.activeAsHandler = true; }

    protected void adoptAsHandlerFor(HandledException ex) { ex.currentHandler(this); disableAsHandler(); }
    protected void releaseHandlerFor(HandledException ex) { ex.currentHandler(null); enableAsHandler(); }
    protected <R> R fireHandler(HandledException exception) {
        adoptAsHandlerFor(exception); frame().bind(0, exception);
        R value = nullOrTryLoudly(() -> evaluated(), () -> releaseHandlerFor(exception));
        return $return(value); }

    public Valued handleSignaled(HandledException exception) {
        if (handles(exception)) return fireHandler(exception);
        UnhandledException.type().raise(exception); return this; }

    public ExceptionContext resume(Valued value) { return context().resume(value); }
    public ExceptionContext retry(NiladicValuable aBlock) { return context().retry(aBlock); }
    public ExceptionContext retry() { return context().retry(); }

    public <R> R $return(R v) {
//        unwindIfNeeded();
        return (R)v;
    }

    @SuppressWarnings("unchecked")
    @Override public Valued on_do(Valued.Metatype exceptionType, MonadicValuable handler) {
        Enclosure alias = (Enclosure)handler;
        alias.asHandlerOf((HandledException.Metatype)exceptionType);
        return nullOrTryQuietly(
            () -> ExceptionContext.during_handle(this, handler),
            () -> alias.clearExceptions()); }

    private ExceptionContext context;
    public ExceptionContext context() { return this.context; }
    public Enclosure context(ExceptionContext ctx) { this.context = ctx; return this; }
    public void passFrom(HandledException ex) { if (hasAny(context())) context().passFrom(ex); }

} // Enclosure