【声明】如需复制、传播,请附上本声明,谢谢。原文出处:http://morningspace.51.net/,moyingzz@etang.com
本文为TextFormator Framework的说明文档(原名《TextFormator Framework Introduce》),源码下载请到这里。
萌发编写TextFormator的最初原因和以下两件事情有关:
- 年初的时候曾在csdn的论坛上提过一个问题,内容是询问哪里有好的“代码统计分析工具”,当时并没有得到什么回应。
- 前段时间,一个同事向我展示了他在业余时间编写的一个小工具,其功能是根据一段c++程序生成一个经过着色的html文本。
此后,我一直都在尝试寻找一种通用的解决方案,以尽可能满足包括上面提到的情况在内的多种应用要求。最近幸好有一段相对空闲的时间,从而让我得以将这个想法付诸实现。我用一周的时间,完成了从设计到编码到添加文档注释的全部工作,当然还包括撰写此文^^,这让我重新找回了大学时代的创作激情。记得还是大三时,自己曾经花了3天时间用汇编语言编写了一个可以在文本状态下支持分级菜单操作的“学生档案管理程序”。也许只是一时兴趣的驱使,也许这个简单的框架并不一定具有相当的实用价值,也许确实存在制作精良的代码统计分析工具。但是,编写这个框架,对于我而言,却是一次运用OO设计思想,实践STL的不错体验。并且,将之作为Open Source发布的目的,也是在于“抛砖引玉”,希望对之有兴趣的同仁,可以在此基础上继续发展和改进,这比起现成的工具而言有了更大的灵活性和针对性。
对于框架,如果您有什么好的建议、想法或者问题,敬请与我联系:moying@etang.com
这是一个Open Source的可扩展的应用程序框架,你可以通过对框架代码的扩展以满足不同应用需要。
TextFormator适用于对多种程序设计语言的源代码进行处理(如:C/C++,Java,Pascal,Masm),同时也支持纯文本处理,可以对之进行如下处理:
- 任意形式的格式化输出,例如:生成经过着色的html文本,缩格排版,删除注释行
- 不同形式的代码统计分析,例如:关键字查找,注释行统计,函数统计
- 代码以Open source方式发布,并采用OO设计思想,力求具有相当的灵活性和可扩展性
- 基于C++ STL,力求具有相当的可移植性
原始文本通过字符流的方式读入内存,按行进行解析。解析后的结果字符流,将作为格式化处理/统计处理的输入流。经过一次或多次格式化之后的字符流将被输出到文件或标准输出设备,而经过统计之后的字符流不做输出,相应输出的是某些统计结果。图示如下:
目前而言,这个框架还不甚复杂。尽管如此,在设计时还是下了一些功夫的,这一点可以从稍后的“详细介绍”部分看到。以下是框架的类图总结。从中可以看到,框架的总体结构采用了一种类似Strategy Pattern的组织方式:
抽象类,所有解析过程中所使用的handler都应派生自该类,它定义了此类handler(s)的基本行为。主要是一个accept方法,功能是将传入其中的文本当前行,从当前位置开始,按指定的方式解析。而具体采用何种方式,则是subclass的事情。若解析成功,则将结果置于一个TokenInfo结构体中,然后指向当前行的下一段未解析字串,并返回true;否则返回false,以告知框架将控制权移交给下一个handler。至于如何编写实际的handler,可以参考extend部分的以下诸类:StringParseHandler,NumberParseHandler,OperatorParseHandler,IdentifierParseHandler,WhitespaceParseHandler,CommentParseHandler
另外,ParseHandler中还引入了优先级的概念,具体实现有点类似java中的线程优先级。这么做的理由是依据实际情况而来的:如果分析的是一段代码,以c++/java为例,对于解析注释的handler和解析标识符的handler,前者的优先级显然大过后者,因为当标识符位于注释中时,仍应将之当作注释处理;另外对于解析字符串的handler而言,其优先级又是和解析注释的handler相同的,因为当注释位于字符串内时,仍应将之当作字符串处理,而当字符串位于注释内时,则应当作注释处理。在实际应用时,一般的handler只要使用NORM_PRIORITY即可,而对于处理类似注释和字符串这样的handler则要使用MAX_PRIORITY。此外,框架还预定义了一个特殊的DefaultParseHandler,其作用是当所有其它handler都无法解析当前字串时,就由它来顶替,其优先级被定义为MIN_PRIORITY。在派生你自己的handler时一般不要使用MIN_PRIORITY优先级。DefaultParseHandler的调用机会一般很少,并且其对字串的处理方式也是极其“粗鲁”的,它的调用往往意味着你所拥有的解析现有文本的handler(s)还不够齐全。
对文本逐行逐字解析,解析完成后可以通过getTokensInfoList方法获取解析结果。你可以通过registParseHandler方法和unreigistParseHandler方法在run-time阶段动态的设置各种具体的parse handler,以适时的调整LineParser的行为。比如,你可以将一个原本解析c++程序的LineParser定制成解析汇编程序的LineParser。而实际上,前后是同一个Parser对象。
LineParser在被创建的时候会自动产生一个DefaultParseHandler,解析过程中会按优先级从高到低的顺序调用不同的handler对当前字串进行解析,直至找到一个能处理当前字串的handler,如果所有handler都无法解析当前字串,则交由DefaultParseHandler全权处理。因为每次handler解析完之后,都会修改当前位置“指针”以指向本行的下一段未解析字串,而当该handler无法处理当前字串时是不会修改位置“指针”的,所以DefaultParseHandler的定义并非多余,它的出现是为了避免潜在的死循环,并且使解析过程能够一直进行下去,以尽可能多的得到经过有效解析的信息,即使间或有DefaultParseHandler留下的“未经消化”的结果。
抽象类,所有格式化输出过程中所使用的handler都应派生自该类,它定义了此类handler(s)的基本行为。主要是一个format方法,功能是将传入其中的解析后的字串,按特定方式处理。至于具体方式,则是subclass的事情。处理后的结果直接影响传入其中的字串,所以,当多个handler对同一个字串进行处理时,要注意处理的先后顺序,不同的顺序可能得到不同的结果。
作为FormatHandler的一个附带功能,通过和LineFormator的配合,你可以实现某些代码/文本的分析统计功能。事实上,你只要在handler内部不对传入其中的字串做任何修改动作,而只对其内容进行分析,就可以实现一些很有价值的功能了。比如:统计代码中的注释行,统计某些关键字的出现频率,统计函数个数及长函数的出现频率等等。统计的结果可以放在各自的handler内,extend部分的Count::KeywordCountHandler,Count::CommentCountHandler,以及Count.cpp演示了某些简单的统计功能。
对解析后的文本逐行逐字进行格式化处理,处理完成后可以通过getFormattedLines方法获取处理结果。你可以通过registFormatHandler方法和unreigistFormatHandler方法在run-time阶段动态的设置各种具体的format handler,以适时的调整LineFormator的行为。比如,你可以将一个原本对c++程序进行缩格排版处理的LineFormator定制成产生Html输出的LineFormator。而实际上,前后是同一个Formator对象。
与LineParser不同的一点是,LineFormator允许对同一类解析后的字串注册多个format handler(s),亦即你可以对同一串字符进行多次处理,当然每次处理之后都会影响原有字串的面貌。比如,针对注释,你可以为LineFormator注册一个Indent::NormalFormatHandler,然后再注册一个Htmlize::BodyFormatHandler,这样的结果是,原有文本在经过缩格排版之后才产生html文本,你可以添加任意多的handler,任意组合这些handler,只要这种添加和组合有实际意义。这一特性,给实际应用带来了便利和灵活性。要注意的一点是,框架调用handler(s)的顺序是和你注册它们的先后顺序一致的。extend部分的Htmlize::BodyFormatHandler,Indent::NormalFormatHandler,Indent::WhitespaceFormatHandler,Indent::OperatorFormatHandler,以及Htmilze.cpp,Indent.cpp,IndentHtmilze.cpp演示了上面所述的内容。
Session类的设计模仿了ASP/JSP中的Session功能,当然只是形似(主要是功能相似),还远没有那么复杂。该类的主要作用是为了给各handler之间彼此传递信息提供方便,这包括解析过程中所使用的handler和格式化输出过程中所使用的handler。其内部持有一个map容器,可以通过set方法向map中插入指定key所对应的value,再通过get方法获取该value。采用这种设计是为了减轻框架代码的负担,框架无须具体负责handler之间的信息传递;同时也为日后的扩展带来了方便,你可以在你自己的subclass handler中随意使用Session以应对具体应用。
代表解析过程和格式化输出过程中的上下文环境。框架代码会将诸如当前处理行号,当前处理字串(解析后),当前字串位置等信息传入Context内,以备subclass handler(s)取用。未来的版本中可能会扩充Context所包含的信息。Context内部持有一个session成员。事实上,虽然前面曾经提到,各subclass handler可以通过Session对象彼此传递信息,但handler并不能直接访问到Session,而是需要通过Context的session方法,才能得到实际的Session对象。其实,某种意义上来讲,Session也是一种上下文。但是这种上下文的实际含义在框架代码中是无法预知的,这一点和Context中的其他成员不同。也就是说,Context是一个半开放的对象,在框架力所能及的范围之内,一些明确的稳定的信息不应放在Session里面,它们大可以作为Context的固定成员,与Session平起平坐,因为随着时间的推移,这些信息不会改变,所以就不会被当作hard code。至于其余框架不能确定的易变信息,则交由Session来处理比较合适。
一个文件操作的辅助类,提供了文件读写的功能,在框架的其他地方以及基于框架的具体应用中可以使用到它。
一个文件操作的辅助类,提供了遍历指定目录及其子目录下特定文件的功能,在框架的其他地方以及基于框架的具体应用中可以使用到它。不过目前此类仅限于在Windows平台上使用,使用前需要定义宏__WINDOWS__,具体可见Portability.h文件。
目前,这里包含了如下几个部分:
- 从ParseHandler派生而来的若干解析处理类,包括:
- 解析字符串 StringParseHandler
- 解析数字 NumberParseHandler
- 解析运算符 OperatorParseHandler
- 解析标识符和关键字 IdentifierParseHandler
- 解析空格及制表符 WhitespaceParseHandler
- 解析注释 CommentParseHandler
- 从FormatHandler派生而来的若干格式化处理类,包括:
- 格式化输出Html文本 Htmlize::BodyOutputHandler
- 缩格排版 Indent::NormalOutputHandler,Indent::WhitespaceOutputHandler,Indent::OperatorOutputHandler
- 统计关键字和注释行 Count::KeywordCountHandler,Count::CommentCountHandler
说明:点击链接可以查看对应的源码文件,该文件是经过Htmlize处理之后的html文本
包含一些全局性的类型定义 | |
Portability.h | 包含一些在平台移植时用到的宏定义 |
LineParser.h | 文本解析部件(Line Parser)的.h文件 |
LineParser.cpp | 文本解析部件(Line Parser)的.cpp文件 |
LineFormator.h | 文本格式化部件(Line Formator)的.h文件 |
LineFormator.cpp | 文本格式化部件(Line Formator)的.cpp文件 |
ParseHandler.h | 解析处理器(Parse Handler)的抽象类定义 |
DefaultParseHandler.h | 一个预定义的缺省解析处理器 |
ConcreteParseHandlers.h | 若干解析处理器派生类的定义及实现 |
FormatHandler.h | 格式化处理器(Format Handler)的抽象类定义 |
HtmlFormatHandlers.h | Format Handler的派生类的定义及实现,支持Html格式化输出 |
IndentFormatHandlers.h | Format Handler的派生类的定义及实现,支持缩格排版 |
CountFormatHandlers.h | Format Handler的派生类的定义及实现,支持统计功能 |
Context.h | 定义了解析及格式化输出过程中的上下文背景 |
Session.h | 用于在各Handler(s)之间传递信息的类 |
FileHelper.h | 和文件操作相关的辅助类的.h文件 |
FileHelper.cpp | 和文件操作相关的辅助类的.cpp文件 |
FileFinder.h | 和文件操作相关的辅助类的.h文件 |
FileFinder.cpp | 和文件操作相关的辅助类的.cpp文件 |
Htmlize.cpp | 利用框架,将源代码进行Html格式化输出的演示程序 |
Indent.cpp | 利用框架,对源代码缩格排版的演示程序 |
IndentHtmlize.cpp | 利用框架,对源代码缩格排版后再进行Html格式化输出的演示程序 |
Count.cpp | 利用框架,对源代码进行某些简单统计的演示程序(统计void和for的出现次数,统计注释行的出现次数) |
Batch.cpp | 演示FileFinder的使用方法,与其余演示程序配合使用,可以实现批量文件处理 |
- 关于可移植性
我已先后在MSVC和g++上对框架代码及示例程序做了测试,在MSVC中,还用了P.J. STL和STLport分别做了测试,并改正了一些错误,具体请见随源码所附的ChangeLog。其中,MSVC命令行编译器的版本是12.00.8168 for 80x86,g++命令行编译器的版本是egcs-2.91.57 19980901 (egcs-1.1 release),STLport的版本是4.5 release。目前为止,代码在上述几个平台下的测试是成功的。
- 效率和灵活性的矛盾
该框架在处理效率上并非最优,但与之相比,框架所具有的灵活性和可扩展性更具吸引力,因此在仔细权衡了这对矛盾之后,我选择了后者。
- 目前框架中不甚满意及可以改进的几个地方
- LineFormator在作为统计分析用的时候,某些接口和代码是多余的,虽然这并不影响使用。
- 现有的框架在解析具体文本时,是通过定义ParseHandler的派生类来实现的。这样做的副作用是为了支持新类型文本的解析,需要改动源代码,以添加新的ParseHandler的派生类。另一种可行的方案是定义一个template文件,将与解析新类型文本相关的信息放入其内。这样,每次无需改动源码,而只需修改template文件即可。但是,这样做就需要合理抽取各种程序设计语言的共同特点以形成template,对框架代码的要求,也相应提高了。
- 对于框架扩展的一些建议
- 目前的框架并没有GUI界面,所提供的演示例程也仅是在命令行状态下运行,在将来的扩展中,希望可以加入GUI特性,但是作为扩展,这不应该属于框架的范畴。
- 关于扩展该框架时namespace的命名建议:
- 对于框架之上parse handler的扩展,若适用于多种程序设计语言,则采用形如TextFormator::XXX的命名方式,XXX代表具体的parse handler(比如:TextFormator::StringParseHandler)。若仅适用于一种程序设计语言,则采用形如TextFormator::Language_Name::XXX的命名方式,Language_Name代表语言名称(比如:TextFormator::Pascal::CommentParseHandler)
- 对于某些具体应用,若需求相对稳定,可以考虑加入TextFormator的extend部分,并采用形如TextFormator::Util_Name::XXX的命名方式,Util_Name代表应用名称(比如:TextFormator::Htmlize::BodyFormatHandler)