摘要
经过前面章节的准备,到目前为止一个完整的C++应用框架已经完整的搭建 起来了。现在的事情就是考虑如何利用这个框架来实现自己的目的功能程 序了。在这一章并不涉及到实际的开发而是先学习一下简单的理论知识。 本章将会根据我的个人开发经验来说明一下开发Lex和Yacc程序的一般开发 步骤,这里的内容也会随着后续的开发逐渐的完善起来,当在后续的开发 中遇到不明白的地方可以回到这一章来看一看,也许就明白了:)
就我在开发Lex和Yacc程序的经验来说,如果要解析一个文件,那么必须经过 下面的步骤才能够写出正确的语法分析文件和词法分析文件:
-
将被解析的文本进行分类。
-
由分类构成program。
-
将类及子类分解为标记。
-
完成了上面的所有步骤之后,我们就已经将被解析文本分解成功了,剩下的 事情就是为保存这些分解出来的信息而准备好一个树形数据结构,在这个时 候使用C++的优势就显现出来了,我们可以借用STL里面的很多容器和算法的 。
将被解析的文本进行分类,实际上是一件非常重要的事情,分类的好坏直接影响 到了后续的开发,分类分的好能够使得保存信息的数据结构简单明了和高效,但 是如果分得不好,可能导致冗余数据结构,大量得冗余信息以及需要全部修改代 码。
先举个实际的例子吧:C/C++源代码。怎样把C/C++源代码中的语法元素用最少并 且能够保存全部信息的数据结构来表示,这本身就是一种挑战。在C/C++的发展历 程中也经历过大大小小的变动,这说明要想对C/C++源代码中的语法元素进行分类 是一件非常烦杂的事情,不过对于我们来说,这些语法元素已经存在了好多年了, 我们不需要从来开始,而是可以直接利用已经存在的知识来加快我们的开发。
C/C++的源代码中的每一个语法元素可以作为一个分类,例如:关键字、预处理宏 、函数、变量、语句、结构体、类、联合体等等。
但是,并不是说这里的开发是一帆风顺的,在本类文档的后续章节中也许会对这里 的类容进行修正,这只能说明我们对于C/C++的语法构成还是存在误区而不是C/C++ 语法本身有问题。对我们熟悉的问题尚且如此,那么对于我们不熟悉的问题更加如 此了,所以后续的开发中对前述代码的修正也就难免了,这就是重构,使得代码越 来越合理,越来越高效。
对于上一步已经做好了的分类,最终的目的就是要用这些分类来成功的构成最终的 C/C++源代码。通过前面的章节的学习,我们知道这里的program表达的就是C/C++ 源代码内容,当然你也可以用你自己喜欢的任何名字,例如“source”,这个就看 个人的喜好了,Lex和Yacc对这个没有特别的要求。
这里我们采用program代表C/C++源代码。上面的C/C++分类:关键字、预处理宏 、函数、变量、语句、结构体、类、联合体等等。在这一步里面就是考虑如何利 用这些分类来构成完整的C/C++源代码。这一步也充满了挑战,但是相对于第一步 来说就容易多了,因为有据可寻啊。我们可以遵循Lex和Yacc规范来一步一步的 将上面的分类组合起来构成C/C++源代码。
常见的问题包括:
-
如何用有限的分类来构成无限数量的文本(C/C++源代码)
-
如何避免移进和规约冲突
-
如何设置标记(tokens)
上面的概念暂时只需要初步的了解,所谓的标记就是能够直接表示文本文档(C/C++ 源代码)其中内容的概念,例如关键字类可以通过int,long,char等等直接表示,那 么int,long,char就是标记的值,标记就是代表这些值的一个标志而已,在Lex和 Yacc中就是用C宏来表示标记的例如用INT宏表示int,LONG宏表示long,CHAR宏表示 char等等,但是INT宏,LONG宏和CHAR宏并不是直接定义为对应的int,long,char 的,而仅仅只是一个整数;另外还需要强调的是这里的INT宏,LONG宏和CHAR宏是自 己定义的,而不是Lex和Yacc内置的,因此可以随心所欲的定义,你完全可以用INT 表示char,LONG表示int,CHAR表示long等,但是这样做并不好。关于这里的概念的 详细内容会在后续的开发中进行详细的解释。
在对文本(C/C++源代码)进行了分类之后能否直接用标记表达出来呢?一部分分类 可以用标记直接表达出来了,还有一部分就不能或者不容易用标记表达出来。对于 这些不能或者不容易用标记表达出来的分类就还需要细分,这样最终的目的就是将 所有的类都能够用标记表达出来。
举个例子:在上面的C/C++源代码分类中就有“语句”类,那么语句其实还可以细分 为空语句、简单语句和复合语句三种,而且简单语句和符合语句也还要通过其他的 标记(标识符,运算符等等)进一步表达。
当所有的类以及子类都被标记表达出来之后,Lex和Yacc程序也可以说完成了,但是 这仅仅只是在程序内部将文本分析完成了,对于我们人来说并没有什么实际的作用 ,我们最最希望的就是能够将这些分析出来的信息保存为另外一种方便阅读和理解 的方式。因此就需要自己另外设计数据结构来保存这些信息了,通常的情况 就是为每一个分类设计一个C++类,这样就可以将文本的内容以及结构信息完整的保 存下来啦。
通常的做法就是将这些内容和结构信息以简单的文本形式直接输出,实际上真正的 应用还需要对这些信息进行认真的处理之后再输出。常见的应用有:语法高亮,流 程图自动生成,VC中的类浏览,从C++源代码自动重新生成UML文档,从源程序的注 释自动生成程序文档(javadoc,doxygen)等等都是将分析出来的结构信息和内容 深入处理之后才输出的。呵呵,不过当您学会了Lex和Yacc之后,上面的这些应用 对于您来说也不是什么大不了的事情啦:)
从上面的讨论可以看出“分类”这一步是非常重要的步骤,占用的开发时间也是 非常多的,但是为了保证开发的正确性以及能够保质保量的完成任务,就需要认 真的重视这一步骤的重要性,多花些时间也是值得的。
好了,在这一章里面我讨论了开发Lex和Yacc的一般步骤,但是算不上特别详细, 因为本类文章主要考虑的是一个应用问题,强调的是应用,对于那些特别理论的 东西我就在这里不多讲了,如果需要深入的了解可以参看编译原理相关书籍。
在后续的章节里面将会详细的分析C/C++源文档,采用的方法都是这里所陈述的 方法和步骤,如果在后续的章节中发现不太理解的地方可以参看这一章。
另外还需要格外说明的就是,因为我们分析的是C/C++源代码,所以这里的分类就 已经完成了,如果不太清楚可以参看C/C++语法说明。在后续的文档中将会按照问 题的需要来组织文档的结构了:) 敬请关注!