Package com.autsia.bracer

Source Code of com.autsia.bracer.BracerParser

/*
* Copyright 2012 Dmytro Titov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.autsia.bracer;

import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Collections;
import java.util.Locale;
import java.util.Stack;
import java.util.StringTokenizer;

import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.complex.ComplexFormat;

/**
* Class for parsing and evaluating math expressions
*
* @author Dmytro Titov
* @version 6.0
* @since 1.0
*/
public class BracerParser {
  /* list of available functions */
  private final String[] FUNCTIONS = { "abs", "acos", "arg", "asin", "atan",
      "conj", "cos", "cosh", "exp", "imag", "log", "neg", "pow", "real",
      "sin", "sinh", "sqrt", "tan", "tanh" };
  /* list of available operators */
  private final String OPERATORS = "+-*/";
  /* separator of arguments */
  private final String SEPARATOR = ",";
  /* imaginary symbol */
  private final String IMAGINARY = "I";
  /* variable token */
  private final String VARIABLE = "var";
  /* settings for complex formatting */
  private ComplexFormat complexFormat = new ComplexFormat(IMAGINARY);
  /* settings for numbers formatting */
  private NumberFormat numberFormat = NumberFormat.getInstance(Locale.US);
  /* temporary stack that holds operators, functions and brackets */
  private Stack<String> stackOperations = new Stack<String>();
  /* stack for holding expression converted to reversed polish notation */
  private Stack<String> stackRPN = new Stack<String>();
  /* stack for holding the calculations result */
  private Stack<String> stackAnswer = new Stack<String>();

  /**
   * Class ctor for setting up the complex format of the parser
   *
   * @param precision
   *            Number of digits after the dot
   * @since 2.0
   */
  public BracerParser(int precision) {
    setPrecision(precision);
  }

  /**
   * Set the precision of the real and imaginary parts of numbers
   *
   * @param precision
   *            Number of digits after the dot
   * @since 2.0
   */
  public void setPrecision(int precision) {
    numberFormat.setMinimumFractionDigits(precision);
    numberFormat.setMaximumFractionDigits(precision);
    complexFormat = new ComplexFormat(IMAGINARY, numberFormat, numberFormat);
  }

  /**
   * Get the precision of the real and imaginary parts of numbers
   *
   * @return Precision
   * @since 2.0
   */
  public int getPrecision() {
    return numberFormat.getMinimumFractionDigits();
  }

  /**
   * Parses the math expression (complicated formula) and stores the result
   *
   * @param expression
   *            <code>String</code> input expression (math formula)
   * @throws <code>ParseException</code> if the input expression is not
   *         correct
   * @since 3.0
   */
  public void parse(String expression) throws ParseException {
    /* cleaning stacks */
    stackOperations.clear();
    stackRPN.clear();

    /*
     * make some preparations: remove spaces; handle unary + and -, handle
     * degree character
     */
    expression = expression.replace(" ", "")
        .replace("°", "*" + Double.toString(Math.PI) + "/180")
        .replace("(-", "(0-").replace(",-", ",0-").replace("(+", "(0+")
        .replace(",+", ",0+");
    if (expression.charAt(0) == '-' || expression.charAt(0) == '+') {
      expression = "0" + expression;
    }
    /* splitting input string into tokens */
    StringTokenizer stringTokenizer = new StringTokenizer(expression,
        OPERATORS + SEPARATOR + "()", true);

    /* loop for handling each token - shunting-yard algorithm */
    while (stringTokenizer.hasMoreTokens()) {
      String token = stringTokenizer.nextToken();
      if (isSeparator(token)) {
        while (!stackOperations.empty()
            && !isOpenBracket(stackOperations.lastElement())) {
          stackRPN.push(stackOperations.pop());
        }
      } else if (isOpenBracket(token)) {
        stackOperations.push(token);
      } else if (isCloseBracket(token)) {
        while (!stackOperations.empty()
            && !isOpenBracket(stackOperations.lastElement())) {
          stackRPN.push(stackOperations.pop());
        }
        stackOperations.pop();
        if (!stackOperations.empty()
            && isFunction(stackOperations.lastElement())) {
          stackRPN.push(stackOperations.pop());
        }
      } else if (isNumber(token)) {
        if (token.equals(IMAGINARY)) {
          stackRPN.push(complexFormat.format(new Complex(0, 1)));
        } else if (token.contains(IMAGINARY)) {
          stackRPN.push(complexFormat.format(complexFormat.parse("0+"
              + token)));
        } else {
          stackRPN.push(token);
        }
      } else if (isOperator(token)) {
        while (!stackOperations.empty()
            && isOperator(stackOperations.lastElement())
            && getPrecedence(token) <= getPrecedence(stackOperations
                .lastElement())) {
          stackRPN.push(stackOperations.pop());
        }
        stackOperations.push(token);
      } else if (isFunction(token)) {
        stackOperations.push(token);
      } else {
        throw new ParseException("Unrecognized token", 0);
      }
    }
    while (!stackOperations.empty()) {
      stackRPN.push(stackOperations.pop());
    }

    /* reverse stack */
    Collections.reverse(stackRPN);
  }

