/*******************************************************************************
 * Copyright (c) 2017
 *
 * 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 smile.symbolic;

import java.util.ArrayList;
import java.util.Stack;
import java.util.Arrays;

/**
 * @author Ernest DeFoy
 */
public class ExpressionParser {

    private String expression;
    private String var;
    private ArrayList tokens;

    public String getExpression() {

        return expression;
    }

    public String getVar() {

        return var;
    }

    public ArrayList getTokens() {

        return tokens;
    }

    public static boolean isOperator(String str)
    {
        if(str.matches("[+*/^-]"))
            return true;

        return false;
    }

    public static boolean isOperand(String str, String var)
    {
        if(str.matches("[0-9]+") || str.equals(var))
            return true;

        return false;
    }

    public static boolean isFunction(String str)
    {
        String[] funcs = {"sin", "cos", "tan", "csc", "sec", "cot", "log", "ln"};

        return Arrays.asList(funcs).contains(str);
    }

    public static int getPrecedence(String str)
    {
        if(isFunction(str))
            return 5;

        int val = 0;

        if(str.equals("+"))
            val = 2;
        else if(str.equals("-"))
            val = 2;
        else if(str.equals("*") || str.equals(("/")))
            val = 3;
        else if(str.equals("^") || str.equals("$"))
            val = 4;

        return val;
    }

    public static boolean isLeftAssociative(String s)
    {
        if(s.equals("^") || s.equals("$") || s.equals("+") || s.equals("*"))
            return false;

        return true;
    }

    // modifies the string to look more like it would if someone wrote the expression
    // out on paper
    public String format(String s)
    {
        for(int i = 0; i < s.length(); i++) {
            if(s.substring(i, i + 1).equals("*") && i > 0)
                if(isOperand(s.substring(i - 1, i), var) && i < s.length() - 1 && s.substring(i + 1, i + 2).equals(var))
                    s = s.substring(0, i) + s.substring(i + 1);
        }

        return s;
    }

    public String parse(String expression) throws InvalidExpressionException {

        this.expression = expression;

        try {
            var = check();
        } catch (InvalidExpressionException e) {
            e.printStackTrace();
        }

        this.expression = formatString(expression);
        System.out.println(this.expression);
        tokens = tokenize(this.expression);

        return this.expression;
    }

    private String check() throws InvalidExpressionException {

        expression = expression.replaceAll("\\s","");
        expression = expression.toLowerCase();
        String var = "";

        if(expression.length() == 0) {
            throw new InvalidExpressionException("Empty Expression");
        }

        if(!expression.matches("[a-zA-Z0-9+*/^()-]+")) { // contains only operators, numbers, or letters
            throw new InvalidExpressionException("Syntax Error");
        }

        if(expression.matches("[+*/^()-]+")) { // doesn't contain any operands
            throw new InvalidExpressionException("Syntax Error");
        }

        String firstChar = expression.substring(0, 1);
        String lastChar = expression.substring(expression.length() - 1, expression.length());

        if(!firstChar.equals("-") && isOperator(firstChar) || firstChar.equals(")") || isOperator(lastChar) || lastChar.equals("(")) {
            throw new InvalidExpressionException("Syntax Error");
        }


        for(int i = 0; i < expression.length(); i++) {

            String temp = "";

            while(i < expression.length() && expression.substring(i, i + 1).matches("[a-zA-Z]")) {
                temp += expression.substring(i, i + 1);
                i++;
            }

            if(temp.length() == 1) {
                //i--; // ?? i must be decremented from the above while loop in this if block so the program can check the last character in the string
                if(var.length() == 0)
                    var = temp;
                if(!temp.equals(var)) {
                    throw new InvalidExpressionException("For now, expression must contain a single variable");
                }
                else if(i < expression.length() && expression.substring(i, i + 1).matches("[0-9]+")) {
                    throw new InvalidExpressionException("Syntax Error");
                }
            }
            else if(isFunction(temp)) {

                if(i < expression.length()) {
                    if(!expression.substring(i, i + 1).equals("(")) {
                        //System.out.println("Syntax Error: " + temp + " needs a parenthesis after it");// no parenthesis after function (EX: sin5)
                        throw new InvalidExpressionException("Syntax Error");
                    }
                }
                else {
                    //System.out.println("Syntax Error: " + temp + " needs an input"); // nothing after function (EX: 5 + sin)
                    throw new InvalidExpressionException("Syntax Error");
                }
            }
            else if(temp.length() != 0){
                //System.out.println(temp + ": function not found");
                throw new InvalidExpressionException(temp + ": function not found");
            }

            //i--; // ?? i must be decremented since it was left incremented in the above while loop
        }


        int cntOpenParen = 0;
        int cntCloseParen = 0;

        for(int i = 0; i < expression.length() - 1; i++) {

            String tmp1 = expression.substring(i, i + 1);
            String tmp2 = expression.substring(i + 1, i + 2);

            if(tmp1.equals("-")) {
                if(isOperator(tmp2) || tmp2.equals(")")) {
                    //System.out.println("Syntax Error: " + tmp1 + tmp2);
                    throw new InvalidExpressionException("Syntax Error");
                }
            }
            else if(tmp2.equals("-")) { // Also prevents next else if from rejecting an operator followed by a unary minus
                if(tmp1.equals("(")) {
                    //System.out.println("Syntax Error: " + tmp1 + tmp2);
                    throw new InvalidExpressionException("Syntax Error");
                }
            }
            else if((isOperator(tmp1) || tmp1.equals("(")) && (isOperator(tmp2) || tmp2.equals(")"))) {
                //System.out.println("Syntax Error: " + tmp1 + tmp2); // two operands in a row (examples: ++, (+, ())
                throw new InvalidExpressionException("Syntax Error");
            }
            else if(expression.substring(i, i + 1).equals("("))
                cntOpenParen++;
            else if(expression.substring(i, i + 1).equals(")"))
                cntCloseParen++;
        }

        if(cntOpenParen < cntCloseParen) { // found a ")" when the end of the expression was expected
            //System.out.println("Syntax Error: found ')' but expected end of expression");
            throw new InvalidExpressionException("Syntax Error");
        }

        return var;
    }

