举个例子:在上面的C/C+源代码分类中就有“语句”类,那么语句其实还 可以细分为空语句、简单语句和复合语句三种,而且简单语句和符合语句也还 要通过其他的标记(标识符,运算符等等)进一步表达。 5.第四步:保存信息 当所有的类以及子类都被标记表达出来之后,Lex和Yacc程序也可以说完成 了,但是这仅仅只是在程序内部将文本分析完成了,对于我们人来说并没有什 么实际的作用,我们最最希望的就是能够将这些分析出来的信息保存为另外 种方便阅读和理解的方 式。因此就需要自己另外设计数据结 勾来 保存这些信 了,通常的情况就是为每一个分类设计一个C+类,这样就可以将文本的内容 以及结构信息完整的保存下来啦。 通常的做法就是将这些内容和结构信息以简单的文本形式直接输出,实际 上真正的应用还需要对这些信息进行认真的处理之后再输出。常见的应用有: 语法高亮,流程图自动生成,VC中的类浏览,从C+源代码自动重新生成M 文档,从源程序的注 自动生成程序 文档(javadoc,doxygen 等都是将分 析出来的结构信息和内容深入处理之后才输出的。呵呵,不过当您学会了Lx 和Yacc之后,上面的这些应用对于您来说也不是什么大不了的事情啦:) 6.总结 从上面的讨论可以看出“分类”这一步是非常重要的步骤,占用的开发时间 也 非常多的,但是为了保证开发的正确性以及能够保质保量的完成任务,就 要认真的重视这一步骤的重要性,多花些时间也是值得的。 好了,在这一章里面我讨论了开发Lex和Yacc的一般步骤,但是算不上特 别详细,因为本类文章主要考虑的是一个应用问题,强调的是应用,对于那些特 别理论的东西我就在这里不多讲了,如果需要深入的了解可以参看编译原理相 关书籍 在后续的章节里面将会详细的分析C/C+源文档,采用的方法都是这里所陈 述的方法和步骤,如果在后续的章节中发现不太理解的地方可以参看这一章。 另外还需要格外说明的就是,因为我们分析的是C/C++源代码,所以这里的 分类就已经完成了,如果不太清楚可以参看C/C+语法说明。在后续的文档中 将会按照问题的需要来组织文档的结构了:)敬请关注!
举个例子:在上面的 C/C++源代码分类中就有“语句”类,那么语句其实还 可以细分 为空语句、简单语句和复合语句三种,而且简单语句和符合语句也还 要通过其他的 标记(标识符,运算符等等)进一步表达。 5. 第四步:保存信息 当所有的类以及子类都被标记表达出来之后,Lex 和 Yacc 程序也可以说完成 了,但是 这仅仅只是在程序内部将文本分析完成了,对于我们人来说并没有什 么实际的作用 ,我们最最希望的就是能够将这些分析出来的信息保存为另外一 种方便阅读和理解 的方式。因此就需要自己另外设计数据结构来保存这些信息 了,通常的情况 就是为每一个分类设计一个 C++类,这样就可以将文本的内容 以及结构信息完整的保 存下来啦。 通常的做法就是将这些内容和结构信息以简 单的文本形式直接输出,实际 上真正的 应用还需要对这些信息进行认真的处理之后再输出。常见的应用有: 语法高亮,流 程图自动生成,VC 中的类浏览,从 C++源代码自动重新生成 UML 文档,从源程序的注 释自动生成程序文档(javadoc,doxygen)等等都是将分 析出来的结构信息和内容 深入处理之后才输出的。呵呵,不过当您学会了 Lex 和 Yacc 之后,上面的这些应用 对于您来说也不是什么大不了的事情啦:) 6. 总结 从上面的讨论可以看出“分类”这一步是非常重要的步骤,占用的开发时间 也是 非常多的,但是为了保证开发的正确性以及能够保质保量的完成任务,就 需要认 真的重视这一步骤的重要性,多花些时间也是值得的。 好了,在这一章里面我讨论了开发 Lex 和 Yacc 的一般步骤,但是算不上特 别详细,因为本类文章主要考虑的是一个应用问题,强调的是应用,对于那些特 别理论的 东西我就在这里不多讲了,如果需要深入的了解可以参看编译原理相 关书籍。 在后续的章节里面将会详细的分析 C/C++源文档,采用的方法都是这里所陈 述的 方法和步骤,如果在后续的章节中发现不太理解的地方可以参看这一章。 另外还需要格外说明的就是,因为我们分析的是 C/C++源代码,所以这里的 分类就 已经完成了,如果不太清楚可以参看 C/C++语法说明。在后续的文档中 将会按照问 题的需要来组织文档的结构了:) 敬请关注!
6、解析C/C+包含文件 摘要 在这一章里面将要涉及到处理C/C+的包含宏的解析。也就是说要从一大申 C/C+艹包含文件的声明中提取出文件名,以及相互依赖关系等等。实际上在这 章里面使用的Lex和Yacc技术也是非常重要的,这些都会在本章中进行详细讲 解 我门知首对干C/C++句含文件声明是为程序提优了一些库存的功能,闲出存 在 中依赖关系,如果把这种依赖关系表达成为Makefile的形式 么就可以 自动生成Makefile。在这一章里面并不会实现自动生成Makefile的功能,而 是仅仅解析出所有的包含文件名,并记录下来。 1.分析 我们知首CC++中存在两种形式的句含立件,一种是用“”句含的头文件 种是“”包含的头文件,这两种不同的形式表达了头文件的不同的搜索方式 另外还需要注意的是:这两种方式包含的都是磁盘上存在的文件名。也就是说 只要是磁盘上存在的文件名都可以包含的,都是合法的,因而C/C+里面存在 的有扩展名的头文件和没有扩展名的头文件都是合法的。并且还需要注意的是 C/C+包含的头文件是可以续行的。 因而总结起来需要做到如下的儿件事情 1.处理“◇”和“"”两种包含方式 2.处理文件名 3.处理续行 2.Lex文件 %{ #include“main.hpp"/在其中保存了记录头文件所需要的所有数据结构 #include"frame.tab.h"/由Yacc自动生成的所有标记声明,实际上都是C extern"C"{
6、解析 C/C++包含文件 摘要 在这一章里面将要涉及到处理 C/C++的包含宏的解析。也就是说要从一大串 C/C++ 包含文件的声明中提取出文件名,以及相互依赖关系等等。实际上在这一 章里面 使用的 Lex 和 Yacc 技术也是非常重要的,这些都会在本章中进行详细讲 解。 我们知道对于 C/C++包含文件声明是为程序提供了一些库存的功能,因此存 在一种依赖 关系,如果把这种依赖关系表达成为 Makefile 的形式,那么就可以 自动生成 Makefile 。在这一章里面并不会实现自动生成 Makefile 的功能,而 是仅仅解析出所有的包含文 件名,并记录下来。 1. 分析 我们知道 C/C++中存在两种形式的包含文件,一种是用“<>”包含的头文件, 一种是“""”包含的头文件,这两种不同的形式表达了头文件的不同的搜索方式。 另外还需要注意的是:这两种方式包含的都是磁盘上存在的文件名。也就是说, 只 要是磁盘上存在的文件名都可以包含的,都是合法的,因而 C/C++里面存在 的有扩展 名的头文件和没有扩展名的头文件都是合法的。并且还需要注意的是 C/C++包含的头 文件是可以续行的。 因而总结起来需要做到如下的几件事情: 1. 处理“<>”和“""”两种包含方式 2. 处理文件名 3. 处理续行 2. Lex 文件 %{ #include "main.hpp"// 在其中保存了记录头文件所需要的所有数据结构 #include "frame.tab.h"// 由 Yacc 自动生成的所有标记声明,实际上都是 C 宏 extern "C"{
int yywrap(void): int yylex(void) %x_INCLUDE %x_INCLUDE_FILE_ 器 “#[\t]*"include" BEGIN_INCLUDE_:/∥进入INCLUDE状态 yylval.clear0:/需要将所有的 Include值初始化 return INCLUDE:/∥返回INCLUDE标i记 <_INCLUDE_>[\] BEGIN_INCLUDE_FILE;/进入 INCLUDE_FILE状态 return*yytext:/∥返回引号或者尖括号 <INCLUDE_FILE_>[\]*( yylval.headerfile+=yytext:/记录头文 件字符串 return HEADERFILE;/∥返回头文件标记
int yywrap(void); int yylex(void); } %} %x _INCLUDE_ %x _INCLUDE_FILE_ %% "#"[ \t]*"include" { BEGIN _INCLUDE_;// 进入_INCLUDE_状态 yylval.clear();// 需要将所有的 Include 值初始化 return INCLUDE;// 返回INCLUDE标记 } <_INCLUDE_>[\"|<] { BEGIN _INCLUDE_FILE_;// 进入 _INCLUDE_FILE_状态 return *yytext; // 返回引号或者尖括号 } <_INCLUDE_FILE_>[^\">]* { yylval.headerfile+=yytext;// 记录头文 件字符串 return HEADERFILE;// 返回头文件标记 }
<INCLUDE FILE >[\> BEGIN INITIAL;/∥恢复到初始状态,默认 状态 return *yytext:/返回引号或者尖括号 [\t] :/∥对于额外的空白都不处理直接扔掉 解 int yywrap(void) return 1:/∥只处理一个输入文件 3.Yacc文件 #include〈iostream #include "main.hpp" #define YYDEBUG0/∥将这个变量设置为1则表示启动Yacc的调试功能 extern "C" void yyerror(const char *s): extern int yylex(void): std:vector<Include>g Includes:/用来记录所有的包含声明 Include *g_pInclude:/∥用来保存新增的包含声明信息的指针
<_INCLUDE_FILE_>[\"|>] { BEGIN INITIAL;// 恢复到初始状态,默认 状态 return *yytext;// 返回引号或者尖括号 } [ \t\n] ;// 对于额外的空白都不处理直接扔掉 %% int yywrap(void) { return 1;// 只处理一个输入文件 } 3. Yacc 文件 %{ #include <iostream> #include "main.hpp" #define YYDEBUG 0 // 将这个变量设置为 1 则表示启动 Yacc 的调试功能 extern "C"{ void yyerror(const char *s); extern int yylex(void); } std::vector<Include> g_Includes;// 用来记录所有的包含声明 Include *g_pInclude;// 用来保存新增的包含声明信息的指针
%token INCLUDE %token HEADERFILE 吸 program:/empty*/ |program include_preprocess/∥用这种递归的方式从有限的标i记表 达出无限的内容 include_preprocess: INCLUDE'<HEADERFILE'>' /升注意这里的$3,实际上就是上面的标记的第三个的意思 /∥因为yy1val被声明为Include结构,参见main.hpp文件 /因而每个标记都是Include结构类型。 g_Includes.push_back(Include()); g_pInclude =&g_Includes.back(): g_pInclude->clear();/初始化 g pInclude->headerfile=$3.headerfile:/可以证明$3 的类型就是Include类型 g pInclude->is angle =true:/是尖括号 g pInclude->is_quotation=false:/不是双g引号 INCLUDE'HEADERFILE'\
%} %token INCLUDE %token HEADERFILE %% program:/* empty */ | program include_preprocess // 用这种递归的方式从有限的标记表 达出无限的内容 ; include_preprocess: INCLUDE '<' HEADERFILE '>' { // 注意这里的$3,实际上就是上面的标记的第三个的意思 // 因为 yylval 被声明为 Include 结构,参见 main.hpp 文件 // 因而每个标记都是 Include 结构类型。 g_Includes.push_back(Include()); g_pInclude = &g_Includes.back(); g_pInclude->clear();// 初始化 g_pInclude->headerfile = $3.headerfile;// 可以证明$3 的类型就是Include类型 g_pInclude->is_angle = true;// 是尖括号 g_pInclude->is_quotation = false;// 不是双引号 } | INCLUDE '\"' HEADERFILE '\