parsing math expression in c++

2020-01-25 06:02发布

问题:

I have a question about Parsing Trees:

I have a string (math expresion estring), for example: (a+b)*c-(d-e)*f/g. I have to parse that expression in a Tree:

class Exp{};
class Term: public Exp{
    int n_;
}

class Node: Public Exp{
    Exp* loperator_;
    Exp* roperator_;
    char operation; // +, -, *, /
}

What algorithm can I use to build a tree which represents the expresion string above?

回答1:

Use the Shunting-yard algorithm. The wikipedia description is quite comprehensive, I hope it will suffice.

You can also try to write a formal grammar, for example a parsing-expression grammar, and use a tool to generate a parser. This site about PEGs lists 3 C/C++ libraries for PEG parsing.



回答2:

(a+b)*c-(d-e)*f/g is an in-fix expression.

To easily make a tree, convert that into a Prefix expression first.

From the Example, prefix of (A * B) + (C / D) is + (* A B) (/ C D)

     (+)            
     / \        
    /   \       
  (*)    (/)         
  / \   /  \        
 A   B C    D   

 ((A*B)+(C/D))  

Your tree then looks has + as its root node. You can continue populating the left and right sub-tree, about each operator.

Also, this link explains Recursive Descent Parsing in detail, and can be implemented.



回答3:

First step is to write a grammar for your expressions. Second step for such a simple case is to write a recursive descent parser, that's the algorithm I would recommend. Here's the wiki page on recursive descent parsers which has a good looking C implementation.

http://en.wikipedia.org/wiki/Recursive_descent_parser



回答4:

#include <algorithm>
#include <iostream>
#include <string>
#include <cctype>
#include <iterator>

using namespace std;

class Exp{
public:
//  Exp(){}
    virtual void print(){}
    virtual void release(){}
};
class Term: public Exp {
    string val;
public:
    Term(string v):val(v){}
    void print(){
        cout << ' ' << val << ' ';
    }
    void release(){}
};

class Node: public Exp{
    Exp *l_exp;
    Exp *r_exp;
    char op; // +, -, *, /
public:
    Node(char op, Exp* left, Exp* right):op(op),l_exp(left), r_exp(right){}
    ~Node(){
    }
    void print(){
        cout << '(' << op << ' ';
        l_exp->print();
        r_exp->print();
        cout  << ')';
    }
    void release(){
        l_exp->release();
        r_exp->release();
        delete l_exp;
        delete r_exp;
    }
};

Exp* strToExp(string &str){
    int level = 0;//inside parentheses check
    //case + or -
    //most right '+' or '-' (but not inside '()') search and split
    for(int i=str.size()-1;i>=0;--i){
        char c = str[i];
        if(c == ')'){
            ++level;
            continue;
        }
        if(c == '('){
            --level;
            continue;
        }
        if(level>0) continue;
        if((c == '+' || c == '-') && i!=0 ){//if i==0 then s[0] is sign
            string left(str.substr(0,i));
            string right(str.substr(i+1));
            return new Node(c, strToExp(left), strToExp(right));
        }
    }
    //case * or /
    //most right '*' or '/' (but not inside '()') search and split
    for(int i=str.size()-1;i>=0;--i){
        char c = str[i];
        if(c == ')'){
            ++level;
            continue;
        }
        if(c == '('){
            --level;
            continue;
        }
        if(level>0) continue;
        if(c == '*' || c == '/'){
            string left(str.substr(0,i));
            string right(str.substr(i+1));
            return new Node(c, strToExp(left), strToExp(right));
        }
    }
    if(str[0]=='('){
    //case ()
    //pull out inside and to strToExp
        for(int i=0;i<str.size();++i){
            if(str[i]=='('){
                ++level;
                continue;
            }
            if(str[i]==')'){
                --level;
                if(level==0){
                    string exp(str.substr(1, i-1));
                    return strToExp(exp);
                }
                continue;
            }
        }
    } else
    //case value
        return new Term(str);
cerr << "Error:never execute point" << endl;
    return NULL;//never
}

