0

I found a simple math parser class, but it doesn't support logical operations, or expressions such as:

(a > b) ? (x^2) : (a / 3.56)

Here is the parser:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Calcultra
{
    class Parser
    {
        // The available operators for use in mathematical expressions.
        private string[] _operators = { "-", "+", "/", "*", "^" };

        // The mathematical operations performed by the operators.
        private Func<double, double, double>[] _operations = {
            (a1, a2) => a1 - a2,
            (a1, a2) => a1 + a2,
            (a1, a2) => a1 / a2,
            (a1, a2) => a1 * a2,
            (a1, a2) => Math.Pow(a1, a2)
        };

        /**
         * Parses and evaluates a mathematical expression and returns the result.
         */
        public double Eval(string expression)
        {
            List<string> tokens = getTokens(expression);
            Stack<double> operandStack = new Stack<double>();
            Stack<string> operatorStack = new Stack<string>();
            int tokenIndex = 0;

            while (tokenIndex < tokens.Count)
            {
                string token = tokens[tokenIndex];

                if (token == "(")
                {
                    string subExpr = getSubExpression(tokens, ref tokenIndex);
                    operandStack.Push(Eval(subExpr));
                    continue;
                }

                if (token == ")")
                {
                    throw new ArgumentException("Mis-matched parentheses in expression");
                }

                // If this is an operator.
                if (Array.IndexOf(_operators, token) >= 0)
                {
                    while (operatorStack.Count > 0 && Array.IndexOf(_operators, token) < Array.IndexOf(_operators, operatorStack.Peek()))
                    {
                        string op = operatorStack.Pop();
                        double arg2 = operandStack.Pop();
                        double arg1 = operandStack.Pop();
                        operandStack.Push(_operations[Array.IndexOf(_operators, op)](arg1, arg2));
                    }

                    operatorStack.Push(token);
                }
                else
                {
                    operandStack.Push(double.Parse(token));
                }

                tokenIndex += 1;
            }

            while (operatorStack.Count > 0)
            {
                string op = operatorStack.Pop();
                double arg2 = operandStack.Pop();
                double arg1 = operandStack.Pop();
                operandStack.Push(_operations[Array.IndexOf(_operators, op)](arg1, arg2));
            }

            return operandStack.Pop();
        }

        /**
         * Why even write a description for this function.
         */
        private string getSubExpression(List<string> tokens, ref int index)
        {
            StringBuilder subExpr = new StringBuilder();
            int parenlevels = 1;
            index += 1;

            while (index < tokens.Count && parenlevels > 0)
            {
                string token = tokens[index];

                if (tokens[index] == "(")
                {
                    parenlevels += 1;
                }

                if (tokens[index] == ")")
                {
                    parenlevels -= 1;
                }

                if (parenlevels > 0)
                {
                    subExpr.Append(token);
                }

                index += 1;
            }

            if ((parenlevels > 0))
            {
                throw new ArgumentException("Mis-matched parentheses in expression");
            }

            return subExpr.ToString();
        }

        /**
         * Tokenizes the given mathematical expression.
         */
        private List<string> getTokens(string expression)
        {
            string operators = "()^*/+-";
            List<string> tokens = new List<string>();
            StringBuilder sb = new StringBuilder();

            foreach (char c in expression.Replace(" ", string.Empty))
            {
                if (operators.IndexOf(c) >= 0)
                {
                    if ((sb.Length > 0))
                    {
                        tokens.Add(sb.ToString());
                        sb.Length = 0;
                    }

                    tokens.Add(c.ToString());
                }
                else
                {
                    sb.Append(c);
                }
            }

            if ((sb.Length > 0))
            {
                tokens.Add(sb.ToString());
            }

            return tokens;
        }
    }
}

In the middle of asking this question I tried some more to understand the code, and the main part I really grasp is:

// If this is an operator.
if (Array.IndexOf(_operators, token) >= 0)
{
    while (operatorStack.Count > 0 && Array.IndexOf(_operators, token) < Array.IndexOf(_operators, operatorStack.Peek()))
    {
        string op = operatorStack.Pop();
        double arg2 = operandStack.Pop();
        double arg1 = operandStack.Pop();
        operandStack.Push(_operations[Array.IndexOf(_operators, op)](arg1, arg2));
    }

    operatorStack.Push(token);
}
else
{
    operandStack.Push(double.Parse(token));
}

I see it's checking for operators, and pushing the operations between the arguments onto the operand stack. I figure this here is where I check for conditional statements, and I will need to check for the ? and : symbols, get all three subexpressions, evaluate the conditional expression, and select one of the last two subexpressions to push onto the operand stack. At-least that's how I understand it. I'm not so sure what to do about the comparisons though.

I realize there is a similar question: Adding Conditionals & Functions to a Math Parser

But I'm using C#.NET I can't figure out how to adapt whats done there to my code. I'm not asking that you write the code for me (not that I would complain much xP), but I really need a starting point. What steps do I need to take to get this done.

Community
  • 1
  • 1
Brandon Miller
  • 2,247
  • 8
  • 36
  • 54
  • You could use the builtin compiler instead: [CodeDOM](http://msdn.microsoft.com/en-us/library/650ax5cx(v=vs.110).aspx) – Dmitry Oct 15 '14 at 21:48

1 Answers1

0

I have not tried this, but the code does not look too complex, and you should try doing this yourself. Try stepping through Parse in a debugger.

The first thing you will have to do is add new strings like "<", "?" and ":" to _operators and look for them in getTokens. Also add Funcs to _operations that perform the operations.

One problem I foresee is that the only data type it deals with are doubles, and relational operators like < return bools. You could use the old C convention where zero is false and nonzero is true, but note that will make your parser non-typesafe.

Another roadblock you are going to face is that the arguments to the ternary operator ? are expressions, not tokens, and your parser only looks at tokens. You can use a shortcut from Pascal where you fully evaluate each expression. You're going to have to modify Eval to handle the fact that ? has the lowest precedence but comes before : in your expression and it looks like Eval is strictly left-to-right. IIRC Pascal used another shortcut that would internally add parentheses to expressions to handle precedence, so for example 1>0 ? 1+1 : 1-1 becomes (1>0)?((1+1):(1-1)) becomes1?(2:0) which becomes 2.

Going further you will have to produce Abstract Syntax Trees, not tokens.

Dour High Arch
  • 21,513
  • 29
  • 75
  • 90