    // adds and deletes characters to aid in the creation of the binary expression tree
    private String formatString(String exp)
    {
        exp = exp.replaceAll("\\s",""); // why
        exp = exp.toLowerCase();

        int count = 0;

        if(exp.substring(0, 1).equals("-")) { // if expression starts with a minus sign, it is a unary one
            exp = "$" + exp.substring(1); // replace
        }

        for(int i = 0; i < exp.length(); i++) {
            if(exp.substring(i,  i + 1).equals("("))
                count++;
            else if(exp.substring(i, i + 1).equals(")"))
                count--;
        }
        while(count > 0) {
            exp += ")";
            count--;
        }

        // At the operators, when the operator is "-" and it is preceded by another operator,
        // or preceded by a left parenthesis, or when it is the first character of the input
        // it is a unary minus rather than binary. In this case, I change it to another
        // character, '$', and make its precedence the same as that of '^'.
        for(int i = 0; i < exp.length() - 1; i++) {
            String tmp1 = exp.substring(i, i + 1);
            String tmp2 = exp.substring(i + 1, i + 2);
            if(tmp2.equals("-") && (ExpressionParser.isOperator(tmp1) || tmp1.equals("(")))
                exp = exp.substring(0, i + 1) + "$" + exp.substring(i + 2);
            else if((tmp1.matches("[0-9]+") || tmp1.equals(var)) && (tmp2.equals("(") || tmp2.equals(var)))
                exp = exp.substring(0, i + 1) + "*" + exp.substring(i + 1);
        }

        return exp;
    }

    // separates the expression string into "tokens" and sorts them in
    // postfix order
    public ArrayList tokenize(String exp)
    {
        ArrayList tokens = new ArrayList<>();
        Stack stack = new Stack<>();

        for(int i = 0; i < exp.length(); i++)
        {
            String token = "";

            if(isOperator(exp.substring(i, i + 1)) || exp.substring(i, i + 1).equals("$")) {
                token = exp.substring(i, i + 1);
                while ((!stack.isEmpty() && (isOperator(stack.peek()) || stack.peek().equals("$")))
                        && ((isLeftAssociative(token) && getPrecedence(token) <= getPrecedence(stack.peek()))
                        || (!isLeftAssociative(token) && getPrecedence(token) < getPrecedence(stack.peek())))) {

                    tokens.add(stack.pop());
                }
                stack.push(token);
            }
            else if(exp.substring(i, i + 1).equals(var)) {
                token = var;
                tokens.add(token);
            }
            else if(exp.substring(i, i + 1).equals("(")) {
                token = exp.substring(i, i + 1);
                stack.push(token);
            }
            else if(exp.substring(i, i + 1).equals(")")) {
                while(!stack.isEmpty() && !stack.peek().equals("(")) {
                    tokens.add(stack.pop());
                }

                if(!stack.isEmpty())
                    stack.pop();
                if(!stack.isEmpty() && isFunction(stack.peek())) {
                    tokens.add(stack.pop());
                }
            }
            else if(exp.substring(i, i + 1).matches("[0-9]+")) {
                while(i < exp.length() && exp.substring(i, i + 1).matches("[0-9]+")) {
                    token += exp.substring(i, i + 1);
                    i++;
                }
                tokens.add(token);
                i--; // i was left incremented after the while loop
            }
            else if(exp.substring(i, i + 1).equals(var)) {
                tokens.add(token);
            }
            else {
                while(i < exp.length() && exp.substring(i, i + 1).matches("[a-zA-Z]+")) {
                    token += exp.substring(i, i + 1);
                    i++;
                }

                if(token.length() != 0) {
                    stack.push(token);
                }
            }
        }

        while(!stack.isEmpty()) {
            tokens.add(stack.pop());
        }
        System.out.println(tokens);
        return tokens;
    }
}

 

Ads help maintain this website.