问题描述
我正在尝试使用 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 这样不将布局视为语法一部分的语言,最常用的解析技术是让词法扫描器简单地跳过空格(制表符和空格);由于空格对解析没有任何影响,因此通过强迫语法考虑空格可能会去哪里来混淆语法是没有意义的。这当然是我的建议,但因为我真的不知道你的意图是什么,我真的不能再说了。
祝项目顺利。