当前课程知识点:Compilers Techniques >  2 Lexical Analysis >  2.5 Lex >  2.5 Lex

返回《Compilers Techniques》慕课在线视频课程列表

2.5 Lex在线视频

下一节:3.1.1 The Role of the Parser

返回《Compilers Techniques》慕课在线视频列表

2.5 Lex课程教案、知识点、字幕

各位同学大家好

欢迎大家继续来学习词法分析相关的内容

在这节课

我们主要来学习

词法分析器的生成器 Lex

Lex 是一个特殊的小工具

它也被叫做 Lex 编译器

它是依据前面所学过的正规式的描述

来构造词法分析器

Lex 已经被广泛的应用到

很多描述语言的词法分析器当中

Lex 的输入

是用 Lex 语言来编写的

首先我们来学习一下

如何使用 Lex 来建立词法分析器

包括如下的步骤

首先

我们需要编写 Lex 源程序 lex.l

lex.l 当中包括词法分析器的说明

这是使用 Lex 语言来编写的

然后 lex.l 会通过 Lex 的编译器进行编译

得到了C语言程序 lex.yy.c

lex.yy.c 这段程序

就包括了从 lex.l 的正规式

构造出来的状态转换图

以及使用这张状态转换图

识别词法单元的标准子程序

在 lex.l 当中和正规式相关联的动作

是使用C语言来表示的

lex.yy.c 会通过C语言的编译器

来进行编译

从而得到目标程序 a.out

那么 a.out

就可以用来处理我们的输入字符流

那么 a.out 处理完

输入的字符流之后

就可以将它识别为

对应的词法记号

那么 a.out 就是我们识别词法记号序列的词法分析器

我们首先来看一下Lex的工作原理

Lex是根据正规式来构造出 DFA

然后会基于 DFA

生成词法分析程序的主控制程序

也就是说

我们在 lex 的程序当中

只需要把描述匹配规则的正规式写好

那么 Lex 就会自动地去构造对应的DFA

然后基于 DFA

自动的去生成

词法分析程序的主控制程序

lex的实现过程中

我们发现 lex.l 程序编译之后

会得到 lex.yy.c 的程序

也就是 lex是一个寄宿的语言

它需要有宿主程序的

宿主语言的

那么宿主语言

通常是C或者C++等等

在Lex 实现的过程中

每一个匹配动作

相关的代码

都会被放到对应的状态的

处理代码块当中去

Lex 的程序有很多

包括lex flex等等

我们在实验中会使用flex

lex程序通常包括三个部分

声明、翻译规则以及辅助的过程

声明、翻译规则和辅助过程中间

我们使用两个百分号%%

将它们区分开

声明部分

包括变量的声明

常量的声明及正规定义

那么其中正规定义部分

和我们前面学过的是类似的

也可以作为正规式

出现在翻译规则当中

辅助过程作为第三个部分

它包括了动作所要用到的辅助函数

这些函数

我们可以写在其它文件当中

分别进行编译

然后再和这里的词法分析器

进行连接

lex程序的翻译规则

我们会将它写成P1 {动作1}

P2 {动作2} 这样的形式

也就是每一条翻译规则的形式

都是模式 {动作} 这样的形式

每一个模式P是一个正规式

每个动作都用来描述

这个模式匹配词法单元的时候

词法分析器应当执行的程序段

那么在lex当中

这个动作是使用C语言

来编写的程序段

使用 lex 建立的词法分析器

通常是作为语法分析器的

一个子程序来出现

词法分析

每次被语法分析器所调用的时候

它需要逐个的

逐字符的去读取它的剩余的输入

直到它在剩余的收入中

发现一个能和当前某个模式 P

所匹配的最长前缀为止

然后它就去执行和P对应的动作A

非常典型的就是

动作A会把控制返回给语法分析器

如果不是这样的话

比如说 P 描述的是空白或者注释

词法分析器就会继续去寻找下一个词法单元

直到有一个动作

可以引导控制

回到语法分析器为止

词法分析器

仅仅返回一个值

给语法分析器

也就是返回一个记号名

给语法分析器

记号的属性值

是通过共享的变量进行传递的

下面我们来看一个lex的程序

首先我们观察一下

