【编译原理龙书笔记】(三)词法分析(附联系答案)(仍未完成)

2023-11-09

这篇博客是根据自己学习龙书的过程编写,因为博主习惯了英语环境,在强行从英语转化为中文的时候难免会有些不自然,请大家谅解。

配套的练习题答案可以在 https://github.com/Oh233/Dragon_book_exercise 看到。

感谢沉鱼姐姐,很多答案都是参考了她的github,虽然无缘认识,但也算是一位领路人。


3.1 词法分析器的作用

词法分析是编译的第一阶段。

这里写图片描述

词法分析器读取了源程序,将其打碎成一个个的token之后传入语法分析器。

词法分析器的任务:

  • 读取源程序,过滤掉源程序的注释和空白。
  • 将编译器生成的错误信息与源程序的位置联系起来。
  • 宏的扩展
  • 生成词法单元
3.1.1 词法分析及语法分析

我们将词法分析和语法分析分离开并不是毫无根据的。至少有以下几个好处:

  1. 简化编译器的设计。如果在语法分析阶段,仍旧要考虑什么过滤注释,过滤空白之类的鬼东西,设计起来简直可以杀了程序员。于是我们选择将两部分分开。这种思想在软件工程的设计中十分常见,包括在计算机网络的层结构中也可以看到。
  2. 提高编译器的效率。
  3. 增强编译器的可移植性。有的时候输入的字符会跟设备有关,这样的情况下,我们只需要改一改其中一小部分的词法分析,就可以得到适应机器的结果,而非要修改整个词法分析+语法分析。
3.1.2 词法单元,模式,词素

这三者读起来很相似然而概念上却是完全不同的东西。

  • 词法单元是由一个词法单元名和一个(可选的)属性值组成。词法单元名是一个表示某种词法单位的抽象符号。
  • 模式描述了一个词法单元的词素可能具有的形式。当词法单元是一个关键字时,它的模式就是组成这个关键字的字符序列。对于标识符和其他词法单元,模式就是一个更加复杂的结构,可以和很多字符串匹配。
  • 词素是源程序的一个字符序列,和某个词法单元的模式匹配,会被词法分析器识别为某个词法单元的一个实例。

在绝大多数的程序设计语言中,词法单元由五大部分组成:

  1. 关键字。关键字的模式就是关键字本身
  2. 运算符。这些运算符既可以是单个的运算符,也可以是代指一类运算符(比如比较运算符)
  3. 表示所有标识符的词法单元。初学者可以把其理解成存储各种变量名的地方。
  4. 一个或多个表示常量的词法单元,存储了数字和literal字符串
  5. 标点符号。左右括号,逗号分号等等。
3.1.3 词法单元的属性

从词法单元的定义来看,就可以看出一个词法单元是可以对应到多个词素的。那么区分这些词素的至关重要的一部分就是给编译器提供词素的额外信息来描述各种不同的词素。

最典型的例子就是在identifier这个词法单元中,其对应的词素包括所有的变量名,那么如何区分这些变量名便成了一个主要的任务。我们通常采用其类型,第一次出现的位置等等去描述它,并把它存储在字符表中。

词法分析的一个大问题就是我们无法在只看一个字符串的时候决定它是对是错,著名的fortran例子告诉我们,有的时候,我们需要看整个statement才能发现这个statement的意思是什么。

3.1.4 词法错误

3.2 输入缓冲

之前提到过,在大部分程序中,我们都需要有 look ahead 情景的出现,这就给读入过程增加了复杂性。“我们一次到底该读多少代码呢”,这一节我们就会介绍这些问题。

3.2.1 缓冲区对

在编译一个程序的时候,我们往往需要进行大量的字符串读入。前人做了比较多的优化,其中一项就是采用来个交替读入的缓冲区。每个缓冲区大概能有4096的字节,如果不是疯狂搞破坏的话读一句话肯定够了(不够的情况后文也有解释)。

读入程序中维护了两个指针:分别是

  1. lexemeBegin 指针,顾名思义,就是当前词素的开始处。
  2. forward 指针,就是试图判断词素的结尾是什么。这个很复杂我们会在接下来的章节中详细介绍。

