标签:name 第一个 span osi rac 进一步 添加 规则 程序员
最近看完了<c++ primer>,发现c++11标准库已经有正则表达式了(本人落后编译器多年,现在都已经c++17了),正好我最近想撸个compiler,索性就先撸个词法分析器,类似flex。项目代码量很小,一共不到400行,但是如果不用正则表达式库,自己写NFA,DFA满足最简陋的正则语法也是挺轻松的,但是想要满足Posix标准的语法,那就相当烦了,比如对大括号{}的处理,\d{3}表示3位数字等等。那么为何不直接用flex呢?一是因为flex不能很好地支持c++,第二是想拿c++练练手。下面简单地介绍下项目实现:
github: https://github.com/Yuandong-Chen/mini-lexer
项目一共就三个cpp文件和三个与之对应的hpp头文件。用过flex的都知道,我们可以定义自己的正则表达式宏比如: A [a-z],然后再套用这个宏A去构建更复杂的正则表达式比如: {A}+(ok)\$ 去构建我们的token结构。然后还能在这个token上加上action,比如:{A}+(ok)\$ {printf("ok!\n"); return 1;} 然后对一系列这样的token,根据他们的优先级(前后顺序),去分割文本内的字符串。对外接口是yylex(), yytext,yyleng,yyline,yyin,yyout等等。于是我们就把这一系列的实现分为三个步骤,第一个文件实现正则表达式宏的存取,我们用map<string, string>去存储,头文件如下:
1 #pragma once 2 #include <map> 3 4 namespace minilex { 5 6 class MacroHandler 7 { 8 private: 9 std::map<std::string, std::string> macrotab; 10 11 public: 12 MacroHandler() = default; 13 ~MacroHandler() = default; 14 std::string expandMacro(const std::string& macroname); 15 void addMacro(std::pair<std::string, std::string> macro); 16 }; 17 }
其中addMacro是存宏定义比如 {string("A"), string("[a-z]")}这样一个pair结构,expandMacro取宏定义,但是会在最外层加上括号,如果在map里头没发现就返回空字符串。具体实现就不贴了,完全是上面的描述,具体参考项目代码即可。
第二个文件用于处理类似 {A}+(ok)\$这样的表达式,已经对给定的字符串s,我们根据已有的正则表达式规则和对应的顺序去分割字符串,头文件如下:
1 #pragma once 2 #include <functional> 3 #include <list> 4 #include <regex> 5 #include "MacroHandler.hpp" 6 7 namespace minilex { 8 9 class RegularExp 10 { 11 private: 12 bool success = false; 13 std::string currentMatch = ""; 14 std::unique_ptr<MacroHandler> macrohp; 15 std::list<std::pair<std::unique_ptr<std::regex>, std::string> > regularexps; 16 public: 17 std::string expandRegularExp(const std::string& rexp); 18 std::string extractMacroName(const std::string& rexp, int &index, int max); 19 std::string expandMacro(const std::string& macroname); 20 public: 21 RegularExp(std::unique_ptr<MacroHandler>&& macrorp); 22 ~RegularExp() = default; 23 void addRegularExp(std::string rexp); 24 void removeRegularExp(std::string rexp); 25 std::string eat(std::string& txt); 26 bool isEaten(){return success;}; 27 std::string matchPattern(){return currentMatch;}; 28 }; 29 30 }
注意到,这里也有个addRegularExp,我们可以这样调用 addRegularExp("{A}+(ok)\$"); 我们可以用expandRegularExp函数完全展开正则表达式, 在这个函数里头就会去查找并替换宏定义,如果A定义为{B},那么函数会递归地解析宏定义,如果出现A A{A}这样的递归宏,函数会无限递归调用并抛出栈溢出错误。removeRegularExp函数用于动态地移除规则,比如我们添加了"[a-z]+(ok)\$"表达式,但是文本分割到某个程度时,因为某些原因(比如undef等等)我们不需要这条规则了,我们就可以移除这个表达式。eat函数用于吃字符串,吃掉的字符串返回,吃剩的放在参数中。matchPattern函数告诉我们匹配了哪条正则表达式(或说规则)。具体实现由于利用了c++11的regex,非常简单,可以查看项目具体代码。
第三个文件就是用于实现yylex()等对外接口了,这里就贴出yylex这个函数的实现:
1 int MiniLex::yylex() { 2 std::string eaten; 3 std::string pattern; 4 if(yyin.eof() && linebuffer.empty()) 5 { 6 return 0; 7 } 8 9 if(linebuffer.empty()) 10 { 11 std::getline(yyin, linebuffer); 12 yyline++; 13 } 14 15 eaten = reup->eat(linebuffer); 16 yyleng = eaten.size(); 17 if(!reup->isEaten()) 18 { 19 std::cerr<<"STOP, CANNOT INTERPRET STRING: "<<linebuffer<<std::endl; 20 return 0; 21 } 22 23 pattern = reup->matchPattern(); 24 25 /* you are required to modify the following code for your own purposes */ 26 if(pattern == std::string("{Digit}+")) { 27 std::cerr<<"RECOGNIZE: "<<yyleng<<‘ ‘<<eaten<<std::endl; 28 return 1; 29 } 30 else if(pattern == std::string("{Alpha}+")) { 31 std::cerr<<"RECOGNIZE: "<<yyleng<<‘ ‘<<eaten<<std::endl; 32 return 2; 33 } 34 else if(pattern == std::string("{Equal}")) { 35 std::cerr<<"RECOGNIZE: "<<yyleng<<‘ ‘<<eaten<<std::endl; 36 return 3; 37 } 38 else if(pattern == std::string("{CAL}")) { 39 std::cerr<<"RECOGNIZE: "<<yyleng<<‘ ‘<<eaten<<std::endl; 40 return 4; 41 } 42 else if(pattern == std::string(".")) { 43 std::cerr<<"UNRECOGNIZE: "<<yyleng<<‘ ‘<<eaten<<std::endl; 44 return 5; 45 } 46 47 return 0; 48 }
这里有个小的问题,就是无法正确匹配如 [a-z]"\n"[0-9]这样的正则表达式,因为我们是一行一行地读入,一行匹配完了才去读下一行,并匹配下一个token,但是我想没人会定义这样一个奇怪的跨行的token吧。另外我没有进一步去实现读取配置文档生成代码方式,而是让程序员直接去修改我们的源代码,我觉得这样更加自由,你甚至可以大改特改我们的源文件,而非估摸一堆古怪的配置语法。
C++11利用regex轻松实现词法分析器mini-lexer
标签:name 第一个 span osi rac 进一步 添加 规则 程序员
原文地址:http://www.cnblogs.com/github-Yuandong-Chen/p/7669831.html