算术表达式的合法性判断与求值(上)

来源:互联网 发布:oracle 输入数据 编辑:程序博客网 时间:2024/06/01 10:43

在写一个计算器时遇到了一个问题,就是对字符串表示的算术表达式的合法性判断与求值。下面记录一下我的解决方案。

一、问题描述

问题:给定一个字符串,只包含 +,  -,  *,  / ,  数字,  小数点,  ( ,  )。

要求:(1) 判断该算术表达式是否合法; (2) 如果合法,计算该表达式的值。

二、判断表达式的合法性

相信学过《编译原理》的人都知道,利用里面讲的分析方法可以对源代码进行解析。而算术表达式也是源代码的一部分,所以利用编译方法也可以很容易地判断表达式的合法性。

与源代码相比,算术表达式只包含有很少的字符,所以解析起来也简单很多。下面从词法分析和语法分析两个方面来说明。

1)词法分析

下面先定一下表达式涉及到的单词的种别编码:

识别上表所列的单词的状态转换图:


C++实现:

#include <iostream>#include <vector>#include <string>#include <utility>using namespace std;int word_analysis(vector<pair<string, int>>& word, const string expr){for(int i=0; i<expr.length(); ++i){// 如果是 + - * / ( )if(expr[i] == '(' || expr[i] == ')' || expr[i] == '+' || expr[i] == '-' || expr[i] == '*' || expr[i] == '/'){string tmp;tmp.push_back(expr[i]);switch (expr[i]){case '+':word.push_back(make_pair(tmp, 1));break;case '-':word.push_back(make_pair(tmp, 2));break;case '*':word.push_back(make_pair(tmp, 3));break;case '/':word.push_back(make_pair(tmp, 4));break;case '(':word.push_back(make_pair(tmp, 6));break;case ')':word.push_back(make_pair(tmp, 7));break;}}// 如果是数字开头else if(expr[i]>='0' && expr[i]<='9'){string tmp;while(expr[i]>='0' && expr[i]<='9'){tmp.push_back(expr[i]);++i;}if(expr[i] == '.'){++i;if(expr[i]>='0' && expr[i]<='9'){tmp.push_back('.');while(expr[i]>='0' && expr[i]<='9'){tmp.push_back(expr[i]);++i;}}else  {return -1;  // .后面不是数字,词法错误}}word.push_back(make_pair(tmp, 5));--i;}// 如果以.开头else  {return -1;  // 以.开头,词法错误}}return 0;}int main(){        vector<pair<string, int>> word;    string expr = "(1.5+5.789)*82-10/2";    int err_num = word_analysis(word, expr);    if (-1 == err_num)        cout << "Word Error!" << endl;    else        cout << "No Word Error!" << endl;    return 0;}

上面的代码将识别出的单词-种别编码对 (单词, 种别编码) 存入一个vector<pair<string, int>> 中。

2)语法分析

算术表达式的文法 G[E] 如下:

E → E+T | E-T | T
T → T*F | T/F | F
F → (E) | d

消去非终结符E和T的左递归后,改写 G[E] 文法如下:

E → TE'
E' → +TE' | -TE' | ε
T → FT'
T' → *FT' | /FT' | ε
F → (E) | d

可以证明上述无递归文法是 LL(1) 文法,可以使用 递归下降分析法。递归下降分析法是确定的自上而下分析法,这种分析法要求文法是 LL(1) 文法。它的基本思想是:对文法中的每一个非终结符编写一个函数(或子程序),每个函数(或子程序)的功能是识别由该非终结符所表示的语法成分。

构造递归下降分析程序时,每个函数名是相应的非终结符,函数体是根据规则右部符号串的结构编写:

  • 当遇到终结符 a 时,则编写语句
    if(当前读来的输入符号 == a)读下一个输入符号;

  • 当遇到非终结符 A 时,则编写语句调用 A( );

  • 当遇到 A->ε 规则时,则编写语句
    if(当前读来的输入符号 不属于 FOLLOW(A))error();

  • 当某个非终结符的规则有多个候选式时,按 LL(1) 文法的条件能唯一地选择一个候选式进行推导。

所以我们需要求出 FOLLOW(E')FOLLOW(T')

