LEX&&YACC--编译界的神

3 篇文章 2 订阅
2 篇文章 0 订阅

最近研究lex&&yacc,记录并总结一些重要的概念和解释。
lex&&yacc是gnu开源的全文解析工具,lex用于词法解析,yacc用于语法解析。lex一般也称为token scanner/lexer,yacc称为parser generator(语法解析器生成器)。
lex&&yacc这两个工具已经很老了,现代版本的工具为flex&&bison。两个工具可以结合使用,也可以只使用bison。

基本原理

lex词法解析,使用正则表达式进行匹配,你可以写入正则表达式到lex文本中。
yacc语法解析,学过编译原理的人可能比较熟悉上下文无关文法,巴科斯范式,LR(1),LALR(1)等概念。yacc内部其实就是实现了一个状态机。其执行过程,就是借助状态机和栈来不断地将规则文本进行归约的过程。

归约和移进是lex&&yacc中比较重要的概念,官方文档中使用的是shift/reduce这两个单词,shift表示移进,reduce表示归约。在编写完语法规则后,使用yacc/bison编译,可能会出现一些shift/reduce conflict,即移进归约冲突。这时候,你需要仔细确认自己写的语法规则,其中就存在一些语义上的错误,需要进行修改。

命令介绍

这里讨论flex&&bison,旧版类似。
编写的词法文件后缀一般是.l,语法文件后缀是.y。这里假设你写了一个sql的语法解析工具:
词法文件为sql.l,语法文件为sql.y。(当然,你也可以仅使用语法解析。)

flex sql.l             # 生成C语言版本的词法解析源代码
flex -+ sql.l          # 生成C++版本的词法解析源代码
bison sql.y            # 仅生成一个C语言版本的语法解析源代码,不会导出头文件给其它源码引用
bison -d sql.y         # 功能同上,但是会导出头文件给其他源码引用
bison -L c++ -d sql.y  # 生成C++版本的语法解析源码
gcc *.c -o parser      # 编译

上述命令如果不使用-o选项给输出文件取名,那么执行成功后会生成以下文件:
C语言版本
词法文件为lex.yy.c
语法文件为sql.tab.c,sql.tab.h
C++版本
词法文件为lex.yy.cc
语法文件为sql.tab.cc,sql.tab.h

官方实例与文档

官方文档地址为: http://dinosaur.compilertools.net/
有lex,yacc,flex,bison和相关工具的实例与文档。如果有需要,可以查阅文档。

重要概念

token: 词法解析出的结果,一般是yylex函数return给语法解析的返回值,一般在.y文件中使用%token定义的符号,本质上会被编译成一个int值,给程序识别。yylex函数返回的就是这个int值。
shift/reduce: 移进/归约,移进就是规则解析未结束,继续向栈中推入一个符号。
terminal: 终结符,语法规则一般就是终结符和非终结符构成的,非终结符最终还是由终结符构成。
nonterminal: 非终结符
rule: 语法规则
precedence: 优先级,出现shift/reduce conflict时,需要定义该规则归约的优先级
action: 动作。词法和语法文件中,跟在词法或语法规则后面的一段C/C++代码,会在解析到该词法或语法规则后立马执行这些代码。多行代码使用{}括起来。

词法语法文件格式

%{
  // 在这里写C/C++程序代码,一般是程序模块的引入,和类型或函数的声明与定义
  // #include <stdio.h>  // C语言版本
  // #include <iostream> // C++版本
  // extern int yylex(); // 语法文件中,需要声明一个名为yylex的词法解析函数(这个函数的任务是帮语法模块进行词法解析)
%}

  // 这部分写词法或语法解析自身的一些声明及定义
  // 如语法解析的token定义
%token LPAREN // 定义左括号的token
%token RPAREN // 定义右括号的token
%token KEYWORD_SELECT // 定义关键字select的token

  // 声明优先级,后声明的优先级高,除了%left左结合,还有%right, %nonassoc可以来定义优先级
%left "+" "-" 
%left "*" "/"
// 这里你可以使用上面定义的token,也可以使用未定义的token,来代表声明一个虚拟符号,来代表这个层次的优先级。
// 声明一个虚拟优先级符号后,使用%prec将虚拟符号插入到语法规则的末端,那么该语法规则的优先级就是该虚拟符号的优先级了。
// 这里说明一下,一个语法规则的优先级,一般就是规则中最后一个终结符的优先级,而终结符一般就是token定义的符号。

%union {
	char* ycText; // 这里可以使用union命令定义需要从词法传入到语法解析模块的具体内容,ycText在词法模块中赋值
}
// union命令本质上会在源码中生成一个叫YYTYPE的联合体类型,在语法模块中会有YYTYPE yylval这个变量定义,extern到词法文件中,对其ycText成员进行赋值。那么在语法文件中就可以访问到这个具体的字符串了。当然上面的token声明需要修改一下
%token <ycText> KEYWORD_SELECT
// 除了字符串类型,还可以是int等其他类型,但需要使用代码在词法的action中进行转化

%start root // 生命一下语法规则的起始规则,具体的root规则在下面定义。不使用start命令,yacc也会从第一条规则进行解析。

