Regular expressions and math parser

    Once upon a time I needed a parser of mathematical expressions in C #. Of course, downloading a finished implementation is not a problem. But I just didn’t have the Internet in those years. As a result, absolutely without hesitation and without the theoretical foundations of parsers, state machines, and other things, it was written through regular expressions. About 10 minutes. It is worth noting that only arithmetic operations and brackets were needed. Support for trigonometric functions and other things was not required.

    First, we highlight regular expressions for numbers and actions:
    private const string RegexBr = "\\(([1234567890\\.\\+\\-\\*\\/^%]*)\\)";    // Скобки
    private const string RegexNum = "[-]?\\d+\\.?\\d*";                         // Числа
    private const string RegexMulOp = "[\\*\\/^%]";                             // Первоприоритетные числа
    private const string RegexAddOp = "[\\+\\-]";                               // Второприоритетные числа
    

    Now the method that divides the resulting string into elements and calculates them recursively:
    public static double Parse(string str)
    {
        // Парсинг скобок
        var matchSk = Regex.Match(str, RegexBr);
        if (matchSk.Groups.Count > 1)
        {
            string inner = matchSk.Groups[0].Value.Substring(1, matchSk.Groups[0].Value.Trim().Length - 2);
            string left = str.Substring(0, matchSk.Index);
            string right = str.Substring(matchSk.Index + matchSk.Length);
            return Parse(left + Parse(inner).ToString(CultureInfo.InvariantCulture) + right);
        }
        // Парсинг действий
        var matchMulOp = Regex.Match(str, string.Format("({0})\\s?({1})\\s?({2})\\s?", RegexNum, RegexMulOp, RegexNum));
        var matchAddOp = Regex.Match(str, string.Format("({0})\\s?({1})\\s?({2})\\s?", RegexNum, RegexAddOp, RegexNum));
        var match = matchMulOp.Groups.Count > 1 ? matchMulOp : matchAddOp.Groups.Count > 1 ? matchAddOp : null;
        if (match != null)
        {
            string left = str.Substring(0, match.Index);
            string right = str.Substring(match.Index + match.Length);
            return Parse(left + ParseAct(match).ToString(CultureInfo.InvariantCulture) + right);
        }
        // Парсинг числа
        try
        {
            return double.Parse(str, CultureInfo.InvariantCulture);
        }
        catch (FormatException)
        {
            throw new FormatException(string.Format("Неверная входная строка '{0}'", str));
        }
    }
    

    And the last to write a method that directly calculates the value of the action:
    private static double ParseAct(Match match)
    {
        double a = double.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
        double b = double.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);
        switch (match.Groups[2].Value)
        {
            case "+": return a + b;
            case "-": return a - b;
            case "*": return a * b;
            case "/": return a / b;
            case "^": return Math.Pow(a, b);
            case "%": return a % b;
            default: throw new FormatException($"Неверная входная строка '{match.Value}'");
        }
    }
    

    Such “abnormal programming” I had. I don’t see any point in completely citing the source text — hardly anyone will need it. But in any case, to make a class of three pieces is a matter of a couple of seconds.

    Thanks for attention.

    Also popular now: