前言
- 这里要介绍各种语句和表达式,将通过一个桌面计算器的程序做些事情,该计算器提供四种座位浮点数的中缀运算符的标准算术运算。
- 这个计算器由四个部分组成:一个分析器,一个输入函数,一个符号表和一个驱动程序。
分析器
program:ENDexpr_list END //END表示输入结束 expr_list: expression PRINT // PRINT表示分号 expression PRINT expr_list expression: expression + term expression - term term term: term / primary term * primary primary primary: NUMBER NAME NAME = expression - primary (expression)
- 语法分析器采用通常的递归下降的风格。终结符由词法分析程序get_token()识别,而非终结符由语法分析函数expr(),term()和prim()识别。一旦一个表达式的两个运算对象都知道,就立即对这个表达式求值。
- 个人觉得分析器就像是定义程序能接受什么输入,而这里采用递归的形式,例如program代表程序,program可以接受END符号或者expr_list END,而expr_list又继续递归下去可以由其他的组成,直到可以得到一个结束情况。
总代码
#include<iostream>
#include<string> #include<map> #include<cctype> using namespace std; // 将token用他们的字符所对应的整数表示,这样做既方便有效, // 又能帮助使用排错系统的人。 enum Token_value { NAME, NUMBER, END, PLUS='+', MINUS='-', MUL='*', DIV='/', PRINT=';', ASSIGN='=', LP='(', RP=')', }; Token_value curr_tok = PRINT; double expr(bool); double term(bool); double prim(bool); Token_value get_token(); double error(const string&); map<string, double> table; int no_of_errors; int main() { table["pi"] = 3.1415924535; table["e"] = 2.178; while(cin) { get_token(); if(curr_tok == END) break; if(curr_tok == PRINT) continue; cout<<expr(false)<<'\n'; } return no_of_errors; } // 每个分析器都有一个bool参数, // 指明该函数是否需要调用get_token()去取得下一个参数 // expr处理加减,一直到不是加减返回left double expr(bool get) { double left = term(get); for(;;) { switch (curr_tok){ case PLUS: left += term(true); break; case MINUS: left += term(true); break; default: return left; } } } // 函数term处理乘除,采用的方式与expr()处理方法一样 double term(bool get) { double left = prim(get); for(;;) { switch (curr_tok){ case MUL: left *= prim(true); break; case DIV: if (double d = prim(true)) { left /= d; break; } return error("divide by 0"); default: return left; } } } double number_value; string string_value; // prim处理初等项的方式很像expr和term double prim(bool get) { if (get) get_token(); switch(curr_tok) { case NUMBER: { double v = number_value; get_token(); return v; } case NAME: { double& v = table[string_value]; if (get_token() == ASSIGN) v = expr(true); return v; } case MINUS: return -prim(true); case LP: { double e = expr(true); if (curr_tok != RP) return error(" ) expected"); get_token(); return e; } default: return error("primary expected"); } } Token_value get_token() { char ch; do { // 低级输入,改进输入 if(!cin.get(ch)) return curr_tok = END; } while(ch != '\n' && isspace(ch)); switch(ch) { case 0: return curr_tok=END; case '\n': return curr_tok=PRINT; case '+': case '-': case '*': case '/': case ';': case '(': case ')': case '=': return curr_tok = Token_value(ch); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': cin.putback(ch); cin>>number_value; return curr_tok = NUMBER; default: if (isalpha(ch)) { string_value = ch; while(cin.get(ch) && isalnum(ch)) string_value.push_back(ch); cin.putback(ch); return curr_tok = NAME; } error("bad token"); return curr_tok = PRINT; } } double error(const string& s) { no_of_errors ++; cerr<<"error: "<<s<<'\n'; return 1; }
扩展
- 自己的代码只是简单的拼接,这里发现一个更好的博客。