这个程序当中声明的部分

声明部分

我们会定义了一些

在翻译规则当中

需要使用的变量

这个声明当中

我们使用 %{ }%

包围起来

出现在括号当中的任何东西

任何代码

都会被直接复制到

词法分析器lex.yy.c当中

不作为正规定义和翻译规则的一部分

那么在后面第三部分的辅助函数

我们也是采用同样的方式

来进行处理的

声明部分

还会包括一些正规定义

每个定义使用一个名字

以及该名字指示的正规式组成

比如说正规定义当中的第一行

delim 它表示的是字符类 空格\t\n

也就是它用来表示

空格、制表符、换行符

这三个字符当中的任意一个

第二行正规定义当中ws

是一个叫空格的定义

空格

它就表示一个或者多个分界符的序列

letter用来表示

大写字母和小写字母当中的任意一个

digit用来表示

0~9数字当中的任意一个

接下来是id

也就是标识符

那么id的定义当中

我们使用了圆括号

它是lex当中的元符号

也就是它的含义

是把一些正规式组成一个整体

那么同样的

竖线也在lex当中

用来表示选择

然后number当中

也就是数的定义当中

我们涉及到用了反斜线\

是作为换码

也就是让 lex 当中元符号的字符

取它自身原本的含义

反斜线点就是取10进制小数点的含义

反斜线减号

也就是使用了减号本身的含义

因为减号在lex当中

也是作为元符号

接下来我们来看一下

翻译规则这一部分

那么我们先看

翻译规则当中的第一条规则

花括号 { } 括起来的ws

也就是我们如果看到了

空格、制表符或者换行符这样的串

那么就不需要有任何的动作发生

也不需要返回分析器

词法分析器

就会继续去识别

其它的词法记号

直到所识别的记号的动作

可以引起返回为止

我们需要注意的是

在lex当中

ws必须由花括号包围起来

这是用来区别和两个字母 ws 所组成的模式

后边的id 和number也是这样的

第二条规则是 while 这条规则

也就是说如果看见了字母 while

那么就返回一个记号

大写的WHILE while这个记号

那么while 记号

用来代表某个整数的常量

语法分析器

会把这个整数理解成while

那么后面它这条规则

是用同样的方法来处理do

我们再来看一下 id 的规则

id 对应的规则中

包含着两个语句

第一个语句是调用的函数install_id( )

这个函数的定义

我们放在了第三个部分

辅助的部分

这个函数返回值

赋给变量yylval

那么这个变量

yylval的定义

是出现在lex的输出lex.yy.C当中

这个变量

它对于(词法)分析器也是可用的

那么yylval的作用

就是

保存返回记号的属性值

id的规则中

第二个语句

return(id) 只能返回记号名

Number对应的规则和 id是类似的

是来解析number

它的返回值

也放到了yylval当中

然后 return 语句用来返回

number记号名

接下来6个关系运算符都使用双引号引起来

使用双引号在 lex 当中

引起来之后

这个字符

就用来表示原来的含义

我们再来看一下辅助过程

辅助过程中

出现的第一个函数是

install_id

我们把其中的详细代码省略掉了

那么我们可以想到

install_id它的主要作用就是

在符号表当中

去查找由模式

id 所能匹配的词法单元

那么在这里

涉及到了两个变量

一个是yytext

一个是yyleng

yytext是一个指针

它用来指向词法单元的开始字符

yyleng是一个整数

它指出来当前词法单元的长度

install_number()

和上面的 install_id() 过程是类似的

只是这里的词法单元

不再是标志符

而是数字

针对关系运算符

都可以返回词法记号RELOP

那么我们也可以使用

yylval的值

来区别不同的关系运算符

我们再来看一下

使用Lex 定义常规的表达式

我们可以使用点来匹配

除了 \n 之外的任意的字符

使用这个小横线-

用来表示范围

比如说 A 到Z a到z 0-9

使用$来表示一行的结尾

我们使用花括号

用来表示一个模式可能出现的次数

比如说

A{1,3}

用来表示A可能出现一次或三次

可以使用小尖角^

用来表示否定

操作符小尖角^

只能出现在左中括号后的

第一个字符位置处

比如说 [^ABC]

