Package info.bliki.wiki.template.expr.eval

Source Code of info.bliki.wiki.template.expr.eval.DoubleEvaluator

package info.bliki.wiki.template.expr.eval;

import info.bliki.wiki.template.expr.Parser;
import info.bliki.wiki.template.expr.SyntaxError;
import info.bliki.wiki.template.expr.ast.ASTNode;
import info.bliki.wiki.template.expr.ast.FunctionNode;
import info.bliki.wiki.template.expr.ast.NumberNode;
import info.bliki.wiki.template.expr.ast.SymbolNode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Evaluate math expressions to <code>double</code> numbers.
*
* Use the expression syntax describe here: <a
* href="http://meta.wikimedia.org/wiki/Help:ParserFunctions#.23expr:"
* >http://meta.wikimedia.org/wiki/Help:ParserFunctions#.23expr:</a>
*/
public class DoubleEvaluator {
  /**
   * A "close to zero" double epsilon value for use to compare with 0.0
   *
   */
  public static double EPSILON = 1.0e-17d;

  /**
   * A "close to zero" double epsilon value for use in the Round function
   *
   */
  public static double EPSILON_ROUND = 1.0e-15d;

  Double bd;

  private static Map<String, Double> SYMBOL_DOUBLE_MAP;

  private static Map<String, Object> FUNCTION_DOUBLE_MAP;

  private static Map<String, Object> FUNCTION_BOOLEAN_MAP;

  static class PlusFunction implements IDoubleFunction, IDouble2Function {
    public double evaluate(double arg1, double arg2) {
      return arg1 + arg2;
    }

    public double evaluate(DoubleEvaluator engine, FunctionNode function) {
      double result = 0.0;
      for (int i = 1; i < function.size(); i++) {
        result += engine.evaluateNode(function.get(i));
      }
      return result;
    }
  }

  static class SubtractFunction implements IDoubleFunction, IDouble2Function {
    public double evaluate(double arg1, double arg2) {
      return arg1 - arg2;
    }

    public double evaluate(DoubleEvaluator engine, FunctionNode function) {
      double result = 0.0;
      for (int i = 1; i < function.size(); i++) {
        result += engine.evaluateNode(function.get(i));
      }
      return result;
    }
  }

  static class TimesFunction implements IDoubleFunction, IDouble2Function {
    public double evaluate(double arg1, double arg2) {
      return arg1 * arg2;
    }

    public double evaluate(DoubleEvaluator engine, FunctionNode function) {
      double result = 1.0;
      for (int i = 1; i < function.size(); i++) {
        result *= engine.evaluateNode(function.get(i));
      }
      return result;
    }
  }

  static class DivideFunction implements IDouble2Function {
    public double evaluate(double arg1, double arg2) {
      if (Math.abs(arg2 - 0.0) < DoubleEvaluator.EPSILON) {
        throw new ArithmeticException("Division by zero");
      }
      return arg1 / arg2;
    }
  }

  static class ModFunction implements IDouble2Function {
    public double evaluate(double arg1, double arg2) {
      if (Math.abs(arg2 - 0.0) < DoubleEvaluator.EPSILON) {
        throw new ArithmeticException("Division by zero");
      }
      return ((int) arg1) % ((int) arg2);
    }
  }

  static class PowFunction implements IDouble2Function {
    public double evaluate(double arg1, double arg2) {
      return Math.pow(arg1, arg2);
    }

  }