可以想象,一旦确定了当前词素的位置,那我们就把forward的位置+1之后赋值给lexemeBegin,然后继续上述的过程。

但是简单地做上面的工作会有一个小小的问题,就是如果恰好一个词素被分开了怎么办,这就涉及到了哨兵标记。

这里写图片描述

3.2.2 哨兵标记

主要就是说,当我们移动forward指针的时候,实际上我们同时做了两件事情,第一件事情是判断是否已经能够完成词素的匹配。并且要同时检查我们是否到了缓冲区的结尾(如果到了结尾自然要选择是不是要重新装载缓冲区,是不是要大幅度移动forward指针),这个问题被 eof 很好的解决了。我们在这里描述一种处理这个问题的算法,这个算法十分清晰,让人一目了然。

switch (*forward++) {
    case eof:
        if (forward = buff1.end()) {
            load buff2;
            forward = buff2.begin();
        }
        else if (forward = buff2.end()) {
            load buff1;
            forward = buff1.begin();
        }
        else terminate lexical analysis; // This is end of whole code
    case otherWords:
}

在现代编程语言中,词素的长度往往并没有那么长,然而如果你硬要问我有没有无敌长的字符串,其实还是有的。在这种情况下,我们会采用一些算法,将其视为多个不是很长的字符串的加和,之后的处理中再把他们加和起来。

一种更加严重的情况就是当我们需要往前看很多很多字符才能决定词素的情况。在曾经的PL/I语言中,关键字并不是保留字。曾经出现过 DECLARE (ARG1, ARG2, … , ARGN) 此般凶残的杀人法。我们无法判断 DECLARE到底是一个关键字,还是一个数组的名字,在当时,只能做两个分支,然后由语法分析器来解决这个问题,不过现在的绝大多数编程语言都将关键字保留,以避免这类愚蠢的问题。

3.3 词法单元的规约

书中提到,正则表达式是一种用来描述词素模式的重要方法,虽然我并不能懂这句话的意思,但是让我们先走入正则表达式的世界,去一窥究竟。

3.3.1 串和语言

这一节中给出了我们所需要语言的一些定义。首先,字母表(alphabet)是一个有限的符号集合,我们后面所定义的语言,表达式等等东西都要依靠于这个字母表。

某个字母表上的一个字符串(string)是该字母表中符号的又穷序列。注意这个串可以是空的,我们用 ϵ 来表示。

串的前缀就是从其尾部删除一些符号得到的串,对应来说后缀就是从其头部删除一些符号得到的串。之后串的子串是删除某个前缀加上删除某个后缀后得到的串。

因为前缀和后缀其实可能是串本身,所以我们规定串的真前缀和真后缀,即是非本身的前后缀串。

3.3.2 语言上的运算

这一节又给出了一些繁琐的集合论的定义。大意就是给出字母表集合的时候,其上的并(union),连接(concatenation),闭包的定义。因为跟代数中的形式过于相近,这里就先不给出其表格形式了。

3.3.3 正则表达式

正则表达式实际上是用于描述一套字符串集合所定义的一套语法。我们用特定的字符串加上一些符号去描述我们想描述的一些具有某些性质的字符串。

那么我们为什么需要正则表达式呢,大概是因为在描述一些数量庞大的字符串集合的时候,应用正则表达式的概念,会让我们的描述更加清晰。

同时,我们不会想要每次都强行的写出所有正则表达式的字母表示形式,因此我们开发了递归这种东西。下面给出正则表达式递归式的明确定义。

归纳基础:

1) ϵ 是一个正则表达式,其所对应的语言 L(ϵ)={ ϵ} ,是一个只有空字符的语言。
2) 如果 a

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【编译原理龙书笔记】(三)词法分析(附联系答案)(仍未完成) 的相关文章

  • Linux下编译CEF源码及交叉编译

    Linux下编译CEF chromium源码及交叉编译 官方编译文档 https bitbucket org chromiumembedded cef wiki MasterBuildQuickStart markdown header l
  • linux汇编编译器:GAS和NASM的比较

    GAS即GNU AS汇编编译器 其属于AT T风格 我们常用的GNU的产品还有GCC G NASM是Linux平台下常用的汇编编译器 是intel风格的汇编编译器 MASM是Windows平台下的汇编编译器 也使用Intel风格 我们学80
  • LLVM系列第十五章:写一个简单的中间代码生成器IR Generator

    系列文章目录 LLVM系列第一章 编译LLVM源码 LLVM系列第二章 模块Module LLVM系列第三章 函数Function LLVM系列第四章 逻辑代码块Block LLVM系列第五章 全局变量Global Variable LLV
  • GDB远程调试技术---基于mini2440

    GDB调试器提供了两种不同的调试代理用于支持远程调试 即gdbserver方式和stub 插桩 方式 这两种远程调试方式是有区别的 gdbserver本身的体积很小 能够在具有很少存储容量的目标系统上独立运行 因而非常适合于嵌入式环境 而s
  • 快速排序之“采取“尾递归”和“三数取中”技术的快速排序”

    快速排序之 采取 尾递归 和 三数取中 技术的快速排序 下面针对快速排序进行一些优化 QUICKSORT算法包含两个对其自身的递归调用 即调用PARTITION后 左边的子数组和右边的子数组分别被递归排序 QUICKSORT中的第二次递归调
  • char与signed char, unsigned char的区别

    一 开始 今天有一个困扰的问题 就是char与signed char unsigned char这三者的区别 二 三者之间 1 ANSI C 提供了3种字符类型 分别是char signed char unsigned char 而不是像s
  • ubuntu16.04 安装交叉编译工具aarch64-linux-gnu-gcc/g++

    前言 最近需要把人脸识别代码放到RK3399Pro的嵌入式板子上 所以编写好的c 代码要放到板子上编译 或者在ubuntu系统上使用交叉编译工具 编译好可执行文件在放到板子里运行 为了在能在ubuntu系统上能交叉编译 安装aarch64
  • 龙书虎书鲸书啃不动?试试豆瓣评分9.5的猴书

    相传 编译原理界有三大圣书 龙书是为Compilers Principles Techniques and Tools 虎书是为Modern Compiler Implementation in C 鲸书是为Advanced Compile
  • Thumb和ARM指令不能切换问题(error:unsupported interworking call (Thumb -> ARM))

    1 报错现象 xxx ko ection 3 reloc 4 sym xxxxxx unsupported interworking call Thumb gt ARM 2 报错原因和分析 报错信息的翻译 程序不支持代码交织 thumb态切
  • C语言-宏定义

    C语言 宏定义 1 宏定义是什么 2 宏定义怎么用 2 1 宏定义常量 2 1 1 预定义宏 2 1 2 自定义宏 2 2 带参数的宏 2 3 编译预处理 3 宏展开 4 编译预处理指令 1 宏定义是什么 宏是用来表示一段代码的标识符 宏也
  • 命令行下使用CL.exe编译多cpp文件工程

    一 CL exe是控制 Microsoft C 和 C 编译器与链接器的 32 位工具 编译器产生通用对象文件格式 COFF 对象 obj 文件 链接器产生可执行文件 exe 或动态链接库文件 DLL 用法如下 注意 所有编译器选项都区分大
  • 转帖:DirectShow 在VS2005中环境配置

    转载请标明是引用于 http blog csdn net chenyujing1234 baseclasses参考代码 VS2005下编译通过 http www rayfile com zh cn files 12ac1b0c 7335 1
  • 【VMware】虚拟机中Ubuntu无法连接网络的有效解决办法

    1 Ubuntu网络设置 依次单击 System Settings gt Network gt Wired gt Options 如下图所示 依次选择 General 勾选如下图所示的单选框 最后点击 Save 如下图所示 依次选择 IPv
  • GCC - structure/union前端解析说明

    以GCC8 2 0版本为例 介绍gcc语法解析器 parser 对声明即函数定义的解析过程以及structure union的简单解析说明 1 GCC中声明和定义的解析过程 1 1 解析入口 c parse file GCC中gcc c c
  • 从编译器角度分析C语言中数组名和指针的区别

    数组名和指针是两个往往很容易让人们混淆的概念 很多人以为数组名就是一个指针 也有很多人知道数组名不同于指针但是仅知道数组名的值不能像指针一样改变 例如你可以写出下面这样的代码 int p p 却不能写这样的代码 int a a 那么数组名跟
  • 编译器何时调用默认构造函数

    总的来说 编译器只在它需要的时候才会合成一个默认构造函数 或者扩张所有已存在的构造函数 一个类满足下列其中任何一个条件 1 包含了一个类的对象 这个对象有一个构造函数 包括编译器合成的默认构造函数 2 如果继承自一些基类 其中某些基类有一个
  • 编译原理实验:使用C/C++语言编写C-语言的词法分析器

    文章目录 实验目的 实验任务 实验内容 实验步骤 分析c 的词法规则 算法基本思想 Step1 find token Step2 DFA状态图构建 Step3 使用while switch双循环将DFA代码化 主程序流程 各程序模块之间层次
  • ADS1.2出现erro starting external process,Process error code 87(0x57)参数错误的解决办法

    系统兼容问题 在ADS的兼容性上选择xp sp2兼容模式 以管理员权限启用
  • Ubuntu18.04 编译安装llvm-clang

    背景知识 LLVM和GCC的区别 传统编译器 传统编译器的工作原理基本上都是三段式的 可以分为前端 Frontend 优化器 Optimizer 后端 Backend 前端负责解析源代码 检查语法错误 并将其翻译为抽象的语法树 Abstra
  • 内存的堆分配和栈分配 & 字符数组,字符指针,Sizeof总结

    程序占用的内存分为几个部分 各个部分起什么作用 字符数组 字符指针在实现上有什么区别等等 本文对此做了详细阐述 特转载于此 供大家学习参考之用 一个由C C 编译的程序占用的内存分为以下几个部分 1 栈区 stack 由编译器自动分配释放

