1、实验一词法分析器编制实验实验一:词法分析器编制实验一 教学重点与实现的关键技术1.1词法分析概述人们理解一篇文章(或解析一个程序)起码是在单词级别上来思考的。同样,编译程序也是在单词的级别上来分析和翻译源程序的。词法分析的任务是:从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号(token),把作为字符串的源程序改造成单词符号串的中间程序。因此,词法分析是编译的基础。执行词法分析的程序称为词法分析器。构造词法分析器的方法分为手工编制和自动生成(如用著名的词法分析器的自动生成工具Lex自动为某种语言的编译构造词法分析器)两种,本实验要求学生利用所学习掌握的知识手工编制一个小型的词法分析器
2、。1.2词法分析器的设计要求1.2.1词法分析器的功能和输出形式词法分析器的功能是输入源程序,输出单词符号。单词符号是一个程序语言的基本语法符号。程序语言的单词符号一般可分为下列五种。(1)关键字 是由程序语言定义的具有固定意义的标志符。有时称这些标志符为保留字或基本字。例如,Pascal中的begin,end,if,while都是保留字。这些字通常不用作一般标志符。(2)标识符 用来表示各种名字,如变量名、数组名、过程名等等。(3)常数 常数的类型一般有整型、实型、布尔型、文字型等等。例如,100,3.14159,TRUE,Sample。(4)运算符 如+、-、*、/等等(5)界符 如逗号、
3、分号、括号、/*,*/等等。一个程序语言的关键字、运算符和界符都是确定的,一般只有几十个或上百个。而对于标识符或常数的使用通常都不加什么限制。词法分析器所输出的单词符号常常表示成如下的二元式:(单词种别,单词符号的属性值) 单词种别通常用整数编码。一个语言的单词符号如何分种,分成几种,怎么编码,是一个技术性的问题。它主要取决于处理上的方便。标识符一般统归为一种。常数则宜按类型(整、实、布尔等)分种。关键字可将其全体视为一种,也可以一字一种。采用一字一种的分法实际处理起来较为方便。运算符可采用一符一种的分法,但也可以把具有一定共性的运算符视为一种。至于界符一般用一符一种的分法。 如果一个种别只含
4、一个单词符号,那么,对于这个单词符号,种别编码就完全代表它自身了。若一个种别含有多个单词符号,那么,对于它的每个单词符号,除了给出种别编码之外,还应给出有关单词符号的属性信息。单词符号的属性是指单词符号的特性或特征。属性值则是反映特性或特征的值。例如,对于某个标识符,常将存放它的有关信息的符号表项的指针作为其属性值;对于某个常数,则将存放它的常数表项的指针作为其属性值。在这里,我们给出一种编码方法(以FORTRAN语言为例):单词符号编码举例单词符号种别编码内部值助记符DIM1$DIMIF2$IFDO3$DOSTOP4$STOPEND5$END标识符6内部符号串$IDN整数7标准二进制$INT
5、=8$ASG+9$PLUS*10$STAR*11$POWER,12$COMMA(13$SLP)14$SRP1.2.2词法分析器作为一个独立子程序为何将词法分析作为一个独立阶段呢?是否还应该将它安排为独立的一遍呢?把词法分析安排为一个独立阶段的好处是,它可使整个编译程序的结构更简洁、清晰和条理化。词法分析比语法分析要简单得多,可用更有效的特殊方法和工具进行处理。 但是,这并不意味着我们也必须把词法分析作为独立的一遍。当然,也可以把词法分析安排成独立的一遍。让它把整个源程序翻译成一连串的单词符号存放于文件中。待语法分析器进入工作是在对从文件输进的这些单词符号进行分析。这种做法意味着必须在文件中保存
6、整个源程序的内码形式,这似乎是没有必要的。我们可以把词法分析器安排成一个子程序,每当语法分析器需要一个单词符号时就调用这个子程序。每一次调用,词法分析器就从输入串中识别出一个单词符号,把它交给语法分析器。这样,把词法分析器安排成一个子程序就比较自然。1.3 词法分析器的实现技术在以下的讨论中,我们将按照词法分析的任务和作为一个独立子程序的要求来考虑词法分析器的设计。 1.3.1 输入、预处理 词法分析器工作的第一步是输入源程序文本。输入串一般是放在一个缓冲区中,这个缓冲区称输入缓冲区。词法分析的工作可以直接在这个缓冲区中进行。但在很多情况下,把输入串预处理一下,对单词符号的识别工作将是比较方便
7、的。对于许多程序语言来说,空白符、跳格符、回车符和换行符等编辑性字符除了出现在文字常数中之外,在别处的任何出现都没有意义。对于它们,预处理时可以将其剔掉。 我们可以设想构造一个预处理子程序来完成预处理功能。每当词法分析器调用它时,它就处理出一串确定长度的输入字符,并将其装进词法分析器所指定的缓冲区中(称为扫描缓冲区)。这样,分析器就可以在此缓冲区中直接进行单词符号的识别,而不必照管其它繁琐事务。 分析器对扫描缓冲区进行扫描时一般用两个指示器,一个指向当前正在识别的单词的开始位置(指向新单词的首字符),另一个用于向前搜索以寻找单词的终点。 不论扫描缓冲区设的多大都不能保证单词符号不会被他的边界所
8、打断。因此,扫描缓冲区最好使用一个如下所示的一分为二的区域,即著名的双缓冲区设计。具体的操作步骤如下图所示:1.3.2 单词符号的识别:状态转换图 使用状态转换图是设计词法分析器的一种好途径。转换图是一张有限方向图(有向图)。在状态转换图中,结点代表状态,用圆圈表示。状态之间用箭弧连接。箭弧上的标记(字符)代表在射出结点(即箭弧始结点)状态下可能出现的输入字符或字符类。举例:对于正规式IDNletter(letter|digit)*描述的标识符,其状态图如下所示:letter,digitletter(IDN,入口)digitdigit(其它) (其它)(NUM,值)(ASG,_)=:+(ADD
9、,_)+(INC,_)其它ASG:=INC+ADD+1.3.3 利用状态转换图识别单词(Token)的步骤1. 从初态出发2. 读入一字符3. 按当前字符转入下一状态4. 重复 2,3 直到无法继续转移注:在遇到读入的字符是Token的分割符时,若当前状态是终止状态,说明读入的字符组成一单词;否则,说明输入不符合词法规则。1.3.4算法描述 子程序 scan( ) 输入:字符流 输出: Symbol(Code) :单词种别 Attr(value):属性(全局变量) 数据结构与子例程 数据结构 ch 当前输入字符 token 输入缓冲区(字符数组) symbol 单词种别(子程序的返回值) at
10、tr 属性(全局变量) 子例程 Lookup(token):将 token 存入符号表,返回入口指针 isKeyword(token):判别 token是关键字?返回关键字种别或 -1 getchar():从输入缓冲区中读入一个字符放入ch isdigit() isalpha() 该例的实现算法1. getchar()2. WHILE ch 是空格 /跳过空格2.1 DO getchar();3. CASE ch OF4. isdigit(ch) :4.1 chtoken; getchar();4.2 WHILE isdigit(ch) DO chtoken; getchar();4.3 输入
11、指针回退一个字符;4.4 将token中的字符串变成数值attr4.5 返回 NUM5. isalpha(ch) :5.1 chtoken; getchar();5.2 WHILE isalpha(ch) OR isdigit(ch) DO chtoken; getchar();5.3 输入指针回退一个字符;5.4 key = isKeyword(token);5.5 IF key0 THEN 返回 key5.6 Lookup(token)attr;5.7 返回 IDN6 : : getchar(); 6.1 IF ch等于= THEN 返回 ASG6.2 出错处理7 + : 返回 ADD8
12、- : 返回 SUB9 * : 返回 MUL10 / : 返回 DIV11 = : 返回 EQ12 : 返回 GT13 : 返回 LT14 ( : 返回 LP15 ) : 返回 RP16 ; : 返回 SEMI17 其它 : 出错处理18 END OF CASE1.4实现的关键技术提示 除了前述的双缓冲区设计、识别单词的状态转换图等,符号表的组织与实现也是不容忽视的一项关键技术。但由于学生初次接触编译系统的设计,往往忽略符号表的设计,即使想到了也无从下手。有的学生甚至认为标识符表既是符号表的全部编译的运行环境只需要(也只能有)一张标识符表。这种理解上的偏差正是由于对符号表的作用(特别是对标识符
13、表的特定用途)理解不够,而多数教科书在这个问题上往往只是给出一些宏观上的引导所至。下面将分别对符号表的作用与具体实现进行阐述。 1.4.1 符号表的作用 为了检查语义的正确性和生成代码,编译程序需要知道用户源程序中所使用的各种标识符的属性,这些属性信息常常由编译程序集中起来并存放于一张标识符表或符号表中。 符号表用于存放程序中出现的有关各种名字的属性信息,以反应名字的语义特征,编译的各阶段均涉及符号表的操作。 符号表的作用主要有以下几个方面:1) 收集符号的各种属性。如名字、类型、定义的层次等。2) 作为语义的合法性检查的依据。如引用时类型是否一致、层次是否得当等。3) 作为目标代码生成阶段地
14、址分配的依据。如根据符号表中该变量的特性可为其在适当的存储区域分配大小合适的存储空间。1.4.2 符号表的建立 符号表一般在编译程序的开始阶段(词法分析或语法分析阶段)就建立了,其内容的填写一般在词法分析、语法分析、语义分析阶段陆续填入,而它的使用与管理则贯穿于整个编译程序工作的各个阶段。1.4.3 符号表的内容 符号表的每一项(也称入口)由若干域构成,存放一个符号的所有属性信息。程序设计语言中的符号一般分为两大部分:固定部分和非固定部分。(1) 固定部分 符号表固定部分包括符号的名字域和种属域。(2) 非固定部分 符号表的非固定部分包括符号的各种信息域。 不同种属的符号有不同的信息域,如简单
15、变量有类型、地址、存储类别、作用域等信息域,数组名可有类型、内情向量地址等信息域,过程名、函数名可有参数个数、参数表地址、入口地址等信息域。1.4.4 符号表的组织 符号表的组织直接关系到语义功能的实现和语义处理的时空效率。 符号表的总体组织 所谓符号表的总体组织就是构造多少张符号表,以及哪些符号放在同一张表中,一般可选以下三种方式之一:1)将属性完全相同的符号(即同一种属的符号)组织在一起构成一张符号表,从而编译程序将使用多张符号表。优点是每张符号表的属性个数和结构完全相同,每个表项等长、表项中每个栏目均有效,对其中的每个符号的管理方便一致。缺点是编译程序要同时管理多个符号表,管理工作量和复
16、杂度较大。 2)所有符号都组织在一张符号表中。优点是管理集中,缺点是符号表的结构及相应的表处理较为复杂。 3)前两种方式的折中,即按符号所具有的属性的相似程度分类组织成若干张表,其优缺点自然也是前两种方式的折中。以上是关于符号表的一般描述。由于符号表是指存放与管理源程序中出现的各种名字的相关信息的表的总称,对于多趟扫描的编译系统,在具体实现时应包含名字表和标识表(如N.Wirth在其为著名的Tiny Pascal构造的范例编译程序就是这样设计的),名字表,顾名思义,只是用来存放源程序中出现的不同的各种名字(即标识符)的拼法(字符串),而标识符表则是在语义分析阶段随着分析的进程为每个过程体中说明
17、的标识符动态地建立起来,并为语义分析与中间代码生成服务的;显然,同名的出现在不同过程体中的标识符(由于其作用域不同)将分别出现在与该过程体相关的标识符表中,这一点与名字表中的情形是不同的。这也是由于名字表与标识符表中存入的信息是在编译的不同阶段获取的。事实上,在词法分析阶段,并不会涉及到标识符的作用域分析与类型检查等工作,而只需收集源程序中出现的名字信息以及这些名字是否为关键字即可。因此只需要查填名字表。所以,名字表的实现较为简单,可用一字符串型的一维数组来实现。不同的名字在表(数组)中有不同的下标,我们就以名字在表中的下标代表不同的标识符。当词法分析器析出一个名字时,还不能肯定其就是一个用户
18、定义的标识符,因为它还可能是语言本身的保留字又称关键字,而关键字是不能被用作标识符的。为了在析出一个名字时能高效地判断其是否为关键字,在词法分析阶段要建立一张保留字(关键字)表。可设置一个结构型数组ResWords,其第一个域sp按保留字的长度从短到长的顺序存放保留字的拼法(spelling),第二个域sy存放相应的内部符号。数组frw是为了加速比较而引入的,它的第i个元素是在保留字表ResWords中长度为i的第一个保留字的下标。下图以Pascal语言的一个子集的保留字集合来说明设计思想。二 词法分析器的具体要求2.1实验目的基本掌握计算机语言的词法分析器的开发方法。2.2实验内容编制一个能
19、够分析三种整数、标识符、主要运算符和主要关键字的词法分析器。2.3实验要求1根据以下的正规式,编制正规文法,画出状态图 标识符 (|)* 十进制整数 0 | (1|2|3|4|5|6|7|8|9)(0|1|2|3|4|5|6|7|8|9)* 八进制整数 0(1|2|3|4|5|6|7)(0|1|2|3|4|5|6|7)* 十六进制整数 0x(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f)* 运算符和分隔符 + - * / 0x3f 00 while正确结果:这些单词的单词种别及其属性 INT10 0 INT10 92 + _ IDN data _ INT16 63 INT8 0 WHILE _2.7 实验报告要求实验报告应包括以下几个部分:1词法的正规式描述2变换后的正规文法3状态图4词法分析器的数据结构与算法2.8 思考题1词法分析能否采用空格来区分单词?2程序设计中哪些环节影响词法分析的效率?如何提高效率?
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1