PDA

View Full Version : Parser for mathematic expression from QString



sr1s
7th May 2018, 15:39
Hello!
I try to use a parser for simple math expressions from a QString, as 3+2.(5^2)/9....and so on (no variables to calculate, just numbers). I started with a recursive-descent parser, but then I found a simpler solution with QJSEngine:

#include "math.h"
#include "expression_calc.h"
#include "ui_expression_calc.h"
#include "QDebug"
#include <QApplication>
#include <QJSEngine>
#include <QJSValue>
#include <QObject>


Expression_calc::Expression_calc(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Expression_calc)
{
ui->setupUi(this);

}

Expression_calc::~Expression_calc()
{
delete ui;
}

void Expression_calc::on_CalcButton_clicked()
{
QString expression= ui->exp->text();
QJSEngine parsexpression;
double result=parsexpression.evaluate(expression).toNumbe r();
ui->Result->setText(QString::number(result));
}

Ok, very simply, ...but this method can interpret just +,-,*,/,()...
I decide to manipulate the main QString expression with a

expression.replace("sqrt","Math.sqrt");
expression.replace("abs","Math.abs");
expression.replace("cos","Math.cos");
......

Not very elegant, but a fast solution. In add, as I have to store the expressions, I can save the corrected versions.
All it works, except for the damned "^". Math requires "pow(3,2)" instead of common expression 3^2, does someone have a fast solution to workaround this, or you suggest to return to a recursive-descent parser?
Thanks!

d_stranz
7th May 2018, 17:37
Write yourself a QRegularExpression string match that pre-processes your input string, looking for "expr1^expr2" and substitutes "pow( expr1, expr2)" in the same way as you replace your other expressions.

Does the JS parser recognize "**" for exponentiation? That could be an even simpler search and replace.

sr1s
7th May 2018, 19:55
I tried with "**" , hoping to have the same behavior as Java... but it didn't work. But your suggestion seems to be the right way, I'll try it.
Thank you very much!

sr1s
8th May 2018, 10:44
I followed your suggestion, and I solved my problems!
Perhaps, with more knowledge, I could do better, but for now it's ok. The main problem is that RegularExpression considers "+" as a part of a number, and also the hierarchy of function didn't work well, so I "frozen" the operators (changing them with PLUS, MINUS, etc.), unfreezing them when I need... By now it goes correctly for the main functions, I have to implement the trigonometric and ABS ones, (now, as program simplifies expression between brackets, "ABS(-5)" becomes "ABS-5" ->nan), but it shouldn't be a great problem.

I found a useful help using https://regex101.com/, it's simpler to understand QRegularEspression's syntax than compiling the code each time

This is the code:

#include "math.h"
#include "expression_calc.h"
#include "ui_expression_calc.h"
#include "QDebug"
#include <QApplication>
#include <QJSEngine>
#include <QJSValue>
#include <QObject>

Expression_calc::Expression_calc(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Expression_calc)
{
ui->setupUi(this);

}

Expression_calc::~Expression_calc()
{
delete ui;
}

void Expression_calc::on_CalcButton_clicked()
{
QString expression= ui->exp->text();
QString exp1,exp2;
expression.replace("sin","Math.sin");
expression.replace("cos","Math.cos");
expression.replace("tan","Math.tan");
//.... and so on for trigonometrical functions)

expression.replace("sqrt","Math.sqrt");
expression.replace("(","[");
expression.replace(")","]");
expression.replace("^","POW");
expression.replace("+","PLUS");
expression.replace("-","MINUS");
expression.replace("*","MULT");
expression.replace("/","DIV");

while (expression.contains("[")) //START SOLVING EXPRESSIONS BETWEEN BRACKETS
{

QRegularExpression rep("\\[([^\\]]+)\\]");
QRegularExpressionMatch matchp = rep.match(expression);
QString expp_w_brackets = matchp.captured(0); //original expression with brakets
QString expp = matchp.captured(1);//expression without brakets
expp.replace ("PLUS","+");
expp.replace ("MINUS","-");
expp.replace("MULT","*");
expp.replace("DIV","/");

QJSEngine parsexpressionp;
double resultp=parsexpressionp.evaluate(expp).toNumber();
QString BraketResult=(QString::number(resultp));
expression.replace(""+expp_w_brackets+"" ,""+BraketResult+"");
}

while (expression.contains("POW")) //SOLVE POE EXPRESSION
{
QRegularExpression re1("(\\d+.\\d+POW)|(\\d+POW)");
QRegularExpressionMatch match1 = re1.match(expression);

QRegularExpression re2("(POW\\d+.\\d+)|(POW\\d+)");
QRegularExpressionMatch match2 = re2.match(expression);


if (match1.hasMatch())
{
exp1 = match1.captured(0);
exp1.replace("POW","");
}

if (match2.hasMatch())
{
exp2 = match2.captured(0);
exp2.replace("POW","");
}


expression.replace (""+exp1+"" "POW"""+exp2+"" , "Math.pow(" ""+exp1+"" "," ""+exp2+"" ")");

}
expression.replace("[","(");
expression.replace("]",")");
expression.replace ("PLUS","+");
expression.replace ("MINUS","-");
expression.replace("MULT","*");
expression.replace("DIV","/");
QJSEngine parsexpression;
double result=parsexpression.evaluate(expression).toNumbe r();
ui->Result->setText(QString::number(result));
}