  /**
   * Evaluates once parsed math expression with no variable included
   *
   * @return <code>String</code> representation of the result
   * @throws <code>ParseException</code> if the input expression is not
   *         correct
   * @since 1.0
   */
  public String evaluate() throws ParseException {
    if (!stackRPN.contains("var")) {
      return evaluate(0);
    }
    throw new ParseException("Unrecognized token: var", 0);
  }

  /**
   * Evaluates once parsed math expression with "var" variable included
   *
   * @param variableValue
   *            User-specified <code>Double</code> value
   * @return <code>String</code> representation of the result
   * @throws <code>ParseException</code> if the input expression is not
   *         correct
   * @since 3.0
   */
  public String evaluate(double variableValue) throws ParseException {
    /* check if is there something to evaluate */
    if (stackRPN.empty()) {
      return "";
    }

    /* clean answer stack */
    stackAnswer.clear();

    /* get the clone of the RPN stack for further evaluating */
    @SuppressWarnings("unchecked")
    Stack<String> stackRPN = (Stack<String>) this.stackRPN.clone();

    /* enroll the variable value into expression */
    Collections.replaceAll(stackRPN, VARIABLE,
        Double.toString(variableValue));

    /* evaluating the RPN expression */
    while (!stackRPN.empty()) {
      String token = stackRPN.pop();
      if (isNumber(token)) {
        stackAnswer.push(token);
      } else if (isOperator(token)) {
        Complex a = complexFormat.parse(stackAnswer.pop());
        Complex b = complexFormat.parse(stackAnswer.pop());
        if (token.equals("+")) {
          stackAnswer.push(complexFormat.format(b.add(a)));
        } else if (token.equals("-")) {
          stackAnswer.push(complexFormat.format(b.subtract(a)));
        } else if (token.equals("*")) {
          stackAnswer.push(complexFormat.format(b.multiply(a)));
        } else if (token.equals("/")) {
          stackAnswer.push(complexFormat.format(b.divide(a)));
        }
      } else if (isFunction(token)) {
        Complex a = complexFormat.parse(stackAnswer.pop());
        if (token.equals("abs")) {
          stackAnswer.push(complexFormat.format(a.abs()));
        } else if (token.equals("acos")) {
          stackAnswer.push(complexFormat.format(a.acos()));
        } else if (token.equals("arg")) {
          stackAnswer.push(complexFormat.format(a.getArgument()));
        } else if (token.equals("asin")) {
          stackAnswer.push(complexFormat.format(a.asin()));
        } else if (token.equals("atan")) {
          stackAnswer.push(complexFormat.format(a.atan()));
        } else if (token.equals("conj")) {
          stackAnswer.push(complexFormat.format(a.conjugate()));
        } else if (token.equals("cos")) {
          stackAnswer.push(complexFormat.format(a.cos()));
        } else if (token.equals("cosh")) {
          stackAnswer.push(complexFormat.format(a.cosh()));
        } else if (token.equals("exp")) {
          stackAnswer.push(complexFormat.format(a.exp()));
        } else if (token.equals("imag")) {
          stackAnswer.push(complexFormat.format(a.getImaginary()));
        } else if (token.equals("log")) {
          stackAnswer.push(complexFormat.format(a.log()));
        } else if (token.equals("neg")) {
          stackAnswer.push(complexFormat.format(a.negate()));
        } else if (token.equals("real")) {
          stackAnswer.push(complexFormat.format(a.getReal()));
        } else if (token.equals("sin")) {
          stackAnswer.push(complexFormat.format(a.sin()));
        } else if (token.equals("sinh")) {
          stackAnswer.push(complexFormat.format(a.sinh()));
        } else if (token.equals("sqrt")) {
          stackAnswer.push(complexFormat.format(a.sqrt()));
        } else if (token.equals("tan")) {
          stackAnswer.push(complexFormat.format(a.tan()));
        } else if (token.equals("tanh")) {
          stackAnswer.push(complexFormat.format(a.tanh()));
        } else if (token.equals("pow")) {
          Complex b = complexFormat.parse(stackAnswer.pop());
          stackAnswer.push(complexFormat.format(b.pow(a)));
        }
      }
    }

    if (stackAnswer.size() > 1) {
      throw new ParseException("Some operator is missing", 0);
    }

    return stackAnswer.pop();
  }

