词法分析器(Lexer)的实现

  • 写在前面

写下Compiler系列的主要目的,是为了记录一下本人在学习编译原理以及做出一个简单的Compiler的历程,为后续向二进制安全的更深领域的学习打下基础。

  • Lexer是什么

Lexer是Lexical analyzer的缩写,中文意思为词法分析器,是进行词法分析的程序或者函数,这也是编译器所做的第一项工作。注意:如果没有编译原理的相关知识,请先学了编译原理之后再来看本文章。

  • 词法分析的任务

词法分析的任务就是让编译器搞懂我们究竟写了什么,编译器会先将我们的程序切片成一个一个的单词,将其作为一个token,每个token都会带有一个编号。

  • Lexer的实现

从这里开始,将会开始进行第一步,也就是实现一个简单的词法分析器,文章中只会讲述思想的思路以及部分代码,完整的代码请看我的github:h1J4cker

我们先思考一下,在我们的代码中,哪些东西虽然表达的方式不同,但其实可以被划分为一类呢?

首先是我们定义变量的时候,用到的int,char等,我们可以认为他们都是标识类型的关键字,所以即便int与char在单词的组成以及含义方面不同,但我们仍然可以把他抽象为同一个类型:关键字

注意:我们可以把宏定义的关键词define,以及外部链接extern单独分出来,因为对于这两个关键词我们需要单独处理。

其次,我们可以想到,在一个程序中,被大量使用的不仅是int,char等关键字,还有由他们所定义的数据,为了简单起见,我们把所有的数据都认为是double类型,那么再次,我们又可以抽象出另一个类型:数值

最后我们需要把文件的结束符,也就是EOF单独作为一个类型。

那么从上面的分析中我们可以定义一个枚举类型Tokenizer:

1
2
3
4
5
6
7
8
enum Tokenizer
{
tok_eof = -1,
tok_def = -2,
tok_extern = -3,
tok_identifier = -4,
tok_number = -5
};

此外我们还需要定义两个变量来存储标识符的值,以及数据的数值。

1
2
static std::string IdentifierStr; //标识符一般是一串字符串,所以将存储它的变量定义为string类
static double NumVal; //数据统一认为是double类型

当我们抽象出一个个token之后,我们需要一段程序来识别这些token,而我们需要做的第一步,就是要去除为了程序的工整性而加入的一个个空格。

1
2
3
4
5
6
7
8
static int __Get_Tok()
{
static int LastChar = " ";

while (isspace(LastChar))
{
LastChar = getchar();
}

这段程序的作用是判断LastChar是否是空格,如果是就不处理,并接收下一个字符。

然后我们需要识别对应的字符串是否属于我们前面定义中的某一类,如果属于,则返回相应的值,如果不属于,那么他可能是一些运算符如:+-。那么我们就需要返回他的ASCII码值。

首先我们来识别关键字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (isalpha(LastChar))
{
IdentifierStr = LastChar;
while (isalnum(LastChar = getchar()))
{
IdentifierStr += LastChar;
}

if (IdentifierStr == "def")
{
return tok_def;
}
else if (IdentifierStr == "extern")
{
return tok_extern;
}
else
{
return tok_identifier;
}
}

这段程序首先判断了是否LastChar是字符,如果是那么就进入下面的阶段,接收下一个字符,判断它是否是数字,如果不是,那么就将LastChar赋值给IdentifierStr进行存储,同时判断是否是三种关键字中的某一个,如果是就返回相应的值。

接下来我们来判断数值类型,只要遇到代表数值的字符串,我们就将其转换为数值,并存入Numval中,并返回相应的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (isdigit(LastChar) || LastChar == ".")
{

std::string NumStr;

do
{
NumStr += LastChar;
LastChar = getchar();
} while (isdigit(LastChar) || LastChar == ".");

NumVal = strtod(NumStr.c_str(), 0);
return tok_number;
}

当我们遇到注释时,我们只需要跳过当前行即可,也就是说我们遇到注释符时,我们就一直读到\n为止:

1
2
3
4
5
6
7
8
9
10
11
12
if (LastChar == "#")
{
do
{
LastChar = getchar();
} while (LastChar != EOF && LastChar != '\n' && LastChar != '\r');

if (LastChar != EOF)
{
return __Get_Tok();
}
}

当上面这些步骤做完以后,如果能存在未处理的字符串,那么只可能是两种情况:

1.读到了运算符

2.读到了EOF

那么,此时的处理过程也相当简单,我们只需要判断一下到底是哪种情况,并返回相应的值即可:

1
2
3
4
5
6
7
8
if (LastChar == EOF)
{
return tok_eof;
}

int ThisChar = LastChar;
LastChar = getchar();
return ThisChar;
  • 结尾

到这里,一个简单的词法分析器就基本上完成了,我们已经可以识别数据,关键词,标识符等等识别出来为下一步语法分析做准备了。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2023 h1J4cker
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信