意义: 四则混合运算表达式可以看作一定语言中的表达式分析及求值,虽然它很小,却是一个语法分析的很好的例子!
一、目标:可以对输入的四则混合运算表达式进行分析,并求出其结果。程序须支持:整数及小数运算、支持加(+),减(-),乘(*),除(/),技术括号嵌套。
二、原理:表达式的分析过程可以看作三步,(1)分析出表达式中各个标记,包括数值、括号以及操作符,将这些标记按顺序存放起来;(2)根据步骤1所得的标记,根据四则运算规则将其转换成一棵语法树(二叉树);(3)使用后序遍历步骤2所得语法树,按操作进行求值,得出最后结果。程序的整个核心在于语法树如何建立。
三、语法树的建立:
如:10+(5+9)/3-7*3可以解析得如下语法树:
使用后序遍历方式对语法树进行遍历,可以得出其值;
尽管上面的语法树是正确的,构建上面的语法树却不是那么简单,因为在此过程中,我们需要考虑:如何使得一对括号内的数值作为一个整体优先被计算? 如果只是按操作符的优先级进行比较,那么我们很可能得出错误的结论。比如上述的表达式换成“10+(5+9)+7*3”,在构建完“10+(5+9)+”的语法树后,此时所得的树根是“10+(”中的“+”,它的运算优先级比“7*3”中的“*”的优先级要低,如果只是按照运算符优先级规则,我们很可能得一棵错误的语法树!因此,我们要简化对问题的思考方式,如何将一个有括号的表达式转换成一个没有括号的表达式,这样,我们只须考虑操作符的优先级就可以创建正确的语法树了!方法很简单,我们首先将没有嵌套的括号内的表达式进行解析,然后对它求值,将其结果作为一个数值结点替换原来的括号表达式,如果括号外部有括号,则按上述方法重复进行,直到去掉所有括号为止。如上面的“10+(5+9)/3+(7*3)”可以转换成:“10+14/3+21”,这样就很容易创建语法表达式了。
测试:
核心代码如下:
//
-----------------------------------------------------------------------------
//
语法解析器的实现:
//
-----------------------------------------------------------------------------

//
对一个语法树进行求值
double
SyntaxParser::Eval(SYNTAX_TREE tr,
int
&
step, FILE
*
pf)

...
{
double rslt = 0.0;

if (0 == tr) return rslt;

if (tr->token->GetType() == Token::NUM)

...{
return (static_cast<Number*>(tr->token))->value;
}

double lsh = Eval(tr->lchild, step, pf);
double rsh = Eval(tr->rchild, step, pf);
Operator *pop = static_cast<Operator*>(tr->token);
rslt = Operator::Calc(lsh, rsh, pop->value);

// 写入计算过程
fprintf(pf,
"%04d. %-20.6f %-10c %-20.6f = %-20.6f ",
step,
lsh,
static_cast<char>(pop->value),
rsh,
rslt);
++step;
return rslt;
}



//
----------------------------------------------------------------------------
//
对输入标记进行解析并求值
//
为简化解析过程,整个表达式可以看作是只有操作符和数值构成的标记表,
//
括号内的表达式可以在解析后进行求值,将所得的结果作为一个数值加点替
//
换原来的括号表达式。因此,整过解析过程中只需考虑操作符的优先级,遇
//
到括号时,作为一棵子树的新解析开始。表达式中只有+,-,*,/ 这个四个操作符。
//
+和-以及*和/它们的优先级是分别相等的。*、/和优先级高于+、-。
//
----------------------------------------------------------------------------
double
SyntaxParser::Solve(
TokenTable
&
table,
//
标记表
TreeStack
&
stack,
//
语法树栈
EvalFactory
&
fac,
//
标记和树结点工厂
FILE
*
pf
//
计算过程记录文件
)

...
{
if (table.GetCount()==0)

...{
throw Exception("输入表达式不能为空!");
}

typedef Token::TokenType TokenType;
SYNTAX_TREE tree = 0; // 语法树
int parSignal = 0; // 括号配对信号量,解析结束时若parSignal须为0
int step = 1; // 计算步骤
Token* prevTok = 0; // 前一标记
Token* firstTok = table[0]; // 第一个标记
Token* lastTok = table[table.GetCount()-1]; // 最后一个标记

// 首先检查首尾是否正确
if ((Token::OPER==firstTok->GetType() || Token::OPER==lastTok->GetType())
|| ((Token::LRP==firstTok->GetType()
&& (static_cast<Parenthesis*>(firstTok))->value!=Parenthesis::LP))
|| ((Token::LRP==lastTok->GetType()
&& (static_cast<Parenthesis*>(lastTok))->value!=Parenthesis::RP)))

...{
throw Exception("无效表达式! 开头或结尾有误!");
}


// 从标记表中提取每个标记,生成语法树
for (int i=0; i<table.GetCount(); ++i)

...{
Token* token = table[i];

if (token->GetType()==Token::LRP) // 如果输入标记是括号

...{
Parenthesis* pp = static_cast<Parenthesis*>(token);
if (Parenthesis::LP == pp->value) // 当前标记是一个左括号

...{
if (prevTok && prevTok->GetType() != Token::OPER
&& (Token::LRP==prevTok->GetType()
&&(static_cast<Parenthesis*>(prevTok))->value!=Parenthesis::LP))

...{
throw Exception("无效表达式:'('之前只能为操作符或'('");
}

++parSignal; // 信号量递增
stack.Push(tree);
tree = 0;
}
else // 当前标记是一个右括号

...{
if (prevTok && (prevTok->GetType()==Token::OPER
|| (prevTok->GetType()==Token::LRP
&& (static_cast<Parenthesis*>(prevTok))->value==Parenthesis::LP)))

...{
throw Exception("无效表达式:')'之前只能为数或')'");
}


if (parSignal == 0) ...{ // 检查左右括号是否匹配
throw Exception("无效表达式:'('和')'不匹配!");
}

// 将已解析的括号内的子树进行计算,将结果作为一个数值结点插入到语法树中
SYNTAX_TREE_NODE *pnode = fac.CreateSyntaxTreeNode();
Number *pnum = static_cast<Number*>(fac.CreateToken(Token::NUM));
pnum->value = Eval(tree, step, pf);
pnode->lchild = pnode->rchild = 0;
pnode->token = pnum;

SYNTAX_TREE tr = (stack.IsEmpty() ? 0 : stack.Pop());
if (tr)

...{

if (tr->rchild) ...{
tr->rchild->rchild = pnode;
}

else ...{
tr->rchild = pnode;
}
tree = tr;
}

else ...{
tree = pnode;
}

--parSignal; // 信号量递减
}
}
else // 如果输入标记是操作符或数值

...{
SYNTAX_TREE_NODE *pnode = fac.CreateSyntaxTreeNode();
pnode->lchild = pnode->rchild = 0;
pnode->token = token;

if(token->GetType() == Token::OPER) // 如果输入标记是操作符

...{
// 判断上下文是否有效
if (prevTok->GetType()==Token::OPER
|| (prevTok->GetType()==Token::LRP
&& (static_cast<Parenthesis*>(prevTok))->value!=Parenthesis::RP))

...{
throw Exception("无效表达式:操作符之前只能是')'或一个数!");
}

Operator *pop = static_cast<Operator*>(token);
if (tree->token->GetType() == Token::NUM) // 当前树根结点的类型为数

...{
pnode->lchild = tree;
tree = pnode;
}
else // 当前树根结点的类型操作符

...{
// 如果当前节点优先级高于当前树根结点的优先级, 替换右子树
if (pop->Compare(static_cast<const Operator&>(*tree->token)) > 0)

...{
pnode->lchild = tree->rchild;
tree->rchild = pnode;
}
else

...{
pnode->lchild = tree;
tree = pnode;
}
}
}
else // 如果当前标记是数值

...{
if (prevTok && ((prevTok->GetType()==Token::LRP
&& (static_cast<Parenthesis*>(token))->value==Parenthesis::RP)
|| prevTok->GetType() == Token::NUM))

...{
throw Exception("无效表达式: 数之前只能是操作符或'('!");
}

if (0 == tree)

...{
tree = pnode;
}
else

...{
SYNTAX_TREE tr = tree;

if (tr->rchild) ...{
tr->rchild->rchild = pnode;
}

else ...{
tr->rchild = pnode;
}
}
}
}
// 记录当前标记, 因为解析过程是上下文相关的
prevTok = token;
}

// 检查左右括号数是否匹配

if (parSignal != 0) ...{
throw Exception("'('和')'的个数不匹配!");
}

// 若树栈不为空,则依次弹出一个树tree, 将root作为tree的右孩子,
// 然后将tree保存到树根root;
while(!stack.IsEmpty())

...{
SYNTAX_TREE tr = stack.Pop();

if (tr) ...{
tr->rchild = tree;
tree = tr;
}
}

return Eval(tree, step, pf);
}