Bison 非终结符在语法中无用,规则在解析器中无用

问题描述

我正在尝试使用 flex-bison 来“从头开始”制作一个编译器。 我试图在网上寻求帮助,但我挖掘的东西并不多 我设法找到了一本书:John Levine 的 flex & bison

它非常有用,但我不知道该怎么做。

这是我的弹性代码

Stack

这是我的野牛文件

%option noyywrap 

%{
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include "parser.tab.h"

    extern FILE *yyin;
    extern FILE *yyout;
    int line_no = 1;

    //the function of lexer analysis. Return the token
    int yylex();
    //error function 
    void yyerror();
    //print statement function
    void print_return(char *token);

%}

%x ML_COMMENT

alphabet    [a-zA-Z]        
digit           [0-9]
alphanumeric    {alphabet}|{digit}
print       [ -~]
underscore  _
identifier      ({alphabet}|{underscore})+({alphanumeric}|{underscore})* 
integer         "0"|[0-9]{digit}*
float_number    "0"|{digit}*"."{digit}+
char        \'{print}\'

%%

"PROGRAM"       {  print_return("PROGRAM"); return PROGRAM}

"%".*           {  print_return("COMMENT"); return COMMENT; }

"BREAK"         {  print_return("BREAK"); return BREAK; }
"VARS"          {  print_return("VARS"); return VARS; }    

"STARTMAIN"     {  print_return("STARTMAIN");  return STARTMAIN; }   
"ENDMAIN"       {  print_return("ENDMAIN"); return ENDMAIN;}

"IF"            {  print_return("IF");  return IF; }
"THEN"          {  print_return("THEN"); return THEN;}
"ELSEIF"        {  print_return("ELSEIF"); return ELSEIF; }
"ELSE"          {  print_return("ELSE"); return ELSE; }
"ENDIF"         {  print_return("ENDIF"); return ENDIF; }

"FOR"           {  print_return("FOR"); return FOR; }
"TO"            {  print_return("TO"); return TO; }
"STEP"          {  print_return("STEP"); return STEP; }
"ENDFOR"        {  print_return("ENDFOR"); return ENDFOR; }

"SWITCH"        {  print_return("SWITCH"); return SWITCH; }
"CASE"          {  print_return("CASE"); return CASE; }
"ENDSWITCH"     {  print_return("ENDSWITCH"); return ENDSWITCH; }

"RETURN"        {  print_return("RETURN"); RETURN; }

"FUNCTION"      {  print_return("FUN"); return FUN; }
"ENDFUNCTION"   {  print_return("ENDFUNCTION"); return ENDFUNCTION; }

"PRINT"         {  print_return("PRINT"); return PRINT; }

"WHILE"         {  print_return("WHILE"); return WHILE;}
"ENDWHILE"      {  print_return("ENDWHILE"); return ENDWHILE;}

";"             {  print_return("QM"); return QM; }
"\n"            {  line_no++; print_return("NEWLINE"); return NEWLINE; }     
"\t"            {  print_return("INDENT"); return INDENT; }     
       
"+="            {  print_return("ADD_ASSIGN"); return ADD_ASSIGN; }        
"-="            {  print_return("SUB_ASSIGN"); return SUB_ASSIGN; }       
"/="            {  print_return("DIV_ASSIGN"); return DIV_ASSIGN; }       
"%="            {  print_return("MOD_ASSIGN"); return MOD_ASSIGN; }     
"--"            {  print_return("DEC_OP"); return DEC_OP; }      
"++"            {  print_return("INC_OP"); return INC_OP; }        
"AND"           {  print_return("AND_OP"); return AND_OP; }      
"OR"            {  print_return("OR_OP"); return OR_OP; }       
"=="            {  print_return("EQ_OP"); return EQ_OP; }      
">="            {  print_return("GE_OP"); return GE_OP; }      
"<="            {  print_return("LE_OP"); return LE_OP; }      
"!="            {  print_return("NE_OP"); return NE_OP; }     
"{"             {  print_return("L_BRACE"); return L_BRACE; }        
"}"             {  print_return("R_BRACE"); return R_BRACE; }        
","             {  print_return("COMMA"); return COMMA; }       