此外还有 * | ? + 等等

用来表示一些常用的闭包、逻辑或操作等等

我们再来看一下

lex当中

一些重要的外部变量

那么 lex 当中

使得词法单元的拼写

对于出现在第三部分当中的

辅助函数是可用的

比如说这里

变量yytext

它就是一个指针

是用来指向外部字符数组的

它的内容是

当前被某个规则所匹配的字符串

yyleng

表示当前yytext当中字符的个数

我们可以在翻译规则当中

写下

这个模式

然后它对应的动作

我们就可以写

printf{"word=%s, length=%d", yytext, yyleng);

然后后边我们直接可以调用yytext yyleng

这样就可以输出当前的字符串

以及它的长度

如果针对当前这个模式

所匹配的字符串

我们希望只输出它的字符串本身

那么就可以写成printf("%s", yytext)

当然也可以

进行简写

直接 printf("%s", yytext) 这一句

就可以写成ECHO

此外还有yyin

用来表示词法分析的输入文件

yyout用来表示

词法分析的输出文件

yyin和yyout

这两个函数

经常和函数yywrap() 连用

如果函数的返回值是1

那么就停止去进行解析

因此

可以用它来解析多个文件

我们的代码可以写在第三段

这样我们就可以解析多个文件

方法就是使用yyin文件指针

指向不同的文件

直到所有的文件

都被解析完成

最后 yywrap()

可以返回 1 来表示解析的结束

yylineno

用来给出当前的行数信息

接下来我们再说一下

lex 当中识别规则二义性的处理方法

首先能够匹配最多字符的规则优先

也就是说

如果同时有两个规则

都可以匹配一个字符串

那么我们选取

能匹配多字符的规则

它是优先的

比如说

我们针对integer

我们写出一个规则

再针对 [a-z]+

我们写出另一个规则

那么这个时候

如果我们输入integers

加上了s

那么我们就不再匹配第一条规则

而是去匹配 [a-z]+ 这条规则

第二

能匹配相同数目的字符的规则

书写顺序在前面的优先

也就是谁写在前面

谁的优先级别

会更高一些

就会优先去进行匹配

假设我们需要去计算

输入文本当中

she 和 he 的个数

那么在碰到she的时候

我们进行匹配 s++

执行 s++ 的动作

然后返回

再碰见 he 的时候

我们统计 h++

然后返回

在碰见 \n 的时候

我们不做什么动作

在碰见其它符号的时候

我们去匹配它

使用 . 分号(即无动作)

用来匹配任意的字符

除了 \n 之外的任意字符

我们再来看几个简单的例子

比如说

我们希望其删除输入当中

每行结尾处所有的空白字符

那么我们就可以直接写

[ \t\n]+$

然后在规则对应的动作方面

我们就直接写一个分号;

这样我们就可以删除输入当中

每行结尾处所有的空白字符

再比如

如果我们希望

将字符串当中的空格

或者制表符

转换为单个的空格

那么我们就再增加一条规则

[ \t]+

对应的动作

我们就写上printf(" ")

这样我们就可以将字符串当中的

空格或者制表符

转换为对应的单个空格

我们再来看一下上机实验的例子

exampl.l这个 lex源程序

前边是变量的声明部分

中间是模式的匹配过程

也就是翻译的规则

最后 main 函数

这个地方

是对应的辅助函数

那么我们在变量的声明部分

定义了 num_lines 和num_chars

用它来进行计数

记行数和字符的数目

然后在翻译规则的部分

我们使用 \n 和 .

用来做模式的匹配

在碰到 \n 的时候

我们的num_lines 就要做 ++

num_chars

需要做 ++ 的操作

然后在碰到其它的符号的时候

只有num_chars 的需要做++的操作

然后在 main 函数当中

我们需要去调用yylex

去使用printf

将所有的行

和字符的数目打印输出

然后如果我们给出一个文本(文件)

这个文本当中

包括这样的三行文本

hello world

wo ai tian an men 的英文

以及hello world i love

那么我们在编译完example.l

lex源程序之后

会得到 lex.yy.exe

这个程序

然后我们使用lex.yy.exe 这个程序

去读取刚才的文本文件

就会得到对应的结果

也就是输出函数等于3