  static {
    SYMBOL_DOUBLE_MAP = new HashMap<String, Double>();
    FUNCTION_BOOLEAN_MAP = new HashMap<String, Object>();
    SYMBOL_DOUBLE_MAP.put("E", new Double(Math.E));
    SYMBOL_DOUBLE_MAP.put("Pi", new Double(Math.PI));

    FUNCTION_BOOLEAN_MAP.put("And", new IBooleanBoolean2Function() {
      public boolean evaluate(boolean arg1, boolean arg2) {
        return arg1 && arg2;
      }
    });
    FUNCTION_BOOLEAN_MAP.put("Not", new IBooleanBoolean1Function() {
      public boolean evaluate(boolean arg1) {
        return !arg1;
      }
    });
    FUNCTION_BOOLEAN_MAP.put("Or", new IBooleanBoolean2Function() {
      public boolean evaluate(boolean arg1, boolean arg2) {
        return arg1 || arg2;
      }
    });

    FUNCTION_BOOLEAN_MAP.put("Equal", new IBooleanDouble2Function() {
      public boolean evaluate(double arg1, double arg2) {
        return Math.abs(arg1 - arg2) < EPSILON;
      }
    });
    FUNCTION_BOOLEAN_MAP.put("Greater", new IBooleanDouble2Function() {
      public boolean evaluate(double arg1, double arg2) {
        return arg1 > arg2;
      }
    });
    FUNCTION_BOOLEAN_MAP.put("GreaterEqual", new IBooleanDouble2Function() {
      public boolean evaluate(double arg1, double arg2) {
        return arg1 >= arg2;
      }
    });
    FUNCTION_BOOLEAN_MAP.put("Less", new IBooleanDouble2Function() {
      public boolean evaluate(double arg1, double arg2) {
        return arg1 < arg2;
      }
    });
    FUNCTION_BOOLEAN_MAP.put("LessEqual", new IBooleanDouble2Function() {
      public boolean evaluate(double arg1, double arg2) {
        return arg1 <= arg2;
      }
    });
    FUNCTION_BOOLEAN_MAP.put("Unequal", new IBooleanDouble2Function() {
      public boolean evaluate(double arg1, double arg2) {
        return !(Math.abs(arg1 - arg2) < EPSILON);
      }
    });

    FUNCTION_DOUBLE_MAP = new HashMap<String, Object>();
    FUNCTION_DOUBLE_MAP.put("Sin", new IDouble1Function() {
      public double evaluate(double arg1) {
        return Math.sin(arg1);
      }
    });
    FUNCTION_DOUBLE_MAP.put("Cos", new IDouble1Function() {
      public double evaluate(double arg1) {
        return Math.cos(arg1);
      }
    });
    FUNCTION_DOUBLE_MAP.put("Tan", new IDouble1Function() {
      public double evaluate(double arg1) {
        return Math.tan(arg1);
      }
    });
    FUNCTION_DOUBLE_MAP.put("ASin", new IDouble1Function() {
      public double evaluate(double arg1) {
        return Math.asin(arg1);
      }
    });
    FUNCTION_DOUBLE_MAP.put("ACos", new IDouble1Function() {
      public double evaluate(double arg1) {
        return Math.acos(arg1);
      }
    });
    FUNCTION_DOUBLE_MAP.put("ATan", new IDouble1Function() {
      public double evaluate(double arg1) {
        return Math.atan(arg1);
      }
    });
    FUNCTION_DOUBLE_MAP.put("Ln", new IDouble1Function() {
      public double evaluate(double arg1) {
        return Math.log(arg1);
      }
    });
    FUNCTION_DOUBLE_MAP.put("Exp", new IDouble1Function() {
      public double evaluate(double arg1) {
        return Math.exp(arg1);
      }
    });
    FUNCTION_DOUBLE_MAP.put("Abs", new IDouble1Function() {
      public double evaluate(double arg1) {
        return Math.abs(arg1);
      }
    });
    FUNCTION_DOUBLE_MAP.put("Ceil", new IDouble1Function() {
      public double evaluate(double arg1) {
        return Math.ceil(arg1);
      }
    });
    FUNCTION_DOUBLE_MAP.put("Floor", new IDouble1Function() {
      public double evaluate(double arg1) {
        return Math.floor(arg1);
      }
    });
    FUNCTION_DOUBLE_MAP.put("Trunc", new IDouble1Function() {
      public double evaluate(double arg1) {
        if (arg1 < 0.0d) {
          return Math.ceil(arg1);
        } else {
          return Math.floor(arg1);
        }
      }
    });
    FUNCTION_DOUBLE_MAP.put("Plus", new PlusFunction());
    FUNCTION_DOUBLE_MAP.put("Subtract", new SubtractFunction());
    FUNCTION_DOUBLE_MAP.put("Times", new TimesFunction());
    FUNCTION_DOUBLE_MAP.put("Divide", new DivideFunction());
    FUNCTION_DOUBLE_MAP.put("Mod", new ModFunction());
    FUNCTION_DOUBLE_MAP.put("Pow", new PowFunction());

    FUNCTION_DOUBLE_MAP.put("Round", new IDouble2Function() {
      public double evaluate(double arg1, double arg2) {
        double pow10arg2 = Math.pow(10, arg2);
        if (arg1 > 0) {
          return Math.round((arg1 + EPSILON_ROUND) * pow10arg2) / pow10arg2;
        } else {
          return Math.round((arg1 - EPSILON_ROUND) * pow10arg2) / pow10arg2;
        }
      }
    });
  }

  private ASTNode fNode;

  public DoubleEvaluator() {
    this(null);
  }

