Evaluator.java

package com.github.dakusui.pcond.core;

import com.github.dakusui.pcond.experimentals.currying.context.CurriedContext;

import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import static com.github.dakusui.pcond.core.EvaluationContext.formNameOf;
import static com.github.dakusui.pcond.core.EvaluationContext.resolveEvaluationEntryType;
import static com.github.dakusui.pcond.core.EvaluationEntry.Type.*;
import static com.github.dakusui.pcond.core.EvaluationEntry.composeDetailOutputActualValueFromInputAndThrowable;
import static com.github.dakusui.pcond.core.ValueHolder.CreatorFormType.FUNC_HEAD;
import static com.github.dakusui.pcond.core.ValueHolder.CreatorFormType.FUNC_TAIL;
import static com.github.dakusui.pcond.core.ValueHolder.State.*;
import static com.github.dakusui.pcond.internals.InternalUtils.explainValue;
import static com.github.dakusui.pcond.internals.InternalUtils.isDummyFunction;
import static java.util.Objects.requireNonNull;

/**
 * A visitor interface that defines a mechanism to "evaluate" printable predicates.
 */
public interface Evaluator {
  /**
   * Evaluates `value` with `conjunction` predicate ("and").
   *
   * @param <T>               The type of the `value`.
   * @param evaluableIo       An object to hold an evaluable and its input and output.
   * @param evaluationContext An evaluation context.
   * @see com.github.dakusui.pcond.core.Evaluable.Conjunction
   */
  <T> void evaluateConjunction(EvaluableIo<T, Evaluable.Conjunction<T>, Boolean> evaluableIo, EvaluationContext<T> evaluationContext);

  /**
   * Evaluates `value` with a `disjunction` predicate ("or").
   *
   * @param <T>               The type of the `value`.
   * @param evaluableIo       An object to hold an evaluable and its input and output.
   * @param evaluationContext An evaluation context.
   * @see com.github.dakusui.pcond.core.Evaluable.Disjunction
   */
  <T> void evaluateDisjunction(EvaluableIo<T, Evaluable.Disjunction<T>, Boolean> evaluableIo, EvaluationContext<T> evaluationContext);

  /**
   * Evaluates `value` with a `negation` predicate ("not").
   *
   * @param <T>               The type of the `value`.
   * @param evaluableIo       An object to hold an evaluable and its input and output.
   * @param evaluationContext An evaluation context.
   * @see com.github.dakusui.pcond.core.Evaluable.Negation
   */
  <T> void evaluateNegation(EvaluableIo<T, Evaluable.Negation<T>, Boolean> evaluableIo, EvaluationContext<T> evaluationContext);

  /**
   * Evaluates `value` with a leaf predicate.
   *
   * @param <T>               The type of the `value`.
   * @param evaluableIo       An object to hold an evaluable and its input and output.
   * @param evaluationContext An evaluation context.
   * @see com.github.dakusui.pcond.core.Evaluable.LeafPred
   */
  <T> void evaluateLeaf(EvaluableIo<T, Evaluable.LeafPred<T>, Boolean> evaluableIo, EvaluationContext<T> evaluationContext);

  /**
   * Evaluates `value` with a "function" predicate.
   *
   * @param evaluableIo       An object to hold an evaluable and its input and output.
   * @param evaluationContext An evaluation context.
   * @see com.github.dakusui.pcond.core.Evaluable.Func
   */
  <T, R> void evaluateFunction(EvaluableIo<T, Evaluable.Func<T>, R> evaluableIo, EvaluationContext<T> evaluationContext);

  /**
   * Evaluates `value` with a context predicate.
   *
   * @param evaluableIo       An object to hold an evaluable and its input and output.
   * @param evaluationContext An evaluation context.
   * @see Evaluable.CurriedContextPred
   */
  void evaluateCurriedContextPredicate(EvaluableIo<CurriedContext, Evaluable.CurriedContextPred, Boolean> evaluableIo, EvaluationContext<CurriedContext> evaluationContext);

