Package parser

Source Code of parser.Parser

package parser;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.LinkedList;

import util.Utils;
import util.ValueMap;
import enumeration.TypeEnum;
import exception.LexicalException;
import exception.ParsingException;

/**
* Lexically verifies, parses and evaluates an expression string.
*
* @author mauren
*/
public class Parser {
  private String expression;
  private int lookahead = 0;
  private int roundingPrecision;
  private RoundingMode roundingMode;
 
  /**
   * Constructs a new parser object for given expression, with a rounding
   * precision of 10 and a rounding mode of HALF_EVEN (banker's rounding).
   *
   * @param expression
   *            expression to be parsed and evaluated.
   */
  public Parser(String expression) {
    this(expression, 10);
  }
 
  /**
   * Constructs a new parser object for given expression, with the specified
   * rounding precision and a rounding mode of HALF_EVEN (banker's rounding).
   *
   * @param expression
   *            expression to be parsed and evaluated.
   */
  public Parser(String expression, int roundingPrecision) {
    this(expression, roundingPrecision, RoundingMode.HALF_EVEN);
  }
 
  /**
   * Constructs a new parser object for given expression, with the specified
   * rounding precision and the specified rounding mode.
   *
   * @param expression
   *            expression to be parsed and evaluated.
   */
  public Parser(String expression, int roundingPrecision, RoundingMode roundingMode) {
    this.expression = expression;
    this.roundingPrecision = roundingPrecision;
    this.roundingMode = roundingMode;
  }
 
  /**
   * Creates a token.
   *
   * @param text
   *            token string value.
   * @param type
   *            token type.
   * @param initIndex
   *            start index where the token was found.
   * @return a new instance of {@link Token}.
   */
  private Token createToken(String text, TypeEnum type, int initIndex) {
    Token token = new Token();
    token.setText(text);
    token.setType(type);
    token.setInitIndex(initIndex);
    return token;
  }
 
  /**
   * Verifies lexically the expression passed.
   *
   * @return a list of tokens recognized.
   * @throws LexicalException
   *             when an unrecognized lexem is found.
   */
  public LinkedList<Token> lexicalVerifier() throws LexicalException {
    LinkedList<Token> tokens = new LinkedList<Token>();
    int pos = 0;
   
    char currChar;
    StringBuilder tk = new StringBuilder();
    TypeEnum type = null;
    int initIndex = pos;
    while (pos < this.expression.length()) {
      currChar = this.expression.charAt(pos);
     
      if (Character.toLowerCase(currChar) >= 'a' && Character.toLowerCase(currChar) <= 'z') {
        if (!tk.toString().isEmpty() && Utils.nvl(type, TypeEnum.IDENTIFIER) != TypeEnum.IDENTIFIER) {
          tokens.add(this.createToken(tk.toString(), type, initIndex));
          tk = new StringBuilder();
          initIndex = pos;
        }
       
        tk.append(currChar);
        type = TypeEnum.IDENTIFIER;
      } else if (currChar >= '0' && currChar <= '9') {
        if (!tk.toString().isEmpty() && Utils.nvl(type, TypeEnum.NUMBER) != TypeEnum.NUMBER) {
          if (type == TypeEnum.IDENTIFIER) {
            tk.append(currChar);
          } else {
            tokens.add(this.createToken(tk.toString(), type, initIndex));
            tk = new StringBuilder();
            initIndex = pos;
           
            tk.append(currChar);
            type = TypeEnum.NUMBER;
          }
        } else {
          tk.append(currChar);
          type = TypeEnum.NUMBER;
        }
      } else if (currChar == '.') {
        if (!tk.toString().isEmpty() && Utils.nvl(type, TypeEnum.NUMBER) != TypeEnum.NUMBER) {
          tokens.add(this.createToken(tk.toString(), type, initIndex));
          tk = new StringBuilder();
          initIndex = pos;
        }
       
        tk.append(currChar);
        type = TypeEnum.NUMBER;
      } else if (currChar == '+') {
        if (!tk.toString().isEmpty()) {
          tokens.add(this.createToken(tk.toString(), type, initIndex));
          tk = new StringBuilder();
          initIndex = pos;
        }
       
        tk.append(currChar);
        type = TypeEnum.PLUS;
      } else if (currChar == '-') {
        if (!tk.toString().isEmpty()) {
          tokens.add(this.createToken(tk.toString(), type, initIndex));
          tk = new StringBuilder();
          initIndex = pos;
        }
       
        tk.append(currChar);
        type = TypeEnum.MINUS;
      } else if (currChar == '*') {
        if (!tk.toString().isEmpty()) {
          tokens.add(this.createToken(tk.toString(), type, initIndex));
          tk = new StringBuilder();
          initIndex = pos;
        }
       
        tk.append(currChar);
        type = TypeEnum.MULT;
      } else if (currChar == '/') {
        if (!tk.toString().isEmpty()) {
          tokens.add(this.createToken(tk.toString(), type, initIndex));
          tk = new StringBuilder();
          initIndex = pos;
        }
       
        tk.append(currChar);
        type = TypeEnum.DIV;
      } else if (currChar == '%') {
        if (!tk.toString().isEmpty()) {
          tokens.add(this.createToken(tk.toString(), type, initIndex));
          tk = new StringBuilder();
          initIndex = pos;
        }
       
        tk.append(currChar);
        type = TypeEnum.MOD;
      } else if (currChar == '^') {
        if (!tk.toString().isEmpty()) {
          tokens.add(this.createToken(tk.toString(), type, initIndex));
          tk = new StringBuilder();
          initIndex = pos;
        }
       
        tk.append(currChar);
        type = TypeEnum.POWER;
      } else if (currChar == '(') {
        if (!tk.toString().isEmpty()) {
          tokens.add(this.createToken(tk.toString(), type, initIndex));
          tk = new StringBuilder();
          initIndex = pos;
        }
       
        tk.append(currChar);
        type = TypeEnum.OPEN_BRACK;
      } else if (currChar == ')') {
        if (!tk.toString().isEmpty()) {
          tokens.add(this.createToken(tk.toString(), type, initIndex));
          tk = new StringBuilder();
          initIndex = pos;
        }
       
        tk.append(currChar);
        type = TypeEnum.CLOSE_BRACK;
      } else {
        throw new LexicalException("Unrecognized character: " + currChar);
      }
     
      pos++;
    }
   
    if (!tk.toString().isEmpty()) {
      tokens.add(this.createToken(tk.toString(), type, initIndex));
    }
   
    this.lookahead = 0;
    return tokens;
  }
 
