2012-12-13 30 views
2

如何在中缀计算器语法中解析和评估表达式?我想到了两种方法。中缀计算器表达式解析器

第一个涉及使用两个堆栈。一个用于数字,另一个用于运营商,我会评估运营商的优先级和关联性,以便找出如何评估表达式。

第二种方法涉及将中缀表达式转换为后缀,我不知道该怎么做。这只是一个想法。目前,我设置了我的程序,目的是使用第一种方法。

#include <iostream> 
#include <string> 
#include <sstream> 
using namespace std; 

bool die(const string &msg); 

//stack class 
class Stack{ 
public: 
    Stack(); 
    void push(const double &val); 
    void push(const string &oper); 
    double popnum(); 
    string popop(); 
    double getopele(); 
    double getnumele(); 
private: 
    static const unsigned MAX=30; 
    string opstack[MAX]; 
    double numstack[MAX]; 
    unsigned opele; 
    unsigned numele; 
}; 

//operator type 
struct OP{ 
    string name; 
    void * func; 
    unsigned arity; 
    unsigned prec; 
    bool lass; 
    string descrip; 
}; 
//operator table 
OP op[]={{"+", add, 2, 4, true, "2+3 is 5"}, 
     {"-", subtract, 2, 4, true, "2-3 is -1"}, 
     {"*", multiply, 2, 6, true, "2*3 is 6"}, 
     {"/", divide, 2, 6, true, "2/3 is 0.666666..., div by 0 illegal"}}; 
unsigned OPELE =sizeof(op)/sizeof(op[0]); 

//operators 
bool add(double &r, double &x, double &y); 
bool subtract(double &r, double &x, double &y); 
bool multiply(double &r, double &x, double &y); 
bool divide(double &r, double &x, double &y); 

//Manip 
unsigned findindex(string token, OP op[], unsigned OPELE); 
bool parse(double &t, const string &token); 
bool evaluate(double &result, string line); 
bool weird(double x); 

int main(){ 
    for(string line; getline(cin, line);){ 
     if(line=="QUIT") break; 
     if(line.empty()) continue; 
     if(line=="DOC") 
      for(unsigned i=0; i<OPELE; i++) 
       cout<<op[i].name<<" | "<<op[i].descrip<<'\n'; 
     double result; 
     if(evaluate(result, line)){ 
      cout<<result<<'\n'; 
     }else{ 
      cout<<"Could not understand input\n\n"; 
     } 
    } 
} 

Stack::Stack(){ 
    opele=0; 
    numele=0; 
} 

void Stack::push(const double &val){ 
    if(MAX) die("Stack Overflow"); 
    numstack[numele++]=val; 
} 

void Stack::push(const string &oper){ 
    if(MAX) die("Stack Overflow"); 
    opstack[opele++]=oper; 
} 

double Stack::popnum(){ 
    if(!numele) die("Stack Underflow"); 
    return numstack[--numele]; 
} 

string Stack::popop(){ 
    if(!opele) die("Stack Underflow"); 
    return opstack[--opele]; 
} 

double Stack::getopele(){ 
    return opele; 
} 

double Stack::getnumele(){ 
    return numele; 
} 

bool add(double &r, double &x, double &y){ 
    double t = x + y; 
    if(weird(t)) return false; 
    r = t; 
    return true; 
} 

bool subtract(double &r, double &x, double &y){ 
    double t = x - y; 
    if(weird(t)) return false; 
    result = t; 
    return true; 
} 

bool multiply(double & r, double& x, double &y){ 
    double t = x * y; 
    if(weird(t)) return false; 
    result = t; 
    return true; 
} 

bool divide(double & result, double &x, double &y){ 
    double t = x/y; 
    if(weird(t)) return false; 
    result = t; 
    return true; 
} 

unsigned findindex(string token, OP op[], unsigned OPELE){ 
    for(unsigned i=0l i<OPELE; i++) 
     if(op[i].name==token) 
      return i; 
    return UINT_MAX; 

} 

bool parse(double &t, const string &token){ 
    istringstream sin(token); 
    double t; 
    if(!(sin >>t)) return false; 
    char junk; 
    if(sin >>junk) return false; 
    value = t; 
    return true; 
} 

