递归下降文法
什么是递归下降分析法
递归下降(Recursive Descent)分析法是确定的自上而下分析法,这种分析法要求文法是LL(1)文法。
- 为每个非终结符编制一个递归下降分析procedure,每个函数名是相应的非终结符,函数体则是根据规则右部符号串的结构和顺序编写。
- procedure相互递归调用。
递归下降文法的详细说明见龙书或者http://www.cs.binghamton.edu/~zdu/parsdemo/recintro.html
详细描述了递归下降和如何消除左递归。
对于递归下降文法的使用,一般软件有Yacc和Parse::RecDescent 等。
- Parse::RecDescent
Parse::RecDescent是一个轻量级的解析器,不仅轻量(只需要一个.pm文件),而且非常容易可视化和方便BNF的查看,同时在调试时,可以很明显的看到在给定输出下如何解析每个规则。文档可见:https://metacpan.org/pod/Parse::RecDescent
- Yacc
Yacc(Yet Another Compiler Compiler)是一个经典的生成词法分析器的工具。Yacc生成用C语言写成的词法解释器,需要与词法解析器Lex一起使用,然后把两部分分别生成的C程序一起编译。
从逻辑上是Lex->Yacc->BackendCompiler-linker的顺序。
在目前使用的linux系统上,Lex/Yacc实际上是使用的Flex&Bison工具,至于为什么Linux上的是Flex&Bison,历史问题可以自行探究,实际上Lex的作者包括Google的前CEO。对应关系是flex-lex,可以认为flex=Fast Lex,Bison=Yacc++。
网络上有很多Lex/Yacc的教程,说是需要使用Lex/Yacc来进行文本处理和程序的解析,很多的说法都来自于《flex&Bison》一书。以个人的经验来看,这本书的内容实在太过陈旧,目前简单的文本处理使用Python即可,复杂到需要一些编译的东西,首先Python自己本身就是个解析器,使用Python开发的各个语言的解析器也不少,使用很方便,如果需要对语言的特性有非常好的支持,使用Python调用clang/llvm的东西也非常方便,在今天,flex&Bison的主要用处是一个轻量级的编译器入门教程,相比Clang/llvm的庞大代码量和复杂的结构和API问题,flex&Bison作为教学使用还是不错的。
Flex&Bison安装问题
虽然有人说现在Linux相关公开发行版本中,默认安装Flex&Bison,但是经个人测试AWS提供的Ubuntu16.04系统中并没有提供相关的环境。需要使用来进行安装。
sudo apt-get install flex
sudo apt-get install bison
我本人在说Lex/Yacc或者Flex&Bison喜欢将其放在一起进行,实际上,他们都是独立的工具,可以单独使用。
Flex&Bison与正则表达式(Regular Expression)
对于Flex&Bison的用户来说,必须要掌握词法和语法的规则,这个规则是由正则表达式和特定规则构造的。正则表达式是使用一些特殊字符描述的字符串,用来表示一种匹配的模式(pattern)。下面给出一些简单的元字符说明。具体来源不列,source中除了这部分以外,给的说法存在太多错误。
元字符 |
匹配内容 |
. |
除了换行符之外的任意字符 |
\n |
换行符 |
* |
0次或者多次匹配 |
+ |
1次或者多次匹配 |
? |
0次或者1次匹配 |
^ |
行首 |
$ |
行尾 |
a|b |
a或者b |
(ab)+ |
ab的一次或者多次匹配 |
“a+b” |
a+b(字面意思) |
[] |
分组 |
Flex实现的wc使用说明
下面将首先介绍使用仅Flex来实现linux中的wc功能。(代码来源于《Flex&Bison》)
编写这样的一个文件,并将其命名为wc.l。
/* wc.l */ %{ int chars = 0; int words = 0; int lines = 0; %} %% [a-zA-Z]+ {chars+=strlen(yytext); words+=1;} \n {lines+=1; chars+=1;} . {chars+=1;} %% main(int argc, char** argv) { yylex(); printf("%8d %8d %8d\n", chars, words, lines); }
flex wc.l //---generate lex.yy.c然后使用flex进行编译处理
gcc lex.yy.c -lfl -o a.out
首先说明编译和使用过程,再对flex&Bison的语法进行说明。
wc.l是lex(flex)的程序文件,如果用到了yacc(Bison)的话,一般以.y结尾。
lex程序文件经flex编译后,会生成lex.yy.c;yacc程序文件经Bison后,生成了yacc.tab.c(可能还会有yacc.tab.h文件),经过分别编译后,最后可以统一链接称为最终的目标文件。
因此一个典型的flex&Bison project的Makefile可以写成这样的:
LEX=flex YACC=bison CC=gcc OBJECT=main.out #object file $(OBJECT): lex.yy.o yacc.tab.o $(CC) lex.yy.o yacc.tab.o -o $(OBJECT) lex.yy.o: lex.yy.c yacc.tab.h $(CC) -c lex.yy.c yacc.tab.o: yacc.tab.c $(CC) -c yacc.tab.c yacc.tab.c yacc.tab.h: yacc.y $(YACC) -d yacc.y lex.yy.c: wc.l $(LEX) wc.l clean: @rm -f $(OBJECT) *.o
如果实在看不懂Makefile的,请参照《跟我一起写Makefile》https://seisman.github.io/how-to-write-makefile/
Lex&Yacc文件结构说明
在Yacc中,这个说明文件可以分为三部分,以符号%%分开:
[第一部分:声明定义段]
%%
第二部分:规则段
%%
第三部分:辅助函数段
其中,第一部分及第三部分和第三部分之上的%%都可以省略(即上述方括号括起的部分可以省略)。以%开头的符号和关键字,或者是规则段的各个规则一般顶着行首来写,前面没有空格。
一般来说,声明定义段用%{和}%来进行包裹,里边是C语言写的一些定义和声明,包括文件包含,宏定义,全局变量,函数声明什么的。
规则段用%%和%%来包裹,主要是你的规则部分,在wc.l中,是识别[a-zA-Z]这样的word等,当然这里还包括状态等,不细节进行描述,本文档只负责进行介绍。
最后的辅助函数段,需要实现前边声明的函数还有最重要的main函数。
参考文档:
- 《flex&Bison》
- http://dinosaur.compilertools.net/
- https://www.geeksforgeeks.org/introduction-to-yacc/
- Yacc:Yet Another Compiler-Compiler http://dinosaur.compilertools.net/yacc/
- https://blog.csdn.net/wp1603710463/article/details/50365640
- https://www.jianshu.com/p/1fe5a61fd9dc
- https://blog.csdn.net/pandaxcl/category_188988.html?spm=1001.2014.3001.5482
- https://blog.csdn.net/huyansoft/article/details/8860224
- https://blog.csdn.net/Augusdi/article/details/64127840