  public DoubleEvaluator(ASTNode node) {
    fNode = node;
  }

  /**
   * Parse the given <code>expression String</code> and store the resulting
   * ASTNode in this DoubleEvaluator
   *
   * @param expression
   * @return
   * @throws SyntaxError
   */
  public ASTNode parse(String expression) {
    Parser p = new Parser();
    fNode = p.parse(expression);
    if (fNode instanceof FunctionNode) {
      fNode = optimizeFunction((FunctionNode) fNode);
    }
    return fNode;
  }

  /**
   * Parse the given <code>expression String</code> and return the resulting
   * ASTNode
   *
   * @param expression
   * @return
   * @throws SyntaxError
   */
  public static ASTNode parseNode(String expression) {
    DoubleEvaluator doubleEvaluator = new DoubleEvaluator();
    return doubleEvaluator.parse(expression);
  }

  public double evalStep(String expression) {
    Parser p = new Parser();
    fNode = p.parse(expression);
    if (fNode instanceof FunctionNode) {
      fNode = optimizeFunction((FunctionNode) fNode);
    }
    return evaluateNode(fNode);
  }

  private List<String> splitByBrackets(String expression) {
    ArrayList<String> sp = new ArrayList<String>();
    int level = 0;
    int lastPos = 0;
    int startPos = -1;
    for (int i = 0; i < expression.length(); i++) {
      if (expression.charAt(i) == '(') {
        if (level == 0) {
          sp.add(expression.substring(lastPos, i));
          lastPos = i;
          startPos = i;
        }
        level++;
      } else if (expression.charAt(i) == ')') {
        level--;
        if (level == 0 && startPos >= 0) {
          lastPos = i + 1;
          double val = evaluate(expression.substring(startPos + 1, i));
          sp.add(Double.toString(val));
        }
      }
    }
    if (level == 0) {
      if (lastPos == 0) {
        double val = evalStep(expression.substring(0, expression.length()));
        sp.add(Double.toString(val));
      } else {
        sp.add(expression.substring(lastPos, expression.length()));
      }
    }
    return sp;
  }

  /**
   * Parse the given <code>expression String</code> and evaluate it to a double
   * value
   *
   * @param expression
   * @return
   * @throws SyntaxError
   */
  public double evaluate(String expression) {
    List<String> list = splitByBrackets(expression);
    int listSize = list.size();
    while (listSize > 1) {
      int len = 0;
      for (int i = 0; i < listSize; i++) {
        len += list.get(i).length();
      }
      StringBuilder buf = new StringBuilder(len);
      for (int i = 0; i < listSize; i++) {
        buf.append(list.get(i));
      }
      list = splitByBrackets(buf.toString());
      listSize = list.size();
    }
    return Double.parseDouble(list.get(0));
  }

  /**
   * Reevaluate the <code>expression</code> (possibly after a new Variable
   * assignment)
   *
   * @param expression
   * @return
   * @throws SyntaxError
   */
  public double evaluate() {
    if (fNode == null) {
      throw new SyntaxError(0, 0, 0, " ", "No parser input defined", 1);
    }
    return evaluateNode(fNode);
  }

  /**
   * Evaluate an already parsed in abstract syntax tree node into a
   * <code>double</code> number value.
   *
   * @param node
   *          abstract syntax tree node
   *
   * @return the evaluated double number
   *
   * @throws ArithmeticException
   *           if the <code>node</code> cannot be evaluated.
   */
  public double evaluateNode(final ASTNode node) {
    if (node instanceof DoubleNode) {
      return ((DoubleNode) node).doubleValue();
    }
    if (node instanceof FunctionNode) {
      return evaluateFunction((FunctionNode) node);
    }
    if (node instanceof SymbolNode) {
      Double dbl = SYMBOL_DOUBLE_MAP.get(node.toString());
      if (dbl != null) {
        return dbl.doubleValue();
      }
    }
    if (node instanceof NumberNode) {
      return ((NumberNode) node).doubleValue();
    }

    throw new ArithmeticException("EvalDouble#evaluate(ASTNode) not possible for: " + node.toString());
  }

