码迷,mamicode.com
首页 > 编程语言 > 详细

C++11利用regex轻松实现词法分析器mini-lexer

时间:2017-10-15 11:09:43      阅读:441      评论:0      收藏:0      [点我收藏+]

标签: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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!