%%

  // 词法和语法文件的主体部分,词法就写解析的词法及其附带的action(动作)
root: KEYWORD_SELECT tail {/* 动作代码,这里假装输出select这个词,使用$1引用,yacc编译器后期会把$1替换成具体的栈上变量 */printf("%s\n",$1);}
; // 别忘记使用分号注明root规则结束
  // 如果规则有多种情况,使用‘|‘符号分多种规则情况
  // 这里,root其实就是一个nonterminal,而KEYWORD_SELECT是一个terminal。tail也是nonterminal,因为下面还是要对它进行定义。
  // 规则定义时,尽量使用左递归的写法,右递归会出现很多未知情况,除非你知道他们的意义,否则还是使用左递归
  // 另外,介绍一下$$,这个可以给非终结符进行赋值,具体使用可以参考官方文档

%%

  // 这里一般写C/C++代码,一般是一些函数的具体实现
  // 词法模块一般需要定义一个yywrap函数
  int yywrap() {return 1;} // yywrap函数的作用请查阅文档,不定义该函数,gcc编译源码时会报错

工具使用说明

在编写词法语法文件时需要明确几点:
一般过程是,将文本通过标准输入(stdin)传入程序,先经过词法分析模块,后将词法分析结果传入语法解析模块,然后循环该过程。

语法解析需要定义并实现如下函数:

int yylex(); // 词法解析函数,如果是c语言版本的,那么词法文件编译成源码后,里面就自带一个yylex的c语言函数。这时候,你仅需要在语法文件的头部声明一下外部的函数即可。
void yyerror(char* msg) {printf("error : %s\n", msg);}; // 错误处理函数

语法模块生成的头文件,需要在词法文件的头部引入:

%{
#include "sql.tab.h" // 因为里面包含了yylex要返回的token值,这个值要传给语法解析模块
}%

词法解析模块,内部需要定义yywrap函数。

最后,你需要自己写一个main函数,条用语法解析的yyparse函数进行全文解析。

最终词法和语法文件大概的样子:

// sql.l文件内容
%{
#include <stdio.h>
#include <sql.tab.h>
extern YYTYPE yylval;
}%

%%

"select" {yylval.ycText = yytext;return KEYWORD_SELECT;}

%%
int yywrap() {return 1;}

// sql.y文件内容
%{
#include <stdio.h>
extern int yylex();
void yyerror(char* msg) {printf("error : %s\n", msg);}
}%

%union {
	char* ycText;
}

%token <ycText> KEYWORD_SELECT

%start root

%%

root: KEYWORD_SELECT {printf("%s\n", $1);}
;

%%
int main(int , char** ) {yyparse();return 0;}

总结

关于lex&&yacc的使用,还有很多需要注意的,尽量还是查看官方文档。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作者: 胡彦 本框架是一个lex/yacc完整的示例,用于学习lex/yacc程序基本的搭建方法,在linux/cygwin下敲入make就可以编译和执行。 本例子虽小却演示了lex/yacc程序最常见和重要的特征: * lex/yacc文件格式、程序结构。 * 如何在lex/yacc中使用C++和STL库,用extern "C"声明那些lex/yacc生成的、要链接的C函数,如yylex(), yywrap(), yyerror()。 * 重定义YYSTYPE/yylval为复杂类型。 * 用%token方式声明yacc记号。 * 用%type方式声明非终结符的类型。 * lex里正则表达式的定义、识别方式。 * lex里用yylval向yacc返回属性值。 * 在yacc嵌入的C代码动作里,对记号属性($1, $2等)、和非终结符属性($$)的正确引用方法。 * 对yyin/yyout重赋值,以改变yacc默认的输入/输出目标。 * 如何开始解析(yyparse函数),结束或继续解析(yywrap函数)。 本例子功能是,对当前目录下的file.txt文件,解析出其中的标识符、数字、其它符号,显示在屏幕上。linux调试环境是Ubuntu 10.04。 总之,大部分框架已经搭好了,你只要稍加扩展就可以成为一个计算器之类的程序,用于《编译原理》的课程设计。 文件列表: lex.l: lex程序文件。 yacc.y: yacc程序文件。 main.hpp: 共同使用的头文件。 Makefile: makefile文件。 file.txt: 给程序解析的文本文件。 使用方法: 1-把lex_yacc_example.rar解压到linux/cygwin下。 2-命令行进入lex_yacc_example目录。 3-敲入make,这时会自动执行以下操作: (1) 自动调用flex编译.l文件,生成lex.yy.c文件。 (2) 自动调用bison编译.y文件,生成yacc.tab.c和yacc.tab.h文件。 (3) 自动调用g++编译、链接出可执行文件main。 (4) 自动执行main,得到如下结果:。 bison -d yacc.y g++ -c lex.yy.c g++ -c yacc.tab.c g++ lex.yy.o yacc.tab.o -o main id: abc id: defghi int: 123 int: 45678 op: ! op: @ op: # op: $ AllId: abc defghi 参考资料:《LexYacc从入门到精通(6)-解析C-C++包含文件》, http://blog.csdn.net/pandaxcl/article/details/1321552 其它文章和代码请留意我的blog: http://blog.csdn.net/huyansoft 2013-4-27

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值