上周一直在设计语法层到语意层的转换模块。
在目前我参与设计的这个系统里,语法层面和语意层面采用的是两套不同的数据结构的,为了便于描述。
我将语法层面的数据结构称之为GDB(Grammar Database),将语意层面对应的数据结构称之为
SDB(Semantic Database)。引入GDB的主要目的是为了解决文本形式的源文件描述到易于程序处理
的描述形式的转换。具体来说,文本形式的源文件描述是human readable的,而GDB则是计算机处理
模块readable的。
在具体的设计里,几乎是每个语法元素都会有一个对应的GDB类,比如,条件分支语句对应于
GDBBranchStmt,Case语句对应于GDBCaseStmt,二元表达式对应于GDBBinaryExpr,等等。
由于GDB在层次关系上跟语法描述靠得比较近,生成GDB的动作也就比较直接,在实现上基本上是在语
法规则所对应的语义动作里直接加上顺序创建GDB相关对象的代码就可以了。
在系统设计的初始阶段,我的经理曾经建议过GDB不要跟语法描述靠得那么近,而是可以尽可能多一些
抽象和变换,以便于后续模块的处理。但是考虑了一段时间,我还是决定采用目前的这种跟语法描述靠得
比较近的设计方案,主要是基于以下两点考虑:
1。一个大系统,不同的模块往往需要有不同的开发人员专门来进行开发,扩展,维护,而不同的模块
对开发人员的技能要求也有不同的侧重点,比如语法处理模块可能会要求开发人员对编译原理的前端部
分,Lex/Yacc比较熟悉的;而语意处理模块则可能要求开发人员对语言所针对的问题域有着比较深入的
了解,比如脚本编程语言的具体应用场景,硬件描述语言所描述的电路行为;而编译优化模块则要求开
发人员对编译优化算法,图论,布尔代数的一些东西有较深入的掌握。既然不同的模块的知识背景跨度较
大,也就不能要求一个具体模块的开发人员对所有环节所需的知识技能都有着不错的掌握。所以,针对不
同的模块设计相应的数据结构,适当的引入中间层次来隔离不同模块之间的信息干扰就是很有必要的了。
GDB的设计也正是为了让语法模块的开发人员能够较少地考虑后续模块的内容,可以专注于语法方面的
开发,维护工作。
2。GDB描述很适合于语法层面上的处理,比如说,跟语法规则对应,便于编写语义动作,再比如
说,因为跟语法层面靠得比较近,也便于编写Dumper工具类,从GDB中反向dump出源文件描述,以
协助调试。但是GDB并不适用于语意层面以及编译优化层面的处理,在GDB里包含了大量的冗余信息是
语法处理模块的后续模块所不需要的,比如说,一些定义语句;在GDB里也有相当一部分信息是以比
较原始的,靠近语法描述的形式存放的,需要进行过再处理之后,才适合于后续模块使用,比如说,常量
表达式。为了解决这个问题,我们就引入了SDB这个数据结构,SDB关注的重点是语意处理,具体来
说,包括语意校验以及为数据流,控制流图的生成提供适宜的输入.
引入了GDB和SDB之后,设计一个GDB到SDB的转换模块就很必要了。
GDB到SDB转换的设计方案,算上我之前已经部分实现的,一共有过四个方案。
方案1,不引入真正的SDB数据结构,而是在GDB上包装一层Adaptor,在需要使用SDB的场合,通
过这层Adaptor来访问GDB中的内容,Adaptor会完成过滤,数据再处理的工作。我一开始采用的就是
这个方案,实现了一多半以后,发现存在诸多的不足,最终放弃,并思考了下面的设计方案。这个方案的
好处是没有引入多少额外的数据结构,一定程度上可以复用GDB中的内容,只在必要的时候才增加
Adaptor的wrapper,但是缺点则是将对两个模块的处理混在一起,产生较强的耦合关系。直观上来说,
就是在两个模块之间产生了过多的交互关系。
方案2,3,4则都引入了独立的SDB数据结构了。
方案2,直接从GDB中产生SDB,我称之为on-the-fly的转换方式。这个方案对内存的开销较少,因
为是直接从GDB中生成SDB,没有太多额外的内存消耗,但是有一个很大的弊端就是在SDB的生成过程
中可能会需要访问之前已经处理完的某个定义的描述,而被处理完的该描述则是以SDB的形式存放的,
而待生成的数据对象则是GDB形式的,这就产生了两种异构数据对象的混用,模块之间的耦合性过强了
一些。
方案3,先从GDB中Clone一份出来,再从这份Clone的实体产生SDB。
同样存在方案2中的问题
方案4,先从GDB中Clone一份出来,再对这份Clone实体进行处理,然后才从中提取信息,生成
SDB。
这个方案解决了方案2,3中存在的问题,但是在内存占用以及转换效率上略逊了一筹。
权衡了一下利弊,最终我还是选择了方案4来实现GDB到SDB的转换模块。
在目前我参与设计的这个系统里,语法层面和语意层面采用的是两套不同的数据结构的,为了便于描述。
我将语法层面的数据结构称之为GDB(Grammar Database),将语意层面对应的数据结构称之为
SDB(Semantic Database)。引入GDB的主要目的是为了解决文本形式的源文件描述到易于程序处理
的描述形式的转换。具体来说,文本形式的源文件描述是human readable的,而GDB则是计算机处理
模块readable的。
在具体的设计里,几乎是每个语法元素都会有一个对应的GDB类,比如,条件分支语句对应于
GDBBranchStmt,Case语句对应于GDBCaseStmt,二元表达式对应于GDBBinaryExpr,等等。
由于GDB在层次关系上跟语法描述靠得比较近,生成GDB的动作也就比较直接,在实现上基本上是在语
法规则所对应的语义动作里直接加上顺序创建GDB相关对象的代码就可以了。
在系统设计的初始阶段,我的经理曾经建议过GDB不要跟语法描述靠得那么近,而是可以尽可能多一些
抽象和变换,以便于后续模块的处理。但是考虑了一段时间,我还是决定采用目前的这种跟语法描述靠得
比较近的设计方案,主要是基于以下两点考虑:
1。一个大系统,不同的模块往往需要有不同的开发人员专门来进行开发,扩展,维护,而不同的模块
对开发人员的技能要求也有不同的侧重点,比如语法处理模块可能会要求开发人员对编译原理的前端部
分,Lex/Yacc比较熟悉的;而语意处理模块则可能要求开发人员对语言所针对的问题域有着比较深入的
了解,比如脚本编程语言的具体应用场景,硬件描述语言所描述的电路行为;而编译优化模块则要求开
发人员对编译优化算法,图论,布尔代数的一些东西有较深入的掌握。既然不同的模块的知识背景跨度较
大,也就不能要求一个具体模块的开发人员对所有环节所需的知识技能都有着不错的掌握。所以,针对不
同的模块设计相应的数据结构,适当的引入中间层次来隔离不同模块之间的信息干扰就是很有必要的了。
GDB的设计也正是为了让语法模块的开发人员能够较少地考虑后续模块的内容,可以专注于语法方面的
开发,维护工作。
2。GDB描述很适合于语法层面上的处理,比如说,跟语法规则对应,便于编写语义动作,再比如
说,因为跟语法层面靠得比较近,也便于编写Dumper工具类,从GDB中反向dump出源文件描述,以
协助调试。但是GDB并不适用于语意层面以及编译优化层面的处理,在GDB里包含了大量的冗余信息是
语法处理模块的后续模块所不需要的,比如说,一些定义语句;在GDB里也有相当一部分信息是以比
较原始的,靠近语法描述的形式存放的,需要进行过再处理之后,才适合于后续模块使用,比如说,常量
表达式。为了解决这个问题,我们就引入了SDB这个数据结构,SDB关注的重点是语意处理,具体来
说,包括语意校验以及为数据流,控制流图的生成提供适宜的输入.
引入了GDB和SDB之后,设计一个GDB到SDB的转换模块就很必要了。
GDB到SDB转换的设计方案,算上我之前已经部分实现的,一共有过四个方案。
方案1,不引入真正的SDB数据结构,而是在GDB上包装一层Adaptor,在需要使用SDB的场合,通
过这层Adaptor来访问GDB中的内容,Adaptor会完成过滤,数据再处理的工作。我一开始采用的就是
这个方案,实现了一多半以后,发现存在诸多的不足,最终放弃,并思考了下面的设计方案。这个方案的
好处是没有引入多少额外的数据结构,一定程度上可以复用GDB中的内容,只在必要的时候才增加
Adaptor的wrapper,但是缺点则是将对两个模块的处理混在一起,产生较强的耦合关系。直观上来说,
就是在两个模块之间产生了过多的交互关系。
方案2,3,4则都引入了独立的SDB数据结构了。
方案2,直接从GDB中产生SDB,我称之为on-the-fly的转换方式。这个方案对内存的开销较少,因
为是直接从GDB中生成SDB,没有太多额外的内存消耗,但是有一个很大的弊端就是在SDB的生成过程
中可能会需要访问之前已经处理完的某个定义的描述,而被处理完的该描述则是以SDB的形式存放的,
而待生成的数据对象则是GDB形式的,这就产生了两种异构数据对象的混用,模块之间的耦合性过强了
一些。
方案3,先从GDB中Clone一份出来,再从这份Clone的实体产生SDB。
同样存在方案2中的问题
方案4,先从GDB中Clone一份出来,再对这份Clone实体进行处理,然后才从中提取信息,生成
SDB。
这个方案解决了方案2,3中存在的问题,但是在内存占用以及转换效率上略逊了一筹。
权衡了一下利弊,最终我还是选择了方案4来实现GDB到SDB的转换模块。