如何在中缀计算器语法中解析和计算表达式?我想到了两种方式。
第一个涉及使用两个堆栈。一个是数字,另一个是运算符,我会评估运算符的优先级和关联,以便弄清楚如何计算表达式。
第二种方法涉及将中缀表达式转换为后缀,我不知道我该怎么做。这只是一个想法。目前我设置我的程序是为了使用第一种方法。
#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;
}
答案 0 :(得分:8)
这将是一个很长的阅读,但无论如何,我将与您分享我用来解析中缀表达式并将其存储为二叉树的算法。不是堆栈,而是二叉树。解析将轻松给出后缀顺序。我不是说这是最好的算法,但这适用于我的脚本语言。
算法:
我们有一种方法,它对二叉树的“当前节点”和“当前表达式”进行操作。节点包含“数据”字段和“类型”字段。
阶段1:简单的事物,例如“4”直接进入节点,我们将类型指定为“DATA”,即。按原样使用此信息。
阶段2:现在,让我们考虑以下表达式:
a) 2 + 3
这将转换为以下二叉树:
+
/ \
2 3
因此,运营商进入节点,操作数进入叶子。将表达式a)转换到树中非常简单:找到运算符,放入树的“当前”节点,指定节点的类型为运算符“PLUS”,剩下的是进入树的节点在节点的左侧部分,它的右侧是否进入右侧树。很好很简单,使用第1阶段的信息,两个叶子将是“DATA”叶子,值为2和3。
阶段3:但是对于更复杂的表达:
b) 2 * 3 + 4
树将是:
+
/ \ 4
*
/ \
2 3
因此我们需要将上面的算法修改为以下内容:找到具有最高优先级的第一个运算符(考虑c ++指南... +(加号)和 - (减号)的优先级为6,而*的优先级为*(乘法),/(除)和%(modulo)是表达式中的5),将表达式分为两部分(优先级最高的操作数之前和优先级最高的操作数之后)并递归调用两部分的方法,同时放置优先级最高的运算符进入当前节点。所以,我们用hdata创建一个树,如:
+
/ \
/ call method with "4"
call method with "2*3"
并且在这个阶段我们回到呼叫“4”的呼叫(“2 * 3”)和“阶段1”的“阶段2”。
阶段4:如果表达中有副词怎么办?如
c) 2 * (3 + 4)
这将给我们树:
*
/ \
2 +
/ \
3 4
我们将算法修改为:
现在找到第一个在表达式的parantheses之外具有最高优先级的运算符。再次,取表达式的左侧和右侧,并一次又一次地调用该方法,直到您最终到达“阶段1”即。使用单个数据元素。
现在,这是一个由纯数和运算符组成的表达式算法。有关更复杂的信息,您可能需要对其进行优化以满足您的需求。如果您认为值得,请查看https://sourceforge.net/p/nap-script/mercurial/ci/default/tree/compiler/interpreter.cpp。这包含上面算法的完整实现(在C中)关于更复杂的概念(变量,方法调用,后缀/前缀运算符等等)。方法是build_expr_tree,从第1327行开始。
< / LI>答案 1 :(得分:4)
recursive descent的方法是手动实现正确表达式解析器的最简单方法。这里编程语言堆栈与您尝试使用的显式堆栈完成相同的操作。谷歌有很多RD示例,任何好的编译器书都会有一些。
链接的Wikipedia页面显示解析器,但不显示如何添加评估。下面是C中一个完整的基本表达式求值器。它可以很容易地包装在C ++类中,并且全局变量成为实例变量。它缺少生产系统中您需要的功能。例如,当它发现错误时,它就会退出。 C ++异常很容易让你解除递归并继续。它还需要保护数字溢出,被零除等等,你显然知道如何做。
递归下降的想法是将所需语言的语法转换为称为LL(1)的形式。完成后,有固定的规则 - 每次都要保证工作 - 将语法规则转换为程序。我已经手动完成了这个。有工具可以自动完成。
所以这个评估者很容易扩展。只需添加必要的语法规则,然后对扫描程序,解析器和评估代码实施所需的增强功能。例如,内置函数规则为unsigned_factor -> FUNCTION_NAME ( expr )
,其中扫描程序将所有函数名称识别为相同的标记,unsigned_factor
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