FOLLOW( E' )= FOLLOW(E)={ ),#}
FOLLOW( T' ) = FOLLOW( T ) ={+,−,),#}

好了,下面直接上代码,在词法分析的基础上进行语法分析:

#include <iostream>#include <vector>#include <string>#include <utility>using namespace std;vector<pair<string, int>> word;string expr = "(1.5+5.789)*82-10/2";int idx = 0;int sym;int err = 0; // 错误void E();void E1();void T();void T1();void F();/*--------------------------------词法分析----------------------------*/int word_analysis(vector<pair<string, int>>& word, const string expr){for(int i=0; i<expr.length(); ++i){// 如果是 + - * / ( )if(expr[i] == '(' || expr[i] == ')' || expr[i] == '+' || expr[i] == '-' || expr[i] == '*' || expr[i] == '/'){string tmp;tmp.push_back(expr[i]);switch (expr[i]){case '+':word.push_back(make_pair(tmp, 1));break;case '-':word.push_back(make_pair(tmp, 2));break;case '*':word.push_back(make_pair(tmp, 3));break;case '/':word.push_back(make_pair(tmp, 4));break;case '(':word.push_back(make_pair(tmp, 6));break;case ')':word.push_back(make_pair(tmp, 7));break;}}// 如果是数字开头else if(expr[i]>='0' && expr[i]<='9'){string tmp;while(expr[i]>='0' && expr[i]<='9'){tmp.push_back(expr[i]);++i;}if(expr[i] == '.'){++i;if(expr[i]>='0' && expr[i]<='9'){tmp.push_back('.');while(expr[i]>='0' && expr[i]<='9'){tmp.push_back(expr[i]);++i;}}else  {return -1;  // .后面不是数字,词法错误}}word.push_back(make_pair(tmp, 5));--i;}// 如果以.开头else  {return -1;  // 以.开头,词法错误}}return 0;}/*--------------------------------语法分析----------------------------*/// 读下一单词的种别编码void Next(){if(idx < word.size())sym = word[idx++].second;elsesym = 0;}// E → TE' void E(){T();E1();}// E' → +TE' | -TE' | ε void E1(){if(sym == 1){Next();T();E1();}else if(sym == 2){Next();T();E1();}else if(sym != 7 && sym != 0){err = -1;}}// T → FT' void T(){F();T1();}// T' → *FT' | /FT' | ε void T1(){if(sym == 3){Next();F();T1();}else if(sym == 4){Next();F();T1();}else if(sym != 1 && sym != 2 && sym != 7 && sym != 0){err = -1;}}// F → (E) | dvoid F(){if(sym == 5){Next();}else if(sym == 6){Next();E();if(sym == 7){Next();}else{err = -1;}}else{err = -1;}}int main(){int err_num = word_analysis(word, expr);cout << expr << endl << "Word Analysis:" << endl;if (-1 == err_num){cout << "Word Error!" << endl;}else{// 测试输出vector<pair<string, int>>::iterator beg = word.begin();for(;beg!=word.end(); ++beg)cout << "   (" << beg->first << ", " << beg->second << ")" << endl;// 词法正确,进行语法分析Next();E();if (sym == 0 && err == 0)  // 注意要判断两个条件cout << "Right Expression." << endl;elsecout << "Wrong Expression." << endl;}return 0;}


另外,还有一种更简单的形式,将文法 G(E) 用扩充BNF表示法进行改写:

E → T { +T | -T }
T → F { *F | /F }
F → (E) | d

然后对这种变形文法使用递归下降分析法:

#include <iostream>#include <vector>#include <string>#include <utility>using namespace std;vector<pair<string, int>> word;string expr = "(1.5+5.789)*82-10/2";int idx = 0;int sym;int err = 0; // 错误void T();void F();/*--------------------------------词法分析----------------------------*/int word_analysis(vector<pair<string, int>>& word, const string expr){for(int i=0; i<expr.length(); ++i){// 如果是 + - * / ( )if(expr[i] == '(' || expr[i] == ')' || expr[i] == '+' || expr[i] == '-' || expr[i] == '*' || expr[i] == '/'){string tmp;tmp.push_back(expr[i]);switch (expr[i]){case '+':word.push_back(make_pair(tmp, 1));break;case '-':word.push_back(make_pair(tmp, 2));break;case '*':word.push_back(make_pair(tmp, 3));break;case '/':word.push_back(make_pair(tmp, 4));break;case '(':word.push_back(make_pair(tmp, 6));break;case ')':word.push_back(make_pair(tmp, 7));break;}}// 如果是数字开头else if(expr[i]>='0' && expr[i]<='9'){string tmp;while(expr[i]>='0' && expr[i]<='9'){tmp.push_back(expr[i]);++i;}if(expr[i] == '.'){++i;if(expr[i]>='0' && expr[i]<='9'){tmp.push_back('.');while(expr[i]>='0' && expr[i]<='9'){tmp.push_back(expr[i]);++i;}}else  {return -1;  // .后面不是数字,词法错误}}word.push_back(make_pair(tmp, 5));--i;}// 如果以.开头else  {return -1;  // 以.开头,词法错误}}return 0;}/*--------------------------------语法分析----------------------------*/// 读下一单词的种别编码void Next(){if(idx < word.size())sym = word[idx++].second;elsesym = 0;}// E → T { +T | -T } void E(){T();while(sym == 1 || sym == 2){Next();T();}}// T → F { *F | /F } void T(){F();while(sym == 3 || sym == 4){Next();F();}}// F → (E) | dvoid F(){if (sym == 5){Next();}else if(sym == 6){Next();E();if (sym == 7){Next();}else{err = -1;}}else{err = -1;}}int main(){int err_num = word_analysis(word, expr);cout << expr << endl << "Word Analysis:" << endl;if (-1 == err_num){cout << "Word Error!" << endl;}else{// 测试输出vector<pair<string, int>>::iterator beg = word.begin();for(;beg!=word.end(); ++beg)cout << "   (" << beg->first << ", " << beg->second << ")" << endl;// 词法正确,进行语法分析Next();E();if (sym == 0 && err == 0)  // 注意要判断两个条件cout << "Right Expression." << endl;elsecout << "Wrong Expression." << endl;}return 0;}

推荐这种文法形式!因为基于这种文法形式写程序,只需要写3个函数(因为只有3个非终结符),而且不需要求 FOLLOW 集合。

测试结果:



                                                                                     算术表达式的合法性判断与求值(下)>>








0 0