sr1s
8th May 2018, 15:58
I have to correct a mistake (if I had "2^(6/3)", the code returns a nan), in a second time I'll create a pow() function to have a more clear code...
For the problem abs(-1)->abs1, or cos(0) -> cos0, a very rough solution: I change, for example, abs(-1) into math.abs{-1), and then, before processing expression, replace again "{" with "(".. Very ugly, but it's enough to avoid the transforming into math.abs-1...
So, the code is this:

#include "math.h"
#include "expression_calc.h"
#include "ui_expression_calc.h"
#include "QDebug"
#include <QApplication>
#include <QJSEngine>
#include <QJSValue>
#include <QObject>

Expression_calc::Expression_calc(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Expression_calc)
{
ui->setupUi(this);

}

Expression_calc::~Expression_calc()
{
delete ui;
}

void Expression_calc::on_CalcButton_clicked()
{
QString expression= ui->exp->text();
QString exp1,exp2;

expression.replace("pi","Math.PI");
expression.replace("abs(","Math.abs{");
expression.replace("sin(","Math.sin{");
expression.replace("cos(","Math.cos{");
expression.replace("tan","Math.tan");
//.... and so on for trigonometrical functions)

expression.replace("sqrt","Math.sqrt");
expression.replace("(","[");
expression.replace(")","]");
expression.replace("^","POW");
expression.replace("+","PLUS");
expression.replace("-","MINUS");
expression.replace("*","MULT");
expression.replace("/","DIV");

while (expression.contains("[")) //START SOLVING EXPRESSIONS BETWEEN BRACKETS
{

QRegularExpression rep("\\[([^\\]]+)\\]");
QRegularExpressionMatch matchp = rep.match(expression);
QString expp_w_brackets = matchp.captured(0); //original expression with brakets
QString expp = matchp.captured(1);//expression without brakets

while (expp.contains("POW")) //SOLVE POE EXPRESSION
{
QRegularExpression re1("(\\d+.\\d+POW)|(\\d+POW)");
QRegularExpressionMatch match1 = re1.match(expp);

QRegularExpression re2("(POW\\d+.\\d+)|(POW\\d+)");
QRegularExpressionMatch match2 = re2.match(expp);

if (match1.hasMatch())
{
exp1 = match1.captured(0);
exp1.replace("POW","");
}

if (match2.hasMatch())
{
exp2 = match2.captured(0);
exp2.replace("POW","");
}

expp.replace (""+exp1+"" "POW"""+exp2+"" , "Math.pow(" ""+exp1+"" "," ""+exp2+"" ")");
}


expp.replace ("PLUS","+");
expp.replace ("MINUS","-");
expp.replace("MULT","*");
expp.replace("DIV","/");

QJSEngine parsexpressionp;
double resultp=parsexpressionp.evaluate(expp).toNumber();
QString BraketResult=(QString::number(resultp));
expression.replace(""+expp_w_brackets+"" ,""+BraketResult+"");
}

while (expression.contains("POW")) //SOLVE POE EXPRESSION
{
QRegularExpression re1("(\\d+.\\d+POW)|(\\d+POW)");
QRegularExpressionMatch match1 = re1.match(expression);

QRegularExpression re2("(POW\\d+.\\d+)|(POW\\d+)");
QRegularExpressionMatch match2 = re2.match(expression);


if (match1.hasMatch())
{
exp1 = match1.captured(0);
exp1.replace("POW","");
}

if (match2.hasMatch())
{
exp2 = match2.captured(0);
exp2.replace("POW","");
}


expression.replace (""+exp1+"" "POW"""+exp2+"" , "Math.pow(" ""+exp1+"" "," ""+exp2+"" ")");
}

expression.replace("{","(");
expression.replace("[","(");
expression.replace("]",")");
expression.replace ("PLUS","+");
expression.replace ("MINUS","-");
expression.replace("MULT","*");
expression.replace("DIV","/");
QJSEngine parsexpression;
double result=parsexpression.evaluate(expression).toNumbe r();
ui->Result->setText(QString::number(result));
}

d_stranz
8th May 2018, 16:41
Well, the more possibilities you add, the more difficult it will be to keep the code from getting ugly. At some point I think you may have to consider a recursive descent parser - you'll get into problems of precedence, embedded expressions (like "2^(6/3)"), etc. where the work of getting them suitable for the JS parser is as much as evaluating it yourself.

Here is a simple one in C# (http://blog.roboblob.com/2014/12/16/recursive-descent-parser-for-arithmetic-expressions-with-real-numbers/) which looks like it can be translated into C++ and modified pretty easily to support functions and precedence. Or you can go old-school and use Flex / Bison or Flex++ / Bison++. You'll learn more than you ever wanted to know about regular expressions.