View Full Version : Parser for mathematic expression from QString
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.
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!
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));
}
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.
Powered by vBulletin® Version 4.2.5 Copyright © 2024 vBulletin Solutions Inc. All rights reserved.