2.06 使用Flex处理嵌套的包含文件(include files)与起始条件(start condition)

这个程序将处理嵌套包含文件,同时打印出这些文件中每一行的行号。这个程序需要维护一个包含嵌套输入文件和行号的堆栈,在每次遇到一个#include时压栈当前文件和行号等信息,在处理完包含文件后再把它们从堆栈弹出。

此外本程序将使用flex一个很强大的特性——起始条件(start condition),它允许我们指定一个特定时刻哪些模式可以被用来匹配。

起始条件定义:
  1. 起始条件需要在第一部分中声明;

  2. 声明语句的行首不能有空白符

  3. 格式为:%x CONDNAME1或者%s CONDNAME2

    %x表示将CONDNAME1声明为一个独占(exclusive)的起始条件。意味着该条件被激活时,只有该状态的模式才可以进行匹配。

    %s表示将CONDNAME2声明为一个包含(inclusive)的起始条件,它允许未标记为任何条件的模式也可以进行匹配。

起始条件的使用
// 这是Flex的第一部分
%x CONDNAME1
%s CONDNAME2

// 这是Flex的第二部分
%{
#include <stdio.h>
%}

// 这是Flex的第三部分
%%
\" {
	BEGIN(CONDNAME1); // 激活CONDNAME1条件,起始条件周围的圆括号是可选的
}

<CONDNAME1>[^"]*	{ // 仅在CONDNAME1条件激活时,才会使用该模式
	/* eat up the string body ... */
	...
}

"<" {
	BEGIN(CONDNAME2); // 激活CONDNAME2条件,且停止CONDNAME1条件的激活
}

<CONDNAME2>[^>]*	{ // 仅在CONDNAME2条件激活时,才会使用该模式
	/* eat up the string body ... */
	...
}

<INITIAL,CONDNAME1,QUOTE>\.  {  // 仅在INITIAL或CONDNAME1或QUOTE条件激活时,才会使用该模式
	/* handle an escape ... */
    ...
}
            
<*>.|\n     ECHO; // <*> 将匹配任意的起始条件,所以无论在什么情况下,该条规则总能够用于匹配。实际上这是Flex的默认定义的一条规则
%%
// 这是Flex的第四部分

BEGIN动作也可以在规则开头部分的以缩进开头,例如:

%x SPECIAL

%{
int enter_special;
%}

%%
        if ( enter_special ){ // 每当调用yylex()函数且enter_special不为0时,将立刻激活SPECIAL起始条件
            BEGIN(SPECIAL);
        }
%%

注意:词法分析器从起始条件0开始,也被称为INITIAL起始条件。BEGIN(INITIAL)等效于 BEGIN(0)

处理嵌套包含文件

这个程序将处理嵌套包含文件,同时打印出这些文件中每一行的行号。这个程序需要维护一个包含嵌套输入文件和行号的堆栈,在每次遇到一个#include时压栈当前文件和行号等信息,在处理完包含文件后再把它们从堆栈弹出。
Flex代码如下:

/*
// file: 2.06_include_file_and_start_state.y
// 使用Flex处理嵌套的包含文件(include files)与起始条件
// 这个程序将处理嵌套包含文件,同时打印出这些文件中每一行的行号。
// 这个程序需要维护一个包含嵌套输入文件和行号的堆栈,在每次遇到一个`#include `时压栈当前文件和行号等信息,在处理完包含文件后再把它们从堆栈弹出。
// 此外本程序将使用flex一个很强大的特性——起始条件(start condition),它允许我们指定一个特定时刻哪些模式可以被用来匹配。
// */


/* 不使用-lfl定义的默认main函数,使用自定义的main函数,你就不需要链接-lfl了。 */
%option noyywrap

/* 把`IFILE`定义为起始条件,它将在我们寻找`#include`语句中的文件名时被使用。 */
%x IFILE

%{
struct buffstack {
  struct buffstack *prev; /* 上一个文件信息 */
  YY_BUFFER_STATE bufferstate; /* 保存的缓冲区 */
  int lineno; /* 保存的行号 */
  char *filename; /* 文件名 */
  FILE *f; /* 当前文件 */
} *curbstk = 0;

char *curfilename; /* 当前输入文件的名字 */
int newfile(char *fn);
int popfile(void);
%}

%%
^"#"[ \t]*include[ \t]*[\"<]   { // 匹配#include语句,直到双引号或者<
  BEGIN(IFILE); // 宏`BEGIN`用来切换到另外一个起始条件。
}

