四则运算表达式的语法分析
之前有《语法分析器初步学习——LISP语法分析》也是参考自《如何手写语法分析器》。
通过语法规则表达操作符的优先级。
四则运算表达式的语法为:
1.Term = <数字> | “(”Exp”)”
2.Factor = Term ((“*” | “/”) Term)*
3.Exp = Factor ((“+” | “-”) Factor)*
根据语法写代码。这里不对四则运算表达式求值,而是将其转换为等价的Lisp表达式,然后输出。
具体的代码如下:
// 四则运算表达式的语法分析——生成LISP表达式 #include <iostream> #include <string> using namespace std; // 表达式结构体 struct Expression { bool IsNumber; // 是否是数字 int Number; // 数字内容,只有当Isnumber = true的时候有效 char Operator; // 操作符,只有当IsNumber = false的时候有效 Expression* Left; // 左表达式,只有当IsNumber = false的时候有效 Expression* Right; // 右表达式,只有当IsNumber = false的时候有效 Expression(int aNumber) { IsNumber = true; Number = aNumber; Operator = 0; Left = 0; Right = 0; } Expression(char aOperator, Expression* aLeft, Expression* aRight) { IsNumber = false; Number = 0; Operator = aOperator; Left = aLeft; Right = aRight; } // 析构 ~Expression() { if (Left != 0) { delete Left; Left = 0; } if (Right != 0) { delete Right; Right = 0; } } void PrintLISP() { if (IsNumber) { cout << Number; } else { cout << "(" << Operator << " "; Left->PrintLISP(); cout << " "; Right->PrintLISP(); cout << ")"; } } }; struct Exception { int Start; // 错误位置 string Error; // 错误信息 Exception(int aStart, const string& aError) { Start = aStart; Error = aError; } }; // 检测是否是空白符 bool IsBlank(char ch) { return ch == ' ' || ch == ' '; } // 检测Text是否是Stream从pos起始的开头 // 如果是,则将pos前移Text.size()个字符,且返回true // 否则pos保持不变,返回false // 对于pos后的空白符忽略 bool IsPrefix(const string& Stream, int& pos, const string& Text) { int read = pos; // 设置真实游标 // 过滤空格 while (IsBlank(Stream[read])) { ++read; } if (Stream.substr(read, Text.size()) == Text) { pos = read + Text.size(); return true; } else { return false; } } // 检测Stream从pos开头是否是数字 Expression* GetNumber(const string& Stream, int& pos) { int Result = 0; bool GotNumber = false; int read = pos; while (IsBlank(Stream[read])) { ++read; } while (true) { char ch = Stream[read]; if (ch >= '0' && ch <= '9') { Result = Result * 10 + ch - '0'; GotNumber = true; ++read; } else { break; } } if (GotNumber) { pos = read; return new Expression(Result); } else { throw Exception(read, "此处需要表达式"); // 不仅仅是需要数字 } } Expression* GetTerm(const string& Stream, int& pos); Expression* GetFactor(const string& Stream, int& pos); Expression* GetExp(const string& Stream, int& pos); // 检测Stream从pos起始是否是一个Term // 实现语法1:Term = <数字> | “(”Exp”)” Expression* GetTerm(const string& Stream, int& pos) { try { return GetNumber(Stream, pos); } catch (Exception& e) { int read = pos; // 检测左括号 if (IsPrefix(Stream, read, "(")) { // 检测表达式 Expression* Result = GetExp(Stream, read); if (IsPrefix(Stream, read, ")")) { // 如果使用右括号结束,则返回结果 pos = read; return Result; } else // 否则抛出异常 { delete Result; Result = 0; throw Exception(read, "此处需要右括号"); } } else { throw e; // 这里要求GetNumber函数中抛出的异常信息为“需要表达式”,而非“需要数字” // 或者: // throw Exception(read, "此处需要数字或左括号"); } } } // 检测Stream从pos起始,开头是否是Factor // 实现语法2:Factor = Term ((“*” | “/”) Term)* Expression* GetFactor(const string& Stream, int& pos) { int read = pos; Expression* Result = GetTerm(Stream, read); while (true) { char Operator = 0; if (IsPrefix(Stream, read, "*")) { Operator = '*'; } else if (IsPrefix(Stream, read, "/")) { Operator = '/'; } else { break; } if (Operator) { // 如果是乘除号,则获得下一个Term try { Result = new Expression(Operator, Result, GetTerm(Stream, read)); } catch (Exception& e) { delete Result; Result = 0; throw e; } } } pos = read; return Result; } // 检测Stream从pos起始,开头是否是一个Exp // 实现语法3:Exp = Factor ((“+” | “-”) Factor)* Expression* GetExp(const string& Stream, int& pos) { int read = pos; Expression * Result = GetFactor(Stream, read); while (true) { char Operator = 0; if (IsPrefix(Stream, read, "+")) { Operator = '+'; } else if (IsPrefix(Stream, read, "-")) { Operator = '-'; } else { break; } if (Operator != 0) { // 如果是加减号,则获得下一个Factor try { Result = new Expression(Operator, Result, GetFactor(Stream, read)); } catch (Exception& e) { delete Result; Result = 0; throw e; } } } pos = read; return Result; } int main() { while (true) { string Stream; cout << "输入一个表达式:" << endl; getline(cin, Stream); int pos = 0; if (IsPrefix(Stream, pos, "exit")) { break; } pos = 0; try { Expression* exp = GetExp(Stream, pos); while (pos < Stream.size() && IsBlank(Stream[pos])) { ++pos; } if (pos < Stream.size()) { delete exp; exp = 0; throw Exception(pos, "发现多余的字符"); } else { exp->PrintLISP(); cout << endl << endl; delete exp; exp = 0; } } catch (Exception& e) { cout << "发生错误" << endl; cout << "错误位置:" << e.Start << endl; cout << "错误信息:" << e.Error << endl; cout << endl; } } return 0; }
上述四则运算表达式语法分析程序中的数据结构有:Expression结构体和Exception结构体。其中Expression结构体实质是一个表达式的递归二叉树,而成员函数PrintLISP通过前序遍历输出了LISP表达式。Expression当IsNumber为true时表示一个数字,当是false时表示一个表达式。
异常结构体Exception记录了异常发生时的位置和异常信息。
该程序中的函数有:IsBlank、IsPrefix、GetNumber、GetTerm、GetFactor、GetExp。其中主要是后面的四个Get*函数。
GetTerm、GetFactor、GetExp分别对应了四则运算表达式的三条语法:1.Term = <数字> | “(”Exp”)”、2.Factor = Term ((“*” | “/”) Term)*、3.Exp = Factor ((“+” | “-”) Factor)*。
本程序通过对四则运算表达式的三条语法进行实现,得到了四则运算表达式的递归二叉树,进而通过对该二叉树的前序遍历得到了对应的LISP表达式。
下面讨论一下如何不仅仅只得到LISP表达式,还得到四则运算表达式的值。首先第一种方式是:既然得到了LISP表达式了,我们可以通过之前的《语法分析器初步学习——LISP语法分析》通过对得到的LISP表达式进行分析,最终得到四则运算表达式的值。
另一种方法是,既然得到了四则运算表达式对应的递归二叉树,那么求其值,我们可以通过后序遍历得到四则运算表达式的值,当然,需要在Expression结构体中增加一个字段用来记录以该节点为根节点的子树的值。
既然我们的程序是对四则运算表达式进行的语法分析,在语法分析过程中,我们就可以对其进行求值,根据+、-、*、/四种运算符进行相应的求值。具体程序如下:
// 四则运算表达式的语法分析——求值 #include <iostream> #include <string> using namespace std; struct Exception { int Start; // 错误位置 string Error; // 错误信息 Exception(int aStart, const string& aError) { Start = aStart; Error = aError; } }; // 表达式结构体 struct Expression { bool IsNumber; // 是否是数字 int Number; // 数字内容,只有当Isnumber = true的时候有效 char Operator; // 操作符,只有当IsNumber = false的时候有效 Expression* Left; // 左表达式,只有当IsNumber = false的时候有效 Expression* Right; // 右表达式,只有当IsNumber = false的时候有效 int Value; // 以该节点为根节点的子树的值 Expression(int aNumber) { IsNumber = true; Number = aNumber; Operator = 0; Left = 0; Right = 0; Value = aNumber; } // 在建立表达式的同时,求值 Expression(char aOperator, Expression* aLeft, Expression* aRight) { IsNumber = false; Number = 0; Operator = aOperator; Left = aLeft; Right = aRight; switch (aOperator) { case '+': Value = aLeft->Value + aRight->Value; break; case '-': Value = aLeft->Value - aRight->Value; break; case '*': Value = aLeft->Value * aRight->Value; break; case '/': if (aRight->Value == 0) { throw Exception(-1, "除数为0"); // 这里没有发生错误时的位置信息 } Value = aLeft->Value / aRight->Value; break; default: break; } } // 析构 ~Expression() { if (Left != 0) { delete Left; Left = 0; } if (Right != 0) { delete Right; Right = 0; } } void PrintLISP() { if (IsNumber) { cout << Number; } else { cout << "(" << Operator << " "; Left->PrintLISP(); cout << " "; Right->PrintLISP(); cout << ")"; } } // 通过后续遍历四则运算表达式对应的递归二叉树,得到该表达式的值 int GetValue() { if (IsNumber) { return Value; } int left = Left->GetValue(); int right = Right->GetValue(); switch (Operator) { case '+': return left + right; break; case '-': return left - right; break; case '*': return left * right; break; case '/': if (right == 0) { throw Exception(-1, "除数为0"); // 这里没有发生错误时的位置信息 } return left / right; break; default: break; } } }; // 检测是否是空白符 bool IsBlank(char ch) { return ch == ' ' || ch == ' '; } // 检测Text是否是Stream从pos起始的开头 // 如果是,则将pos前移Text.size()个字符,且返回true // 否则pos保持不变,返回false // 对于pos后的空白符忽略 bool IsPrefix(const string& Stream, int& pos, const string& Text) { int read = pos; // 设置真实游标 // 过滤空格 while (IsBlank(Stream[read])) { ++read; } if (Stream.substr(read, Text.size()) == Text) { pos = read + Text.size(); return true; } else { return false; } } // 检测Stream从pos开头是否是数字 Expression* GetNumber(const string& Stream, int& pos) { int Result = 0; bool GotNumber = false; int read = pos; while (IsBlank(Stream[read])) { ++read; } while (true) { char ch = Stream[read]; if (ch >= '0' && ch <= '9') { Result = Result * 10 + ch - '0'; GotNumber = true; ++read; } else { break; } } if (GotNumber) { pos = read; Expression* ret = new Expression(Result); ret->Value = Result; return ret; } else { throw Exception(read, "此处需要表达式"); // 不仅仅是需要数字 } } Expression* GetTerm(const string& Stream, int& pos); Expression* GetFactor(const string& Stream, int& pos); Expression* GetExp(const string& Stream, int& pos); // 检测Stream从pos起始是否是一个Term // 实现语法1:Term = <数字> | “(”Exp”)” Expression* GetTerm(const string& Stream, int& pos) { try { return GetNumber(Stream, pos); } catch (Exception& e) { int read = pos; // 检测左括号 if (IsPrefix(Stream, read, "(")) { // 检测表达式 Expression* Result = GetExp(Stream, read); if (IsPrefix(Stream, read, ")")) { // 如果使用右括号结束,则返回结果 pos = read; return Result; } else // 否则抛出异常 { delete Result; Result = 0; throw Exception(read, "此处需要右括号"); } } else { throw e; // 这里要求GetNumber函数中抛出的异常信息为“需要表达式”,而非“需要数字” // 或者: // throw Exception(read, "此处需要数字或左括号"); } } } // 检测Stream从pos起始,开头是否是Factor // 实现语法2:Factor = Term ((“*” | “/”) Term)* Expression* GetFactor(const string& Stream, int& pos) { int read = pos; Expression* Result = GetTerm(Stream, read); while (true) { char Operator = 0; if (IsPrefix(Stream, read, "*")) { Operator = '*'; } else if (IsPrefix(Stream, read, "/")) { Operator = '/'; } else { break; } if (Operator) { // 如果是乘除号,则获得下一个Term try { Result = new Expression(Operator, Result, GetTerm(Stream, read)); } catch (Exception& e) { delete Result; Result = 0; throw e; } } } pos = read; return Result; } // 检测Stream从pos起始,开头是否是一个Exp // 实现语法3:Exp = Factor ((“+” | “-”) Factor)* Expression* GetExp(const string& Stream, int& pos) { int read = pos; Expression * Result = GetFactor(Stream, read); while (true) { char Operator = 0; if (IsPrefix(Stream, read, "+")) { Operator = '+'; } else if (IsPrefix(Stream, read, "-")) { Operator = '-'; } else { break; } if (Operator != 0) { // 如果是加减号,则获得下一个Factor try { Result = new Expression(Operator, Result, GetFactor(Stream, read)); } catch (Exception& e) { delete Result; Result = 0; throw e; } } } pos = read; return Result; } int main() { while (true) { string Stream; cout << "输入一个表达式:" << endl; getline(cin, Stream); int pos = 0; if (IsPrefix(Stream, pos, "exit")) { break; } pos = 0; try { Expression* exp = GetExp(Stream, pos); while (pos < Stream.size() && IsBlank(Stream[pos])) { ++pos; } if (pos < Stream.size()) { delete exp; exp = 0; throw Exception(pos, "发现多余的字符"); } else { exp->PrintLISP(); cout << endl << exp->Value; cout << endl << exp->GetValue(); cout << endl << endl; delete exp; exp = 0; } } catch (Exception& e) { cout << "发生错误" << endl; cout << "错误位置:" << e.Start << endl; cout << "错误信息:" << e.Error << endl; cout << endl; } } return 0; }
之前我们讨论了三种求值的方法:通过对LISP表达式的计算、对二叉树后序遍历、在分析表达式的同时进行求值。这三种方法存在先后顺序,其中通过LISP求值是最后的,对二叉树后续遍历是处于中间,在分析表达式的同时进行求值是最早的。
上述程序中我们对第二、三种方法进行了实现,其中通过对Expression构造函数的改造实现了第三种方法,通过对Expression结构体增加了GetValue成员函数实现了第二种方法。
由于这两种方法都是在Expression结构体中完成的,所以对于除法除数为0时的异常处理,是无法记录错误发生的位置信息的,这里我们标注为-1。
以上是对四则运算表达式进行语法分析的相关内容,语法分析的方法是最基本的递归向下方法。
另外,通过对四则运算表达式的语法分析,我们得到了其对应的递归二叉树,进而根据该二叉树得到表达式对应的前缀、中缀、后缀表达式变得很简单了。这不失为一种统一的将四则运算表达式在前缀、中缀、后缀等三种形式之间相互转换的一种好方法。
语法分析相关的其他内容有待进一步学习和实践,另推荐《构造正则表达式引擎》等。