470 likes | 623 Views
第三章 词法分析. 概述 词法分析是编译过程的第一步,其主要任务是对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。 因此,词法分析是编译的基础。执行词法分析是编译的基础。 词法分析:根据词法规则识别及组合单词,进行词法检查。 对数字常数完成数字字符串到(二进制)数值的转换。 删去空格字符和注解。. 主要内容 : 3.1 对于词法分析器的要求 3.2 词法分析器的设计 3.3 正规表达式与有限自动机 3.4 词法分析器的自动产生. 3.1 对于词法分析器的要求. 任务
E N D
第三章 词法分析 概述 • 词法分析是编译过程的第一步,其主要任务是对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。因此,词法分析是编译的基础。执行词法分析是编译的基础。 • 词法分析:根据词法规则识别及组合单词,进行词法检查。 • 对数字常数完成数字字符串到(二进制)数值的转换。 • 删去空格字符和注解。
主要内容: • 3.1 对于词法分析器的要求 • 3.2 词法分析器的设计 • 3.3 正规表达式与有限自动机 • 3.4 词法分析器的自动产生
3.1 对于词法分析器的要求 • 任务 • 执行词法分析的程序称为词法分析器. • 词法分析器的功能是输入源程序,输出单词符号. • 单词符号是一个程序语言的基本符号。 • 程序语言的单词符号一般分为五种.
1) 单词符号的分类 • 关键字: 由程序语言定义的具有固定意义的标识符。有时称这些标识符为保留字或基本字。例如,Pascal中的begin,end,if,while等。 • 标识符:用来表示各种名字,如:变量名,数组名等. • 常数: 整型,实型,布尔型等. • 运算符: + - * / 等. • 界符:逗号,分号,括号,/*, */等. 一个程序语言的关键字,运算符,界符都是确定的,一般只有几十或上百个,而对标识符和常数的使用都不加什么限制.
2) 单词符号的表示 • 单词符号常常表示成如下二元式: (单词种别, 单词符号的属性值) • 单词种别可用以下形式表示: • 单词种别通常用整数编码. • 一个语言的单词符号如何分种,分成几种,怎样编码,是一个技术性的问题。它主要取决于处理上的方便。 • 标识符一般统归一种。常数则宜按类型分种。关键字可将其全体视为一种,也可以一字一种。运算符可采用一符一种的分法,但也可以把具有一定共性的运算符视为一种。至于界符一般用一符一种的分法。 • 一类单词统一用一个整数值代表其属性.如 1 代表保留字,2 代表标识符等. • 每一个单词一个类别.如 1 代表BEGIN,2 代表END等.
如果一个种别只含一个单词符号,那么,对于这个单词符号,种别编码就完全代表它自身了。若一个种别含有多个单词符号,那么,对于它的每个单词符号,除了给出种别编码之外,还应给出有关单词符号的属性信息。如果一个种别只含一个单词符号,那么,对于这个单词符号,种别编码就完全代表它自身了。若一个种别含有多个单词符号,那么,对于它的每个单词符号,除了给出种别编码之外,还应给出有关单词符号的属性信息。 • 属性值: • 单词符号的属性是指单词符号的特性或特征.属性值则是反应特性或特征的值. • 例如,对于某个标识符,常将存放它的有关信息的符号表项的指针作为其属性值;对于某个常数,则将存放它的常数表项的指针作为其属性值。 • 在本书中,假定关键字、运算符和界符都是一符一种。对于它们词法分析器只给出某种别编码,不给出它自身的值。标识符单列一种。常数按类型分种类。
1)按单词种类分类 类别编码 1 2 3 4 5 6 7 单词值 内部字符串 整数值 数值 0 或 1 内部字符串 保留字或内部编码 分界符或内部编码 单词名称 标识符 无符号常数(整) 无符号浮点数 布尔常数 字符串常数 保留字 分界符
2)保留字和分界符采用一符一类 单词值 内部字符串 整数值 数值 0 或 1 内部字符串 - - - - ….. - - - - -- 单词名称 标识符 无符号常数(整) 无符号浮点数 布尔常数 字符串常数 IF THEN ELSE FOR ……… : + * , ( 类别编码 1 2 3 4 5 6 7 8 9 ……. 20 21 22 23 …….
考虑下述C++代码段: while (i>=j) i--; 经词法分析器处理后,它将被转换为如下的单词符号序列: <while, -> <(,-> <id,指向i的符号表项的指针> <>=,-> < id,指向j的符号表项的指针> <),-> <id,指向i的符号表项的指针> <--,-> <;,->
3.1.2词法分析程序的实现方案 优点: 结构清晰、各遍功能单一 缺点:效率低 1. 词法分析单独作为一遍 单词串 第二遍 第一遍 S.P. (字符串) S.P. (符号串) 词法分析 语法分析 2. 词法分析程序作为单独的子程序 取单词 S.P. (字符串) 语法分 析程序 词法分 析程序 单词
3.2词法分析器的设计 • 3.2.1输入、预处理 1.输入缓冲区:词法分析器工作的第一步是输入源程序文本。输入串一般是放在一个缓冲区中,这个缓冲区称为输入缓冲区。 2.预处理:就是将程序语言中的空白符、跳格符、回车符、换行符和注解等编辑性字符剔掉。 3.预处理子程序:就是用来完成编译的预处理。 4.分析器:分析器对扫描缓冲区进行扫描时一般用两个指示器,一个指向当前正在识别的单词的开始位置,另一个用于向前搜索以寻找单词的终点。不论扫描缓冲区设得多大都不能保证单词符号不会被它的边界所打断。因此,扫描缓冲区最好使用一个如下所示的一分为二的区域: 起点指示器搜索指示器 这意味着对标识符和常数的长度实际上必须加以限制,否则,即使缓冲区再大也无济于事。
3.2.2单词符号的识别:超前搜索 • 在某些语言中,要识别一个单词符号必须超前看若干字符,直到能区别开这些单词为止,常应用在如下几个方面: 关键字的识别; 标识符的识别; 常数的识别; 算符和界符的识别; 可参看书P39的简要说明。
关键字的识别 • 在FORTRAN语言中,关键字和用户自定义的标示符或标号之间没有特殊的界符作间隔,所以识别比较麻烦,看如下例子: • 1 DO99K=1,10 • 2 IF(5.EQ.M) I=10 • 3 DO99K=1.10 • 4 IF(5)=55 • 语句1、3的区别在于等号之后的第一个界符:一个为逗点,另一个为句末符。所以一直搜索到这里 才能区分开1句是DO语句,3语句是赋值句。 • 语句2、4主要区别在于右括号之后的第一个字符:一个为字母,另一个为等号。所以也只能搜索到该字符才能得到语句2是IF语句,语句4是赋值句。
3.2.3 状态转换图 (1)状态转换图 • 状态转换图是一张有限方向图.它只包含有穷多个状态,即有穷多个结点,用○表示;状态结点都代表文法的非终结符号,而且至少要包含一个终止状态,用◎表示. 状态之间用箭弧连接,箭弧上的标记指明在射出弧的结点状态下可能出现的输入字符或字符类. • 状态转换图实质上是语法图的一种变形。
(2)利用状态图识别(或接受)字符串的过程 • 从初始状态出发,按照与符号串余留部分中最左字符相匹配的原则,游历状态图,直至符号串的末端为止。如果这时恰好到达终止状态,则符号串为该文法的句子;否则不是。 • 大多数程序设计语言的单词符号都可以用状态转换图予以识别。可以用一张状态转换图或若干张状态转换图来描述一个语言的所有单词。
(3)状态图的画法 由右线性正规文法(产生式形如 U->aW|a)构造状态转换图。 • 除了原有代表非终结符号的状态,另增加一个终止状态Z, 对于每一条产生式U->a,从状态U向Z画一条弧,标记为a,即
(3)状态转换图的画法 • 对于每一条产生式U->aW,从状态U向状态W画一条弧,标记为a,即 以识别符号为开始状态。
(3)状态图的画法 由左线性正规文法(产生式形如 U->Wa|a)构造状态转换图。 • 除了原有代表非终结符号的状态,另增加一个开始状态S,对于每一条产生式U->a,从状态S向U画一条弧,标记为a,即
(3)状态转换图的画法 • 对于每一条产生式U->Wa,从状态W向状态U画一条弧,标记为a,即 以识别符号为终止状态。
例1: • (a)表示在状态1下,若输入字符为X,则读进X,并转换到状态2.若输入字符为Y,则读进Y,并转换到状态3. • (b)表示在状态0下,若输入字符是一个字母,则读进它,并转入状态1.在状态1下,若下一个输入字符为字母或数字,则读进它,并重现进入状态1.一直重复这个过程,直到状态1发现输入不再是字母或数字就进入状态2. • (c)为识别整数的转换图. • 终态结上打个星号*意味着多读进了一个不属于标识符部分的字符,应把它退还给输入串。
例2:C语言无符号整数的描述 八进制数: ( OCT,值 ) • oct→ o ( 0|...|7 ) ( 0|...|7 )* 十进制数: ( DEC,值 ) • dec→ 0 | ( 1|...|9 ) ( 0|...|9 )*
整数的状态图(1/2) 0-7 八进制整数 o 1 2 3 4 0-7 0-9 十进制整数 1 5 6 1-9 0
识别十六进制整数的状态图。 • 将上述三个状态转换图合并为一个,得到C语言无符号整数识别的状态图,方法步骤: • (1)从开始状态出发;(2)选择输入符号,构成目标状态集;(3)从新状态集出发,重复(1)、(2)
例3:识别某个简单语言的所有单词 用一些带$的特殊符号来表示种别编码和内部值
为了对例3进行更好的说明,做了如下的限制:为了对例3进行更好的说明,做了如下的限制: 1)所有关键字(如IF,WHILE等)都是“保留字”; 2)对关键字不需专设对应的转换图,而只需要将他们预先安排在一张表格中; 3)如果关键字、标识符和常数之间没有确定的运算符或界符作间隔,则必须至少用一个空白符作间隔。 有了上述假定后,多数单词符号的识别就不必使用超前搜索技术。下图是一张识别表的单词符号的状态转换图。在图中,状态0为初态;凡带双圈者均为终态;状态13是识别不出单词符号的出错情形。
3.2.4状态转换图的实现 • 让每个状态结点对应一段小程序,即可用程序实现状态图。在此引进了一组全局变量和过程,将他们作为实现转换图的基本成分。它们是: 1)ch 字符变量,存放最新读进的源程序字符。 2)strToken 字符数组,存放构成单词符号的字符串。 3)GetChar 子程序过程,将下一输入字符读到ch中,搜索指示器前移一字符位置。 4)GetBC 子程序过程,检查ch中的字符是否为空白。若是,则调用GetChar直至ch中进入一个非空白字符。 5)Concat 子程序过程,将ch中的字符连接到strToken之后。例如,假定strToken原来的值为“AB”,而ch中存放‘C’,经调用concat后,strToken的值就变为“ABC”。 6)IsLetter和IsDigit布尔函数过程,分别判断ch中的字符是否是字母和数字 7)Reserve 整型函数过程,对strToken中的字符串查找保留字表,若它是一个保留字则返回它的编码,否则返回0值。 8)Retract子程序过程,将搜索指示器回调一个字符位置,将ch置为空白字符 9)InsertId 整型函数过程,将strToken中的标识符插入符号表,返回符号表指针。 10)InsertConst 整型函数过程,将strToken中的常数插入常数表,返回常数表指针。
字母 j 数字 i k / l 对于不含回路的分叉结点来说,可让它对应一个switch语句或一组if…then…else语句。对应程序如下: Getchar(); If (isLetter()){状态j的对应程序段;} Else if(isDigit()){状态k的对应程序段;} Else if (ch=‘/’){状态l对应程序段;} Else {错误处理;} 当程序执行到达“错误处理”时,意味着现行状态I和当前所面临的输入串不匹配。如果后面还有状态图,出现在这个地方的代码应为:将搜索指示器回退一个位置,并令下一个状态图开始工作。如果后面没有其它状态图,则出现在上述位置的代码应进行真正的出错处理,报告源程序含有非法符号,并进行善后处理。
字母或数字 其它 i j 对于含回路的状态结点来说,可让它对应一个由while语句和if语句构成的程序段,右图所对应的程序段可为: GetChar(); While(isLetter() or isDigit()) GetChar(); …状态j的对应程序段… 终态结点一般对应一个形如return(code,value)的语句。其中,code为单词种别编码;value或是单词符号的属性值,或无定义。这个return意味着从分析器返回到调用者,一般指返回到语法分析器。凡带*的终态结点意味着多读了一个不属于现行单词符号的字符,这个字符应予退回,也就是说,必须把搜索指示器回调一字符位置。这项工作有Retract过程来完成。
转换图3.3的词法分析器可构造如下: 例3的词法分析器: int code,value; strToken :=“”; /*置strToken 为空串*/ GetChar(); GetBC(); /*读入一字符*/ if (IsLetter()) begin while(IsLetter() or IsDigit()) begin Concat(); GetChar(); /*将字符进行连接*/ end
Retract(); /*回调一位*/ code:=Reserve(); /*是保留字还是标识符*/ if (code=0) /*如是标识符*/ begin value:=InsertId(strToken); return($ID,value); /*返回编码和属性值*/ end else return(code,-); end
else if(IsDigit()) begin while(IsDigit()) begin Concat(); GetChar(); end Retract(); value:=InsertConst(strToken); return($INT,value); end else if(ch== ‘=’ ) return ($ASSIGN, -); else if(ch== ‘+’ ) return ($PLUS, -);
else if(ch= ‘*’ ) begin GetChar(); if (ch = ‘*’) return ($POWER,-); Retract();return($SATR,-); end else if(ch= ‘;’ ) return ($SEMICOLON, -); else if(ch= ‘(’ ) return ($LPAR, -); else if(ch= ‘)’ ) return ($RPAR, -); else if(ch= ‘{’ ) return ($LBRACE, -); else if(ch= ‘}’ ) return ($RBRACE, -); else ProcError();
例4 给定文法G[Z]: Z->U0|V1 U->Z1|1 V->Z0|0 试构造其对应的状态图,并利用所得到的状态图判别符号串100101 和 100111 是否该文法的句子。
解: 根据由左线性正规文法构造状态转换图的方法,增加开始状态S得到该文法相应的状态转换图。 100101经历了状态S,U,Z,V,Z,V,Z,最后到达终止状态Z.是此文法的句子. 而对符号串100111在识别出10011后,无法继续识别,所以不是文法的句子。 1 S U 0 1 0 1 V Z 0
3.3 正规表达式与有限自动机 对于字母表Σ而言,正规式和它所代表的正归集的递归定义为: • ε和φ是正规式,它们所表示的正规集分别为{ε} 和φ; • 任何a∈Σ, a是Σ上的一个正规式,它所表示的正规集为{ a } ; • 假定U和V都是Σ上的正规式,它们所表示的正规集分别为L(U)和L(V),那么,(U|V),(U*V),(U)*也都是正规式,它们所表示的正规集分别为L(U)∪L(V),L(U)L(V)(连接积)和(L(U))* (闭包)。仅由有限次使用上述三步骤而得到的表达式才是Σ上的正规式。仅由这些正规式所表示的字集才是Σ上的正规集。
例 令Σ={a,b},Σ上所有正规式和相应正规集的例子有: 正规式 正规集 ba* Σ上所有以b为首后跟任意多个a的字 a(a|b)* Σ上所有以a为首的字 令Σ={A,B,0,1}, 则: 正规式 正规集 (A|B)(A|B|0|1)* Σ上标识符的全体 (0|1)(0|1)* Σ上“数”的全体 正归集是正规语言的另一种表示方法。
等价 若两个正规式所表示的正规集相同,则认为二者等价。两个等价的正规式U 和 V 记为U=V.例如: b(ab)*=(ba)*b (a|b)*=(a*b*)*
注意 (1)a*b* (2) (a|b)* (3)(ab)* (4)a*|b* 相互不等价. a a b 1 2 1 a a*b* b 1 ε ε (a|b)* 1 x y a 2 ε ε b 2 b a*|b* (ab)*
另:U,V 和W均为正规式,下列关系普遍成立: (1) U|V=V|U (交换律) (2)U|(V|W)=(U|V)|W (结合律) (3)U(VW)=(UV)W (结合律) (4) U(V|W)=UV|UW (分配律) (V|W)U =VU|WU (分配律) (5) εU=Uε=U
正规文法与正规式 • 一个正规语言可以由正规文法定义,也可以由正规式定义。 • 对任何正规文法,存在定义同一语言的正规式; • 对任何正规式,存在生成同一语言的正规文法。
正规式转换成正规文法 • 将Σ上的一个正规式转换成文法G=(VT,VN,P,S).另其中的的VT= Σ, 确定产生式和VN的元素用如下办法: • 对任何正规式 r, 选择一个非终结符号S生成产生式 S → r, 并将S 定为G 的识别符号。 • 若x和y都是正规式 , 对形如A → xy 的产生式, 重写成A →xB, B →y两个产生式, 其中B是新选择的非终结符号,即 B∈VN
正规式转换成正规文法 • 对已转换的文法中的形如A→x*y的产生式,重写为: A →xB A →y B →xB B →y 其中B为一新非终结符. • 对形如A →x|y 的产生式,重写为: A →x , A →y
例: • 将R=a(a|d)*转化成相应的正规文法,令S是文法的开始符号,首先形成S →a(a|d)*,然后形成S →aA和A→(a|d)*,再重写第二条产生式形成: S →aA, A →(a|d)B, A->ε, B →(a|d)B和 B ->ε 进而变换为: S →aA, A →aB, A →dB, B →aB, B →dB, A->ε和 B ->ε
正规文法转换成正规式 • 即上述过程的逆过程, 转换规则为:
例: • 文法G[S] S →aA,S →a,A →aA,A →dA,A →a,A →d 先有 S= aA|a A=(aA|dA)|(a|d) 再将A的正规式变换为A=(a|d)A|(a|d), 据表中规则2变换为A=(a|d)*|(a|d), 再将A右端代入S的正规式得: S=a(a|d)*|(a(a|d)|a 再利用正规式的代数变换可以此得到 S=a(a|d)* |(a|d)|ε S=a(a|d)* 即 a(a|d)* 为所求。