字符的数目等于49

这一讲就到这里

谢谢大家

Compilers Techniques课程列表:

1 Overview of Compilers Techniques

-1.1 Overview of Compilers Techniques

--Chapter 1 Overview of Compilers Techniques

--Overview of Compilers Techniques

2 Lexical Analysis

-2.1  Lexical Tokens, Strings and Language

--2.1  Lexical Tokens, Strings and Language

--2.1 Lexical Tokens, Strings and Language

-2.2  Regular form

--2.2 Regular form

--2.2 Regular form

-2.3  Finite automata

--2.3 Finite automata

--2.3 Finite automata

-2.4  DFA construction, Subset construction, Simpleset DFA

--2.4  DFA construction, Subset construction, Simpleset DFA

-2.5 Lex

--2.5 Lex

3 Syntax Analysis

-3.1 Context-free Grammars

--3.1.1 The Role of the Parser

--3.1.2 The Formal Definition of a Context-free Grammar

--3.1.3 Derivations

--3.1.4 Ambiguity

-3.2 Writing a Grammar

--3.2.1 Elimination of Left Recursion

--3.2.2 Left Factoring

--3.2 Top-Down Parsing

-3.3 Languages and Grammars

--3.3 Languages and Grammars

--3.3 Language and Grammars

-3.4 Top-Down Parsing

--3.4.1 First and Follow

--3.4.2 LL(1) Grammers

--3.4.3 Recursive Descent Analysis

--3.4.4 Nonrecursive Descent Analysis

-3.5 Bottom-up Parsing

--3.5.1 Reductions and Handle

--3.5.2 Shift- Reduce Parsing

--Bottom-up Parsing

-3.6 LR Parsing

--3.6.1 LR Parsing

--3.6.2 Viable Prefixes

--3.6.3 Simple LR

--3.6.4 LR(1)

--3.6.5 LALR

--3.6.6 Characteristics of LR Parsing

--3.6.7 Non Ambiguous and Not LR Context-Free Grammars

4 Syntax-Directed Translation

-4.1 Syntax-Directed Definitions

--4.1.1 Attribute Grammars

--4.1.2 Attribute Dependency Graphs and Ordering the Evaluation of Attributes

--Syntax-Directed Definitions

-4.2 Bottom-Up Calculation of S Attribute

--4.2.1 Bottom-Up Calculation of S-Attributed

--4.2.2 Stack Code

-4.3 L-Attributed Definitions

--4.3.1 L-Attributed Definitions

--4.3.2 Translation Schemes

--4.3.3 Design of Predictive Translator

--L-Attributed Definitions

-4.4 Bottom-Up Parsing of L-Attributed Translation

--4.4.1 Bottom-Up Parsing of L-Attributed Translation

--4.4.2 Simulate the Parsing of Inherited Properties

--Bottom-Up Parsing of L-Attributed Translation

5 Organization and Management of Run-Time Storage Space

-5.1 Overview

--5.1 Overview

--Overview

-5.2 Global Stack Storage

--5.2 Global Stack Storage

-5.3 Calling Sequences

--5.3 Calling Sequences

-5.4 Non Local Names

--5.4 Non Local Names and dynamic scope

--Non Local Name

6 Intermediate Code Generation

-6.1 Overview of Intermediate Code Generation

--6.1 Overview of Intermediate Code Generation

-6.2 Declaration Statements

--6.2 Declaration Statements

7 Code Generation

-7.1 Issues in the Design of Code Generator

--7.1 Issues in the Design of Code Generator

-7.2 Target Machine

--7.2 Target Machine

--Target Machine

-7.3 Basic Blocks and Flow Graphs

--7.3 Basic Blocks and Flow Graphs

-7.4 A Simple Code Generator

--7.4 A Simple Code Generator

8 Design and Implementation of a Simple Compiler

-8.1 Demonstration of Compiler Framework based on Python

--8.1 Demonstration of Compiler Framework based on Python

-8.2 Basic

--8.2.1 Scanner

--8.2.2 Parser -1LRItem

--8.2.3 Parser-2ActionGoto

--8.2.4 SA

-8.3 SimpleJava

--8.3 SimpleJava

2.5 Lex笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。