编译原理 - 1 手撸状态机词法分析器

感谢vczh轮子叔的坑了的教程,我的编译原理第一次入了个门,词法分析写完了,今后可以看看书继续往下学了。

http://www.cppblog.com/vczh/archive/2014/03/02/206014.html

词法分析,就是对于一段代码,把他们分割成一个个的token,同时记录他们的行列号,丢掉不必要的信息,这个词法分析器很简单,简单的状态机就能胜任,用正则就没有自己造轮子的快感了,所以要自己手撸状态机拆token出来。

模仿vczh的语言,我的语言包括了以下要素

  标识符:大小写字母和下划线构成的连续串[a-zA-Z_]

  数字:自然书写的整数和浮点数,整数部分为0的浮点数必须要用0.开头

  运算符:单个符号

  注释:#开头,#或\n结尾的任意内容,也就是说注释最多是一整行

  字符串:双引号"开头结尾的任意内容,支持\\,\n两种转义字符。

这样简单的语言,用正则表达式处理也很简单,但是为了贯彻自己造轮子的原则,我们手写一波状态机

首先把状态机的图画出来,就像这样(因为没有钱买visio,所以只能用一个叫lucidchart的在线应用画图了)

虚线表示这个状态可以直接跳到End状态,而所谓End状态就是Start状态,所以到了有虚线的状态,可以看情况直接跳到Start,并且有些状态也可以直接优化掉,比如InStringEnd, InOperator等,遇到这样的状态,直接输出一个token,然后把state设为Start就好了。

这样的状态机是不是很简单,更加复杂的一些状态,也可以手动构造正则表达式,然后用vczh的另一篇教程手写出状态机来。

忘了说Token的结构了,这个很简单

struct Token
{
    int line;
    int column;
    TokenType type;
    std::wstring value;
    Token(): Token(0, 0, TokenType::Unknown, L"", 0)
    {
    }
    Token(int _line, int _column, TokenType _type, const wchar_t* str, int len)
        : line(_line)
        , column(_column)
        , type(_type)
        , value(str, len)
    {
    }
    void Reset()
    {
        line = 0;
        column = 0;
        type = TokenType::Unknown;
        value.clear();
    }
    void Push(const wchar_t ch)
    {
        value.push_back(ch);
    }
};

那个Reset和Push纯粹是因为我偷懒,把这个第一个版本的代码写得很烂,只能用这种不科学的封装来减少一下代码的重复了。

有了状态机和具体的状态:

enum class TokenType
{
    Unknown,
    Number,
    Comment,
    Operator,
    Identifier,
    String
};

enum class ParseState
{
    Begin,
    InComment,
    //InCommentEnd,
    InInteger,
    InFloat,
    InOperator,
    InIdentifier,
    InString,
    InStringEscaping,
    //InStringEnd
};

可以构造状态机了,模板是这样的:

while (*reading)
{
    switch (state)
    {
    case 某个状态:
    {
        switch (*reading)
        {
        case 某个或某几个字符:
            #根据状态机里当前状态能接受和不能接受的字符,修改新的状态,读入字符,reading++,或者直接抛异常
        break;
        }
    }
}

第一个版本的代码写得很烂,纯粹是能用的程序,放在这里纯粹是显摆一下,之后我会考虑重构什么的,然后再来更新这篇文章,而且没有写单元测试,正确性纯粹是肉眼检查,报错信息也很不友善,这一点下一个版本的代码再改。

下面贴代码:

TokenStream.h

  1 #pragma once
  2 #ifndef TOKEN_STREAM
  3 #define TOKEN_STREAM
  4
  5 #include <string>
  6 #include <vector>
  7 enum class TokenType
  8 {
  9     Unknown,
 10     Number,
 11     Comment,
 12     Operator,
 13     Identifier,
 14     String
 15 };
 16
 17 enum class ParseState
 18 {
 19     Begin,
 20     InComment,
 21     //InCommentEnd,
 22     InInteger,
 23     InFloat,
 24     InOperator,
 25     InIdentifier,
 26     InString,
 27     InStringEscaping,
 28     //InStringEnd
 29 };
 30 struct Token
 31 {
 32     int line;
 33     int column;
 34     TokenType type;
 35     std::wstring value;
 36     Token(): Token(0, 0, TokenType::Unknown, L"", 0)
 37     {
 38     }
 39     Token(int _line, int _column, TokenType _type, const wchar_t* str, int len)
 40         : line(_line)
 41         , column(_column)
 42         , type(_type)
 43         , value(str, len)
 44     {
 45     }
 46     void Reset()
 47     {
 48         line = 0;
 49         column = 0;
 50         type = TokenType::Unknown;
 51         value.clear();
 52     }
 53     void Push(const wchar_t ch)
 54     {
 55         value.push_back(ch);
 56     }
 57 };
 58
 59 class TokenStream
 60 {
 61 public:
 62     static std::vector<Token> Parse(std::wstring& source)
 63     {
 64         const wchar_t* reading = source.c_str();
 65         ParseState state = ParseState::Begin;
 66         std::vector<Token> tokenList;
 67         int lineNumber = 1;
 68         int columnNumber = 1;
 69         Token token;
 70         while (*reading)
 71         {
 72             switch (state)
 73             {
 74             case ParseState::Begin:
 75             {
 76                 switch (*reading)
 77                 {
 78                 case‘+‘:case‘-‘:case‘*‘:case‘/‘:case‘(‘:case‘)‘:case‘<‘:case‘>‘:case‘=‘:case‘;‘:
 79                 case‘^‘:case‘&‘:case‘.‘:
 80                 {
 81                     tokenList.push_back(Token(lineNumber, columnNumber, TokenType::Operator, reading, 1));
 82                     token.Reset();
 83                     reading++;
 84                     columnNumber++;
 85                     break;
 86                 }
 87                 case‘0‘:case‘1‘:case‘2‘:case‘3‘:case‘4‘:case‘5‘:case‘6‘:case‘7‘:case‘8‘:case‘9‘:
 88                 {
 89                     state = ParseState::InInteger;
 90                     token.type = TokenType::Number;
 91                     token.column = columnNumber;
 92                     token.line = lineNumber;
 93                     token.Push(*reading);
 94                     reading++;
 95                     columnNumber++;
 96                     break;
 97                 }
 98                 case‘a‘:case‘b‘:case‘c‘:case‘d‘:case‘e‘:case‘f‘:case‘g‘:case‘h‘:case‘i‘:case‘j‘:case‘k‘:case‘l‘:case‘m‘:case‘n‘:case‘o‘:case‘p‘:case‘q‘:case‘r‘:case‘s‘:
 99                 case‘t‘:case‘u‘:case‘v‘:case‘w‘:case‘x‘:case‘y‘:case‘z‘:case‘A‘:case‘B‘:case‘C‘:case‘D‘:case‘E‘:case‘F‘:case‘G‘:case‘H‘:case‘I‘:case‘J‘:case‘K‘:case‘L‘:
100                 case‘M‘:case‘N‘:case‘O‘:case‘P‘:case‘Q‘:case‘R‘:case‘S‘:case‘T‘:case‘U‘:case‘V‘:case‘W‘:case‘X‘:case‘Y‘:case‘Z‘:case‘_‘:
101                 {
102                     state = ParseState::InIdentifier;
103                     token.type = TokenType::Identifier;
104                     token.column = columnNumber;
105                     token.line = lineNumber;
106                     token.Push(*reading);
107                     reading++;
108                     columnNumber++;
109                     break;
110                 }
111                 case‘#‘:
112                 {
113                     state = ParseState::InComment;
114                     token.type = TokenType::Comment;
115                     token.column = columnNumber;
116                     token.line = lineNumber;
117                     reading++;
118                     columnNumber++;
119                     break;
120                 }
121                 case‘"‘:
122                 {
123                     state = ParseState::InString;
124                     token.type = TokenType::String;
125                     token.column = columnNumber;
126                     token.line = lineNumber;
127                     reading++;
128                     columnNumber++;
129                     break;
130                 }
131                 case‘\n‘:
132                 {
133                     reading++;
134                     lineNumber++;
135                     columnNumber = 1;
136                     break;
137                 }
138                 case‘ ‘:
139                 {
140                     reading++;
141                     columnNumber++;
142                     break;
143                 }
144                 default:
145                 {
146                     throw std::exception("parse error");
147                     break;
148                 }
149                 }
150                 break;
151             } //case ParseState::Begin
152             case ParseState::InInteger:
153             {
154                 switch (*reading)
155                 {
156                 case‘0‘:case‘1‘:case‘2‘:case‘3‘:case‘4‘:case‘5‘:case‘6‘:case‘7‘:case‘8‘:case‘9‘:
157                 {
158                     token.Push(*reading);
159                     reading++;
160                     columnNumber++;
161                     break;
162                 }
163                 case‘.‘:
164                 {
165                     state = ParseState::InFloat;
166                     token.type = TokenType::Number;
167                     token.Push(*reading);
168                     reading++;
169                     columnNumber++;
170                     break;
171                 }
172                 default:
173                 {
174                     tokenList.push_back(token);
175                     token.Reset();
176                     state = ParseState::Begin;
177                     break;
178                 }
179                 }
180                 break;
181             } //case ParseState::InInteger
182             case ParseState::InComment:
183             {
184                 if (*reading == ‘\n‘ || *reading == ‘#‘)
185                 {
186                     //state = ParseState::InCommentEnd;
187                     if (*reading == ‘\n‘)
188                     {
189                         lineNumber++;
190                     }
191                     reading++;
192                     columnNumber = 1;
193                     tokenList.push_back(token);
194                     token.Reset();
195                     state = ParseState::Begin;
196                 }
197                 else
198                 {
199                     token.Push(*reading);
200                     reading++;
201                     columnNumber++;
202                 }
203                 break;
204             } //ParseState::InComment
205             case ParseState::InIdentifier:
206             {
207                 switch (*reading)
208                 {
209                 case‘a‘:case‘b‘:case‘c‘:case‘d‘:case‘e‘:case‘f‘:case‘g‘:case‘h‘:case‘i‘:case‘j‘:case‘k‘:case‘l‘:case‘m‘:case‘n‘:case‘o‘:case‘p‘:case‘q‘:case‘r‘:case‘s‘:
210                 case‘t‘:case‘u‘:case‘v‘:case‘w‘:case‘x‘:case‘y‘:case‘z‘:case‘A‘:case‘B‘:case‘C‘:case‘D‘:case‘E‘:case‘F‘:case‘G‘:case‘H‘:case‘I‘:case‘J‘:case‘K‘:case‘L‘:
211                 case‘M‘:case‘N‘:case‘O‘:case‘P‘:case‘Q‘:case‘R‘:case‘S‘:case‘T‘:case‘U‘:case‘V‘:case‘W‘:case‘X‘:case‘Y‘:case‘Z‘:case‘_‘:
212                 {
213                     token.Push(*reading);
214                     reading++;
215                     columnNumber++;
216                     break;
217                 }
218                 default:
219                 {
220                     tokenList.push_back(token);
221                     token.Reset();
222                     state = ParseState::Begin;
223                     break;
224                 }
225                 }
226                 break;
227             } //ParseState::InIdentifier
228             case ParseState::InString:
229             {
230                 switch (*reading)
231                 {
232                 case ‘\\‘:
233                 {
234                     state = ParseState::InStringEscaping;
235                     reading++;
236                     columnNumber++;
237                     break;
238                 }
239                 case ‘"‘:
240                 {
241                     //state = ParseState::InStringEnd;
242                     reading++;
243                     columnNumber++;
244                     tokenList.push_back(token);
245                     token.Reset();
246                     state = ParseState::Begin;
247                     break;
248                 }
249                 case ‘\n‘:
250                 {
251                     throw std::exception("parse error in string");
252                     break;
253                 }
254                 default:
255                 {
256                     token.Push(*reading);
257                     reading++;
258                     columnNumber++;
259                     break;
260                 }
261                 }
262                 break;
263             } // ParseState::InString
264             case ParseState::InFloat:
265             {
266                 switch (*reading)
267                 {
268                 case‘0‘:case‘1‘:case‘2‘:case‘3‘:case‘4‘:case‘5‘:case‘6‘:case‘7‘:case‘8‘:case‘9‘:
269                 {
270                     token.type = TokenType::Number;
271                     token.Push(*reading);
272                     reading++;
273                     columnNumber++;
274                     break;
275                 }
276                 default:
277                 {
278                     tokenList.push_back(token);
279                     token.Reset();
280                     state = ParseState::Begin;
281                     break;
282                 }
283                 }
284                 break;
285             } //ParseState::InFloat
286             case ParseState::InStringEscaping:
287             {
288                 switch (*reading)
289                 {
290                 case ‘n‘:
291                 {
292                     state = ParseState::InString;
293                     token.Push(‘\n‘);
294                     reading++;
295                     lineNumber++;
296                     break;
297                 }
298                 case ‘\\‘:
299                 {
300                     state = ParseState::InString;
301                     token.Push(‘\\‘);
302                     reading++;
303                     lineNumber++;
304                     break;
305                 }
306                 case ‘"‘:
307                 {
308                     state = ParseState::InString;
309                     token.Push(‘"‘);
310                     reading++;
311                     lineNumber++;
312                     break;
313                 }
314                 default:
315                 {
316                     throw std::exception("parse error in escaping");
317                     break;
318                 }
319                 }
320                 break;
321             } //ParseState::InStringEscaping
322             } //switch state
323         }
324         return tokenList;
325     }
326 };
327
328 #endif // !TOKEN_STREAM

300行的大函数,我也是醉了

时间: 2024-10-08 21:42:27

编译原理 - 1 手撸状态机词法分析器的相关文章

99%的程序员都在用Lombok,原理竟然这么简单?我也手撸了一个!|建议收藏!!!

罗曼罗兰说过:世界上只有一种英雄主义,就是看清生活的真相之后依然热爱生活. 对于 Lombok 我相信大部分人都不陌生,但对于它的实现原理以及缺点却鲜为人知,而本文将会从 Lombok 的原理出发,手撸一个简易版的 Lombok,让你理解这个热门技术背后的执行原理,以及它的优缺点分析. 简介 在讲原理之前,我们先来复习一下 Lombok (老司机可以直接跳过本段看原理部分的内容). Lombok 是一个非常热门的开源项目 (https://github.com/rzwitserloot/lomb

初学编译原理,终篇。

大概一个月的时间吧, 一直在学习编译原理,直到5月31号我仍然还在看编译原理的视频教程, 但是我直接其实我的各方面知识还是有很多欠缺, 比如我没有办法把我分析得出的抽象语法树进一步转化为汇编代码或者java字节码, 因为那些对于我而言又是一个全新的领域, 同时我知道我大4还是会有编译器的课程的, 同时我一直在用java写编译器, 一个很重要的原因就在于, 我的C语言水平不够支持我完成这样一个比较复杂的领域, 所以我感觉是时候先放一放编译原理了, 回想这段时间的学习, 过程中我深深地体会到了算法更

.NET手撸2048小游戏

.NET手撸2048小游戏 2048是一款益智小游戏,得益于其规则简单,又和2的倍数有关,因此广为人知,特别是广受程序员的喜爱. 本文将再次使用我自制的"准游戏引擎"FlysEngine,从空白窗口开始,演示如何"手撸"2048小游戏,并在编码过程中感受C#的魅力和.NET编程的快乐. 说明:FlysEngine是封装于Direct2D,重复本文示例,只需在.NET Core 3.0下安装NuGet包FlysEngine.Desktop即可. 并不一定非要做一层封装

编译原理实战——使用Lex/Flex进行编写一个有一定词汇量的词法分析器

编译原理实战--使用Lex/Flex进行编写一个有一定词汇量的词法分析器 by steve yu 2019.9.30 参考文档:1.https://blog.csdn.net/mist14/article/details/486413492.https://wenku.baidu.com/view/1c6398903868011ca300a6c30c2259010202f3a4.html 1.Flex工具的概述 Flex工具是生成C语言的工具,我们在日常生活中,如果直接使用C语言进行编写词法分析

编译原理简单词法分析器(first,follow,分析表)源码下载

编译原理(简单词法分析器下载) http://files.cnblogs.com/files/hujunzheng/%E7%AE%80%E5%8D%95%E8%AF%AD%E6%B3%95%E5%88%86%E6%9E%90%E5%99%A8.zip

编译原理(简单自动词法分析器LEX)

编译原理(简单自动词法分析器LEX)源程序下载地址:  http://files.cnblogs.com/files/hujunzheng/%E6%B1%87%E7%BC%96%E5%8E%9F%E7%90%86%E7%AE%80%E5%8D%95LEX%EF%BC%88%E8%AF%8D%E6%B3%95%E8%87%AA%E5%8A%A8%E5%88%86%E6%9E%90%E5%99%A8%EF%BC%89.zip

词法分析器——哈工大编译原理课程(一)

词法分析器——哈工大编译原理课程(一) 程序输入:从code.txt文件中读取内容 程序输出:识别出的单词序列,格式为:(种别码,属性值) ①对于关键字和运算符.分隔符来说,输出格式为(种别码,0),因为每个种别码能唯一地标识出是哪个单词 ②对于标识符来说,输出格式为(id的种别码即36,在哈希桶中的位置) ③对于常量(整数.浮点数.字符串)来说,输出格式为(种别码,在数组中的位置) 1 #include<stdio.h> 2 #include<stdlib.h> 3 #inclu

编译原理:C语言词法分析器

编译原理的实验:完成对C语言的词法分析 先说一下整体框架: 基类:Base  封装了一些基础的字符判断函数,如下: int charkind(char c);//判断字符类型 int spaces(char c); //当前空格是否可以消除 int characters(char c);//是否是字母 int keyword(char str[]);//是否是关键字 int signwords(char str[]);//是否是标识符 int numbers(char c);//是否是数字 in

&lt;编译原理 - 函数绘图语言解释器(1)词法分析器 - python&gt;

<编译原理 - 函数绘图语言解释器(1)词法分析器 - python> 背景 编译原理上机实现一个对函数绘图语言的解释器 - 用除C外的不同种语言实现 解释器分为三个实现块: 词法分析器:用于识别一条语句中的关键词是否符合预先定义的规则. 语法分析器:用来确定一条语句是否满足语法规则. 解释器:用来确定满足语法规则的句子,在意思上是否符合要求. 设计思路: 设计记号:词法分析器读取一个序列并根据构词规则把序列转化为记号流 定义一个字典:把所有符合一个模式的保留字.常量名.参数名.函数名等放进字