词汇分析器入门
在老一代的计算机中,运行简单的代码要比人们想象的困难得多。这种挑战是因为计算机是用写在打卡机上的机器语言做一切事情的。然后,就变成了汇编语言中使用的助记符。
这些都比较简单,但仍然需要对机器语言的理解。开发人员比以往任何时候都更需要高级语言,因为计算机在重复性任务中似乎很有效。
高级语言编译器的发明是计算机发展阶段的一个重大突破。这是因为高级语言对大多数人来说是可以理解的,尽管需要很多开销。
因此,为了能够欣赏编译器以及它们在从源码到优化的目标码的代码转换中的工作,人们必须了解它们是如何设计的。
编译器设计涉及几个阶段。这些阶段包括以下几个方面。
- 词法分析
- 语法分析
- 语义分析
- 中间代码生成
- 代码优化
- 目标代码生成
- 错误处理
- 符号表生成

在编译器构建的第一阶段发现了一个词法分析器。
主要收获
在这篇文章中,人们将学到以下内容。
- 什么是词法分析
- 词汇分析:核心原则
- 词法分析的重要性
- 在创建词法分析器中应用这些原则
- 用代码实例建立一个词法分析器
前提条件
要跟进这篇文章,需要具备以下知识。
- 一个良好的互联网连接
- 知道如何使用正则表达式
- 具有C语言编程的知识
- 一个好的IDE或自己选择的文本编辑器
现在让我们进入文章的主题。
什么是词法分析?
词汇分析是编译器的第一个阶段,也被称为扫描器。它是将高层次的源代码转换成一系列编译器可以轻易识别的标记的过程。
然后,这些标记会通过一系列的步骤来检查它们的格式是否正确。这些步骤包括检查其语法和语义。因此,词法分析器的输入是程序员的高级语言源代码。
然后,它将其分割成较小的块,可以快速、方便地进行分析和处理。
注意:词法分析器不改变代码或检查错误。
基本术语
使用的一些术语包括。
- 标记
- 模式
- 词素
让我们来看看它们是什么。
语词
代号被定义为任何可以在无内容限制语法(CFG)中被识别的东西。它代表一个信息单位。它们可以在终端、非终端、生产规则和Start 符号之间(T, N, P, S) 。
- 终端(T)是代表最终值的字符
- 非终端(N)可以用终端或非终端替换。
- 生产规则(P)是终端和非终端的组合,可以用一种有意义的格式来组合,代表某些操作。
Start(S)符号,则是一个特殊的非终端符号,表示一个应用程序的开始。
模式
模式可以是特定的格式,用来识别令牌。编译器可以用它们来识别字母、单词和特殊字符。然后,这些可以被归类为终端或非终端,等等。
模式可以是用户定义的,也可以是事先在程序中预先定义的。
词素
另一方面,词素可以说是一种标记的组合,当它们组合在一起时,传达出更容易理解的意思。
一个基于示例代码的标记计数
当运行下面的代码时,这段Java代码会打印出 "Hello World "信息。
/*Print 'Hello World!'*/
/*Comments and whitespaces are not counted*/
public class HelloWorld
{
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
词法分析器所识别的标记数量是27 。
请看下面的图片,看看它们是如何被计算的。

词法分析器识别的标记如:
- 关键词
- 特殊字符
- 宏指令和特殊指令
- 字符串、整数、浮点数、标识符,等等。
然后,这种标记可以被传递给语法分析器,根据符号表生成解析树,看它们是否正确。
非标记包括空格、制表符和注释,等等。
词汇分析工具
用于创建和使用词法分析器的一些工具包括。
- LEX工具
- YACC
- 一个C语言编译器
LEX工具
它也被称为Flex(GNU工具)。它创建了一个C源核心。
它可以与任何应用程序集成,也可以作为一个独立的应用程序工作。
它首先设置了一些配置,这些配置定义了从输入中期待的标记,以及在解析(将它们发送到句法分析器)之前要采取的行动。
YACC(另一个编译器编译器)
一个UNIX标准工具,使用Look Ahead Left-to-Right 解析风格编译LALR (1) 语法。它是一个开源的编译器,用C语言为解析器生成代码。
C语言编译器
这是一个典型的C语言编译器。GNU CC将完美地解决这个问题。
设置工具
本节将介绍上面讨论的词汇分析工具的安装。
Linux安装
要在Ubuntu上这样做,请运行以下命令。
- 获取已安装应用程序的当前软件更新
sudo apt-get update
- 安装Flex GNU工具
sudo apt-get install flex
- 安装Bison,如下所示。
sudo apt-get install bison
Bison是一个通用的兼容YACC的解析器生成器,它将LALR(1)上下文自由语法的语法描述转换为解析该语法的C程序。人们可以用它来开发一系列的语言分析器。Bison与Yacc有向上的兼容性。
- 安装Yacc
sudo apt-get install byacc
- 人们也可以安装Bison包,从BNF符号中生成C或C++的解析器。你可以通过运行下面的命令来完成这个任务。
sudo apt-get install bison++
Windows安装
要安装Yacc的Flex和Bison,请按以下步骤操作。
- 在同一网站上下载Bison Setup。
- 通过运行它来安装它。
- 下载Flex设置,在下载过程完成后安装它。
通过以下方式将二进制文件添加到PATH 变量中。
- 打开安装Bison和Flex的程序文件的文件夹。默认情况下,它是在
C:\Program Files (x86)\GnuWin32文件中找到的。 - 复制其位置路径。
- 在Windows任务栏中搜索
Environment Variables,选择 "编辑系统环境变量 "选项。 - 选择 "环境变量 "选项。
- 在'路径'选项中,粘贴bin文件夹的位置。要粘贴的文件夹路径是
C:\Program Files (x86)\GnuWin32\bin。

- 保存它并关闭窗口。
Lex文件的结构
Lex文件的扩展名是.l 。
然而,在创建Lex文件之前,让我们先了解Lex文件的组成部分。
Lex文件的部分
Lex文件有三个主要部分。这些部分包括以下内容。
- 定义
- 规则
- 子程序
定义
它包含文字块、定义、内部表的声明、启动条件和翻译。
它可以包含像C程序中发现的'include'部分的东西。这个声明说明了程序可能需要的东西。
定义可以写在%{ 和%} 符号里面。
规则和行动
这一部分包含了模式和C代码(动作)。这些模式可以是ECMAScript、RegEx或通配符格式。
它指定了程序检查的标记,而C代码则说明了在Actions部分发现这些标记时的情况。
行动可以在大括号内找到({ ... })。这些动作或宏也可以是可选的。因此,它们可以留空。
子程序
当找到或未找到模式时执行的额外的用户定义的功能的C代码。它们还包含程序在运行中可能执行的其他功能。
这些也是可选的。
这三个部分是由分隔符(%% )划分的,如下所示。
%{
%}
%%
%%
y.tab.c 是一个输出文件,它被编译产生一个 函数。它是用C语言编译器产生的。yyparse
下面的图片显示了基于上面定义和解释的部分的代码结构的简要解释。

创建一个Lex文件并运行它
让我们创建一个Lex分析器,计算文件中的字数和它们的总大小。
创建一个名为 "flex-projects "的根文件夹。在其中,创建一个名为counter.l 的文件。打开它,添加说明程序声明的代码行,如下所示。
/* Definitions */
%{
int lines = 1,
words = 0,
lowercase_letters = 0,
uppercase_letters = 0,
numbers = 0,
special_characters = 0,
totals = 0,
size = 0; %
}
/* Delimiters: These separate the definitions from the Rules and actions section */
%%
添加规则和动作。大写和小写字母、数字、特殊字符、字母和行实例都将被检查。在下面的代码中检查一下。
\n { lines++; words++;} /* Adds to the lines and words variables a value of 1 when one goes to a new line */
[\t ' '] words++; /* Adds a value to the words variable when one moves from one word to another (words are separated by a space) */
[A-Z] uppercase_letters++;
[a-z] lowercase_letters++;
[0-9] numbers++;
[$&+,:;=?@#|'<>.^*()%!-] special_characters++;
/* Separate the Rules and Action Section from the User sub-routines */
%%
在用户子程序部分,添加打印总数的主程序。
int yywrap() {
}
int main() {
yyin = fopen("test.txt", "r"); /* Open the 'test.txt' file in a read only fomart */
yylex();
totals = special_characters + numbers + uppercase_letters + lowercase_letters;
size = (totals * 1); /* The total size in bytes is equal to the total number of characters multiplied by one byte since each is one byte */
/* Prints the output */
printf("This file contains the following:");
printf("\n\t%d lines", lines);
printf("\n\t%d words", words);
printf("\n\t%d Lowercase letters", lowercase_letters);
printf("\n\t%d Uppercase letters", uppercase_letters);
printf("\n\t%d digits", numbers);
printf("\n\t%d special characters", special_characters);
printf("\n\t The total size of the file characters in bytes is: %d bytes.\n", size);
}
创建一个名为 "test.txt "的新文件,并打开它。在其中添加以下文字。
Hello Mark and Jane! Did you go for launch?
I am coming back tomorrow. Don't go home yet...
I will visit you at 0945hrs on 27th November this year
在终端或命令行(在Windows)中打开文件所在的文件夹。使用下面的命令将lex文件改成C源码核心。
flex counter.l
通过在终端运行下面的代码,将C源码核心转换为可执行文件。
gcc lex.yy.c
运行该可执行文件。这个过程运行创建的分析器,这样它就可以使用输入文件作为输入,并在终端生成输出。
./a.out
这个过程看起来是怎样的。

终端上的输出如下所示。

YACC带有预定义的语言保留词,也被称为关键词。在这些术语中,有 "yytext"、"yylex"和 "yywrap",仅举几例。
人们可以注意到,它们以yy 开始。这一步是因为YACC在默认情况下在关键词前使用这个前缀来识别它们。
人们还可以将程序设置为使用另一个两个字符的前缀。这些字符可以是小写的,一个是大写的,另一个是小写的。
这两个字符不能是大写的,因为所有大写的组合已经用于其他的关键词。
yylex() 是一个函数,每次调用时都将找到的标记传递给 。每当返回 (文件结束)错误时,它就会返回0或一个负值。它利用一个叫做 的程序来获取这些值。yyparse() EOF yygetc()
yyparse() 调用 ,以获得传递给程序的任何令牌。然后将其与用于匹配的模式相匹配,以确定要执行的行动类型。yylex()
yywrap() 用来显示代码执行的结束。如果程序的运行已经结束,它返回的值是1,如果正在运行,它返回的值是0。
Yacc也有一些预定义的变量。这些变量包括以下内容。
- yytext。它定义了当前可由词法扫描器识别的输入标记。
- yylen:它是一个变量,用于保存输入文本的长度。
- yyout。它确定输出宏的输出流,处理不符合任何规则设置的输入。
- yyin: 它决定了输入函数和
yylex()的输入流。 - yylineno:它指定了当前的输入行号,该行号由输入和
yycomment保持最新。
yyin和 ,可以互换。这种互换可以通过赋值来完成。yyout
一个将某些字符的实例转换为特定模式的Lex分析器
让我们创建一个lex分析器,将输入中出现的'abc'改为'ABC'。创建一个名为strings.l 的文件。在该文件中,在定义部分添加以下内容。
%{
/* Find instances of 'abc' to 'ABC' */
#include<stdio.h>
#include<string.h>
int i;
%}
%%
至于lex文件中的注释,确保首先以双倍行距开始,以避免错误。
添加规则,检查只输入(大写或小写)的单词是否含有'abc'字符,并将其相互连接。如果是,则只替换指定的字符。
[a - z A - Z] * {
for (i = 0; i <= yyleng; i++)
{
if ((yytext[i] == 'a') && (yytext[i + 1] == 'b') && (yytext[i + 2] == 'c'))
{
yytext[i] = 'A';
yytext[i + 1] = 'B';
yytext[i + 2] = 'C';
}
}
printf("%s", yytext);
}
.*{
ECHO;
}
\
n {
printf("%s", yytext);
}
%%
至于用户子程序部分,请复制-粘贴以下内容。
int main() {
yylex();
}
int yywrap() {
return 1;
}
ECHO是一个宏,作为一个动作,将yytext 变量中匹配的输入令牌复制到yyout 中找到的lex输出流。
它是一个宏,就像yygetc() 和BEGIN ,以及其他宏一样。在终端中运行以下命令。
lex strings.l
然后,运行下面的命令。
cc lex.yy.c
Flex 和 ,可以用 和 来代替,如上图所示。gcc lex cc
运行下面的命令来运行该可执行文件。
./a.out
现在它允许从终端输入。键入以下内容,看看它是否工作。
We can see abc turn to ABC in javaabcfilewithoutknowingABCDabcd
输出结果如下。

一个能识别某些词的词法分析器
这个词法分析器将在一个预先定义的集合中识别单词。然后它返回一个输出,如果它是在该集合中或不在其中。
在这种情况下,它应检查动词。创建一个新文件,称为verbs.l 。从下面的定义开始。
%{
//program to classify a word as a verb or not a verb
%}
%%
在规则和动作部分,粘贴以下内容。
[\t]+ //Ignore the white spaces/tabs
is |
am |
are |
were |
was |
be |
being |
been |
do |
does |
did |
will |
would |
should |
can |
could |
has |
have |
had |
go { printf("%s: is a verb\n", yytext);}
[a-zA-Z]+ {printf("%s: is not a verb\n",yytext);}
. |\n {ECHO; /* normal default anyway*/}
%%
这段代码包含一系列动词,作为定义的模式。如果它找到其中任何一个,就会返回并说它是一个动词;否则就不是。对于传递给它的每个词,例如在一个句子中,它都会这样做。
在用户子程序部分,创建如下所示的主程序。
int yywrap(){}
int main(){
yylex();
return 0;
}
通过在终端执行以下内容来运行它。
lex verbs.l
使用下面的命令将创建的C源核转换为可执行的分析器。
cc lex.yy.c
运行该可执行程序,如下所示。
./a.out
在终端上键入一些字符并查看结果。例如,键入下面的句子。
Kelly is going home today
结果看起来如下图所示。

结论
创建高级编译器是计算机软件开发过程中最重要的步骤之一。高级语言编译器因其易于理解而吸引了更多的开发者加入到现有的语言中。
词汇分析是编译过程中至关重要的一个阶段。它可以识别标记,并在进入其他步骤之前将它们传递给语法分析。
关于分析器是如何创建的知识对于让人尝试理解编译器的工作原理是至关重要的。