Package com.barrybecker4.common.expression.mathexpression

Source Code of com.barrybecker4.common.expression.mathexpression.MathExpressionParser

// Copyright by Barry G. Becker, 2013. Licensed under MIT License: http://www.opensource.org/licenses/MIT
package com.barrybecker4.common.expression.mathexpression;

import com.barrybecker4.common.expression.ExpressionParser;
import com.barrybecker4.common.expression.Operator;
import com.barrybecker4.common.expression.OperatorsDefinition;
import com.barrybecker4.common.expression.TreeNode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static com.barrybecker4.common.expression.Tokens.LEFT_PAREN;
import static com.barrybecker4.common.expression.Tokens.RIGHT_PAREN;

/**
* Parses the text form of an expression (in x) into a tree representation.
* Create a subclass for parsing a math expression.
* @author Barry Becker
*/
public class MathExpressionParser extends ExpressionParser {

    public MathExpressionParser(OperatorsDefinition opDef) {
        super(opDef);
    }

    /**
     * Recursive method to find all the tree nodes for the terms a the current level.
     * For example, given this expression
     * 2x^3 +  5(x + 3x^2) / (x - 1)
     * the items in []'s represent the array of nodes returned.
     * [2] [*] [x] [^] [3] [+] [5][*][x + 3x^2] [/] [x - 1]
     * The parts that were in {()'s become their own subtrees via recursive calls.
     *
     * @param exp the expression to get the nodes at the current level for
     * @return array of nodes representing terms that the current level.
     * @throws Error if there is a syntax error causing the expression to be invalid
     */
    @Override
    protected List<TreeNode> getNodesAtLevel(String exp) {

        int pos= 0;
        List<TreeNode> nodes = new ArrayList<TreeNode>();
        String token = "";
        char ch = exp.charAt(pos);

        while (pos < exp.length() && !token.equals("" + RIGHT_PAREN.getSymbol())) {
            if (ch == ' ') {
                // spaces are ignored
            }
            else if (ch == LEFT_PAREN.getSymbol()) {
                int closingParenPos = findClosingParen(exp, pos + 1);
                // this method will make the recursive call
                token = processSubExpression(exp, pos + 1, token, closingParenPos, nodes);
                pos = closingParenPos + 1;
            }
            else if (ch == MathOperator.MINUS.getSymbol() && token.length() == 0 && opDef.isLastNodeOperator(nodes)) {
                // a leading minus sign
                token += ch;
            }
            else if (isNumericChar(ch)) {
                token += ch;
                if (token.contains("x")) { //NON-NLS
                    throw new Error("Cannot have numbers after x in a term "+ token +" within " + exp);
                }
            }
            else if (ch == 'x') {
                token += ch;
            }
            else if (opDef.isOperator(ch)) {
                pushNodesForToken(token, nodes);
                token = "";
                nodes.add(new TreeNode(ch, opDef));
            }
            else {
                throw new Error("Unexpected character " + ch +" in expression: " + exp);
            }
            pos++;
            if (pos < exp.length()) {
                ch = exp.charAt(pos);
            }
        }
        // add the final node
        pushNodesForToken(token, nodes);

        return nodes;
    }


    /**
     * Parse a parenthesized sub expression recursively.
     * @return current token. May have been reset to "".
     */
    @Override
    protected String processSubExpression(
        String exp, int pos, String token, int closingParenPos, List<TreeNode> nodes) {

        // recursive call for sub expression
        String subExp = exp.substring(pos, closingParenPos);
        TreeNode subTree = parse(subExp);
        subTree.hasParens = true;

        if (token != null && token.length() > 0) {
            // there was some leading token before the parenthesized expression.
            pushNodesForToken(token, nodes);
            token = "";
            nodes.add(new TreeNode(MathOperator.TIMES.getSymbol(), opDef));
        }
        else if (!nodes.isEmpty() && nodes.get(nodes.size() - 1).hasParens) {
            nodes.add(new TreeNode(MathOperator.TIMES.getSymbol(), opDef));
        }
        nodes.add(subTree);
        return token;
    }

    /**
     * The token may represent several nodes because of implicit multiplication.
     * For example,
     *   -4x should become  [-4] [times] [x]
     *    -x should become [-1] [times] [x]
     * @param token the token to parse
     * @param nodes array of nodes that the token was parsed into.
     */
    @Override
    protected void pushNodesForToken(String token, List<TreeNode> nodes) {

        if (token == null || token.length() == 0) return;

        int len = token.length();
        if (token.charAt(len - 1) == 'x') {
            if (len > 1) {
                if (token.charAt(0) == MathOperator.MINUS.getSymbol()) {
                    nodes.add(new TreeNode("-1", opDef));
                }
                else {
                    nodes.add(getNodeForNumber(token.substring(0, len - 1)));
                }
                nodes.add(new TreeNode(MathOperator.TIMES.getSymbol(), opDef));
            }
            nodes.add(new TreeNode("x", opDef)); //NON-NLS
        }
        else {
            nodes.add(getNodeForNumber(token));
        }
    }

    /**
     * Converts a list of nodes to a single node by reducing them to
     * subtrees in order of operator precedence.
     */
    @Override
    protected TreeNode makeTreeFromNodes(List<TreeNode> nodes) {

        for (Operator[] ops : opDef.getOperatorPrecedence()) {
            System.out.println("nodes=" + nodes + " ops="+ Arrays.toString(ops)); //NON-NLS
            nodes = reduceNodes(ops, nodes);
        }

        if (nodes.size() > 1) {
            throw new Error("Expected to have only one node after reducing, but have "
                + nodes.size() +" : " + nodes);
        }
        if (nodes.get(0).isOperator() && nodes.get(0).children.size() == 0) {
            throw new Error("Missing operands");
        }

        return nodes.get(0);
    }

    /**
     * Simplify the list of terms by evaluating the terms joined by the specified operators.
     * Reduce the nodes list to a single node and return it.
     * @param ops list of operators that all have the same precedence.
     * @param nodes the list of nodes to reduce
     * @return same list of nodes, but reduced.
     */
    private List<TreeNode> reduceNodes(Operator[] ops, List<TreeNode> nodes) {

        int index = 1;
        if (nodes.size() == 2) {
            throw new Error("Missing operand : " + nodes);
        }

        while (index < nodes.size()) {
            if (isOperator(nodes.get(index), ops)) {
                nodes.get(index).children = Arrays.asList(nodes.get(index - 1), nodes.get(index + 1));
                if (nodes.size() < index + 1) {
                    throw new Error("Not enough operands for operator in nodes=" + nodes);
                }
                //System.out.println("before splice : " + nodes);
                splice(nodes, index-1, 3, nodes.get(index));
                //System.out.println("after splice : " + nodes);
            } else {
                index += 2;
            }
        }
        return nodes;
    }

    private boolean isNumericChar(char ch) {

        return (ch >= '0' && ch <= '9') || ch == '.';
    }

    private TreeNode getNodeForNumber(String token) {
        double num = Double.parseDouble(token);
        if (Double.isNaN(num)) {
            throw new IllegalStateException("Invalid number in expression: " + token);
        }
        return new TreeNode("" + num, opDef);
    }
}
TOP

Related Classes of com.barrybecker4.common.expression.mathexpression.MathExpressionParser

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.