编译原理(二)词法分析

词法分析

说明:以老师PPT为标准,借鉴部分教材内容,AlvinZH学习笔记。

语法分析基础

1. 词法分析程序的功能

  • 词法分析:根据词法规则识别及组合单词,进行词法检查;
  • 对数字常数完成数字字符串到(二进制)数值的转换;
  • 删去空格、换行、制表等字符和注释。

2. 实现方案

  • 词法分析单独做一遍。结构清晰,个遍功能单一,但是效率低。
  • 词法分析作为单独的子程序,由语法分析程序调用。结构较复杂,效率高。

3. 单词种类及输出形式
单词种类:保留字、标识符、常数、分界符等。

输出形式:二元式,<单词类别,单词值>。按单词种类分类,也可以将保留字和界符采用一符一类。

正则文法和状态图

状态图:用于识别(接受)一定的字符串。包含一个初始状态(初态),至少一个终止状态(终态)。画法比较简单:

006jtihFly1fnj0e8n0ojj30d9094wfo.jpg

注意:

  • 利用状态图分析识别字符串——自底向上
  • 状态图只能用于左线性文法(形如A→Bb)。这是明显和后面的DFA区别之处。

正则表达式

1.正则表达式运算符:|选择、·连接、*重复、()括号。优先级:括号优先,先*后·最后|。

2.正则表达式与3型文法等价。相互转换后续讲解。

确定的有穷自动机(DFA)

1. DFA五元式定义:\(M = (S, ∑, δ, S0, Z)\)。其中 \(S\) 为有穷状态集,\(∑\) 为输入字母表,\(δ\) 为状态转换函数(S×∑ → S的映射),\(S_0\) 为初始状态,\(Z\) 为终止状态集。

2. 何为确定?表现在 \(δ\) 中的状态转换函数是单值函数。

3. DFA不可接受 \(ε\),DFA M所接受的语言为:\(L(M) = \{ α|δ(S0, α)= Sn, Sn∈Z \}\)

非确定的有穷自动机(NFA)

1.\(δ\) 是一个多值函数,且输入允许为 \(ε\),则有穷自动机是不确定的。即对于某个输入字符存在多个后继状态。

2. NFA五元式定义:\(M' = (S, Σ∪{ε}, δ, S0, Z)\)\(δ\) 为状态转换函数(S×∑∪{ε} → 2^S的映射,2^S:S的幂集,即S的子集构成的集合)。

3. NFA M'所接受的语言为:\(L(M') = \{ α|δ( S0,α) = S’ S’∩Z≠Φ \}\)

NFA的确定化——子集法

1. 已经证明:非确定的有穷自动机与确定的有穷自动机从功能上来说是等价的。这意味着,对于NFA M',可以构造出DFA M,使得L(M)=L(M')。

2. 集合 \(I\)\(ε\) 闭包。\(I\) 是NFA状态集S的一个子集,则ε-closure(I)为:

  • 若s∈I,则s ∈ ε-closure(I);
  • 若s∈I,则从 s 出发经过任意条ε弧能够到达的任何状态都属于ε-closure(I)。

简单理解,ε-closure(I)就是由I中所有状态接收字符ε得到的状态集。

3. 集合 \(I\) 对应 \(Ia\)\(I\) 是NFA状态集 \(S\) 的一个子集,a∈∑,则 \(I_a = ε-closure(J)\),其中 \(J = ∪ δ(S, a),S∈I\)

简单理解,分为两步,第一步找到 \(I\) 中所有状态接收字符 \(a\) 得到的状态集 \(J\),再求 \(ε-closure(J)\)

4. 确定化方法,过程如下。建议结合教材例子,实际求解过程画表格更清晰。

