package edu.stanford.rsl.conrad.numerics.mathexpressions;
/**
* Class to parse arbitrary well formed mathematical expression
* This class also contains a custom scanner class
* @author Rotimi X Ojo
*/
public class ExpressionParser {
private String expression;
public ExpressionParser(String expression){
this.expression = preProcessExpression(expression).trim();
if(!isWellFormed()){
throw new RuntimeException("Expression is not well formed");
}
}
private String preProcessExpression(String input) {
input = input.replace("+", " + ");
input = input.replace("(", " ( ");
input = input.replace(")", " ) ");
input = input.replace("-", " - ");
input = input.replace("*", " * ");
input = input.replace("/", " / ");
return input;
}
/**
* Returns a parse tree equivalent for supplied mathematical expression
* @return the expression
*/
public AbstractMathExpression getMathExpression(){
ExpressionScanner scanner = new ExpressionScanner(expression);
String token = scanner.nextToken().trim();
AbstractMathExpression leftexp = getExp(token);;
while (scanner.hasNextToken()) {
String operator = scanner.nextToken();
if (operator.equals("*") || operator.equals("/")) {
AbstractMathExpression rightexp = getExp(scanner.nextToken());
leftexp = new CompoundExpression(leftexp,rightexp,operator.charAt(0));
} else if (operator.equals("+") || operator.equals("-")) {
AbstractMathExpression rightexp = new ExpressionParser(scanner.nextLine()).getMathExpression();
leftexp = new CompoundExpression(leftexp,rightexp,operator.charAt(0));
}
}
return leftexp;
}
/**'
* Determines if token contains a single mathematical operation
* @param token expression to be parsed
* @return true if expression requires a single operation.
*/
private boolean isSingleOperation(String token) {
int numoperators = numOperators(token);
if(numoperators > 1 ){
return false;
}
if(numoperators == 1 && token.charAt(0) != '-'&& token.charAt(0) != '+'){
return false;
}
return true;
}
/**
* Counts the number of operators in a given token
* @param token expression to be parsed
* @return number of supported operators in a given token.
*/
private int numOperators(String token) {
int count = 0;
for(int i =0;i < token.length();i++){
char curr = token.charAt(i);
if(curr == '-' || curr == '+' || curr == '/'|| curr == '*'){
if(i < 1 || curr == '/'|| curr == '*' || (i > 1 && token.charAt(i-2)!= 'E')){
count++;
}
}
}
return count;
}
/**
* Converts the given token to an expression tree.
* @param currToken: expression to be parsed
* @return An expression tree representing the input expression.
*/
private AbstractMathExpression getExp(String currToken) {
currToken = currToken.trim();
if(!isSingleOperation(currToken) && !isFormula(currToken)){
return new MathExpression(currToken);
}
String firstChar = currToken.substring(0,1);
if(firstChar.equals("-")){
AbstractMathExpression leftExp = new RealExpression(-1);
AbstractMathExpression rightExp = getExp(currToken.substring(currToken.indexOf("-")+1));
return new CompoundExpression(leftExp, rightExp, '*');
}else if(firstChar.equals("+")){
return getExp(currToken.substring(currToken.indexOf("+")+1));
}else if(firstChar.matches("\\p{Digit}+")){
return new RealExpression(Double.parseDouble(currToken.replace(" ", "")));
}else if(firstChar.matches("\\p{Alpha}+") && !currToken.contains("(")){
return new IdentifierExpression(currToken);
}else if(isFormula(currToken)){
AbstractMathExpression input = new MathExpression(currToken.substring(currToken.indexOf("(")+1,currToken.length()-1));
return new FunctionExpression(currToken.substring(0,currToken.indexOf("(")),input);
}else if(currToken.charAt(0)=='(' && currToken.charAt(currToken.length()-1) == ')'){
return new MathExpression(currToken);
}
throw new UnsupportedOperationException("invalid token " + currToken);
}
/**
* Determines if input token is an expression expression
* @param currToken
* @return: true if token is an expression.
*/
private boolean isFormula(String currToken) {
String firstChar = currToken.substring(0,1);
return firstChar.matches("\\p{Alpha}+") && currToken.contains("(");
}
/**
* Determines if an expression is well-formed by counting braces.
* @return
*/
private boolean isWellFormed() {
runBracketCheck();
return true;
}
/**
* Determines if the braces delineating expressions are balanced and well formed.
*/
public void runBracketCheck(){
int count = 0;
for(int i = 0; i < expression.length(); i++){
if(expression.charAt(i)== '('){
count++;
}else if(expression.charAt(i)==')'){
count--;
}
if(count < 0){
throw new RuntimeException("unbalanced Bracketing at index: " + i + " of " + expression);
}
}
if(count != 0){
throw new RuntimeException("unbalanced Brackets");
}
}
/**
* Class to retrieve standalone mathematical expressions.
* @author Rotimi X Ojo
*/
private class ExpressionScanner{
private String expression;
private String lastToken = "*";
public ExpressionScanner(String expression) {
this.expression = expression.trim();
this.expression = removeOutterBrackets(this.expression);
}
private String removeOutterBrackets(String exp) {
int expLength = exp.length();
if(isSingleGroup(exp)){
String buff = exp.substring(1,expLength -1);
if(isSingleGroup(buff)){
return removeOutterBrackets(buff);
}else{
if(buff.indexOf("(") > buff.indexOf(")")){
return exp;
}
return buff.trim();
}
}
return exp.trim();
}
private boolean isSingleGroup(String exp) {
int expLength = exp.length();
if(exp.charAt(0)=='(' && exp.charAt(expLength-1) ==')'){
int count = 0;
for(int i = 0; i < expLength; i++){
if(exp.charAt(i)== '('){
count++;
}else if(exp.charAt(i)==')'){
count--;
}
if(count == 0 && i!=expLength-1){
return false;
}
}
return true;
}
return false;
}
public String nextToken() {
expression = expression.trim();
String firstChar = expression.substring(0,1);
if(expression.charAt(0) == '('){
lastToken = getEnclosedToken();
return lastToken;
}else if(isOperator(firstChar)){
lastToken = getOperatorOrNegation(firstChar);
return lastToken;
}else if(firstChar.matches("\\p{Alpha}+")){
lastToken = getIdentifierOrFormula();
return lastToken;
}else{
if(expression.contains("E - ") || expression.contains("E + ")){
lastToken = expression;
expression = null;
}else{
lastToken = getRealExpression();
}
return lastToken;
}
}
private String getRealExpression() {
String token = "";
if(expression.contains(" ")){
token = expression.substring(0,expression.indexOf(" "));
expression = expression.substring(expression.indexOf(" ")).trim();
}else{
token = expression;
expression = null;
}
return token;
}
private String getEnclosedToken() {
String token = "";
int bracketcloseIndex = getBracketCloseIndex();
if (bracketcloseIndex == expression.length() - 1) {
token = expression.substring(1, bracketcloseIndex).trim();
expression = null;
} else {
token = expression.substring(1, bracketcloseIndex).trim();
expression = expression.substring(bracketcloseIndex+1).trim();
}
return token;
}
private String getOperatorOrNegation(String firstChar) {
String token = expression.substring(0, 1);
expression = expression.substring(1).trim();
if (isNegation(firstChar)) {
lastToken = token;
token = token + nextToken();
}
return token;
}
private String getIdentifierOrFormula() {
String token = "";
int openbracIndex = expression.indexOf('(');
if(openbracIndex != -1){
if(!isIdentifier(expression.substring(0,openbracIndex))){
token = getFormula();
}
}else{
token = expression.substring(0,expression.indexOf(" "));
expression = expression.substring(expression.indexOf(" ")).trim();
}
return token;
}
private String getFormula() {
String token = "";
int closebrackindex = getBracketCloseIndex();
if(closebrackindex == expression.length()-1){
token = expression;
expression = null;
}else{
token = expression.substring(0,closebrackindex + 1);
expression = expression.substring(closebrackindex + 1).trim();
}
return token;
}
private boolean isIdentifier(String buff) {
return buff.contains("-")|| buff.contains("+")||buff.contains("*")||buff.contains("/");
}
private boolean isNegation(String token) {
return (token.equals("-") && isOperator(lastToken)) || (token.equals("+") && isOperator(lastToken));
}
private boolean isOperator(String token) {
return token.equals("-")|| token.equals("+")||token.equals("*")||token.equals("/");
}
private int getBracketCloseIndex() {
int count = 0;
int index = 0;
boolean nohit = false;
do{
nohit = false;
if(expression.charAt(index)== '('){
count++;
}else if(expression.charAt(index)==')'){
count--;
}else{
nohit =true;
}
index++;
}while(count > 0 || nohit);
return index - 1;
}
/**
* Checks if scanner has more tokens
* @return true if scanner has more tokens.
*/
public boolean hasNextToken() {
if(expression != null ){
return true;
}
return false;
}
/**
* Returns the next line of expressions
* @return
*/
public String nextLine() {
String buff = expression;
expression = null;
return buff;
}
}
}
/*
* Copyright (C) 2010-2014 Rotimi X Ojo
* CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
*/