词法分析(1)---词法分析的有关概念以及转换图
词法分析(1)---词法分析的有关概念以及转换图
2009-09-26 23:52:03 阅读(9) 发表评论
词法分析是编译的第一个阶段,前面简介中也谈到过词法分析器的任务就是:
字符流------>词法记号流
这里词法分析和语法分析会交错进行,也就是说,词法分析器不会读取所有的词法记号再使用语法分析器来处理,通常情况下,每取一个词法记号,就送入语法分析器进行分析,图解:
词法分析器是编译器中与源程序直接接触的部分,因此词法分析器可以做诸如
1). 去掉注释,自动生成文档(c#中的///注释)
2). 提供错误位置(可以通过记录行号来提供),当字符流变成词法记号流以后,就没有了行的概念
3). 完成预处理,比如宏定义
1. 词法记号,词法单元(lexeme),模式
模式是一种规则
每个词法单元都有一个特定记号
比如 int a=3,这里 int,a,=,3都是词法单元,每个词法单元都属于某个词法记号,比如3就是"num"这个词法记号的一个词法单元,而模式规定了什么样的字符串的词法记号是什么样的(模式是一种规则)
某一特定模式规定了某个词法记号下的一类词法单元,比如:
模式:用字母开头的包含字母和数字的串
上面模式的词法记号:id(所有符合上面模式的字符串的记号都是id)
词法单元:a123 或者 aabc 等
词法记号举例(简称为记号):
1) 每个的关键字都有属于自己的一个记号,比如关键字for,它可以使用记号for;关键字int,可以使用记号int
2) 所有的关系运算符只有一个记号,比如 >=,<=都用记号relation
3) 所有的标识符只有一个记号,比如a123,aab使用记号id
4) 所有的常数只有一个记号,比如123,22,32.3,23E10使用记号num
5) 所有的字符串只有一个记号,比如"123","ab1"使用记号literal
在实际的编译器设计中,词法记号,一般用一个整形数字表示
词法记号的属性:
我们喜欢用<词法记号, 属性>这个二元组来描述一个词法单元,比如,对于源代码:position := initial + rate * 60
对于词法单元 +,我们可以使用 <add_op, '+'> 来表示。
有些情况,更加复杂一点,比如对于 position,我们表示是这样的,<id, 指向符号表中的position元素的指针>,详细来说应该是这样的,假定属性是一个字符串,那么id将指向这样一个字符串"position\0",我们把存放这个字符串的地方叫做符号表。有些时候,属性是不必要的,比如 := ,表示赋值,我们可以使用 <assign_op,257> 这样的表示这个词法单元,不过这个显得有些多于,因为assign_op和词法单元是一对一的,也就是assign_op只对应了:=,所以额外信息(属性)就显得多余的了
词法错误:
词法分析器是很难(有些错误还是可以检测)检测错误的,因为词法分析器的目的是产生词法记号流,它没有能力去分析程序结构,因此无法检测到和程序结构有关的错误,比如:
fi(a == b)
词法分析器不会找到这个错误,它认为 fi 是一个标识符,而不是一个关键字,只有在后面的阶段中,这个错误才会被发现,这是一个与程序结构有关的错误
词法分析器,只能检测到词法单元上的问题,比如 12.ab ,作为一个词法单元,却不没有对应的模式,那么就是产生一个错误。
2. 正规式:
前面说过模式是一种规则,为了使用,我们需要一种规范的方式来表达模式,这就是正规式
1) 串和语言
字符类(又叫字母表):关于字符的有限集合
串:字符类上字符的有穷序列,串这个概念,具体来说是,某个字符类上的串
串的长度:串中字符的个数,比如串 s = abc ,那么串的长度为3,用|s|表示串的长度
空串:用 ε 表示
语言:某字符类上的串的集合,属于语言的串,成为语言的句子或字
比如:{abc, a}这就是一个语言,abc和a就是句子。另外空集也是属于语言
连接:x是串,y是串,x和y连接,结果就是 xy 这个串。假如 x 是串,x^3为 xxx。对于 x^n (n>=0),x^0 = ε
语言的运算(假定L和M是语言):
1. L U M = {s|s属于L或者M},例如:
L={1,2} M={3,4} 那么 L U M = {1,2,3,4}
2. LM = {st|s属于L且t属于M},例如:
L={a,b} M={1,2} 那么 LM = {a1,a2,b1,b2} ML={1a,1b,2a,2b}
3. L^n = LLL...LLL (n个L),例如:
L={a,b} 那么 L^3 = {aaa,aab,aba,abb,baa,bab,bbb,bba}
注意 n 可以为0,L^0 = {ε}
4. L* = L^0 U L^1 U L^2 U L^3 U ...
L*表示,语言L中,所有的句子(串)以任意数目任意顺序组成的句子的集合,包括 ε,例如:
{a,b}* = {ε,a,b,ab,ba,aab,aba,baa,bba,bab,abb,aaa,bbb...}
L*叫做L的闭包
5. L+ = L^1 U L^2 U L^3 U...
L+表示,语言L中,所有的句子(串)以任意数目任意顺序组成的句子的集合,但是不包括 ε
L+中的句子和 L*中的句子相比少一个 ε
那么,我们通过上面的知识就可以表示一个标识符了,我们知道一般语言规定标识符是由字母开头,后接若干个字母或数字,我们可以这样来表示: L={a-z A-Z} N={0-9},那么标识符就是 L(L U N)*
2) 正规式
正规式又叫正规表达式,正规式是模式得一种规范的表达形式,正规式描述了一个集合,这个集合是由串组成的,其实这个集合就是我们前面说过的语言,不过这里大家喜欢使用正规集这个术语。正规式 r 表示正规集L(r)
正规式的运算:
1. 闭包运算,运算优先级最高,(r)* 表示 (L(r))*
2. 连接运算,运算优先集合低于闭包,(r)(s) 表示 (L(r))(L(s))
3. 或运算,运算优先集合最低,(r) | (s) 表示 (L(r)) U (L(s))
例如:
a | b 表示集合(语言,正规集) {a,b}
(a | b)(a | b) 表示集合(语言,正规集) {aa,ab,ba,bb}
a* 表示由一切a字符组成的集合(语言,正规集),包括 ε
(a | b) 表示由a,b组成的集合(语言,正规集),包括 ε
等价的正规式:(a | b) = (b | a)
正规式的代数性质:
1. r|s = s|r2. r|(s|t) = (r|s)|t3. (rs)t = r(st)4. r(s|t) = rs|rt5. εr = r6. r** = r*7. r* = (r|ε)*
注意,rs != sr 因为连接运算是有顺序的,记住并理解2个最基本的运算:a|b表示{a,b},ab表示{ab}
3. 正规定义
我们可以使用 名字 -> 正规式这种表示,来说明一个等价的代替,比如:
dight -> 0|1|2|3|4|5|6|7|8|9
这里,我们就可以使用名字 digit 来代替后面的正规表达式
我们可以对某个串集进行正规定义,比如我们对标识符集合进行正规定义:
letter -> A|B|...|Z|a|b|...|zdight -> 0|1|2|3|4|5|6|7|8|9id -> letter(letter|dight)*
请通过上面的例子理解正规定义。
在我们表达正规表达式的时候,可以使用一些符号使得表达简化
1) + ,表示一个或者多个实力,比如,a+ 表示 {a,aa,aaa,aaaa,...}。区别一下*,他们的关系是这里 r+ = r* | ε
2) 字符组,[abc]表示a|b|c,还可以这样表示[a-zA-Z]表示字母表中的字符
4. 状态转换图
状态转换图是对词法分析器进行分析过程的描述,我们看一个判断关系运算的状态转化图:
1) 图中圆圈表示状态
2) 箭头叫做边。X状态的边,一般指的是由X状态出发,指向其他状态的边
3) 边上的符号叫做标记
如何来使用这个图?假定输入字符串是 <= ,那么识别开始时,发现 < 和状态0与状态1间的边上的标记一样,那么就进入1状态,下一个输入字符为=,将进入2状态,识别结束,返回二元组<relop,LE>
上图中2,3,4,5,7,8状态,他们表示识别了一个关系运算符,这个状态叫做接受状态
状态4上面有一个*,表示说,输入指针需要回移。所谓的输入指针,就是指向输入字符串中现在被读入的字符的位置,4状态会多读取一个字符,所以需要回移,也就是要注意的是,识别完成之后,输入指针指向的是被识别对象的最后一个字符,而不是待识别对象的第一个字符,这样的规定在实现词法分析器时,是有一定的意义,举例说明:
输入字符串为: a>b
识别的时候,从>开始,读入下一个字符b时,进入4状态,这个时候,输入指针指向b,这时候需要回移
我们在需要回移的状态上加一个*
每个状态后面有一个return(relop,XX)这个是状态的行为,这里具体来说就是返回一个二元组的行为,词法分析器分析的结果就是得到二元组(词法记号和属性的二元组),这个二元组可以表示一个特定的字符串。其实上面的*,也是表示行为,也就是输入指针回移的行为,我们可以看见,只有在接受状态才会有行为出现
对一门典型的语言来说状态可能有几百个
5. 如何编写一个词法分析器
1) 根据需要写出正规定义
2) 根据正规定义画出转换图
3) 根据转换图写出词法分析器
这里详细讨论面向过程的语言来实现一个词法分析器(比如c语言),并且主要讨论的是第3步
1) 我们需要一个 nextchar() 函数,取得缓存中下一个等待分析的字符,这个函数完成年2个任务
1. 让输入指针向前移动一位
2. 返回输入指针指向的字符
2) 定义一个变量 token_beginning,在每个状态转换图开始的时候,记录输入指针的位置,定义forward变量作为输入指针
3) 状态转换图被实现成为代码之后,每个状态都有属于自己的一块代码,这些代码按顺序完成以下工作:
1. 读取一个字符,通过nextchar()函数
2. 读取的字符(标志),如果它和当前状态的边上的标记相同,那么状态将转换到边所指向的状态,具体实现只需要一个语句就是 state = xxx(xxx为目标状态);如果当前状态的所有边的标记和这个读取字符不一样,那么表示没有找到token(词法记号),这时候需要调用 fail() 函数
3. fail() 函数完成这样的功能:a.指针回移,完成 forward = token_beginning 的操作 b.找到适当的开始状态(也就是寻找另外一个转换图的开始状态)。假定所有的转换图都被尝试过,并且无法匹配,这时候会调用一个发现错误的小程序,来报告错误
4. 请不要随意添加行为到各个状态所持有的代码中,应该以转换图中表示的行为为准
4) 定义一个全局变量 lexical_value,用于保存一个指针,这个指针由 install_id() 和 install_num() 两个函数中的一个返回
5) 定义两个整形变量 start,state,分别表示一个转换图的开始状态和当前的状态
6) nexttoken(),这是词法分析器的主程序,可以说,我们通过调用nexttoken()就完成了词法分析,这个函数一定是这样的格式:
while(1){ switch(state){ case xx: ... case yy: ... default: ... }}
关于详细的设计这里就不说了,举例说明一个转换图如何转换成为程序:
这是一个识别浮点数的例子,看下面的代码:
#include <stdio.h>#include <ctype.h>#include <string.h>char *nexttoken();char nextchar();void next();void back();char* gettoken();char cbuf[]="12.3*********klj12.2e2jj778";int forward = -1; int main(){ while(1){ printf("%s\n",nexttoken()); if(forward >= strlen(cbuf)-1){ getchar(); return 0; } }}int state;int start;char* nexttoken(){ char c; state = 12; while(1){ switch(state){ case 12: c = nextchar(); start = forward; if(isdigit(c)){ state = 13; }else{ next(); } break; case 13: c = nextchar(); if(isdigit(c)) state = 13; else if(c == 'e'||c == 'E') state = 16; else if(c == '.') state = 14; else state = 19; break; case 14: c = nextchar(); if(isdigit(c)) state = 15; break; case 15: c = nextchar(); if(isdigit(c)) state = 15; else if(c == 'e'|| c == 'E') state = 16; else state = 19; break; case 16: c = nextchar(); if(isdigit(c)) state = 18; else if(c == '+' || c == '-') state = 17; break; case 17: c = nextchar(); if(isdigit(c)) state = 18; break; case 18: c = nextchar(); if(isdigit(c)) state = 18; else state = 19; break; case 19: back(); return gettoken(); } }}char nextchar(){ forward ++; return cbuf[forward];}void back(){ forward --;}void next(){ forward ++;}char token_buf[128];char* gettoken(){ int i,j=0; for(i = start; i <= forward; i ++){ token_buf[j++] = cbuf[i]; } token_buf[j] = '\0'; return token_buf;}
词法分析(1)---词法分析的有关概念以及转换图相关推荐
- 编译原理教程_3 词法分析
文章原稿 https://gitee.com/fakerlove/fundamentals-of-compiling 文章目录 3. 词法分析 3.1 设计--状态转换图 3.1.1 词法分析概述 3 ...
- 【笔记】编译原理——第三章 词法分析
目录 编译过程结构框架 3.1 对于词法分析器的要求 3.1.1 词法分析器的功能和输出形式 3.1.2 词法分析器作为一个独立子程序 3.2 词法分析器的设计 3.2.1 输入.预处理 3.2.2 ...
- 编译原理笔记(二)之词法分析
编译原理笔记(二)之词法分析 1. 词法分析中的若干问题 1.1 基本概念 1.2 记号的属性 1.3 词法分析器的作用与工作方式 1.4 输入缓冲区 2. 模式的形式化描述 2.1 字符串与语言 2 ...
- 03 | 词法分析程序与有穷自动机
03 | 词法分析程序与有穷自动机 词法分析程序概述 正规式与正规集 正规文法与正规式 正规式与有穷自动机 确定有穷自动机(DFA) 非确定有穷自动机(NFA) 二者对比 正规式构造有穷自动机 有穷自 ...
- 词法分析(NFA与DFA)
词法分析(1)---词法分析的有关概念以及转换图 词法分析是编译的第一个阶段,前面简介中也谈到过词法分析器的任务就是: 字符流------>词法记号流 这里词法分析和语法分析会交错进行,也就是说 ...
- 【编译原理】词法分析
第二章 词法分析 词法分析是编译的第一个阶段,它的主要任务是扫描输入字符流,产生用于语法分析的词法记号序列. 2.1 词法记号 词法记号(tokentokentoken): 是由记号名(又称种别码)和 ...
- 【编译原理】学习笔记1 词法分析
进行词法分析,打印分析结果. 编译器是一个程序:输入字符串,输出目标代码. 词法分析: 读入源码字节,将其组成有意义的TOKEN流. 语法分析: 根据TOKEN流构建树形的中间表示. 语义分析: 检查 ...
- 编译原理——第三章词法分析总结
词法分析 在词法分析这一章主要通过学习了词法分析.正规表达式和有限自动机来了解词法分析器的构造. 词法分析器是执行词法分析的程序.将源程序输入词法分析器后,词法分析器从左至右逐个字符的对源程序进行扫描 ...
- [转载] ANTLR——词法分析
来源:ANTLR中文网站:http://www.antlr.org.cn 词法分析是编译过程的第一步,是编译过程的基础.词法分析除了上一章讲过它为语法分析提拱记号流,滤掉编译过程不关心的内容以外,还有 ...
最新文章
- fedora java 开发环境_Linux(Fedora 14)下 java开发环境配置 ——jdk的安装与配置
- 后浪们 : 难道要先结婚后恋爱?
- 【FPGA】SRIO IP核系统总览以及端口介绍(二)(I/O Port 含义介绍)
- python3 报错 ‘builtin_function_or_method‘ object has no attribute 解决方法
- App Store遭到攻击后如何保护iPhone安全
- 猴子选大王 java,PAT-JAVA-5-28 猴子选大王 (20分)
- 【高并发解决方案】5、如何设计一个秒杀系统
- ZetCode 数据库教程
- 转 Androidpn里的Xmpp的理解(消息推送)
- 计算机英语基础课程论文,计算机专业英语结课论文.doc
- Cartesian k-means论文理解
- 完整详尽的解决MySql:Could not create connection to database server
- UML用例图怎么画 有手就会
- oracle中on和where的区别,Oracle里面的外连中where和on之后and有啥区别
- 辞职文案火了,程序员的辞职理由要命不要钱。
- 神州数码c语言笔试题,神州数码笔试题,神州数码笔试题.doc
- GoLang之取地址符、指针
- 编程语言php加密与解密的方法
- 《棒球英豪》:青春球场·棒球1号位
- ElementUI从PNG开始,自己添加ICON