①求初始状态:S0 = ε-closure(S0');
②for 每个状态
    for 每个x∈∑
      计算新状态Ix;
注:过程②出现的新状态也在循环中,直到不出现新状态为止;

包含NFA M'原初态的状态集为DFA M的初态;包含NFA M'原终态的状态集为DFA M的终态。

5.简单例子

006jtihFly1fnjslb23umj30dp0hyq4d.jpg

DFA的最小化——分割法

1. 状态s和状态t等价条件:

  • 一致性条件:状态s和t必须同时为接受状态或不接受状态。
  • 蔓延性条件:对于所有输入符号,状态s和t必须转换到等价的状态里。

2. 最小化方法:分割法。思路:把DFA所有状态分割成不相关的子集,使得任意两个子集状态是可区分的,而同一子集中状态是等价的。具体操作如下:

①区分终态和非终态,为区号1,2;(一致性条件)
②每个原状态,对于每个输入,对应后继状态的区号,全部相同的视为等价(暂时);(蔓延性条件)
③由于过程②划分出新的区间,对应的区号有所改变,重复过程②直至没有新区间出现。

正则文法、正则表达式、NFA(DFA)相互转换

006jtihFly1fnj0drdd6pj30ev0asjrv.jpg

1. 有穷自动机M => 正则文法G

①对转换函数f(A, t) = B,可写成一个产生式:A→tB,其中t∈Vt,A、B∈Vn
②对可接受状态Z,增加一个产生式:Z→ε
③有穷自动机的初态对应于文法的开始符号,有穷自动机的字母表 ∑ 为文法的终结符号集Vt。

【例】

006jtihFly1fnj0sed2o7j30iz0cejsm.jpg

注意: 不知你注意到了没有,有穷自动机M转换出来的文法规则都是右线性的,这就体现了有穷自动机自顶向下的特点,在语法分析LR分析法中会用到这一特性。

2. 正则文法G=>有穷自动机M
【算法】

① M的字母表 ∑ 与G的终结符号 Vt 相同
②为G中的每个非终结符Vn生成M的一个新状态,G的开始符号S作为M的开始状态S
③增加一个新状态Z,作为NFA的终态
④对G中产生式形如A→tB,其中t为终结符或ε,A和B为非终结符,构造M的一个转换函数f(A, t) = B
⑤对G中的形如A→t的产生式,构造M的一个转换函数f(A, t) = Z

【例】

006jtihFly1fnj0sifdqcj30i90a7jsa.jpg

3. 正则表达式 => 有穷自动机M
【算法】

006jtihFly1fnj0dkfop4j30ic0mgq60.jpg

【例】

006jtihFly1fnj0dnq10xj30ma10z0w7.jpg

4. 有穷自动机M => 正则表达式
【算法】

006jtihFly1fnj0dduclfj30fz0azgnu.jpg

【例】

006jtihFly1fnj0dho6gdj30ii0kgh36.jpg

5. 正则文法转正则表达式
【算法】

  • 代入规则:对于A→xB,B→y,转换为A→xy;
  • 消除递归规则:对于A→xA|y,转换为A→x*y;
  • BNF规则:对于A→x,A→y,转换为A→x|y。

6. 正则表达式转正则文法
【算法】

  • 对任何正则式r,选择一个非终结符S作为识别符号,并产生产生式 S→r;
  • 若x、y是正则式,对形为A→xy的产生式,重写为A→xB和B→y,其中B为新的非终结符,B∈Vn。同样,对于 A→x*y,重写为A→xA,A→y;对于A→x|y,重写为 A→x,A→y。

【例】将S = a ( a | d )* 转换成相应的正则文法

解: 1) S→a(a|d)*

2) S→aA
A→(a|d)*

3) S→aA
A→(a|d)A
A→ε

4) S→aA
A→aA|dA
A→ε

LEX扩展知识点