int main(){
    string exp(" ( a + b ) * c - ( d - e ) * f / g");
    //remove space character
    exp.erase(remove_if(exp.begin(), exp.end(), ::isspace), exp.end());
    Exp *tree = strToExp(exp);
    tree->print();
    tree->release();
    delete tree;
}
//output:(- (* (+  a  b ) c )(/ (* (-  d  e ) f ) g ))


回答5:

You can use this grammar to create your expression.

exp:
    /* empty */
  | non_empty_exp { print_exp(); }
  ;
non_empty_exp:
    mult_div_exp
  | add_sub_exp
  ;
mult_div_exp:
    primary_exp
  | mult_div_exp '*' primary_exp { push_node('*'); }
  | mult_div_exp '/' primary_exp { push_node('/'); }
  ;
add_sub_exp:
    non_empty_exp '+' mult_div_exp { push_node('+'); }
  | non_empty_exp '-' mult_div_exp { push_node('-'); }
  ;
primary_exp:
  | '(' non_empty_exp ')'
  | NUMBER { push_term($1); }
  ;

And the following for your lexer.

[ \t]+   {}
[0-9]+   { yylval.number = atoi(yytext); return NUMBER; }
[()]     { return *yytext; }
[*/+-]   { return *yytext; }

The expression is built as you go, using these routines:

std::list<Exp *> exps;

/* push a term onto expression stack */
void push_term (int n) {
    Term *t = new Term;
    t->n_ = n;
    exps.push_front(t);
}

/* push a node onto expression stack, top two in stack are its children */
void push_node (char op) {
    Node *n = new Node;
    n->operation_ = op;
    n->roperator_ = exps.front();
    exps.pop_front();
    n->loperator_ = exps.front();
    exps.pop_front();
    exps.push_front(n);
}

/*
 * there is only one expression left on the stack, the one that was parsed
 */
void print_exp () {
    Exp *e = exps.front();
    exps.pop_front();
    print_exp(e);
    delete e;
}

The following routine can pretty print your expression tree:

static void
print_exp (Exp *e, std::string ws = "", std::string prefix = "") {
    Term *t = dynamic_cast<Term *>(e);
    if (t) { std::cout << ws << prefix << t->n_ << std::endl; }
    else {
        Node *n = dynamic_cast<Node *>(e);
        std::cout << ws << prefix << "'" << n->operation_ << "'" << std::endl;
        if (prefix.size()) {
            ws += (prefix[1] == '|' ? " |" : "  ");
            ws += "  ";
        }
        print_exp(n->loperator_, ws, " |- ");
        print_exp(n->roperator_, ws, " `- ");
    }
}


回答6:

I wrote a class to handle this back in the day. It's a little verbose and perhaps not the most efficient thing on the planet but it handles signed/unsigned integers, double, float, logical and bitwise operations.

It detects numeric overflow and underflow, returns descriptive text and error codes about syntax, can be forced to handle doubles as integers, or ignore signage, supports user definable precision and smart rounding and will even show its work if you set DebugMode(true).

Lastly...... it doesn't rely on any external libraries, so you can just drop it in.

Sample usage:

CMathParser parser;

double dResult = 0;
int iResult = 0;

//Double math:
if (parser.Calculate("10 * 10 + (6 ^ 7) * (3.14)", &dResult) != CMathParser::ResultOk)
{
    printf("Error in Formula: [%s].\n", parser.LastError()->Text);
}
printf("Double: %.4f\n", dResult);

//Logical math:
if (parser.Calculate("10 * 10 > 10 * 11", &iResult) != CMathParser::ResultOk)
{
    printf("Error in Formula: [%s].\n", parser.LastError()->Text);
}
printf("Logical: %d\n", iResult);

The latest version is always available via the CMathParser GitHub Repository.



标签: c++ parsing tree