如何使用 bison/flex 解析 C 代码头文件中自定义数据类型的类型?

How do I parse the type of custom defined datatypes in header file of a C code using bison/flex?

我在学习bison/flex。我用 bison/flex 成功解析了一个简单的 C 代码文件。 现在我想知道如何使用 bison/flex 解析测试 c 代码中包含的头文件。它能做到吗?

为了简单起见,我附上了示例代码以说明我的问题。

这是包含头文件 (.h) 的测试文件。

test.c 其中包含头文件 header.h

#include <stdio.h>
#include "header.h"

int main (int c, int b) 
{
    bigNumber a;                       /* I wanted that when parser come across to 
                                          "bigNumber" then it knows the datatype of 
                                          "bigNumber" and print its 
                                          type as defined in "header.h" */
    while ( 1 ) 
    {

    newData d;                            /* Same should happen to "newData" also */

    }
}

header.h

#define newData int
#define bigNumber double

lexer.l

%{

#include <stdio.h>
#include <string.h>
#include "c.tab.h"

%}

alpha [a-zA-Z]
digit [0-9]

%%

[ \t]                           { ; }
[ \n]                           { yylineno = yylineno + 1;}
int                             { return INT; }
float                           { return FLOAT; }
char                            { return CHAR; }
void                            { return VOID; }
double                          { return DOUBLE; }
for                             { return FOR; }
while                           { return WHILE; }
if                              { return IF; }
else                            { return ELSE; }
printf                          { return PRINTF; }
struct                          { return STRUCT; }
^"#include ".+                  { ; }
{digit}+                        { return NUM; }
{alpha}({alpha}|{digit})*       { return ID; }
"<="                            { return LE; }
">="                            { return GE; }
"=="                            { return EQ; }
"!="                            { return NE; }
">"                             { return GT; }
"<"                             { return LT; }
"."                             { return DOT; }
\/\/.*                          { ; }
\/\*(.*\n)*.*\*\/               { ; }
.                               { return yytext[0]; }

%%

野牛文件 (c.y)

%{
#include <stdio.h>
#include <stdlib.h>

#include"lex.yy.c"
#include<ctype.h>
int count=0;

extern FILE *fp;

%}

%token INT FLOAT CHAR DOUBLE VOID
%token FOR WHILE
%token IF ELSE PRINTF
%token STRUCT
%token NUM ID
%token INCLUDE
%token DOT

%right '='
%left AND OR
%left '<' '>' LE GE EQ NE LT GT
%%

start
    : Function
    | Declaration
    ;

/* Declaration block */
Declaration
    : Type Assignment ';'
    | Assignment ';'
    | FunctionCall ';'
    | ArrayUsage ';'
    | Type ArrayUsage ';'
    | StructStmt ';'
    | error
    ;

/* Assignment block */
Assignment
    : ID '=' Assignment
    | ID '=' FunctionCall
    | ID '=' ArrayUsage
    | ArrayUsage '=' Assignment
    | ID ',' Assignment
    | NUM ',' Assignment
    | ID '+' Assignment
    | ID '-' Assignment
    | ID '*' Assignment
    | ID '/' Assignment
    | NUM '+' Assignment
    | NUM '-' Assignment
    | NUM '*' Assignment
    | NUM '/' Assignment
    | '\'' Assignment '\''
    | '(' Assignment ')'
    | '-' '(' Assignment ')'
    | '-' NUM
    | '-' ID
    |   NUM
    |   ID
    ;

/* Function Call Block */
FunctionCall 
    : ID'('')'
    | ID'('Assignment')'
    ;

/* Array Usage */
ArrayUsage 
    : ID'['Assignment']'
    ;

/* Function block */
Function
    : Type ID '(' ArgListOpt ')' CompoundStmt
    ;

ArgListOpt
    : ArgList
    |
    ;

ArgList
    : ArgList ',' Arg
    | Arg
    ;

Arg
    : Type ID
    ;

CompoundStmt
    : '{' StmtList '}'
    ;

StmtList
    : StmtList Stmt
    |
    ;

Stmt
    : WhileStmt
    | Declaration
    | ForStmt
    | IfStmt
    | PrintFunc
    | ';'
    ;

/* Type Identifier block */
Type
    : INT
    | FLOAT
    | CHAR
    | DOUBLE
    | VOID
    ;

/* Loop Blocks */
WhileStmt
    : WHILE '(' Expr ')' Stmt
    | WHILE '(' Expr ')' CompoundStmt
    ;

/* For Block */
ForStmt
    : FOR '(' Expr ';' Expr ';' Expr ')' Stmt
    | FOR '(' Expr ';' Expr ';' Expr ')' CompoundStmt
    | FOR '(' Expr ')' Stmt
    | FOR '(' Expr ')' CompoundStmt
    ;

/* IfStmt Block */
IfStmt 
    : IF '(' Expr ')' Stmt
    ;

/* Struct Statement */
StructStmt 
    : STRUCT ID '{' Type Assignment '}'
    ;

/* Print Function */
PrintFunc 
    : PRINTF '(' Expr ')' ';'
    ;

/*Expression Block*/
Expr
    :
    | Expr LE Expr
    | Expr GE Expr
    | Expr NE Expr
    | Expr EQ Expr
    | Expr GT Expr
    | Expr LT Expr
    | Assignment
    | ArrayUsage
    ;

%%

int main(int argc, char *argv[])
{
    yyin = fopen(argv[1], "r");

   if(!yyparse())
        printf("\nParsing complete\n");
    else
        printf("\nParsing failed\n");

    fclose(yyin);
    return 0;
}

yyerror(char *s) {
    printf("%d : %s %s\n", yylineno, s, yytext );
}

