输入及词法分析详解

2024-04-30 21:38
文章标签 分析 详解 输入 词法

本文主要是介绍输入及词法分析详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

欢迎大家来到coding迪斯尼,我的愿景是:让天下没有难学的知识 

这句话是跟马云学的,就算没马云的命,那就用马云的话,也是不错的。

阅读博客的朋友可以到我的网易云课堂中,通过视频的方式查看代码的调试和执行过程:

 

http://study.163.com/course/courseMain.htm?courseId=1002830012

 

大家好,继上几节我们通过实现一个简易编译器,通过实践获得一定的感性认识后,这里,我们开始对编译器的各个技术点深入分析,逐个击破。从这节开始,以及后续的相应课程,我们注重输入系统的设计和词法解析所用到的各种算法和理论基础。

 

本节的重点,是对词法解析系统和输入系统做一个大概的介绍

 

词法解析是一种相当实用的技术,除了在编译器中,在很多应用场合同样是非常实用的。如果把词法解析看成是一种模式识别引擎,那它可以应用到例如文本编辑器,数据模式识别等方面。稍加扩展,还可以应用到网络协议解析等方面去。

 

2.1 词法解析器在编译器中的作用

 

词法解析器在编译器中的作用,是将输入流解析为一种能够被语法解析器使用和管理的格式。他将输入文本分割,打标签,也就是用一些数值来指代一系列相应的字符串,例如关键字while 可以和一个标签关联起来,一般情况下,一个标签可以和很多字符串关联,例如以前看到的NUM_OR_ID,它指代的是代码中的变量定义和数字常量,显然,他们是无穷的, 词法解析器还可以将源码中的注释剔除,忽略空格,换行符等,将这些琐事的工作从语法解析中剥离出来,简化语法解析器的设计。

 

词法解析是编译过程的一个独立阶段,它通过一组简单的接口与语法解析器相互交互,下面这幅图可以将编译器的骨架给展示出来(描述一下结构图):


从这个结构,我们可以得到几点启示:词法分析器由于与其他各个部分通过接口进行通信,那么词法解析器内部的修改不会影响到其他部分。例如,如果要编译的程序语言从 c++ 换成c#, 那么只要把词法解析的内部实现换掉,其他部分不变,c++编译器就会转换成c#编译器。另一个好处是,一个独立的词法解析器可以优化它的字符读取速度,例如一次读取足够多的内容,而这种局部优化可以带来整体效果的改进,而优化的过程可以局限在词法解析内部。这其实是典型的将面向对象的设计方法运用到架构设计上。

 

有时候,词法解析和语法解析之间需要更复杂的交互流程,例如C语言中的typedef ,该语句可以构造一个新的关键字,当语法解析器处理下面这条语句:

typedef int  Integer;

词法解析器必须将字符串“Integer” 当做一个类型标签而不能是变量标签(NUM_OR_ID).  这种交互流程就需要一种复杂的数据结构,就是图中的符号表。 语法解析器可以将 Integer插入符号表,词法解析器遇到字符串Integer 时,到符号表中检索一下,检索到后就给Integer打上标签TYPE, 而不是NUM_OR_ID.

 

2.2 词法解析中的错误恢复

在词法解析过程中,发现错误是很正常的,例如在C语言中,@num 这样的变量定义是非法的,词法解析器遇到这种错误的时候,有若干种处理方法,最简单的是,词法解析器在解析过程中,忽略符号@, 同时输出错误提示。还有更复杂的处理方法,如果读取到一个关键字是 swatch ,那么词法解析器根据匹配算法,将swatch识别为switch等。

 

2.3 输入系统

在上图中,词法解析器的下方有一个模块叫输入系统。它的作用是将源文件从磁盘或内存中读入,根据模块化设计原理,如果输入系统是一个独立模块,通过固定接口与词法解析器交互的话,那么它的修改和维护将会非常灵活。

 

输入系统的效率,决定着整个编译系统的效率。我们用C语言的时候,经常会用到他提供的输入函数例如scanf 等。C语言的输入系统设计得不是很合理,当C语言的库函数将数据读入程序的过程中,有三次拷贝, 一是从磁盘上将数据拷贝到操作系统中,二是将数据从操作系统拷贝到一个 FILE 结构中,三是将数据从FILE 结构拷贝到程序的内存中。这些拷贝都需要耗费时间和空间。另外,词法解析器在解析时需要预先读入一些字符(look ahead,在前面的简易编译器中使用过这种技巧), 以便对输入的字符串打上合适的标签(想象前面的typeof 语句,一旦读到typeof 那就需要将typeof后面的字符读进来才好解析), 预先读入的字符,使用完后,可能需要重新放回到缓冲区中,这一取一放,如果不加以良好的设计,那很可能会产生I/O性能上的影响

 

2.3 一个输入系统的实现

鉴于以上特点,现有的输入系统无法满足编译系统输入的各种要求,因此,我们需要自己开发一个专用的输入系统。我们的输入系统应当具备以下特点:

1.    输入过程要尽可能的快,尽力减少不必要的拷贝。

2.    支持多个字符的预读取和回放。

3.    当解析当前分割的字符串时,上一个解析过的字符串需要容易的获取。

4.    磁盘读写要足够便捷。

 

下图是我们要实现的输入系统的内存模型:

 

startBuf 指向缓冲区的起点

END 指向缓冲区的物理结束位置

endBuf 指向缓冲区的逻辑结束位置,也就是数据最多存储到endBuf 处

例如我们申请的内容是300k, 那么END就在300k处,而实际我们用到的是256k,那么endBuf就在256k处。

 

MAXLEX 是分割后字符串的最多长度,一般设为128,大家在写代码时没有写过长度达128个字符的变量名吧。我们一次从磁盘读入缓冲区的数据量是MAXLEX 的倍数,也就是说,endBuf – startBuf 一定是MAXLEX的整数倍。

 

Next指向下一个要读取的字符,词法解析器是一个字符一个字符从缓冲区读取数据的。

 

danger  后面解释

 

下图显示的是词法解析器解析若干个字符串后,缓冲区的情况:

 

 

pMark指向上一个被解析的字符串的起始位置

 

sMark 指向当前被解析的字符串的起始位置

 

eMark 指向当前解析的字符串的结束位置

 

sMark – pMark 是上一个字符串的长度

 

eMark – sMark 是当前解析字符串的长度

 

词法解析器在解析时需要读取字符串后的若干字符,所有Next 往后挪动了一段距离。这也就是前面提到的look ahead.

 

我们前面提到过,预先读取和读取后放回缓冲区需要加以处理,在我们给的的内存模型中,当需要把预先读到的字符退回缓存区时,只要把Next 设置成eMark就可以了。

 

look ahead 也就是预读取的字符个数是有限制的,限制的个数用常量MAXLOOK表示,也就是

 

Next – eMark <= MAXLOOK

 

当Next 的值接近danger 时, 表明缓冲区内的有效数据快被读取完了,当Next指针越过danger 时,就会触发一次flush操作

 

flush 操作的效果是,将pMark 到endBuf间的数据整体平移到startBuf处,

平移的距离是:

pMark – startBuf

 

从磁盘中读入数据,把平移后空出来的可用空间给填满,如下图所示(字有点难看):

 

从磁盘读入数据:这一段空间的长度就是pMark – startBuf

 

 

下一节我们将以代码的形式讲解输入系统的实现

 

这篇关于输入及词法分析详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/949982

相关文章

Mysql 中的多表连接和连接类型详解

《Mysql中的多表连接和连接类型详解》这篇文章详细介绍了MySQL中的多表连接及其各种类型,包括内连接、左连接、右连接、全外连接、自连接和交叉连接,通过这些连接方式,可以将分散在不同表中的相关数据... 目录什么是多表连接?1. 内连接(INNER JOIN)2. 左连接(LEFT JOIN 或 LEFT

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者

Linux内核之内核裁剪详解

《Linux内核之内核裁剪详解》Linux内核裁剪是通过移除不必要的功能和模块,调整配置参数来优化内核,以满足特定需求,裁剪的方法包括使用配置选项、模块化设计和优化配置参数,图形裁剪工具如makeme... 目录简介一、 裁剪的原因二、裁剪的方法三、图形裁剪工具四、操作说明五、make menuconfig

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

详解Java中的敏感信息处理

《详解Java中的敏感信息处理》平时开发中常常会遇到像用户的手机号、姓名、身份证等敏感信息需要处理,这篇文章主要为大家整理了一些常用的方法,希望对大家有所帮助... 目录前后端传输AES 对称加密RSA 非对称加密混合加密数据库加密MD5 + Salt/SHA + SaltAES 加密平时开发中遇到像用户的

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

Springboot使用RabbitMQ实现关闭超时订单(示例详解)

《Springboot使用RabbitMQ实现关闭超时订单(示例详解)》介绍了如何在SpringBoot项目中使用RabbitMQ实现订单的延时处理和超时关闭,通过配置RabbitMQ的交换机、队列和... 目录1.maven中引入rabbitmq的依赖:2.application.yml中进行rabbit

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初

Python绘制土地利用和土地覆盖类型图示例详解

《Python绘制土地利用和土地覆盖类型图示例详解》本文介绍了如何使用Python绘制土地利用和土地覆盖类型图,并提供了详细的代码示例,通过安装所需的库,准备地理数据,使用geopandas和matp... 目录一、所需库的安装二、数据准备三、绘制土地利用和土地覆盖类型图四、代码解释五、其他可视化形式1.

SpringBoot使用Apache POI库读取Excel文件的操作详解

《SpringBoot使用ApachePOI库读取Excel文件的操作详解》在日常开发中,我们经常需要处理Excel文件中的数据,无论是从数据库导入数据、处理数据报表,还是批量生成数据,都可能会遇到... 目录项目背景依赖导入读取Excel模板的实现代码实现代码解析ExcelDemoInfoDTO 数据传输