  /**
   * Initiates the parsing phase.
   *
   * @param tokens
   *            tokens recognized by the lexical verifier.
   * @param values
   *            values to translate identifiers.
   * @return value of expression.
   * @throws Exception
   */
  public BigDecimal eval(LinkedList<Token> tokens, ValueMap values) throws ParsingException {
    BigDecimal result = this.exp(tokens, values);
    result = result.setScale(this.roundingPrecision, this.roundingMode);
    return result;
  }
 
  /**
   * Expression representation.
   *
   * @param tokens
   *            the tokens to be parsed.
   * @param values
   *            variables values.
   * @param value
   *            previous expression parsing value.
   * @return the current value for parsed expression.
   * @throws ParsingException
   */
  public BigDecimal exp(LinkedList<Token> tokens, ValueMap values) throws ParsingException {
    BigDecimal value = BigDecimal.ZERO;
    value = this.interm1(tokens, values, value);
    value = this.expLevel3(tokens, values, value);
   
    return value;
  }
 
  /**
   * Intermediate parsing, level 1.
   *
   * @param tokens
   *            the tokens to be parsed.
   * @param values
   *            variables values.
   * @param value
   *            previous expression parsing value.
   * @return the current value for parsed expression.
   * @throws ParsingException
   */
  public BigDecimal interm1(LinkedList<Token> tokens, ValueMap values, BigDecimal value) throws ParsingException {
    value = this.interm2(tokens, values, value);
    value = this.expLevel2(tokens, values, value);
   
    return value;
  }
 
  /**
   * Intermediate parsing, level 2.
   *
   * @param tokens
   *            the tokens to be parsed.
   * @param values
   *            variables values.
   * @param value
   *            previous expression parsing value.
   * @return the current value for parsed expression.
   * @throws ParsingException
   */
  public BigDecimal interm2(LinkedList<Token> tokens, ValueMap values, BigDecimal value) throws ParsingException {
    value = this.term(tokens, values);
    value = this.expLevel1(tokens, values, value);
   
    return value;
  }
 
  /**
   * Parses the level 1 operators (power sign).
   *
   * @param tokens
   *            tokens recognized by the lexical verifier.
   * @param values
   *            values to translate identifiers.
   * @param value
   *            the last processed value.
   * @return value for the evaluated operation.
   * @throws ParsingException
   *             when an unexpected token is found.
   */
  public BigDecimal expLevel1(LinkedList<Token> tokens, ValueMap values, BigDecimal value) throws ParsingException {
    Token tk = null;
    // Verifies if there is a next token
    if (this.lookahead < tokens.size()) {
      tk = tokens.get(this.lookahead);
    }
   
    if (tk != null) {
      // Parses the operation value
      if (tk.getType() == TypeEnum.POWER) {
        this.lookahead++;
        BigDecimal operand = this.term(tokens, values);
       
        operand = this.expLevel1(tokens, values, operand);
       
        // Parses current operation
        value = value.pow(operand.intValue());
      }
    }
   
    return value;
  }
 
