用遞迴下降的文法分析器實現簡單的計算機:
看了這篇部落格之後,總算搞明白了以前編譯原理沒搞懂的ll文法的意義。下面用這種方法實現一個簡單的電腦器程式:
定義文法為了體現出遞迴下降法簡潔之處,計算運算式採用lisp語言的方式來書寫,這樣就能避免中綴運算式轉換成尾碼運算式,破壞了遞迴下降的格局。在lisp語言裡,會強制指出運算子優先順序:(1+2 * 5)*(3+4)在LISP會被寫成(*(+
1 (* 5 2)) (+ 3 4) )其BNF範式如下:Operator=”+”
Operator=”-“
Operator=”*”
Operator=”/”
Expression=<0,1,2,3,4,..,9>
Expression= “(” Operator Expression Expression “)”
Expression=“(”Expression “)”實現
#include <string>#include <memory>struct Expression{Expression() : result(), Error(), Start(NULL) {}float result;std::string Error;char*Start;};class Lexer{public:Lexer(const std::string& str);private:void SkipWhitespace();bool match(const char* text);char isOpreator();Expression GetExpression();Expression GetNumber();std::unique_ptr<char[]> buffer;size_tcur;size_tsize;};
#include "Parser.h"#include <iostream>#include <cctype>#include <cassert>Lexer::Lexer(const std::string& str):cur(),size(){size_t len = str.length();std::unique_ptr<char[]> ss(new char[len + 1]);memcpy(ss.get(),str.c_str(),len);ss[len] = 0;buffer = std::move(ss);size = len + 1;//std::cout << buffer.get() <<std::endl; //<<<<<<<<<debug : this line should be delete//SkipWhitespace();//Expression exp = GetExpression();if (exp.Error.empty()) {std::cout << exp.result <<std::endl; //<<<<<<<<<debug : this line should be delete} else {std::cout << "發生錯誤:" <<std::endl;std::cout << "位置: " << exp.Start <<std::endl;std::cout << "資訊: " << exp.Error <<std::endl;}}void Lexer::SkipWhitespace(){while (buffer[cur] == ' ' && cur != size) {++cur;}}bool Lexer::match(const char* text){size_t len = strlen(text);SkipWhitespace();if (strncmp(&buffer[cur],text,len) == 0) {cur += len;return true;}return false;}char Lexer::isOpreator(){if (match("+") || match("-") || match("*") || match("/")) {return buffer[cur - 1];//返回操作符}return 0;}Expression Lexer::GetExpression(){Expression ret = GetNumber(); //第一個字元是數字或者運算式if (!ret.Error.empty()) { //第一個不是數字,匹配括弧if (match("(")) { // (...)//匹配操作符char op = isOpreator(); //( op (exp) (exp) ) if (op > 0) {ret.Error = "";//擷取左參數Expression left = GetExpression();if (!left.Error.empty()) { //left出錯return left;}//擷取右邊的運算式Expression right = GetExpression();if (!right.Error.empty()) {return right;}switch (op) {case '+':ret.result = left.result + right.result;break;case '-':ret.result = left.result - right.result;break;case '*':ret.result = left.result * right.result;break;case '/':if (right.result == 0) {ret.Error = "被除數不能為0";ret.Start = static_cast<char*>(&buffer[cur]);} else {ret.result = left.result / right.result;}break;default:assert(false && "should never get here");break;}} else { //不是操作符 (expression)ret = GetExpression();if (!ret.Error.empty()) {return ret;}}if (!match(")")) {ret.Error = "此處缺少反括弧)";}} else {ret.Error = "此處出現未知字元";ret.Start = static_cast<char*>(&buffer[cur]);return ret;}}return ret;}Expression Lexer::GetNumber(){Expression ret;SkipWhitespace();bool getnum = false;bool negative = false;int point = 0;while (true) {char c = buffer[cur];if (isdigit(c)) {float ci = c - '0';if (point > 0) { //小數點後面for (int i = 0; i < point; ++i) {ci /= 10;}++point;ret.result = ret.result + ci;} else {ret.result = ret.result * 10 + ci;}//多個數字++cur;getnum = true;} else if (c == '-' && !getnum) { //第一位是負數negative = true;++cur;} else if (c == '.') { //小數點point = 1;++cur;} else {break;}}if (negative) {ret.result = - ret.result;}if (!getnum) {ret.Error = "這裡應該是數字";ret.Start = static_cast<char*>(&buffer[cur]);}return ret;}
#include <iostream>#include <string>#include "Parser.h"#pragma warning(disable:4996)using namespace std;int main(int argc,char* argv[]){freopen("input.txt","rt",stdin);freopen("output.txt","wt",stdout);std::string input;while (getline(std::cin,input)) {Lexer lex(input);}return 0;}
測試:
(* (+ 1.003 2) (+ 3 -4) )(/ (+ 5 6) (- b3 5) )(+ 3 5 ) 輸出:-3.003發生錯誤:位置: b3 5) )資訊: 此處出現未知字元8
改進
用遞迴下降來解析XML和JSON