  /**
   * Evaluates `value` with a "transformation" predicate.
   *
   * @param evaluableIo       An object to hold an evaluable and its input and output.
   * @param evaluationContext An evaluation context.
   * @see com.github.dakusui.pcond.core.Evaluable.Transformation
   */
  <T, R> void evaluateTransformation(EvaluableIo<T, Evaluable.Transformation<T, R>, Boolean> evaluableIo, EvaluationContext<T> evaluationContext);

  /**
   * Evaluates `value` with a predicate for a stream.
   *
   * @param evaluableIo       An object to hold an evaluable and its input and output.
   * @param evaluationContext An evaluation context.
   * @see com.github.dakusui.pcond.core.Evaluable.StreamPred
   */
  <E> void evaluateStreamPredicate(EvaluableIo<Stream<E>, Evaluable.StreamPred<E>, Boolean> evaluableIo, EvaluationContext<Stream<E>> evaluationContext);

  /**
   * Returns a new instance of this interface.
   *
   * @return a new instance of this interface.
   */
  static Evaluator create() {
    return new Impl();
  }

  class Impl implements Evaluator {
    public static final Object EVALUATION_SKIPPED = new Object() {
      @Override
      public String toString() {
        return "(not evaluated)";
      }
    };

    private static final Object NULL_VALUE = new Object() {
      public String toString() {
        return "null";
      }
    };

    public Impl() {
    }

    @Override
    public <T> void evaluateConjunction(EvaluableIo<T, Evaluable.Conjunction<T>, Boolean> evaluableIo, EvaluationContext<T> evaluationContext) {
      evaluationContext.evaluate(
          evaluableIo,
          (Evaluable.Conjunction<T> evaluable, ValueHolder<T> input) -> {
            ValueHolder<Boolean> ret = ValueHolder.create();
            boolean result = true;
            ValueHolder<Boolean> retSkipped = null;
            for (Evaluable<T> each : evaluable.children()) {
              EvaluableIo<T, Evaluable<T>, Boolean> child = createChildEvaluableIoOf(each, input);
              each.accept(child, evaluationContext, this);
              ValueHolder<Boolean> outputFromEach = child.output();
              if (outputFromEach.isValueReturned()) {
                result &= outputFromEach.returnedValue();
                ret = ValueHolder.forValue(result);
              } else if (child.output().isExceptionThrown()) {
                ret = ValueHolder.<Boolean>create().evaluationSkipped();
                retSkipped = retSkipped != null ? retSkipped : ret;
              } else if (child.output().isEvaluationSkipped()) {
                ret = ValueHolder.<Boolean>create().evaluationSkipped();
                retSkipped = retSkipped != null ? retSkipped : ret;
              } else
                assert false;
              if (evaluable.shortcut() && (ret.isEvaluationSkipped() || !result))
                break;
            }
            return retSkipped != null ? retSkipped : ret;
          });
    }

    @Override
    public <T> void evaluateDisjunction(EvaluableIo<T, Evaluable.Disjunction<T>, Boolean> evaluableIo, EvaluationContext<T> evaluationContext) {
      evaluationContext.evaluate(
          evaluableIo,
          (Evaluable.Disjunction<T> evaluable, ValueHolder<T> input) -> {
            ValueHolder<Boolean> ret = ValueHolder.create();
            boolean result = false;
            ValueHolder<Boolean> retSkipped = null;
            for (Evaluable<T> each : evaluable.children()) {
              EvaluableIo<T, Evaluable<T>, Boolean> child = createChildEvaluableIoOf(each, input);
              each.accept(child, evaluationContext, this);
              ValueHolder<Boolean> outputFromEach = child.output();
              if (outputFromEach.isValueReturned()) {
                result |= outputFromEach.returnedValue();
                ret = ValueHolder.forValue(result);
              } else if (outputFromEach.isExceptionThrown()) {
                ret = ValueHolder.<Boolean>create().evaluationSkipped();
                retSkipped = retSkipped != null ? retSkipped : ret;
              } else if (outputFromEach.isEvaluationSkipped()) {
                ret = ValueHolder.<Boolean>create().evaluationSkipped();
                retSkipped = retSkipped != null ? retSkipped : ret;
              } else
                assert false;
              if (evaluable.shortcut() && (ret.isEvaluationSkipped() || result))
                break;
            }
            return retSkipped != null ? retSkipped : ret;
          });
    }