"="             {  print_return("ASSIGN"); return ASSIGN; }       
"("             {  print_return("L_PAR"); return L_PAR; }        
")"             {  print_return("R_PAR"); return R_PAR;}     
"["             {  print_return("L_BRACK"); return L_BRACK; }        
"]"             {  print_return("R_BRACK"); return R_BRACK;}     
"."             {  print_return("DOT"); return DOT; }        
"_"             {  print_return("UNDERscore"); return UNDERscore; }       
"-"             {  print_return("MINUS"); return MINUS; }        
"+"             {  print_return("PLUS"); return PLUS; }       
"*"             {  print_return("MUL"); return MUL; } 
":"             {  print_return("COLON"); return COLON; }

"/"             {  print_return("DIV"); return DIV; }
"<"             {  print_return("LT"); return LT; }
">"             {  print_return("GT"); return GT; }
[ ]             ; 
.               { yyerror("Unkown character"); }

{identifier}    { print_return("ID"); strcpy(yylval.name,yytext);  return IDENTIFIER; } 
{integer}       { yylval.integer_val = atoi(yytext); print_return("INTEGER"); return INTEGER; }
{float_number}  { print_return("FLOAT"); return FLOAT; }
{char}          { print_return("CHAR"); return CHAR; }

%%
/*---------------------------------------------------------------------------------------------------------------------*/
void print_return(char *token)
{
    printf("Token: %s\t\t Line: %d\t\t Text: %s\n",token,line_no,yytext);    
}

我被卡住了,不知道该怎么办。编译器只是不喜欢我的代码

%{

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    #include "print_console.c"

    //pointer to input file of lexer
    extern FILE *yyin; 
    //pointer to output file of lexer
    extern FILE *yyout;
    //line counter
    extern int line_no;
    //reads the input stream generates tokens
    extern int yylex();
    //temporary token save
    extern char* yytext;

    //Function Initilize
    int yylex();
    void yyerror(char *message);

%}

//struct for print_console
%union 
{
    char name[500];
    int integer_val;
}

/* --------------------------------------- TOKENS ---------------------------------------*/
//starting symbol
%start PROGRAM 

%token COMMENT
%token BREAK
%token VARS

%token QM

%token STARTMAIN 
%token ENDMAIN

%token IF
%token THEN
%token ELSEIF
%token ELSE
%token ENDIF

%token FOR
%token TO
%token STEP
%token ENDFOR

%token SWITCH
%token CASE
%token ENDSWITCH

%token RETURN

%token FUNCTION
%token ENDFUNCTION

%token PRINT

%token WHILE
%token ENDWHILE

%token NEWLINE
%token INDENT

%token ADD_ASSIGN
%token SUB_ASSIGN
%token DIV_ASSIGN
%token MOD_ASSIGN
%token DEC_OP
%token INC_OP
%token AND_OP
%token OR_OP
%token EQ_OP
%token GE_OP
%token LE_OP
%token NE_OP
%token L_BRACE
%token R_BRACE
%token COMMA
%token COLON

%token ASSIGN
%token L_PAR
%token R_PAR
%token L_BRACK
%token R_BRACK
%token DOT
%token UNDERscore
%token MINUS
%token PLUS
%token MUL
%token DIV
%token LT
%token GT
%token FLOAT
%token CHAR

%token <name> IDENTIFIER
%token <integer_val> INTEGER

//type for access to $$ 
%type <integer_val> line int_op int_data
%type <name> calc_assignment

%%  

/* --------------------------------------- BNF GRAMMAR ---------------------------------------*/

program: program line;

line:   if_stmt {;} 
        | elseif_stmt {;} 
        | else_stmt {;} 
        | for_statement {;} 
        | function NEWLINE INDENT {;}
        | function NEWLINE indent2 {;}
        | function NEWLINE {;} 
        | function_call {;} 
        | comments NEWLINE {;} 
        | action {;}
        | print NEWLINE {;}
        | switch  NEWLINE  case NEWLINE {;}
        | dictionaries NEWLINE {;} 
    | calc_assignment NEWLINE {;}
        | NEWLINE {;}    ;
/*--------- BREAK -------------*/
break:BREAK QM NEWLINE ;

/*--------- ACTION & indents -------------*/

indent2: INDENT INDENT;
indent3: INDENT INDENT INDENT;
indent4: INDENT INDENT INDENT INDENT;
indent5: INDENT INDENT INDENT INDENT INDENT;

action: INDENT line 
        | indent2 line 
        | indent3 line 
        | indent4 line 
        | indent5 line ;
/*--------- DATA TYPES -------------*/
data_type: CHAR
        | INTEGER 
        | IDENTIFIER;

