Создать примитивный парсер математических выражений

151
20 октября 2018, 17:20

Есть строки:

string f1 = "sin(x)^2+662^2";
string f2 = "e^2+e^5";

После операций замены необходимо получить:

f1 == "Math.Pow(sin(x),2)+Math.Pow(662,2)"
f2 == "Math.Exp(2)+Math.Exp(5)"

Моя цель создать примитивный парсер математических выражений. Как я понимаю выполнить мою задачу реально через Regex, но составить pattern для замены я не могу

Answer 1

Для начала строим синтаксический и лексический анализаторы (парсер и лексер). Возьмём для этого ANTLR4.

grammar Calculator;
/// Parser
prog: expr+ ;
expr : expr '^' expr                  # Pow
     | expr op=('+'|'-') expr         # AddSub
     | expr op=('*'|'/') expr         # MulDiv
     | VAL                            # Val
     | fun=('sin'|'cos') '(' expr ')' # Function
     | '(' expr ')'                   # Parens
     ;
/// Lexer
VAL : [0-9]+ ( '.' [0-9]+ )?;
MUL : '*';
DIV : '/';
ADD : '+';
SUB : '-';
POW : '^';
SIN : 'sin';
COS : 'cos';
WS  : (' '|'\r'|'\n') -> channel(HIDDEN);

Дальше с помощью паттерна Visitor обходим синтаксическое дерево:

public class CalculatorVisitor : CalculatorBaseVisitor<string>
{
    private static readonly Dictionary<int, string> FunctionMap = new Dictionary<int, string> {
        { CalculatorParser.SIN, "Math.Sin" },
        { CalculatorParser.COS, "Math.Cos" },
    };
    public override string VisitPow (CalculatorParser.PowContext context)
    {
        return "Math.Pow(" + Visit(context.expr(0)) + "," + Visit(context.expr(1)) + ")";
    }
    public override string VisitMulDiv (CalculatorParser.MulDivContext context)
    {
        return Visit(context.expr(0)) + context.op.Text + Visit(context.expr(1));
    }
    public override string VisitAddSub (CalculatorParser.AddSubContext context)
    {
        return Visit(context.expr(0)) + context.op.Text + Visit(context.expr(1));
    }
    public override string VisitVal (CalculatorParser.ValContext context)
    {
        return context.VAL().GetText();
    }
    public override string VisitFunction (CalculatorParser.FunctionContext context)
    {
        return FunctionMap[context.fun.Type] + "(" + Visit(context.expr()) + ")";
    }
    public override string VisitParens (CalculatorParser.ParensContext context)
    {
        return "(" + Visit(context.expr()) + ")";
    }
}

И наконец, преобразовываем строку:

const string input = "(1+1)^2*(2+2)^3+sin(10.5)^2";
Console.WriteLine(new CalculatorVisitor().Visit(
    new CalculatorParser(new CommonTokenStream(
        new CalculatorLexer(new AntlrInputStream(input)))).prog()));

Получаем на выходе:

Math.Pow((1+1),2)*Math.Pow((2+2),3)+Math.Pow(Math.Sin(10.5),2)

P.S. Ногами не пинать, первый раз в жизни вижу ANTLR.

Answer 2

Как я понимаю выполнить мою задачу реально через Regex

Нет. Точнее, не совсем. Предпросмотр назад должен позволить написать корректный парсинг, но, формально, регулярными выражениями такая задача не решается.

Моя цель создать примитивный парсер математических выражений.

Вот его и надо создавать. Выражение парсится за один проход по нему. Возведение в степень немного усложняет задачу, если оно выполняется справа налево.

C#

Если ты собираешься потом его программно скомпилировать получившуюся строку как код на C# (что вообще-то нехорошо), то лучше сделай то же самое, но как код на VB.NET - в нём не потребуются замены, про которые ты спрашиваешь.

READ ALSO
Извлечь корень из отрицательного числа

Извлечь корень из отрицательного числа

Как извлечь корень из отрицательного числа? Например, результатом выражения MathPow(-8, 1

195
#DEFINE в C# (Unity)

#DEFINE в C# (Unity)

Прогаю на С# в Unity, нередко используя команды препроцессора #if/#endif, #region/#endregion и прочие

154
Анимация движения элемента [дубликат]

Анимация движения элемента [дубликат]

На данный вопрос уже ответили:

184
Создать и удалить форму C#

Создать и удалить форму C#

Мне нужно создать приложение, которое в фоне выполняет различные действия, и при определённом условии создало форму, показало там что-то...

187