递归下降文法

什么是递归下降分析法

递归下降(Recursive Descent)分析法是确定的自上而下分析法,这种分析法要求文法是LL(1)文法。

  • 为每个非终结符编制一个递归下降分析procedure,每个函数名是相应的非终结符,函数体则是根据规则右部符号串的结构和顺序编写。
  • procedure相互递归调用。

递归下降文法的详细说明见龙书或者http://www.cs.binghamton.edu/~zdu/parsdemo/recintro.html

详细描述了递归下降和如何消除左递归。

对于递归下降文法的使用,一般软件有Yacc和Parse::RecDescent 等。

  1. Parse::RecDescent

Parse::RecDescent是一个轻量级的解析器,不仅轻量(只需要一个.pm文件),而且非常容易可视化和方便BNF的查看,同时在调试时,可以很明显的看到在给定输出下如何解析每个规则。文档可见:https://metacpan.org/pod/Parse::RecDescent

 

  1. 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
View Code

 


 如果实在看不懂Makefile的,请参照《跟我一起写Makefile》https://seisman.github.io/how-to-write-makefile/

 

Lex&Yacc文件结构说明

在Yacc中,这个说明文件可以分为三部分,以符号%%分开:

 

 [第一部分:声明定义段]

 

 %%

 

 第二部分:规则段

 

 %%

 

 第三部分:辅助函数段

 

   其中,第一部分及第三部分和第三部分之上的%%都可以省略(即上述方括号括起的部分可以省略)。以%开头的符号和关键字,或者是规则段的各个规则一般顶着行首来写,前面没有空格。

一般来说,声明定义段用%{和}%来进行包裹,里边是C语言写的一些定义和声明,包括文件包含,宏定义,全局变量,函数声明什么的。

规则段用%%和%%来包裹,主要是你的规则部分,在wc.l中,是识别[a-zA-Z]这样的word等,当然这里还包括状态等,不细节进行描述,本文档只负责进行介绍。

最后的辅助函数段,需要实现前边声明的函数还有最重要的main函数。

 

参考文档:

  1. 《flex&Bison》
  2. http://dinosaur.compilertools.net/
  3. https://www.geeksforgeeks.org/introduction-to-yacc/
  4. Yacc:Yet Another Compiler-Compiler http://dinosaur.compilertools.net/yacc/
  5. https://blog.csdn.net/wp1603710463/article/details/50365640
  6. https://www.jianshu.com/p/1fe5a61fd9dc
  7. https://blog.csdn.net/pandaxcl/category_188988.html?spm=1001.2014.3001.5482
  8. https://blog.csdn.net/huyansoft/article/details/8860224
  9. https://blog.csdn.net/Augusdi/article/details/64127840
posted @ 2021-03-10 15:52  转换无极限  阅读(924)  评论(0编辑  收藏  举报