    @Override
    public <T> void evaluateNegation(EvaluableIo<T, Evaluable.Negation<T>, Boolean> evaluableIo, EvaluationContext<T> evaluationContext) {
      evaluationContext.evaluate(
          evaluableIo,
          (Evaluable.Negation<T> evaluable, ValueHolder<T> input) -> {
            evaluationContext.flipExpectation();
            try {
              EvaluableIo<T, Evaluable<T>, Boolean> childIo = createChildEvaluableIoOf(evaluable.target(), input);
              evaluable.target().accept(childIo, evaluationContext, this);
              return childIo.output().isValueReturned() ?
                  ValueHolder.forValue(evaluationContext.isExpectationFlipped() ^ childIo.output().returnedValue()) :
                  childIo.output();
            } finally {
              evaluationContext.flipExpectation();
            }
          }
      );
    }

    @Override
    public <T> void evaluateLeaf(EvaluableIo<T, Evaluable.LeafPred<T>, Boolean> evaluableIo, EvaluationContext<T> evaluationContext) {
      evaluationContext.evaluate(
          LEAF,
          evaluableIo,
          (evaluable, input) -> {
            ValueHolder<Boolean> ret = ValueHolder.create();
            if (input.isValueReturned()) {
              T value = input.returnedValue();
              Predicate<? super T> predicate = requireNonNull(evaluable.predicate());
              try {
                return ret.valueReturned(predicate.test(value));
              } catch (Throwable t) {
                return ret.exceptionThrown(t);
              }
            } else
              return ret.evaluationSkipped();
          });
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T, R> void evaluateFunction(EvaluableIo<T, Evaluable.Func<T>, R> evaluableIo, EvaluationContext<T> evaluationContext) {
      evaluationContext.evaluate( //#2
          FUNCTION,
          evaluableIo,
          (Evaluable.Func<T> evaluable, ValueHolder<T> input) -> {
            ValueHolder<R> ret;
            {
              EvaluableIo<T, Evaluable<T>, Object> ioForHead = createChildEvaluableIoOf(evaluable, input);
              EvaluationContext<T> childContext = new EvaluationContext<>(evaluationContext);
              childContext.evaluate(FUNCTION, ioForHead, io -> {
                ValueHolder<Object> tmp = ValueHolder.create();
                if (io.input().isValueReturned())
                  tmp = applyFunction(tmp, io.input().returnedValue(), ((Evaluable.Func<T>) io.evaluable()).head());
                else
                  tmp = tmp.evaluationSkipped();
                return tmp.creatorFormType(FUNC_HEAD);
              });
              evaluationContext.importEntries(childContext, 1);
              ret = (ValueHolder<R>) ioForHead.output().creatorFormType(FUNC_TAIL);
            }
            ValueHolder<Object> finalRet = (ValueHolder<Object>) ret;
            return evaluable.tail().map((Evaluable<Object> e) -> {
                  EvaluableIo<Object, Evaluable<Object>, R> ioForTail = createChildEvaluableIoOf(e, finalRet);
                  DebuggingUtils.printIo("FUNC_TAIL:BEFORE", ioForTail);
                  e.accept(ioForTail, (EvaluationContext<Object>) evaluationContext, this);
                  DebuggingUtils.printIo("FUNC_TAIL:AFTER", ioForTail);
                  return ioForTail.output().creatorFormType(FUNC_TAIL);
                })
                .orElse(ret);
          });
    }

    @SuppressWarnings("unchecked")
    private static <T, R> ValueHolder<R> applyFunction(ValueHolder<R> ret, T in, Function<? super T, Object> function) {
      try {
        R returnedValue;
        returnedValue = (R) function.apply(in);
        return ret.valueReturned(returnedValue);
      } catch (Throwable t) {
        return ret.exceptionThrown(t);
      }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public <T, R> void evaluateTransformation(EvaluableIo<T, Evaluable.Transformation<T, R>, Boolean> evaluableIo, EvaluationContext<T> evaluationContext) {
      if (isDummyFunction((Function<?, ?>) evaluableIo.evaluable().mapper())) {
        evaluableIo.evaluable().checker().accept((EvaluableIo<R, Evaluable<R>, Boolean>) (Evaluable) evaluableIo, (EvaluationContext<R>) evaluationContext, this);
        return;
      }
      EvaluationContext<T> childContext = new EvaluationContext<>(evaluationContext);
      childContext.evaluate(
          evaluableIo,
          (Evaluable.Transformation<T, R> evaluable, ValueHolder<T> input) -> {
            DebuggingUtils.printInput("TRANSFORMATION:BEFORE", evaluable, input);
            EvaluableIo<T, Evaluable<T>, R> mapperIo = evaluateMapper(evaluable.mapperName().orElse("transform"), evaluable.mapper(), input, childContext);
            EvaluableIo<R, Evaluable<R>, Boolean> checkerIo = evaluateChecker(evaluable.checkerName().orElse("check"), evaluable.checker(), mapperIo.output(), childContext);
            DebuggingUtils.printInputAndOutput(evaluable, input, checkerIo.output());
            return checkerIo.output();
          }
      );
      evaluationContext.importEntries(childContext, 1);
    }

    private <T, R> EvaluableIo<T, Evaluable<T>, R> evaluateMapper(String mapperName, Evaluable<T> mapper, ValueHolder<T> input, EvaluationContext<T> evaluationContext) {
      EvaluableIo<T, Evaluable<T>, R> ioForMapper = createChildEvaluableIoOf(mapper, input.creatorFormType(ValueHolder.CreatorFormType.TRANSFORM));
      {
        EvaluationContext<T> childContext = new EvaluationContext<>(evaluationContext);

        // #1
        childContext.evaluate(TRANSFORM, mapperName, ioForMapper, io -> {
          DebuggingUtils.printIo("TRANSFORM:BEFORE", io);
          io.evaluable().accept(io, childContext, this);
          DebuggingUtils.printIo("TRANSFORM:AFTER", io);
          return io.output();
        });

        evaluationContext.importEntries(childContext, 0);
      }
      return ioForMapper;
    }

    private <T, R> EvaluableIo<R, Evaluable<R>, Boolean> evaluateChecker(String checkerName, Evaluable<R> checker, ValueHolder<R> input, EvaluationContext<T> evaluationContext) {
      EvaluableIo<R, Evaluable<R>, Boolean> ioForChecker = createChildEvaluableIoOf(checker, input);
      {
        EvaluationContext<R> childContext = new EvaluationContext<>(evaluationContext);

        childContext.evaluate(CHECK, checkerName, ioForChecker, io -> {
          DebuggingUtils.printIo("CHECK:BEFORE", io);
          io.evaluable().accept(io, childContext, this);
          DebuggingUtils.printIo("CHECK:AFTER", io);
          return io.output();
        });

        evaluationContext.importEntries(childContext, 0);
      }
      return ioForChecker;
    }

    //             ValueToCut  ValueOnCut ValueForNone(=default)
    // NoneMatch         true       false                   true
    // AnyMatch          true        true                  false
    // AllMatch         false       false                   true

    @Override
    public <E> void evaluateStreamPredicate(EvaluableIo<Stream<E>, Evaluable.StreamPred<E>, Boolean> evaluableIo, EvaluationContext<Stream<E>> evaluationContext) {
      evaluationContext.evaluate(
          evaluableIo,
          (Evaluable.StreamPred<E> evaluable, ValueHolder<Stream<E>> input) -> input.returnedValue()
              .map((E e) -> {
                if (evaluable.requestExpectationFlip())
                  evaluationContext.flipExpectation();
                try {
                  EvaluationContext<E> childContext = new EvaluationContext<>(evaluationContext);
                  EvaluableIo<E, Evaluable<E>, Boolean> ioForCutPredicate = createChildEvaluableIoOf(evaluable.cut(), ValueHolder.forValue(e));
                  evaluable.cut().accept(ioForCutPredicate, childContext, this);
                  evaluationContext.importEntries(childContext);
                  return ioForCutPredicate.output();
                } finally {
                  if (evaluable.requestExpectationFlip())
                    evaluationContext.flipExpectation();
                }
              })
              .filter(eachResult -> {
                if (!eachResult.isValueReturned())
                  return true;
                return eachResult.returnedValue() == evaluable.valueToCut();
              })
              .map(eachResult -> eachResult.valueReturned(!evaluable.defaultValue())) // compute Value on cut
              .findFirst()
              .orElseGet(() -> ValueHolder.forValue(evaluable.defaultValue())));      // compute Value for none
    }

    @Override
    public void evaluateCurriedContextPredicate(EvaluableIo<CurriedContext, Evaluable.CurriedContextPred, Boolean> evaluableIo, EvaluationContext<CurriedContext> evaluationContext) {
      evaluationContext.evaluate(evaluableIo, (Evaluable.CurriedContextPred evaluable, ValueHolder<CurriedContext> input) -> {
        EvaluableIo<Object, Evaluable<Object>, Boolean> io = createChildEvaluableIoOf(evaluable.enclosed(), ValueHolder.forValue(input.returnedValue().valueAt(evaluable.argIndex())));
        EvaluationContext<Object> childContext = new EvaluationContext<>(evaluationContext);
        evaluable.enclosed().accept(io, childContext, this);
        evaluationContext.importEntries(childContext);
        return io.output();
      });
    }

    private static <T, E extends Evaluable<T>, O> EvaluableIo<T, Evaluable<T>, O> createChildEvaluableIoOf(E evaluable, ValueHolder<T> input) {
      return createChildEvaluableIoOf(resolveEvaluationEntryType(evaluable).formName(evaluable), evaluable, input);
    }

    private static <T, E extends Evaluable<T>, O> EvaluableIo<T, Evaluable<T>, O> createChildEvaluableIoOf(String formName, E evaluable, ValueHolder<T> input) {
      EvaluationEntry.Type evaluableType = resolveEvaluationEntryType(evaluable);
      return createChildEvaluableIoOf(evaluableType, formName, evaluable, input);
    }

    private static <T, E extends Evaluable<T>, O> EvaluableIo<T, Evaluable<T>, O> createChildEvaluableIoOf(EvaluationEntry.Type evaluableType, String formName, E evaluable, ValueHolder<T> input) {
      return new EvaluableIo<>(input, evaluableType, formName, evaluable);
    }
  }

  /**
   * If an input or an output value object of a form implements this interface,
   * The value returned by `snapshot` method is stored in a {@link EvaluationEntry}
   * record, instead of the value itself.
   *
   * An implementation of this interface should override `toString()` method to return a string form of the original state of this object.
   */
  interface Snapshottable {

    Object NULL = new Object() {
      @Override
      public String toString() {
        return "null";
      }
    };

    Object snapshot();

    static Object toSnapshotIfPossible(Object value) {
      if (value instanceof Snapshottable)
        return ((Snapshottable) value).snapshot();
      if (value == null)
        return NULL;
      else
        return value;
    }
  }

  /**
   * An interface to define methods that make a predicate "explainable" to humans.
   */
  interface Explainable {
    Object explainOutputExpectation();

    Object explainActual(Object actualValue);

    static Object explainOutputExpectation(Object evaluable, EvaluableIo<?, ?, ?> evaluableIo) {
      if (evaluable instanceof Explainable)
        return explainValue(((Explainable) evaluable).explainOutputExpectation());
      if (evaluable instanceof Evaluable)
        return formNameOf(evaluableIo);
      return null;
    }

    static Object explainInputActualValue(Object evaluable, Object actualValue) {
      if (evaluable instanceof Explainable)
        return explainValue(((Explainable) evaluable).explainActual(actualValue));
      return null;
    }

    static <T, E extends Evaluable<T>> Object explainActual(EvaluableIo<T, E, ?> evaluableIo) {
      if (evaluableIo.output().state() == VALUE_RETURNED) {
        T ret = evaluableIo.input().returnedValue();
        return ret != null ? ret : Impl.NULL_VALUE;
      } else if (evaluableIo.output().state() == EXCEPTION_THROWN)
        return composeDetailOutputActualValueFromInputAndThrowable(evaluableIo.input().value(), evaluableIo.output().thrownException());
      else if (evaluableIo.output().state() == EVALUATION_SKIPPED) {
        return EVALUATION_SKIPPED;
      } else
        throw new AssertionError("evaluableIo:" + evaluableIo);
    }
  }
}