/*--------- FUNCTIONS --------------*/
function: FUNCTION IDENTIFIER L_PAR optional_parameters R_PAR ;
end_function: ENDFUNCTION NEWLINE;

function_call: IDENTIFIER L_PAR optional_parameters R_PAR 
            | IDENTIFIER L_PAR data_type R_PAR
                | IDENTIFIER L_PAR data_type COMMA data_type R_PAR    
                | IDENTIFIER L_PAR data_type COMMA data_type COMMA data_type R_PAR;

/*------------ INSPECTORS -------------*/
inspector:IDENTIFIER operators IDENTIFIER
        |IDENTIFIER operators INTEGER
        |INTEGER operators IDENTIFIER
        |INTEGER operators INTEGER   ;

inspector_gen: inspector | inspector AND_OR_operators;

/*----------- IF & FOR STATEMENTS -------------*/

if_stmt:IF L_PAR inspector_gen R_PAR THEN NEWLINE action  ;
elseif_stmt: ELSEIF L_PAR inspector_gen R_PAR NEWLINE action  ;
else_stmt: ELSE NEWLINE action  ;
end_if_stmt:ENDIF NEWLINE  ;

for_statement: FOR IDENTIFIER COLON ASSIGN INTEGER TO INTEGER STEP INTEGER NEWLINE action;
end_for_statement: ENDFOR NEWLINE;


/*---------- SWITCH / CASE STATEMENT -----------------*/
switch: SWITCH L_PAR LT IDENTIFIER GT R_PAR NEWLINE action;

case: CASE L_PAR LT INTEGER GT R_PAR NEWLINE action;

end_switch: ENDSWITCH NEWLINE;

/*-------------- WHILE ---------------*/
while: WHILE L_PAR inspector_gen R_PAR NEWLINE action  ;
end_wile: ENDWHILE NEWLINE;

/*-------------- OPERATORS ---------------*/
operators:EQ_OP 
      | GE_OP 
      | LE_OP 
      | NE_OP 
      | DEC_OP 
      | INC_OP 
      | LT 
      | GT;

AND_OR_operators:AND_OP
        |OR_OP;

optional_parameters: IDENTIFIER 
        | optional_parameters COMMA IDENTIFIER ;

/*-------------- COMMENTS ---------------*/
comments: COMMENT;

/*-------------- PRINT ---------------*/
print: PRINT L_PAR data_type R_PAR QM;

/*-------------- MAIN ---------------*/
start_main: STARTMAIN NEWLINE action;
end_main: ENDMAIN NEWLINE  ;

/* --- DICTIONARIES --- */
dictionaries: IDENTIFIER ASSIGN L_BRACE dictionary_data R_BRACE 
        | IDENTIFIER ASSIGN IDENTIFIER L_PAR L_BRACK L_PAR dictionary_data R_PAR R_BRACK R_PAR
     IDENTIFIER ASSIGN IDENTIFIER L_PAR dictionary_data optional_parameters dictionary_data R_PAR ;

dictionary_data: data_type COLON data_type 
        |data_type COLON data_type COMMA dictionary_data 
        | data_type COMMA data_type optional_parameters 
        | IDENTIFIER ASSIGN data_type | /* empty */ ;

/* --- CALculaTE --- */
calc_assignment: IDENTIFIER ASSIGN int_op { Change($1,$3); };
    
int_op: int_data { $$ = $1; }
    | int_op PLUS int_data { $$ = $1 + $3; }
    | int_op MINUS int_data { $$ = $1 - $3; }
    | int_op MUL int_data { $$ = $1 * $3; }
    | int_op DIV int_data { $$ = $1 / $3; } ;

int_data: INTEGER { $$ = $1; } 
        | IDENTIFIER { $$ = Search($1) -> integer_val; };

%%

/* ------------------------------------------------ C FUNCTIONS -------------------------------------------- */

void yyerror(char *message){
    printf("Error: \"%s\"\t in line %d. Token = %s\n",message,yytext);
    exit(1);
}   

/* ------------------------------------------ MAIN FUNCTION --------------------------------------------- */

int main(int argc,char *argv[]){

    hashTable = (hash *) calloc(SIZE,sizeof(hash));

        int flag;

    yyin = fopen(argv[1],"r");
    //yyparse(): reads tokens,executes actions
    flag = yyparse();
    fclose(yyin);

    printf("Parsing finished succesfully!\n\n");
    printf(" __________________________\n");
    Print();
    printf(" __________________________\n");

    return flag;   
}