  /**
   * Evaluate an already parsed in <code>FunctionNode</code> into a
   * <code>souble</code> number value.
   *
   * @param functionNode
   * @return
   *
   * @throws ArithmeticException
   *           if the <code>functionNode</code> cannot be evaluated.
   */
  public double evaluateFunction(final FunctionNode functionNode) {
    if (functionNode.size() > 0 && functionNode.get(0) instanceof SymbolNode) {
      String symbol = functionNode.get(0).toString();
      if (functionNode.size() == 1) {
        Object obj = FUNCTION_DOUBLE_MAP.get(symbol);
        if (obj instanceof IDouble0Function) {
          return ((IDouble0Function) obj).evaluate();
        }
      } else if (functionNode.size() == 2) {
        Object obj = FUNCTION_DOUBLE_MAP.get(symbol);
        if (obj instanceof IDouble1Function) {
          return ((IDouble1Function) obj).evaluate(evaluateNode(functionNode.get(1)));
        }
        return evaluateNodeLogical(functionNode) ? 1.0 : 0.0;
      } else if (functionNode.size() == 3) {
        Object obj = FUNCTION_DOUBLE_MAP.get(symbol);
        if (obj instanceof IDouble2Function) {
          return ((IDouble2Function) obj).evaluate(evaluateNode(functionNode.get(1)), evaluateNode(functionNode.get(2)));
        }
        return evaluateNodeLogical(functionNode) ? 1.0 : 0.0;
      } else {
        Object obj = FUNCTION_DOUBLE_MAP.get(symbol);
        if (obj instanceof IDoubleFunction) {
          return ((IDoubleFunction) obj).evaluate(this, functionNode);
        }
      }
    }
    throw new ArithmeticException("EvalDouble#evaluateFunction(FunctionNode) not possible for: " + functionNode.toString());
  }

  public boolean evaluateNodeLogical(final ASTNode node) {
    if (node instanceof FunctionNode) {
      return evaluateFunctionLogical((FunctionNode) node);
    }
    if (node instanceof DoubleNode) {
      double d = ((DoubleNode) node).doubleValue();
      return !(Math.abs(d - 0.0) < DoubleEvaluator.EPSILON);
    }
    if (node instanceof NumberNode) {
      double d = ((NumberNode) node).doubleValue();
      return !(Math.abs(d - 0.0) < DoubleEvaluator.EPSILON);
    }

    throw new ArithmeticException("EvalDouble#evaluateNodeLogical(ASTNode) not possible for: " + node.toString());
  }

  public boolean evaluateFunctionLogical(final FunctionNode functionNode) {
    if (functionNode.size() > 0 && functionNode.get(0) instanceof SymbolNode) {
      String symbol = functionNode.get(0).toString();
      if (functionNode.size() == 2) {
        Object obj = FUNCTION_BOOLEAN_MAP.get(symbol);
        if (obj instanceof IBooleanBoolean1Function) {
          return ((IBooleanBoolean1Function) obj).evaluate(evaluateNodeLogical(functionNode.get(1)));
        }
      } else if (functionNode.size() == 3) {
        Object obj = FUNCTION_BOOLEAN_MAP.get(symbol);
        if (obj instanceof IBooleanDouble2Function) {
          return ((IBooleanDouble2Function) obj).evaluate(evaluateNode(functionNode.get(1)), evaluateNode(functionNode.get(2)));
        } else if (obj instanceof IBooleanBoolean2Function) {
          return ((IBooleanBoolean2Function) obj).evaluate(evaluateNodeLogical(functionNode.get(1)),
              evaluateNodeLogical(functionNode.get(2)));
        }
      }
    }
    throw new ArithmeticException("EvalDouble#evaluateFunctionLogical(FunctionNode) not possible for: " + functionNode.toString());

  }

  /**
   * Optimize an already parsed in <code>functionNode</code> into an
   * <code>ASTNode</code>.
   *
   * @param functionNode
   * @return
   *
   */
  public ASTNode optimizeFunction(final FunctionNode functionNode) {
    if (functionNode.size() > 0) {
      boolean doubleOnly = true;
      ASTNode node;
      for (int i = 1; i < functionNode.size(); i++) {
        node = functionNode.get(i);
        if (node instanceof NumberNode) {
          functionNode.set(i, new DoubleNode(((NumberNode) functionNode.get(i)).doubleValue()));
        } else if (functionNode.get(i) instanceof FunctionNode) {
          ASTNode optNode = optimizeFunction((FunctionNode) functionNode.get(i));
          if (!(optNode instanceof DoubleNode)) {
            doubleOnly = false;
          }
          functionNode.set(i, optNode);
        } else {
          doubleOnly = false;
        }
      }
      if (doubleOnly) {
        try {
          return new DoubleNode(evaluateFunction(functionNode));
        } catch (Exception e) {

        }
      }
    }
    return functionNode;
  }

}
TOP

Related Classes of info.bliki.wiki.template.expr.eval.DoubleEvaluator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.