关闭

四则混合运算表达式分析程序的原理及其实现

标签: tokentreeexceptiontablefile语言
1646人阅读 评论(3) 收藏 举报

意义: 四则混合运算表达式可以看作一定语言中的表达式分析及求值,虽然它很小,却是一个语法分析的很好的例子!

一、目标:可以对输入的四则混合运算表达式进行分析,并求出其结果。程序须支持:整数及小数运算、支持加(+),减(-),乘(*),除(/),技术括号嵌套。

二、原理:表达式的分析过程可以看作三步,(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);
}
 
关于分析程序的具体实现,请下载代码:http://download.csdn.net/source/341787
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:11497次
    • 积分:250
    • 等级:
    • 排名:千里之外
    • 原创:14篇
    • 转载:1篇
    • 译文:1篇
    • 评论:6条
    文章分类
    最新评论