随机推荐

  • Linux(ubuntu)上安装RDP Server(Xrdp)使用的注意事项

    ubuntu上的基本安装方法 1 apt get install xrdp 基本上就已经安装完成了 但是此时连接会出现异常 类似黑屏的情况 原因 1 Xrdp不支持unity 3D的图形 解决方法 1 使用xfce或者gnome 2d等 如
  • C#小知识

    项目编译后复制文件到生成目录 方法1 对于单个文件 可以点击属性 输出目录里选择始终复制 方法2 把项目中的ServerScripts复制到输出目录 在项目设置中 生成事件里添加批处理 xcopy ProjectDir ServerScri
  • anaconda用法

    查看已经安装的包 pip list 或者 conda list 安装和更新 pip install requests pip install requests upgrade 或者 conda install requests conda
  • LINUX权限-bash: ./startup.sh: Permission denied

    LINUX权限 bash startup sh Permission denied 执行 startup sh 或者 shutdown sh的时候 报 Permission denied 需要用命令 chmod 修改一下bin目录下的 sh
  • spring boot配置双Kafka方法

    第一步 application yml的配置 server port 8080 spring application name demo kafka one bootstrap servers xxx xxx xxx xxx consume
  • android动态毛玻璃,Android模糊处理简单实现毛玻璃效果

    自从iOS系统引入了Blur效果 也就是所谓的毛玻璃 模糊化效果 磨砂效果 各大系统就开始竞相模仿 这是怎样的一个效果呢 我们先来看一下 如下面的图片 实现效果大家都知道了 如何在Android中实现呢 说白了就是对图片进行模糊化处理 小编
  • Vue项目生成二维码

    场景 民主测评 闭卷测试 Vue项目生成二维码 使用手机浏览器扫码录入答题 一 创建vue项目 样式布局 接口联调 npm run build 打包成dist 文件 让后台发送到服务器中 页面地址就获取到了 二 前引入vue qr 二维码地
  • openwrt 编译笔记

    错误一 Creating filesystem with parameters Size 50331648 Block size 4096 Blocks per group 32768 Inodes per group 6000 Inode
  • 基于OpenCV-Python实现的人脸识别

    在初步学习了数字图像处理的相关知识并在Matlab进行了初步的模拟后 我将学习的中重点转向了Python环境下的OpenCV库的学习 以此博客记录一下学习的进程 本文章代码主要参考OpenCV库源代码 刘波译的 OpenCV3计算机视觉Py
  • Apache Tomcat

    简介 简而言之 Tomcat是一个免费的开放源代码的Web应用服务器 属于轻量级应用服务器 Apache Tomcat Tomcat是Apache 软件基金会 Apache Software Foundation 的Jakarta 项目中的
  • 邻接矩阵广度优先遍历算法 连通图采用邻接表深度优先遍历的非递归过程 图G中距离顶点v的最短路径长度最大迪杰斯特拉

    1 采用邻接矩阵存储图的广度优先遍历算法的实现 参考教材算法6 5选作 2 一个连通图采用邻接表作为存储结构 设计一个算法 实现从顶点v出发的深度优先遍历的非递归过程 3 设计一个算法 求图G中距离顶点v的最短路径长度最大的一个顶点 设v可
  • 函数调用之回调函数

    重新回到CSDN 工作以来写第一个博客 不码代码 不追求高大上的专业术语 只求通俗的理解 以前听过回调函数 也研究过 但由于没有在实际中用过 所以也没太懂 每次一听到回调函数这个词 感觉很高大上 最近在工作上遇到了 而且被公司前辈广而用之
  • Pickle包的使用

    想要将Python程序运行中得到的字符串 列表 字典等数据 长久的保存下来 而不是简单的放入内存中关机断电就丢失数据 Pickle模块就是专门用来完成此功能的模块 它可以将对象转换为一种可以传输或存储的格式 它实现了基本的数据序列和反序列化
  • 如何保证token的安全

    接口的安全性主要围绕token timestamp和sign三个机制展开设计 保证接口的数据不会被篡改和重复调用 下面具体来看 Token授权机制 用户使用用户名密码登录后服务器给客户端返回一个Token 通常是UUID 并将Token U
  • Sqli-labs之Less-29和Less-30和Less-31

    Less 29 基于错误 GET 双服务器 单引号 字符型注入 服务器 两层 架构 注 截图等来自 MySQL注入天书 Less 29 服务器端有两个部分 第一部分为 tomcat 为引擎的 jsp 型服务器 第二部分为 apache 为引
  • 传输线的物理基础(十):特性阻抗的频率变化

    到目前为止 我们一直假设传输线的特性阻抗随频率保持不变 正如我们所见 从传输线前端看 输入阻抗与频率密切相关 毕竟 在低频时 远端开路的传输线的输入阻抗看起来像一个电容器 阻抗开始很高 然后下降得很低 特性阻抗是否随频率变化 在本节中 我们
  • 【Linux入门】Linux编译器gcc/g++基础

    目录 1 背景知识 2 gcc g 的用法 3 指令补充 3 1 ldd指令 3 2 file指令 4 Linux下的头文件 库 4 1 指令的库 4 1 1 动态库 4 1 2 静态库 4 1 3 动静态库的优缺点 5 gcc g 静态链
  • v-if 和 v-show的区别 vue面试题

    v for 指令 作用 遍历数组 并重复生成对应长度的相同标签 语法 列表渲染 v for item in 数组名 遍历下标 v for item index in items 注意点 这个指令写在哪一个元素身上 就重复生成哪一个元素 数组
  • 小程序用户开放接口调整时间-2021年4月28日24时

    官方实例demo
  • 【编译原理龙书笔记】(三)词法分析(附联系答案)(仍未完成)

    这篇博客是根据自己学习龙书的过程编写 因为博主习惯了英语环境 在强行从英语转化为中文的时候难免会有些不自然 请大家谅解 配套的练习题答案可以在 https github com Oh233 Dragon book exercise 看到 感