Arithmetic expression parser in C#

By   Calendar icon Nov 29, 2015   Level 1 icon

Description:

We design here a parser of very basic arithmetic expressions (with the 4 basic operations) using regular expressions.

Preferencesoft

Tags: C#

In this page, we implement a parser for arithmetic expressions without parentheses. We limit ourselves to the four arithmetic operations +, *, -, / and real numbers. To represent real numbers, we use engineering notation. We authorize the presence of plus and minus signs in front of a real number.

Implementation

We want to split an expression on numbers and binary operators +, *, -, /, but first we must separate the plus and minus signs placed in front the operands or the exponents.

For example, consider -45 - 8.6 E + 12

The first symbol - is considered as a minus sign, the second - is a binary operator and the + after E is a plus sign.

We first look for possible signs after E, after binary operators or at the beginning of the expression and we use the rule of signs. We replace the minus sign by the letter M and remove plus sign.

We split the expression to get a list of strings made up of operands and operators. Then we replace in each operand, the possible letter M by -.

It remains to evaluate the expression considering the priorities of operators.

The program

public void Scan(string str)
{
    error = false;
    //remove spaces
    string s = str.Replace(" ", "");
    if (s == "")
    {
        error = true;
        return;
    }
    //no other character than Ee,.+-*/0123456789
    // + cannot be followed by * or /
    // - cannot be followed by * or /
    if (Regex.IsMatch(s, "[^Ee,\\.+\\-*/0-9]|[+\\-][*/]"))
    {
        error = true;
        return;
    }
    s = s.Replace("e", "E");
    s = s.Replace("E+", "F");
    s = s.Replace("E-", "G");
    s = s.Replace("E", "F");
    //If there is a + or - at the beginning of the expression necessarily are unary operators.
    //as if there is an addition operation before
    s = "+" + s;
    s = SignsRule(s);
    //remove the + before
    s = s.Substring(1);
    //not two consecutive operators
    if (Regex.IsMatch(s, "[+\\-*/]{2,}"))
    {
        error = true;
        return;
    }
    //a trick to cut each side of operators
    //~ symbol is never used
    s = s.Replace("+", "~+~");
    s = s.Replace("-", "~-~");
    s = s.Replace("*", "~*~");
    s = s.Replace("/", "~/~");
    lex = (s.Split('~')).ToList();
    RestoreVerifyOperators();
}

public void RestoreVerifyOperators()
{
    int i = 0;
    bool err = false;
    while (i < lex.Count)
    {
        if (lex[i].Length == 0)
        {
            error = true;
            return;
        }
        switch (lex[i][0])
        {
        case '+':
        case '-':
        case '/':
        case '*':
            if (i % 2 == 0) err = true;
            break;
        default:
            string s = lex[i].Replace("F", "E");
            s = s.Replace("G", "E-");
            s = s.Replace("M", "-");
            lex[i] = s;
            if (i % 2 == 1) err = true;
            break;
        }
        if (err)
        {
            error = true;
            return;
        }
        i++;
    }
}
public string Evaluate()
{
    int i = 1;
    while (i + 1 < lex.Count)
    {
        switch (lex[i])
        {
        case "/":
            double x = double.Parse(lex[i - 1]);
            double y = double.Parse(lex[i + 1]);
            lex.RemoveAt(i);
            lex.RemoveAt(i);
            lex[i - 1] = (x / y).ToString();
            break;
        case "*":
            x = double.Parse(lex[i - 1]);
            y = double.Parse(lex[i + 1]);
            lex.RemoveAt(i);
            lex.RemoveAt(i);
            lex[i - 1] = (x * y).ToString();
            break;
        default:
            i += 2;
            break;
        }
    }
    i = 1;
    while (i + 1 < lex.Count)
    {
        switch (lex[i])
        {
        case "+":
            double x = double.Parse(lex[i - 1]);
            double y = double.Parse(lex[i + 1]);
            lex.RemoveAt(i);
            lex.RemoveAt(i);
            lex[i - 1] = (x + y).ToString();
            break;
        case "-":
            x = double.Parse(lex[i - 1]);
            y = double.Parse(lex[i + 1]);
            lex.RemoveAt(i);
            lex.RemoveAt(i);
            lex[i - 1] = (x - y).ToString();
            break;
        default:
            i += 2;
            break;
        }
    }
    return lex[0];
}
}

and the call:

private async void button_Click(object sender, RoutedEventArgs e)
{
    p.Scan("78*-89E+9+-8E-7-9*-7-8/6*8/4*2/255/3");
    bool b = p.error;
    if (!p.error)
    {
        textBlock1.Text = p.Evaluate();
    }
}

CSharp