  /**
   * Parses the level 2 operators (multiply and divide signs).
   *
   * @param tokens
   *            tokens recognized by the lexical verifier.
   * @param values
   *            values to translate identifiers.
   * @param value
   *            the last processed value.
   * @return value for the evaluated operation.
   * @throws ParsingException
   *             when an unexpected token is found.
   */
  public BigDecimal expLevel2(LinkedList<Token> tokens, ValueMap values, BigDecimal value) throws ParsingException {
    Token tk = null;
    // Verifies if there is a next token
    if (this.lookahead < tokens.size()) {
      tk = tokens.get(this.lookahead);
    }
   
    if (tk != null) {
      // Parses the operation value
      if (tk.getType() == TypeEnum.MULT) {
        this.lookahead++;
        BigDecimal op1 = value;
        BigDecimal op2;
        // Parses the higher precedence operations
        op2 = this.interm2(tokens, values, value);
       
        // Parses current operation
        value = op1.multiply(op2);
        value = this.expLevel2(tokens, values, value);
      } else if (tk.getType() == TypeEnum.DIV) {
        this.lookahead++;
        BigDecimal op1 = value;
        BigDecimal op2;
        // Parses the higher precedence operations
        op2 = this.interm2(tokens, values, value);
       
        // Parses current operation
        value = op1.divide(op2, this.roundingPrecision, this.roundingMode);
        value = this.expLevel2(tokens, values, value);
      } else if (tk.getType() == TypeEnum.MOD) {
        this.lookahead++;
        BigDecimal op1 = value;
        BigDecimal op2;
       
        // Higher precedence operations
        op2 = this.interm2(tokens, values, value);
       
        // Current op
        value = op1.remainder(op2, new MathContext(this.roundingPrecision, this.roundingMode));
        value = this.expLevel2(tokens, values, value);
      }
    }
   
    return value;
  }
 
  /**
   * Parses the level 3 operators (sum and minus signs).
   *
   * @param tokens
   *            tokens recognized by the lexical verifier.
   * @param values
   *            values to translate identifiers.
   * @param value
   *            the last processed value.
   * @return value for the evaluated operation.
   * @throws ParsingException
   *             when an unexpected token is found.
   */
  public BigDecimal expLevel3(LinkedList<Token> tokens, ValueMap values, BigDecimal value) throws ParsingException {
    Token tk = null;
   
    // Verifies if there is a next token
    if (this.lookahead < tokens.size()) {
      tk = tokens.get(this.lookahead);
    }
   
    if (tk != null) {
      // Parses the operation value
      if (tk.getType() == TypeEnum.PLUS) {
        this.lookahead++;
        BigDecimal op1 = value;
        BigDecimal op2;
        // Executes parsing for higher precedence operations
        op2 = this.interm1(tokens, values, op1);
       
        // Parses current operation
        value = op1.add(op2);
        value = this.expLevel3(tokens, values, value);
      } else if (tk.getType() == TypeEnum.MINUS) {
        this.lookahead++;
        BigDecimal op1 = value;
        BigDecimal op2;
        // Executes parsing for higher precedence operations
        op2 = this.interm1(tokens, values, op1);
       
        // Parses current operation
        value = op1.subtract(op2);
        value = this.expLevel3(tokens, values, value);
      }
    }
   
    return value;
  }
 
  /**
   * Parses the terminal token and subexpressions.
   *
   * @param tokens
   *            tokens recognized by the lexical verifier.
   * @param values
   *            values to translate identifiers.
   * @return terminal token value.
   * @throws ParsingException
   *             when an unexpected token is found.
   */
  public BigDecimal term(LinkedList<Token> tokens, ValueMap values) throws ParsingException {
    Token tk = null;
   
    // Verifies if there is a next token
    if (this.lookahead < tokens.size()) {
      tk = tokens.get(this.lookahead);
    }
   
    boolean positive = true;
    if (tk != null) {
      if (tk.getType() == TypeEnum.PLUS) {
        this.lookahead++;
      } else if (tk.getType() == TypeEnum.MINUS) {
        this.lookahead++;
        positive = false;
      }
     
      if (this.lookahead >= tokens.size()) {
        throw new ParsingException("unexpected token at " + tk.getInitIndex());
      }
     
      tk = tokens.get(this.lookahead);
     
      BigDecimal value = null;
      // Parses the value, depending on terminal type
      if (tk.getType() == TypeEnum.NUMBER) {
        this.lookahead++;
        value = new BigDecimal(tk.getText());
       
        if (!positive) {
          value = value.negate();
        }
       
        return value;
      } else if (tk.getType() == TypeEnum.IDENTIFIER) {
        this.lookahead++;
        value = values.get(tk.getText());
       
        if (!positive) {
          value = value.negate();
        }
       
        return value;
      } else
      // Parses subexpressions
      if (tk.getType() == TypeEnum.OPEN_BRACK) {
        this.lookahead++;
       
        value = this.exp(tokens, values);
       
        tk = tokens.get(this.lookahead);
        if (tk.getType() == TypeEnum.CLOSE_BRACK) {
          this.lookahead++;
          return value;
        }
      }
    }
   
    // Throws an error when an unexpected token is found
    throw new ParsingException("unexpected token at " + tk.getInitIndex());
  }
}
TOP

Related Classes of parser.Parser

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.