词法分析(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)---词法分析的有关概念以及转换图相关推荐

  1. 编译原理教程_3 词法分析

    文章原稿 https://gitee.com/fakerlove/fundamentals-of-compiling 文章目录 3. 词法分析 3.1 设计--状态转换图 3.1.1 词法分析概述 3 ...

  2. 【笔记】编译原理——第三章 词法分析

    目录 编译过程结构框架 3.1 对于词法分析器的要求 3.1.1 词法分析器的功能和输出形式 3.1.2 词法分析器作为一个独立子程序 3.2 词法分析器的设计 3.2.1 输入.预处理 3.2.2 ...

  3. 编译原理笔记(二)之词法分析

    编译原理笔记(二)之词法分析 1. 词法分析中的若干问题 1.1 基本概念 1.2 记号的属性 1.3 词法分析器的作用与工作方式 1.4 输入缓冲区 2. 模式的形式化描述 2.1 字符串与语言 2 ...

  4. 03 | 词法分析程序与有穷自动机

    03 | 词法分析程序与有穷自动机 词法分析程序概述 正规式与正规集 正规文法与正规式 正规式与有穷自动机 确定有穷自动机(DFA) 非确定有穷自动机(NFA) 二者对比 正规式构造有穷自动机 有穷自 ...

  5. 词法分析(NFA与DFA)

    词法分析(1)---词法分析的有关概念以及转换图 词法分析是编译的第一个阶段,前面简介中也谈到过词法分析器的任务就是: 字符流------>词法记号流 这里词法分析和语法分析会交错进行,也就是说 ...

  6. 【编译原理】词法分析

    第二章 词法分析 词法分析是编译的第一个阶段,它的主要任务是扫描输入字符流,产生用于语法分析的词法记号序列. 2.1 词法记号 词法记号(tokentokentoken): 是由记号名(又称种别码)和 ...

  7. 【编译原理】学习笔记1 词法分析

    进行词法分析,打印分析结果. 编译器是一个程序:输入字符串,输出目标代码. 词法分析: 读入源码字节,将其组成有意义的TOKEN流. 语法分析: 根据TOKEN流构建树形的中间表示. 语义分析: 检查 ...

  8. 编译原理——第三章词法分析总结

    词法分析 在词法分析这一章主要通过学习了词法分析.正规表达式和有限自动机来了解词法分析器的构造. 词法分析器是执行词法分析的程序.将源程序输入词法分析器后,词法分析器从左至右逐个字符的对源程序进行扫描 ...

  9. [转载] ANTLR——词法分析

    来源:ANTLR中文网站:http://www.antlr.org.cn 词法分析是编译过程的第一步,是编译过程的基础.词法分析除了上一章讲过它为语法分析提拱记号流,滤掉编译过程不关心的内容以外,还有 ...

最新文章

  1. fedora java 开发环境_Linux(Fedora 14)下 java开发环境配置 ——jdk的安装与配置
  2. 后浪们 : 难道要先结婚后恋爱?
  3. 【FPGA】SRIO IP核系统总览以及端口介绍(二)(I/O Port 含义介绍)
  4. python3 报错 ‘builtin_function_or_method‘ object has no attribute 解决方法
  5. App Store遭到攻击后如何保护iPhone安全
  6. 猴子选大王 java,PAT-JAVA-5-28 猴子选大王 (20分)
  7. 【高并发解决方案】5、如何设计一个秒杀系统
  8. ZetCode 数据库教程
  9. 转 Androidpn里的Xmpp的理解(消息推送)
  10. 计算机英语基础课程论文,计算机专业英语结课论文.doc
  11. Cartesian k-means论文理解
  12. 完整详尽的解决MySql:Could not create connection to database server
  13. UML用例图怎么画 有手就会
  14. oracle中on和where的区别,Oracle里面的外连中where和on之后and有啥区别
  15. 辞职文案火了,程序员的辞职理由要命不要钱。
  16. 神州数码c语言笔试题,神州数码笔试题,神州数码笔试题.doc
  17. GoLang之取地址符、指针
  18. 编程语言php加密与解密的方法
  19. 《棒球英豪》:青春球场·棒球1号位
  20. ElementUI从PNG开始,自己添加ICON

热门文章

  1. openstack前世今生
  2. JSP简单练习-一个简单的计数器
  3. NYOJ 115 城市平乱
  4. NYOJ 330 一个简单的数学题
  5. Django模型层(models.py)之多表操作
  6. Nuget包制作最佳解决方案
  7. 【题解】 bzoj4472: [Jsoi2015]salesman (动态规划)
  8. C# 中科学计数法转成正常值
  9. hdu 2824The Euler function
  10. [算法 笔记]字符串表达式计算(简易版)