bool evaluate(double &result, string line){ 
    istringstream sin(line); 
    Stack s; 
    for(string token; sin>>token;){ 
     double t; 
     if(parse(t, token)){ 
      s.push(t); 
     }else if(
    } 
} 

bool weird(double x){ 
    return x != x || x != 0 && x == 2*x; 
} 
+2

你可能有兴趣在[调度场算法(http://en.wikipedia.org/wiki/Shunting-yard_algorithm)到中缀转换表达式到后缀,或[自顶向下运算符优先解析](http://effbot.org/zone/tdop-index.htm)与Pratt解析器。 –

+0

你也可以看看[GNU Bison](http://www.gnu.org/software/bison/)及其文档。本教程将教你很多关于LALR解析器的知识。 –

回答

6

这将是一个漫长的读取,但无论如何,我将与大家分享我用它来解析缀表达式并将其存储为一个二叉树的算法。不是堆栈,而是二叉树。解析这将给后缀顺序很容易。我不认为这是最好的算法,但这对我的脚本语言有效。

该算法:

我们具有一个二进制树的“当前节点”和“当前表达式”上操作的方法。节点包含“数据”字段和“类型”字段。阶段1:简单的事物,比如“4”直接进入节点,并且我们将类型指定为“数据”,即。按原样使用此信息。

第2步:现在,让我们考虑下面的表达式:

a) 2 + 3 

这会转化成以下二叉树:

+ 
/\ 
2 3 

因此,运营商进入节点和操作数进入叶子。将表达式a)转化为树是非常简单的:找到运算符,放入树的“当前”节点,指定节点的类型为运算符“PLUS”,剩下的部分进入树到节点的左边部分,它的权利进入正确的树。尼斯和简单,使用来自阶段1中的两个叶子的信息将是“DATA”叶子具有值2和3。

阶段3:但是,对于更复杂的表达式:

b) 2 * 3 + 4 

树将是:

+ 
/\ 4 
    * 
/\ 
2 3 

所以我们需要修改上面下面的算法:查找具有最高优先级(考虑到C++指南... +(加的优先级)和第一运营商 - (减号)是6,而表达式中的*(乘),/(除)和%(模)的优先级为5)将表达式分为两部分(在优先级最高的操作数之前,优先级最高的操作数之后),并将递归调用两部分的方法,同时将具有最高优先级的运算符放入当前节点。所以,我们确实创造了一个树机智HDATA喜欢:

​​

调用方法与“2·3”

,并在这个阶段,我们退回到“第二阶段”呼叫(“2 * 3 “)和”阶​​段1“的电话”4“。

阶段4:如果表达式中存在假设,该怎么办?如

c) 2 * (3 + 4) 

这将使我们的树:

 * 
    /\ 
    2 + 
    /\ 
     3 4 

我们修改了算法是这样的:

  • 而目前的表达被封闭在一个paranthesis从删除paranthesis它并重新启动算法。小心。 (2 + 3 * 4 + 5)被认为包含在一个子句中,而(2 + 3)*(4 + 5)则不是。所以,这不仅仅是表达式的开始和结束字符,但是您需要有效地计算出缺口。 (这是一个递归方法,不要害怕第一步...)
  • 现在找到表达式的外部优先级最高的第一个运算符。再次,采取表达式的左侧和右侧,并一次又一次地调用该方法,直到最终出现在“阶段1”即。只有一个数据元素。

    现在这是一个由纯数和运算符组成的表达式的算法。对于更复杂的信息,您可能需要对其进行优化以满足您的需求。如果你认为它值得,请看看http://nap-script.cvs.sourceforge.net/viewvc/nap-script/nap/interpreter.cpp?view=markup。这包含上面关于更复杂的概念(变量,方法调用,后缀/前缀运算符等)的算法的完整实现(在C中)。该方法是build_expr_tree,从982行开始。

+0

我实际上正在考虑在一个括号出现在某个地方的情况下让整个事情递归。我可能会实现这一点。我不得不说我非常喜欢这种方法。 – Painguy

4

recursive descent的方法是手动实现正确的表达式分析器的最简单的方法。在这里,编程语言堆栈与您尝试使用的显式堆栈的功能相同。谷歌有很多关于RD的例子,任何好的编译器书都会有一些。

链接的维基百科页面显示解析器,但不显示如何添加评估。所以下面是C中一个完整的基本表达式求值器。它可以很容易地包装在C++类中,全局变成实例变量。它缺少在生产系统中需要的功能。例如,当它发现错误时,它就退出。 C++异常很容易让你放开递归并继续。它还需要防止数值溢出,零分等等,这显然你知道该怎么做。

递归下降的想法是将所需语言的语法转换为称为LL(1)的形式。完成后,有固定的规则 - 保证每次工作 - 将语法规则转化为程序。我手工完成了这个。有工具可以自动完成。

所以这个评估器很容易扩展。只需添加必要的语法规则,然后对扫描程序,解析器和评估代码实施所需的增强功能。例如,内置的函数规则是unsigned_factor -> FUNCTION_NAME (expr),其中扫描器将所有函数名称识别为相同的标记,并且扩展C函数以解析和计算值。

我不得不包含一个小型扫描仪来获得工作程序。注意超过一半的代码是扫描仪。基本的RD解析器很简单。

如果添加错误恢复,他们会变得更加复杂:智能跳过错误并继续解析的能力,同时只发出一条精确错误的错误消息。但是再一次,这给解析器增加了很多复杂性。

// Bare bones scanner and parser for the following LL(1) grammar: 
// expr -> term { [+-] term }  ; An expression is terms separated by add ops. 
// term -> factor { [*/] factor } ; A term is factors separated by mul ops. 
// factor -> unsigned_factor  ; A signed factor is a factor, 
//   | - unsigned_factor ; possibly with leading minus sign 
// unsigned_factor -> (expr) ; An unsigned factor is a parenthesized expression 
//   | NUMBER    ; or a number 
// 
// The parser returns the floating point value of the expression. 