1. 一个LEX源程序主要由三个部分组成:规则定义式、识别规则、用户子程序。各部分之间用%%隔开。实质是是一个有穷自动机

  • 规则定义式:形如D1→R1,其中D1为正则表达式名字(简名),R1为正则表达式。如digit →0|1|……|9。
  • 识别规则:形如P1 {A1},其中P1为词形,为在Σ∪{D1,D2,¨¨Dn}上的正则表达式;A1为语句序列,指出在识别出词形为Pi的单词以后,词法分析器所应作的动作。其基本动作是返回单词的类别编码和单词值。如digit(digit)* {RETURN(9,DTB }。

.2. 二义性处理原则:最长匹配原则;最优匹配原则。

引用说明

- 邵老师课堂PDF
- 《编译原理级编译程序构造》

转载于:https://www.cnblogs.com/AlvinZH/p/8300166.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大连理工大学软件学院编译技术课程——词法分析上机实验 实验目的:对循环语句和条件判断语句编写词法分析编译程序,只能通过一遍扫描完成。(用c++实现) 实验要求: (1) 关键字: for if then else while do 所有关键字都是小写。 (2)运算符和分隔符: : = + - * / <= >= ; ( ) # (3)其他标识符(ID)和整型常数(NUM),通过以下正规式定义: ID=letter(letter | digit)* NUM=digit digit* (4)空格由空白、制表符和换行符组成。空格一般用来分隔ID、NUM、运算符、分隔符和关键字,词法分析阶段通常被忽略。 各种词法单元对应的词法记号如下: 词法单元 词法记号 词法单元 词法记号 for 1 : 17 if 2 := 18 then 3 < 20 else 4 21 while 5 23 letter(letter+digit)* 10 >= 24 digit digit* 11 = 25 + 13 ; 26 - 14 ( 27 * 15 ) 28 / 16 # 0 词法分析程序的功能 输入:源程序 输出:元组(词法记号,属性值/其在符号表中的位置)构成的序列。 例如:对源程序 x:=5; if (x>0) then x:=2*x+1/3; else x:=2/x; # 经词法分析后输出如下序列: (10,’x’)(18, :=) (11,5) (26, ;) (2, if ) (27,( )…… 1.几点说明: (1)关键字表的初值。 关键字作为特殊标识符处理,把它们预先安排在一张表格中(称为关键字表),当扫描程序识别出标识符,查关键字表。如能查到匹配的单词,则该单词的关键字,否则为一般标识符。关键表为一个字符串数组,其描述如下: char *keyword[6]={”for”, ”if”, ”then” ,”else”,”while”, ”do” }; (2) 程序中需要用到的主要变量为 token , id和num. 1)id用来存放构成词法单元的字符串; 2)num用来存放整数(可以扩展到浮点数和科学计数法表示); 3)token用来存放词法单元的词法记号。 可以参考下面的代码: do{ lexical(); //将词法单元对应的记号保存到token中,属性值保存到num或者id中 switch(token) { case 11: printf ("(token, %d\n) ", num); break; case -1: printf("error!\n");break; default: printf("(%d,%s)\n", token, id); } }while (token!=0);
设计思想 (1)程序主体结构部分: 说明部分 %% 规则部分 %% 辅助程序部分 (2)主体结构的说明 在这里说明部分告诉我们使用的LETTER,DIGIT, IDENT(标识符,通常定义为字母开头的字母数字串)和STR(字符串常量,通常定义为双引号括起来的一串字符)是什么意思.这部分也可以包含一些初始化代码.例如用#include来使用标准的头文件和前向说明(forward ,references).这些代码应该再标记"%{"和"%}"之间;规则部分>可以包括任何你想用来分析的代码;我们这里包括了忽略所有注释中字符的功能,传送ID名称和字符串常量内容到主调函数和main函数的功能. (3)实现原理 程序中先判断这个句语句中每个单元为关键字、常数、运算符、界符,对与不同的单词符号给出不同编码形式的编码,用以区分之。 PL/0语言的EBNF表示 <常量定义>::=<标识符>=<无符号整数>; <标识符>::=<字母>={<字母>|<数字>}; <加法运算符>::=+|- <乘法运算符>::=*|/ <关系运算符>::==|#|<|<=|>|>= <字母>::=a|b|…|X|Y|Z <数字>::=0|1|2|…|8|9 三:设计过程 1. 关键字:void,main,if,then,break,int,Char,float,include,for,while,printfscanf 并为小写。 2."+”;”-”;”*”;”/”;”:=“;”:”;”<“;”<=“;”>“;”>=“;”<>“;”=“;”(“;”)”;”;”;”#”为运算符。 3. 其他标记 如字符串,表示以字母开头的标识符。 4. 空格符跳过。 5. 各符号对应种别码 关键字分别对应1-13 运算符分别对应401-418,501-513。 字符串对应100 常量对应200 结束符# 四:举例说明 目标:实现对常量的判别 代码: digit [0-9] letter [A-Za-z] other_char [!-@\[-~] id ({letter}|[_])({letter}|{digit}|[_])* string {({letter}|{digit}|{other_char})+} int_num {digit}+ %% [ |\t|\n]+ "auto"|"double"|"int"|"struct"|"break"|"else"|"long"|"switch"|"case"|"enum"|"register"|"typedef"|"char"|"extern"|"return"|"union"|"const"|"float"|"short"|"unsigned"|"continue"|"for"|"signed"|"void"|"default"|"goto"|"sizeof"|"do"|"if"|"static"|"while"|"main" {Upper(yytext,yyleng);printf("%s,NULL\n",yytext);} \"([!-~])*\" {printf("CONST_string,%s\n",yytext);} -?{int_num}[.]{int_num}?([E][+|-]?{int_num})? {printf("CONST_real,%s\n",yytext);} "0x"?{int_num} {printf("CONST_int,%s\n",yytext);} ","|";"|"("|")"|"{"|"}"|"["|"]"|"->"|"."|"!"|"~"|"++"|"--"|"*"|"&"|"sizeof"|"/"|"%"|"+"|"-"|">"|"<"|">="|"<="|"=="|"!="|"&"|"^"|"|"|"&"|"||"|"+="|"-="|"*="|"/="|"%="|">>="|"<<="|"&="|"^="|"|="|"=" {printf("%s,NULL\n",yytext);} {id} {printf("ID,%s\n",yytext);} {digit}({letter})+ {printf("error1:%s\n",yytext);} %% #include <ctype.h> Upper(char *s,int l) { int i; for(i=0;i<l;i++) { s[i]=toupper(s[i]); } } yywrap() { return 1; } 五:DFA 六:数据测试 七:心得体会 其实匹配并不困难,主要是C++知识要求相对较高,只要把握住指针就好了。 附源程序: #include<iostream.h> #include<stdio.h> #include<stdlib.h> #include<string.h> int i,j,k,flag,number,status; /*status which is use to judge the string is keywords or not!*/ char ch; char words[10] = {" "}; char program[500]; int Scan(char program[]) { char *keywords[13] = {"void","main","if","then","break","int", "char","float","include","for","while","printf", "scanf"}; number = 0; status = 0; j = 0; ch = program[i++]; /* To handle the lettle space ands tab*/ /*handle letters*/ if ((ch >= 'a') && (ch <= 'z' )) { while ((ch >= 'a') && (ch <= 'z' )) { words[j++]=ch; ch=program[i++]; } i--; words[j++] = '\0'; for (k = 0; k < 13; k++) if (strcmp (words,keywords[k]) == 0) switch(k) { case 0:{ flag = 1; status = 1; break; } case 1:{ flag = 2; status = 1; break; } case 2:{ flag = 3; status = 1; break; } case 3:{ flag = 4; status = 1; break; } case 4:{ flag = 5; status = 1; break; } case 5:{ flag = 6; status = 1; break; } case 6:{ flag = 7; status = 1; break; } case 7:{ flag = 8; status = 1; break; } case 8:{ flag = 9; status = 1; break; } case 9:{ flag = 10; status = 1; break; } case 10:{ flag = 11; status = 1; break; } case 11:{ flag = 12; status = 1; break; } case 12:{ flag = 13; status = 1; break; } } if (status == 0) { flag = 100; } } /*handle digits*/ else if ((ch >= '0') && (ch <= '9')) { number = 0; while ((ch >= '0' ) && (ch <= '9' )) { number = number*10+(ch-'0'); ch = program[i++]; } flag = 200; i--; } /*opereation and edge handle*/ else switch (ch) { case '=':{ if (ch == '=') words[j++] = ch; words[j] = '\0'; ch = program[i++]; if (ch == '=') { words[j++] = ch; words[j] = '\0'; flag = 401; } else { i--; flag = 402; } break; } case'>':{ if (ch == '>') words[j++] = ch; words[j] = '\0'; ch = program[i++]; if (ch == '=') { words[j++] = ch; words[j] = '\0'; flag = 403; } else { i--; flag = 404; } break; } case'<':{ if (ch == '<') words[j++] = ch; words[j] = '\0'; ch = program[i++]; if (ch == '=') { words[j++] = ch; words[j] = '\0'; flag = 405; } else { i--; flag = 406; } break; } case'!':{ if (ch == '!') words[j++] = ch; words[j] = '\0'; ch = program[i++]; if (ch == '=') { words[j++] = ch; words[j] = '\0'; flag = 407; } else { i--; flag = 408; } break; } case'+':{ if (ch == '+') words[j++] = ch; words[j] = '\0'; ch = program[i++]; if (ch == '=') { words[j++] = ch; words[j] = '\0'; flag = 409; } else if (ch == '+') { words[j++] = ch; words[j] = '\0'; flag = 410; } else { i--; flag = 411; } break; } case'-':{ if (ch == '-') words[j++] = ch; words[j] = '\0'; ch = program[i++]; if (ch == '=') { words[j++] = ch; words[j] = '\0'; flag = 412; } else if( ch == '-') { words[j++] = ch; words[j] = '\0'; flag = 413; } else { i--; flag = 414; } break; } case'*':{ if (ch == '*') words[j++] = ch; words[j] = '\0'; ch = program[i++]; if (ch == '=') { words[j++] = ch; words[j] = '\0'; flag = 415; } else { i--; flag = 416; } break; } case'/':{ if (ch == '/') words[j++] = ch; words[j] = '\0'; ch = program[i++]; if (ch == '=') { words[j++] = ch; words[j] = '\0'; flag = 417; } else { i--; flag = 418; } break; } case';':{ words[j] = ch; words[j+1] = '\0'; flag = 501; break; } case'(':{ words[j] = ch; words[j+1] = '\0'; flag = 502; break; } case')':{ words[j] = ch; words[j+1] = '\0'; flag = 503; break; } case'[':{ words[j] = ch; words[j+1] = '\0'; flag = 504; break; } case']':{ words[j] = ch; words[j+1] = '\0'; flag = 505; break; } case'{':{ words[j] = ch; words[j+1] = '\0'; flag = 506; break; } case'}':{ words[j] = ch; words[j+1] = '\0'; flag = 507; break; } case':':{ words[j] = ch; words[j+1] = '\0'; flag = 508; break; } case'"':{ words[j] = ch; words[j+1] = '\0'; flag = 509; break; } case'%':{ if (ch == '%') words[j++] = ch; words[j] = '\0'; ch = program[i++]; if (ch == '=') { words[j++] = ch; words[j] = '\0'; flag = 510; } else { i--; flag = 511; } break; } case',':{ words[j] = ch; words[j+1] = '\0'; flag = 512; break; } case'#':{ words[j] = ch; words[j+1] = '\0'; flag = 513; break; } case'@':{ words[j] = '#'; flag = 0; break; } default:{ flag = -1; break; } } return flag; } main() { i=0; printf("please input a program end with @"); do { ch = getchar(); program[i++] = ch; }while(ch != '@'); i = 0; do{ flag = Scan(program); if (flag == 200) { printf("(%2d,%4d)",flag,number); } else if (flag == -1) { printf("(%d,error)",flag); } else { printf("(%2d,%4s)",flag,words); } }while (flag != 0); system("pause"); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值