<IFILE>[^ \t\n\">]+ { // 匹配文件名,直到结束双引号、>或者换行符。 当模式紧随在<起始条件名字>之后(<IFILE>),表示这个模式只在该起始条件激活时才进行匹配。
  { // 当文件名匹配到这个模式时,#include的语句还有剩下的部分没有处理。使用下面简单的循环读完它并忽略它
    int c;
    while((c=input()) && c != '\n');
  }
  yylineno++;
  if(!newfile(yytext)){
    yyterminate(); // Error: no such file, or other failure.
  }
  printf("[INFO]开始读取include文件:%10s作为输入.\n", curbstk->filename);
  BEGIN(INITIAL); // flex本身会定义的`INITIAL`起始条件
}

<IFILE>.|\n { //  处理IFILE起始条件中错误输入的情况
  fprintf(stderr, "行号:%8d 错误的include语法\n", yylineno);
  yyterminate();
}

<<EOF>> { // <<EOF>>是Flex定义的特殊模式,它匹配输入文件的结束。
  printf("[INFO]结束读取include文件:%10s.\n", curbstk->filename);
  if(!popfile()){ // 当文件结束时弹出文件堆栈,如果是最外层文件就结束
    yyterminate();
  }
}

^. { // 在每一行开始时,打印行号
  fprintf(yyout, "%8d %s", yylineno, yytext); // yylineno是Flex提供的记录行号的变量
}

^\n { // 每遇到一个\n,需要把行号+1
  fprintf(yyout, "%8d %s", yylineno++, yytext);
}

\n {
  ECHO;
  yylineno++;
}

. { // 这是Flex定义的默认的规则,其中ECHO是Flex定义的默认输出宏,它会将字符原样输出到yyout。关于ECHO详见2.05,https://blog.csdn.net/weixin_46222091/article/details/105968391
  ECHO;
}
%%
int main(int argc, char ** argv){
  if(argc < 2){
    fprintf(stderr, "need filename\n");
    return 1;
  }
  if(newfile(argv[1])) yylex();
}

int newfile(char *fn){
  FILE *f = fopen(fn, "r");
  struct buffstack *bstk = malloc(sizeof(struct buffstack));

  /* 如果文件打开失败时,退出 */
  if(!f) {
    perror(fn);
    return 0;
  }
  /* 如果没有足够空间时,退出 */
  if(!bstk){
    perror("malloc");
    exit(1);
  }

  /* 记住当前状态 */
  if(curbstk){
    curbstk->lineno = yylineno;
    bstk->prev = curbstk;
  }

  /* 建立当前文件信息 */
  bstk->bufferstate = yy_create_buffer(f, YY_BUF_SIZE);
  bstk->f = f;
  bstk->filename = fn;
  yy_switch_to_buffer(bstk->bufferstate);
  curbstk = bstk;
  yylineno = 1;
  curfilename = fn;
  return 1;
}

int popfile(void){
  struct buffstack *bstk = curbstk;
  struct buffstack *prevbstk;

  if(!bstk){
    return 0;
  }

  /* 删除当前文件信息 */
  fclose(bstk->f);
  yy_delete_buffer(bstk->bufferstate);

  /* 切换回上一个文件 */
  prevbstk = bstk->prev;
  free(bstk);

  if(!prevbstk){
    return 0;
  }

  yy_switch_to_buffer(prevbstk->bufferstate);
  curbstk=prevbstk;
  yylineno = curbstk->lineno;
  curfilename = curbstk->filename;
  return 1;
}

cmake编译该项目:

execute_process(COMMAND
flex -o ${CMAKE_CURRENT_SOURCE_DIR}/2.06_include_file_and_start_state.c
${CMAKE_CURRENT_SOURCE_DIR}/2.06_include_file_and_start_state.y)

add_executable(2.06_include_file_and_start_state
2.06_include_file_and_start_state.c)

目录结构如下:

cmp@t3600:~$ tree ch2/
ch2/
├── 2.06_include_file_and_start_state.y
└── CMakeLists.txt

测试的文件:
1.txt
2.txt
3.txt

代码运行结果:

cmp@t3600:~/ch2$ ./2.06_include_file_and_start_state 1.txt
       1 
[INFO]开始读取include文件:     2.txt作为输入.
       1 
       2 aa
       3 bb
       4 cc
       5 
[INFO]结束读取include文件:     2.txt.
[INFO]开始读取include文件:     3.txt作为输入.
       1 
       2 xx
       3 yy
       4 zz
[INFO]结束读取include文件:     3.txt.
       4 
       5 12
       6 34
       7 56
[INFO]结束读取include文件:     1.txt.

程序源码,参见

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值