  /**
   * Evaluates non-variable expression and returns it's value as a Complex
   * object
   *
   * @return <code>Complex</code> representation of complex number
   * @throws <code>ParseException</code> if the input expression is not
   *         correct
   * @since 4.0
   */
  public Complex evaluateComplex() throws ParseException {
    return complexFormat.parse(evaluate());
  }

  /**
   * Evaluates variable expression and returns it's value as a Complex object
   *
   * @param variableValue
   *            User-specified <code>Double</code> value
   * @return <code>Complex</code> representation of complex number
   * @throws <code>ParseException</code> if the input expression is not
   *         correct
   * @since 4.0
   */
  public Complex evaluateComplex(double variableValue) throws ParseException {
    return complexFormat.parse(evaluate(variableValue));
  }

  /**
   * Converts <code>Complex</code> object to it's <code>String</code>
   * representation
   *
   * @param number
   *            Input <code>Complex</code> number to convert
   * @return <code>String</code> representation of complex number
   * @since 5.0
   */
  public String format(Complex number) {
    return complexFormat.format(number);
  }

  /**
   * Check if the token is number (0-9, <code>IMAGINARY</code> or
   * <code>VARIABLE</code>)
   *
   * @param token
   *            Input <code>String</code> token
   * @return <code>boolean</code> output
   * @since 1.0
   */
  private boolean isNumber(String token) {
    try {
      Double.parseDouble(token);
    } catch (Exception e) {
      if (token.contains(IMAGINARY) || token.equals(VARIABLE)) {
        return true;
      }
      return false;
    }
    return true;
  }

  /**
   * Check if the token is function (e.g. "sin")
   *
   * @param token
   *            Input <code>String</code> token
   * @return <code>boolean</code> output
   * @since 1.0
   */
  private boolean isFunction(String token) {
    for (String item : FUNCTIONS) {
      if (item.equals(token)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Check if the token is <code>SEPARATOR</code>
   *
   * @param token
   *            Input <code>String</code> token
   * @return <code>boolean</code> output
   * @since 1.0
   */
  private boolean isSeparator(String token) {
    return token.equals(SEPARATOR);
  }

  /**
   * Check if the token is opening bracket
   *
   * @param token
   *            Input <code>String</code> token
   * @return <code>boolean</code> output
   * @since 1.0
   */
  private boolean isOpenBracket(String token) {
    return token.equals("(");
  }

  /**
   * Check if the token is closing bracket
   *
   * @param token
   *            Input <code>String</code> token
   * @return <code>boolean</code> output
   * @since 1.0
   */
  private boolean isCloseBracket(String token) {
    return token.equals(")");
  }

  /**
   * Check if the token is operator (e.g. "+")
   *
   * @param token
   *            Input <code>String</code> token
   * @return <code>boolean</code> output
   * @since 1.0
   */
  private boolean isOperator(String token) {
    return OPERATORS.contains(token);
  }

  /**
   * Gets the precedence of the operator
   *
   * @param token
   *            Input <code>String</code> token
   * @return <code>byte</code> precedence
   * @since 1.0
   */
  private byte getPrecedence(String token) {
    if (token.equals("+") || token.equals("-")) {
      return 1;
    }
    return 2;
  }
}
TOP

Related Classes of com.autsia.bracer.BracerParser

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.