我知道我做错了什么。请帮我! 我不知道如何继续。

解决方法

当您开始使用 Bison 时(实际上,任何时候都在使用它),最好将语法编写成小块并进行调试。对于任何编程语言的任何项目来说,这都是一个好习惯,但当你缺乏经验时尤其如此。不要实现所有运算符,只需实现其中的几个。一旦你完成了这项工作,你就可以添加其余的。同样,不要实现 every 语句语法。从一个开始,让它工作,然后添加另一个。当干草堆不是很大时,更容易发现错误。一旦你养成了这种习惯,你会发现编程实际上变得容易多了。

现在,你的实际问题。如果从未使用过非终端,则它是“无用的”。换句话说,如果您定义了一个非终端并且从未在某些生产中使用它,bison 会警告您定义该非终端毫无意义。而且 Bison 足够聪明,可以递归地进行分析:如果非终结符出现的唯一位置是在无用的非终结符的右侧,则该非终结符也无用,并且您将收到警告它也。 (我认为这不是问题,但我没有对您的代码进行广泛的分析。)

因此,例如,在您的语法中,除了将非终结符 break 定义为 break:BREAK QM NEWLINE ; 外,您没有对其进行任何操作。我想您打算稍后将它添加到您的语句替代方案中,在这种情况下,您可以忽略警告(这就是为什么它是警告而不是错误的原因)。但是,总的来说,在您准备好添加 break 之前不将其添加到语法中,您会产生更少的噪音。

现在,移位/减少冲突。除非你足够幸运地偶然发现了一个明显的问题,否则很难在没有看到冲突的实际状态的情况下找出导致转移/减少冲突的原因;如果您使用 -v 命令行选项,Bison 将生成这些状态的报告。 John Levine 的优秀著作中提供了有关调试冲突的有用信息。

最新的 Bison 版本可以通过生成反例来为您提供更多帮助。 Bison manual 中还有另一个对冲突的很好的解释,以及一些解释如何使用此新功能的示例。

但是,碰巧的是,我确实偶然发现了一个明显的错误。您拥有(部分)以下作品:

line: action | print NEWLINE
action: INDENT line | indent2 line
indent2: INDENT INDENT

还有很多,但这足以引起冲突。撇开 INDENT 令牌的构成不谈,只需注意 print 以令牌 PRINT 开头,假设我们有以下输入:

INDENT INDENT PRINT

现在,你的语法是如何推导出来的?它可以这样做:

line -> action -> INDENT line -> INDENT action 
     -> INDENT INDENT line -> INDENT INDENT print NEWLINE

或者它可以这样做:

line -> action -> indent2 line -> INDENT INDENT line
     -> INDENT INDENT print NEWLINE

(我希望你知道,推导步骤包括用它的右侧之一替换非终结符。所以上面是同一个输入的两个不同的推导,这意味着你的语法是歧义的。Bison 坚持要生成一个明确的解析——这是它的全部目的——如果对同一个输入有两种可能的解析,它就不能那样做。

或者,更准确地说,它可以通过在某些规则的帮助下选择要使用的解析来做到这一点。但是这些规则通常不会按预期工作,而且对于一个模棱两可的语法,除了语法的作者之外,其他人真的没有办法知道要进行哪个解析。所以 Bison 会警告你有 shift/reduce 冲突,然后使用它的内置规则来选择一种可能的解析策略。

经常,与您的语法一样,当 Bison 应用这些规则时,它会发现某些产生式将不再适用于任何输入(因为消歧规则选择了其他一些产生式来应用)。在这种情况下,被淘汰的产生式变得毫无用处,这几乎肯定是一个错误,因此 Bison 也会对此发出警告。

我不知道这是否是所有冲突的原因,但最好解决这个问题,然后看看剩下的。

在我看来,您的意图并不是编写一种类似 Python 的语言,其中布局决定块结构,因为您似乎为所有块语法定义了显式结束标记。不可能使用上下文无关语法来强制执行正确的缩进,所以我希望这不是您的意图。

对于像 C 这样不将布局视为语法一部分的语言,最常用的解析技术是让词法扫描器简单地跳过空格(制表符和空格);由于空格对解析没有任何影响,因此通过强迫语法考虑空格可能会去哪里来混淆语法是没有意义的。这当然是我的建议,但因为我真的不知道你的意图是什么,我真的不能再说了。

祝项目顺利。