int yywrap()
{
    return 1;
}

应该在 lexer (.l) 和 bison (.y) 文件中进行哪些修改,以便在解析 c 文件时,如果该 c 文件包含一些头文件,那么它会转到该头文件并读取它 return 到原始测试 c 文件,如果自定义数据类型存在,那么它将从头文件中知道它的数据类型并打印它。

有可能吗? 我必须进行哪些修改? 谢谢

Flex 有一个特性,可以相对容易地处理诸如 C 的 #include 指令之类的事情。 flex manual chapter on multiple input buffers 中对其进行了详细描述,并附有代码示例,您应该参阅该文档以获取准确的详细信息。 (我在这个答案的末尾放了一些示例代码。)

在 flex 手册中,扫描器本身识别 #include 指令并透明地处理包含;解析器永远不会看到该指令。这具有一定的吸引力;解析器只需要解析一个标记流,词法分析器全权负责生成标记流,包括从包含的文件中读取。

但是正如您的 header.h 所示,处理 #include 指令并不是所需要的全部。要真正解析 C,您需要实现 C 预处理器,或者至少实现您关心的那么多。这包括能够 #define 宏,还包括用宏定义替换程序中对宏的任何使用。这是一个复杂得多的过程。这还不是全部,因为预处理器还允许条件包含(#ifdef#if 等)。为了实现 #if 指令,您需要能够解析和评估任意算术表达式(没有变量但有宏替换),最好通过调用 C 表达式解析器来完成。

有多种方法可以构建包含预处理器的解决方案。一种可能性是在词法扫描器中实现整个预处理器,与此答案中包含的示例代码一致。然而,从该示例代码中也可以看出,这样的实现会非常令人恼火,因为它基本上涉及为每个预处理器指令编写一个状态机,而使用解析器生成器可以更容易地完成这些工作。

所以第二种可能性是将预处理器嵌入到 C 解析器中。但这将需要从解析器到词法分析器的大量通信,由于解析通常不与词法分析同步这一事实而变得复杂(因为解析器通常已经读取了一个在解析器操作执行时尚未解析的先行标记).例如,如果宏定义映射保存在解析器中,则解析器必须能够将替换标记推入输入流,以便随后在先行标记之前读取它们。

另一种可能性是将预处理器作为第三个组件放在词法分析器和解析器之间。 Bison 可以生成“推送解析器”,其中使用每个连续的令牌调用解析器,而不是每次需要令牌时都调用 yylex。这是与宏预处理器集成的更方便的接口。在这个模型中,预处理器可以作为一个单独的 bison 生成的解析器来实现,它以正常的方式从词法分析器读取标记,并使用 push API.[=22= 一次一个地将它们提供给 C 解析器]

完整的预处理器实现不像 C 编译器那么复杂。但这不是可以用 Stack Overflow 答案中的几段来概括的东西。

所以我在这里能做的最好的事情就是为 #include 指令的缓冲区状态堆栈实现提供一个简单的实现(改编自 flex 手册)。我假设您熟悉 (f)lex“开始条件”,它用于构建用于解析预处理器指令的简单状态机。如果不是,请参阅 the previous chapter of the flex manual。)

%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
%}

%x PP PP_INCLUDE PP_INCLUDE_SKIP
%%
  /* If a line starts with a #, handle it as a preprocessor directive */
^[[:blank:]]*#     { begin(PP); }
  /* The rest of your rules go here */
  /* ... */
 
<PP>{
  /* This state parses only the first word of the directive. */
  [[:blank:]]+                        ; /* Ignore whitespace */
  "/*"[^*]*[*]+([^*/][^*]*[*]+)*"/"   ; /* Also ignore comments */
  include   { begin(PP_INCLUDE); }      /* include directive */
      /* Handle other preprocessor directives here */
  \n        { begin(INITIAL); }         /* null directive does nothing */
  .+        { yyerror("Invalid preprocessor directive"); }
}
<PP_INCLUDE>{
  /* This state parses and handles #include directives */
  ["][^"]+["]  |
  [<][^>]+[>]  { yytext[yylen - 1] = 0;
                 FILE* incl = find_include_file(yytext + 1);
                 if (incl) {
                   yyin = incl;
                   yypush_buffer_state(yy_create_buffer(yyin, YY_BUF_SIZE));
                   BEGIN(INITIAL);
                 }
                 else {
                   fprintf(stderr, "Could not find include file %s\n", yytext + 1);
                   /* You might want to stop the parse instead of continuing. */
                   BEGIN(PP_INCLUDE_SKIP);
                 }
               }
<PP_INCLUDE_SKIP,PP_INCLUDE>{
  /* PP_INCLUDE_SKIP is used to ignore the rest of the preprocessor directive,
   * producing an error if anything is on the line other than a comment.
   */
  [[:blank:]]+                        ; /* Ignore whitespace */
  "/*"[^*]*[*]+([^*/][^*]*[*]+)*"/"   ; /* Also ignore comments */
  .+|\n        { yyerror("Invalid #include directive");
                 BEGIN(INITIAL);
               }
}
<*><<EOF>>  { yypop_buffer_state();
              /* The buffer stack starts with a buffer reading from yyin.
               * If the EOF was found in the initial input file, the stack will
               * be empty after the pop, and YY_CURRENT_BUFFER will be NULL. In
               * that case, the parse is finished and we return EOF to the caller.
               * Otherwise, we need to  skip the rest of the #include directive
               * and continue producing tokens from where we left off. 
               */
              if (YY_CURRENT_BUFFER)
                BEGIN(PP_INCLUDE_SKIP);
              else
                return 0;
            }