背景知识
编译原理研究的内容是:词法分析、语法分析、语义分析、中间代码生成等等。第一步,我们先尝试借助小工具完成这些步骤。(之后的文章中,如果有时间的话,再尝试不借助工具自己实现)
- lex,一个词法分析工具(这个名称本身就是词法(Lexical)的前缀)。它可将.l文件编译生成对应的.c文件。
- yacc(yet another compile compile),用于编译编译器的编译器,可完成语法分析、语义分析等工作。它可将.y文件编译生成对应的.c文件。
- 使用c语言编译器(如gcc),将这些生成的.c文件进行编译,得到可执行文件。
工具配置
- 在Unix、Linux系统中,已经默认安装了它们对应的flex和bison;
可使用flex --version
bison --version
验证。
- windows下需要自行安装。
下载地址:winflexbison。 windows下载后可自行解压并配置环境变量: 将含有flex和bison可执行文件所在的目录路径添加到用户变量中的Path。
依然可使用--version
的版本进行测试是否配置成功。
下文都将以Linux系统为例进行演示。
使用规则
lex和yacc文件的格式是有点类似的:
%{
声明部分
%}
辅助定义部分
%%
规则部分
%%
用户函数部分
可以看到,用%%将各部分隔开,其中,若某一个部分为空,则对应的%%也可以省略。
实现整数加减乘除的计算器
代码
先来看.l文件的写法:
%{ /*%{%}包裹c代码,这部分代码会被原样输出*/
#include <stdio.h>
#include "y.tab.h" /*.y文件经yacc编译后会生成这个头文件*/
int
yywrap(void)
{
return 1;
}
%}
%%
"+" return ADD; /*定义加减乘除,回车返回执行结果的规则*/
"-" return SUB;
"*" return MUL;
"/" return DIV;
"\n" return CR;
([1-9][0-9]*)|0 { /*正则表达式,表示接收非0开头的数字或者0*/
int temp;
sscanf(yytext,"%d",&temp);
yylval.int_value=temp;
return INT_LITERAL; /*返回整数运算结果*/
}
[ \t] ;
. {
fprintf(stderr,"lexical error.\n");
exit(1);
}
%%
代码中添加了一些后来加上的注释。
再来看.y文件: 这一部分制定了更详细的语法规则,而且定义了函数主体部分。
%{
#include <stdio.h>
#include <stdlib.h>
#define YYDEBUG 1
%}
%union{
int int_value;
}
%token <int_value> INT_LITERAL
%token ADD SUB MUL DIV CR
%type <int_value> expression term primary_expression
%%
line_list
: line
| line_list line
;
line
: expression CR
{
printf(">>%d\n",$1);
}
expression
: term
| expression ADD term
{
$$ = $1 + $3;
}
| expression SUB term
{
$$ = $1 - $3;
}
;
term
: primary_expression
| term MUL primary_expression
{
$$ = $1 * $3;
}
| term DIV primary_expression
{
$$ = $1 / $3;
}
;
primary_expression
: INT_LITERAL
;
%%
int
yyerror(char const *str)
{
extern char *yytext;
fprintf(stderr,"parser error near %s\n",yytext);
return 0;
}
int main(void)
{
extern int yyparse(void);
extern FILE *yyin;
yyin=stdin;
if(yyparse()){
fprintf(stderr,"Error!\n");
exit(1);
}
}
执行
yacc -dv mycalc.y //d指生成头文件但不指定名称(使用默认名称),v指打印冗余日志信息
lex mycalc.l
cc -o mycalc y.tab.c lex.yy.c
注意:
- 如果是使用bison命令且不指定--yacc参数,通过-d生成的.c文件不是y.tab开头的,因此c编译时得替换为相应名称,且mycalc.l中包含的头文件名称也要改掉,并且重新编译。
- 针对头文件名不一致导致编译错误的问题, 依据bison帮助文档中的命令描述:
Output Files:
--defines[=FILE] also produce a header file
-d likewise but cannot specify FILE (for POSIX Yacc)
感觉可以试试用--define指定头文件名字。(我还没试)
如无意外就会看到生成了mycalc
可执行文件。
输入./mycalc
即可体验。
参考资料
- 《自制编程语言》 [日] 前桥和弥著,刘卓等译
- 编译原理课设指导书
- bison,flex帮助文档