《编程语言实现模式》笔记(一)词法和句法分析

《编程语言实现模式?可以理解为编程语言的《设计模式》,这本书的中文翻译通俗易懂,非常适合没有基础的人阅读。 本节主要介绍第一部分,词法分析和句法分析。

 

1.为什么需要学习这些模式

因为需要自定义DSL(领域自定义语言). 
人的智能非常强大,能够灵活地处理各种问题。计算机虽然迅速,但远远不及人类灵活。因此才有编程语言作为桥梁,建立人和机器的沟通方式。 
然而,通用语言功能强大,但针对特定的应用环境,可能不够简洁,同时有很多噪音,也可能难以被领域专家理解。更夸张的情况是,一些方法用通用语言非常难实现,而使用DSL则容易得多,比如正则表达式。 
在我们还不能构造出足够智能的机器前,使用DSL简洁流畅地反映人的算法和概念,便是一种折中的选择。

即使不去设计DSL,能理解DSL的原理和方法,对编程水平的提高都非常有帮助。

设计DSL看起来有难度,但当有足够的经验之后,就会发现其实有很多固定的模式可以借鉴,通过学习和熟悉这些模式,就能够更快速,方便,精确的构造自己的DSL和语言应用。

2.词法分析

字母的组合构成单词,单词的组合构成句子,所有正确的句子的组合则构成一门语言。 
对语言来说,如何判定句子是否正确?规定句子是否正确的规则称为文法。 
为了能够正确理解句子,就需要先将句子拆分成多个单词。对自然语言这叫做分词;对编程语言,则叫做词法分析。 
通常来说,一个TOKEN可能是数字,符号,单词,或是字符串。因而通过正则表达式,可以流式的切分并确定TOKEN的类型。 
值得指出,TOKEN的类型由于二义性,并不能在词法分析,而需要在语法分析时确定。

3.语法分析

在完成词法分析后,就是语法分析的阶段。语法分析的目标是将文本翻译为句法树。

LL(1)模式

语法分析就像贪吃蛇,词法分析得到的单词就像一颗颗的糖豆,最简单的语法分析可以理解为一次吃一颗糖豆。 
如果没有递归子结构,形成的结果更像一个链表,而非树结构。 
一旦语言支持嵌套结构,就需要递归下降语法分析器。如果使用本模式,生成的将是直接调用自身而无限递归的函数。 
递归下降的问题是,无法识别左递归,否则将陷入无限循环。另外,由于只向前看一个单元,可能并不能直接判断出当前的文法。

LL(k)模式

为了解决这个问题,可能需要向前多看几个单元。往前看的单元越多,贪吃蛇就在遇到交叉时顺着不同的路径看的更远,从而越知道该选择哪条路径。而做解析决策的能力越强,语言就更容易编写。 
最简单的方式,就是使用数组来缓冲所有的输入符号,以整数输入作为下标的指针。使用环形缓冲区能够方便地保存数据。

回溯解析器

有时,当文法要求语言能够支持任意多的重复结构时,要求LL(k)中的缓冲要无限大,这是不可能的。为了解决这个问题,需要采用回溯解析。 
其概念就像走迷宫,既然不能只看不走,那么就尝试去走每一个可能的选项,直到找到合适的为止。这样等价于任意地向前看。 
回溯可以设计为遇到成功的,就不尝试其他路径;也可以不论是否成功,都尝试全部路径,最后选择最长,最短,或其他自定义的筛选方法。 
回溯解析的性能远远不及LL(k),其中一大原因是重复。可能两个路径A和B, B路径中又包含了A路径。这样A路径就可能探查两遍。为了避免重复,可以通过消耗少量的内存引入记忆机制。

记忆解析器

记忆解析器使用了类似动态规划的概念,记录在某一位置时,使用某条文法的尝试结果,如成功,失败或其他的匹配特征。这样就能大大减少重复,使得回溯解析能尽可能地达到LL(k)模式的性能。

谓词解析器

有时,通过纯粹的文法很难判断选择哪一条匹配路径,因而可以通过嵌入判断逻辑,借助运行时信息调整解析策略。 
谓词解析的常用实现,是在文法中嵌入代码(如通用语言),解析引擎在运行时执行这些代码,来调整决策。

3.语言优化

生成AST结构之后,就可以对树进行操作了。主要的操作有两部分,遍历和规约。 
节点结构本身的设计值得考量,基本上有两种风格:

  • 同构弱类型:所有的节点类型一致,只通过标识符区分操作,通过列表保存子节点。 优点是开发方便,缺点是少了运行时检查。
  • 异构强类型:节点类型不一致,但都继承于一个基类。如果方法很多,会产生大量的节点类型。对于编程的工作量可能较大。另外,也不适合自动工具生成时嵌入自定义代码。

遍历

所谓遍历,看似很容易。但有一些需要注意的点。

  • 访问和执行是其实是分离的,先访问不一定代表先执行。根据执行代码相对于访问代码的位置,可以生成前序,中序和后序遍历。
  • 遍历中肯定要执行操作,对操作的描述可以放在节点定义文件中,也可以设计外部访问器

规约

在生成句法树后,可能一些树结构是冗余或无效的,也可能可以被优化为更好的结构,例如: 
0*(x+5) 由于任何数字乘以0都为0,所以应该直接被规约为0。 
规约涉及到两个问题,首先要识别特定的树结构,这就需要对树进行模式匹配,ANLNR已经有现成的工具和语法支持这类操作。

 

4.总结