#include <stdio.h> 
#include <stdlib.h> 

// The token buffer. We never check for overflow! Do so in production code. 
char buf[1024]; 
int n = 0; 

// The current character. 
int ch; 

// The look-ahead token. This is the 1 in LL(1). 
enum { ADD_OP, MUL_OP, LEFT_PAREN, RIGHT_PAREN, NUMBER, END_INPUT } look_ahead; 

// Forward declarations. 
void init(void); 
void advance(void); 
double expr(void); 
void error(char *msg); 

// Parse expressions, one per line. 
int main(void) 
{ 
    init(); 
    while (1) { 
    double val = expr(); 
    printf("val: %f\n", val); 
    if (look_ahead != END_INPUT) error("junk after expression"); 
    advance(); // past end of input mark 
    } 
    return 0; 
}  

// Just die on any error. 
void error(char *msg) 
{ 
    fprintf(stderr, "Error: %s. I quit.\n", msg); 
    exit(1); 
} 

// Buffer the current character and read a new one. 
void read() 
{ 
    buf[n++] = ch; 
    buf[n] = '\0'; // Terminate the string. 
    ch = getchar(); 
} 

// Ignore the current character. 
void ignore() 
{ 
    ch = getchar(); 
} 

// Reset the token buffer. 
void reset() 
{ 
    n = 0; 
    buf[0] = '\0'; 
} 

// The scanner. A tiny deterministic finite automaton. 
int scan() 
{ 
    reset(); 
START: 
    switch (ch) { 
    case ' ': case '\t': case '\r': 
     ignore(); 
     goto START; 

    case '-': case '+': 
     read(); 
     return ADD_OP; 

    case '*': case '/': 
     read(); 
     return MUL_OP; 

    case '(': 
     read(); 
     return LEFT_PAREN; 

    case ')': 
     read(); 
     return RIGHT_PAREN; 

    case '0': case '1': case '2': case '3': case '4': 
    case '5': case '6': case '7': case '8': case '9': 
     read(); 
     goto IN_LEADING_DIGITS; 

    case '\n': 
     ch = ' '; // delayed ignore() 
     return END_INPUT; 

    default: 
     error("bad character"); 
    } 

IN_LEADING_DIGITS: 
    switch (ch) { 
    case '0': case '1': case '2': case '3': case '4': 
    case '5': case '6': case '7': case '8': case '9': 
     read(); 
     goto IN_LEADING_DIGITS; 

    case '.': 
     read(); 
     goto IN_TRAILING_DIGITS; 

    default: 
     return NUMBER; 
    } 

IN_TRAILING_DIGITS: 
    switch (ch) { 
    case '0': case '1': case '2': case '3': case '4': 
    case '5': case '6': case '7': case '8': case '9': 
     read(); 
     goto IN_TRAILING_DIGITS; 

    default: 
     return NUMBER; 
    }   
} 

// To advance is just to replace the look-ahead. 
void advance() 
{ 
    look_ahead = scan(); 
} 

// Clear the token buffer and read the first look-ahead. 
void init() 
{ 
    reset(); 
    ignore(); // junk current character 
    advance(); 
} 

double unsigned_factor() 
{ 
    double rtn = 0; 
    switch (look_ahead) { 
    case NUMBER: 
     sscanf(buf, "%lf", &rtn); 
     advance(); 
     break; 

    case LEFT_PAREN: 
     advance(); 
     rtn = expr(); 
     if (look_ahead != RIGHT_PAREN) error("missing ')'"); 
     advance(); 
     break; 

    default: 
     error("unexpected token"); 
    } 
    return rtn; 
} 

double factor() 
{ 
    double rtn = 0; 
    // If there is a leading minus... 
    if (look_ahead == ADD_OP && buf[0] == '-') { 
    advance(); 
    rtn = -unsigned_factor(); 
    } 
    else 
    rtn = unsigned_factor(); 
    return rtn; 
} 

double term() 
{ 
    double rtn = factor(); 
    while (look_ahead == MUL_OP) { 
    switch(buf[0]) { 
     case '*': 
     advance(); 
     rtn *= factor(); 
     break; 

     case '/': 
     advance(); 
     rtn /= factor(); 
     break; 
    } 
    } 
    return rtn; 
} 

double expr() 
{ 
    double rtn = term(); 
    while (look_ahead == ADD_OP) { 
    switch(buf[0]) { 
     case '+': 
     advance(); 
     rtn += term(); 
     break; 

     case '-': 
     advance(); 
     rtn -= term(); 
     break; 
    } 
    } 
    return rtn; 
} 

并运行程序:

1 + 2 * 3 
val: 7.000000 
(1 + 2) * 3 
val: 9.000000 
+1

对不起,延迟回复。这是完成工作的有趣方式。它看起来不那么复杂,看起来好像完成了工作。一旦我完成了我正在做的事情,我可能会试试这个。 :) 谢谢 – Painguy