编译原理的书看似很枯燥(确实很枯燥),所以一定要去实践,实践后再去看这些概念,就会觉得非常漂亮了。

目前已经采用这些技术,开发了面向序列模式发现和抽取的一套DSL。不久后将会发布。未来将开发针对树结构的分析DSL。

下一部分的内容,是该书的后半部分,程序的解释执行。然而这一部分,我还没有深入去研究,因此估计周期会比较长了。

时间: 2024-12-28 18:24:44

《编程语言实现模式》笔记(一)词法和句法分析的相关文章

无线网卡工作模式笔记

网线网卡可以工作在多种模式下,以实现不同的功能.主要模式(mode)有:master  managed  monitor ad-hoc repeater secondary [master] master模式即常见的AP模式,无线模块本身作为WIFI热点,让其它设备以无线的方式接入构建LAN/WAN .无线路由器的工作模式就是master . 在Linux系统中,无线AP的接入和授权主要采用开源项目hostapd来实现   hostapd 是一个用户态用于AP和认证服务器的守护进程.它实现了IE

SQL 笔记 By 华仔

-------------------------------------读书笔记------------------------------- 笔记1-徐 最常用的几种备份方法 笔记2-徐 收缩数据库的大小的方法 笔记3-徐 设置数据库自动增长注意要点 笔记4-徐 模仿灾难发生时还原adventurework数据库 示例 stopat 笔记5-徐 检查日志文件不能被截断的原因 笔记6-徐 检测孤立用户并恢复孤立用户到新的服务器 解决数据库镜像孤立用户问题 笔记7-徐 SQLSERVER日志记录

详解备忘录模式及其在Java设计模式编程中的实现_java

1. 定义在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态. 2. 使用的原因想要恢复对象某时的原有状态. 3. 适用的情况举例有很多备忘录模式的应用,只是我们已经见过,却没细想这是备忘录模式的使用罢了,略略举几例: eg1. 备忘录在jsp+javabean的使用: 在一系统中新增帐户时,在表单中需要填写用户名.密码.联系电话.地址等信息,如果有些字段没有填写或填写错误,当用户点击"提交"按钮时,需要在新增页面上保存

C++设计模式编程中使用Bridge桥接模式的完全攻略_C 语言

桥接模式将抽象(Abstraction)与实现(Implementation)分离,使得二者可以独立地变化. 桥接模式典型的结构图为: 在桥接模式的结构图中可以看到,系统被分为两个相对独立的部分,左边是抽象部分,右边是实现部分,这两个部分可以互相独立地进行修改:例如上面问题中的客户需求变化,当用户需求需要从 Abstraction 派生一个具体子类时候,并不需要像上面通过继承方式实现时候需要添加子类 A1 和 A2 了.另外当上面问题中由于算法添加也只用改变右边实现(添加一个具体化子类),而右边

C++设计模式编程中的迭代器模式应用解析_C 语言

迭代器模式:提供一种方法顺序访问一个聚合对象中个各个元素,而不暴露该对像的内部表示. 迭代器模式应该是最为熟悉的模式了,最简单的证明就是我在实现组合模式.享元模式.观察者模式中就直接用到了 STL 提供的迭代器来遍历 Vector 或者 List数据结构. 迭代器模式也正是用来解决对一个聚合对象的遍历问题,将对聚合的遍历封装到一个类中进行,这样就避免了暴露这个聚合对象的内部表示的可能. 模式的动机: (1)一个聚合对象,如一个列表(List)或者一个集合(Set),应该提供一种方法来让别人可以访

详解C++设计模式编程中建造者模式的实现_C 语言

建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示.这是建造者模式的标准表达,不过看着让人迷惑,什么叫构建和表示的分离?一个对象使用构造函数构造之后不就固定了,只有通过它方法来改变它的属性吗?而且还要同样的构建过程搞出不同的表示,怎么可能呢?多写几个构造函数? 其实多写几个构造函数,根据不同参数设置对象不同的属性,也可以达到这样的效果,只是这样就非常麻烦了,每次要增加一种表示就要添加一个构造函数,将来构造函数会多得连自己都不记得了,这违背了开放-封闭的原则. 要

JavaScript 中词法作用域、闭包与跳出闭包详解

Lexical Scope:词法作用域 functions are executed using the scope chain that was in effect when they were defined 一般来说,在编程语言里我们常见的变量作用域就是词法作用域与动态作用域(Dynamic Scope),绝大部分的编程语言都是使用的词法作用域.词法作用域注重的是所谓的Write-Time,即编程时的上下文,而动态作用域以及常见的this的用法,都是Run-Time,即运行时上下文.词法作

解析C++编程中如何使用设计模式中的状态模式结构_C 语言

作用:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类. UML图如下: State类,抽象状态类,定义一个接口以封装与Context的一个特定状态相关的行为. ConcreteState类,具体状态,每一个子类实现一个与Context的一个状态相关的行为. Context类,维护一个ConcreteState子类的实例,这个实例定义当前的状态. 状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况.把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把

详解C++设计模式编程中责任链模式的应用_C 语言

职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止. 其思想很简单,比如考虑员工要求加薪.公司的管理者一共有三级,总经理.总监.经理,如果一个员工要求加薪,应该向主管的经理申请,如果加薪的数量在经理的职权内,那么经理可以直接批准,否则将申请上交给总监.总监的处理方式也一样,总经理可以处理所有请求.这就是典型的职责链模式,请求的处理形成了一条链,直到有一个对象处理请求.给出这个例子的UML图.