Lex&Yacc
1 知识储备:
1.1 工具
1.1.1 Bison
bison是一个通用解析器生成器,将LALR(1)上下文无关文法转变成一个解析该语法的C程序,可被用于开发各种语言的解析器,向上兼容Yacc,用C开发语法解析器时要安装Bison
1.1.2 Flex
flex是一个高速的词法分析器生成器,可用于对文字(text)进行模式匹配(pattern-matching), 它是lex的众多版本之一,选择flex是因为可以方便的在Windows系统安装
1.1.3 GCC(GNU Compiler Collection)
bison和flex需要的编译包都是GnuWin32,在win环境下,使用MinGW
工具安装gcc
编译环境
1.2 编译器原理概述
1.2.1 编程语言的编译器通常可被分为五个阶段:
- 词法分析:分析源码,扫描字符串,分离出所有语法单位(tokens), 这个任务可由Lex自动化完成
- 语法分析:分析词法分析传来的tokens, 找出符合规定的语法,确定整个输入串能否构成语法正确的句子和程序,这个任务可由Yacc自动化完成
- 中间代码生成:在语法分析正确的基础上,按照语义规则产生介于源语言和目标代码之间的代码,中间代码不依赖于机器,同时便于产生依赖于机器的代码
- 优化:依据等价变换原则,对中间代码进行加工变换,以便在后期生成高效的代码
- 目标代码生成:将优化后的中间代码转换成具体机器的指令序列
- Note:在微、小型机这类对编译质量要求不高的场合,可跳过中间代码生成阶段,语法分析后直接生成目标代码。语义分析在目标代码生成阶段完成。
1.2.2 词法分析
定义
词法分析器以词法定义为基础,词法定义用正则表达式(正规文法)表示
正则表达式 ->NFA(不确定有限自动机)->DFA(确定有限自动机)->DFA最小化
功能
读取源代码,根据定义的词法分析识别单词,构造词法单元(token),检查此法错误,最后输出token序列(用二元组表示)
token
-
语言中最小的语法单位
-
分为两部分
-
种类(类号)
-
内容(内码)
-
例如:
<id, 1>
-
-
token种类
-
标识符
变量名、常量名、函数名、数组名… -
数字 数值
整型浮点型布尔型…
-
特殊种类
-
保留字(基本字)
被被语言系统自身定义的有特殊意义的单词,例如
int float void null
-
操作符
+ - * /
-
分隔符(界限符)
; : ( )
-
-
1.2.3 语法分析
定义
以语法定义(上下文无关文法)为基础,语法定义用产生式表示
功能
读取词法分析器传来的词法单元序列,根据语法定义生成语法结构(组词成句)
语法结构
描述程序结构,例如:
Program -> Type main() Block
Type -> int | bool
Block -> { Stmts return Num ; }
Decl -> Type Array ;
Array -> Identifier [ Num ] | Identifier [ Identifier ] | Identifier
Stmts -> Stmts M Stmt | Stmt
上下文无关文法(context-free grammar)
- 文法中所有的产生式左边只有一个非终结符
- 只要找到符合产生式右边的串,就可以把它归约为对应的非终结符
S->ab
是一个上下文无关文法aSb->aaSbb
是一个上下文有关文法,因为它的第一个产生式左边有不止一个符号,所以你在匹配这个产生式中的S的时候必需确保这个S有正确的“上下文”,也就是左边的a和右边的b
2 Lex
生成词法分析器源码,lex.yy.c
2.1 Lex用于消除歧义的规则:
- 优先原则,只匹配字符或字符串一次,如果出现单词与两个规则都匹配,匹配靠前的规则
- 最长字串匹配原则,执行能匹配到的最长的匹配对应的action
2.2 lex语法分为三段
每段之间用**%%**隔开
2.2.1 定义部分:
-
例:
%{ /* *这是一个Lex的定义部分 */ #undef input #undef unput unsigned verbose; char *progName; %}
-
用**%{ %}**包住这块
-
包裹住的部分会被直接复制粘贴进**.l文件生成的.c**文件
-
把想要想要粘贴进最终程序的C代码写这
- 要include的头文件
- 其他C代码
-
2.2.2 规则部分:
-
例:
int { printf("%s: is a verb\n", yytext); }
-
规则由模式(pattern)和与应的操作(action)构成
-
pattern:
- 字符串
- 正则表达式
-
action:
- C代码
- return的数据将传递给Yacc作为保留字(token)
-
-
lex生成的词法分析器(lexer)识别出定义的pattern时,将执行对应的action
2.2.3 子程序部分
-
写C代码
- 例子:
main() { yylex(); }
2.3 Lex对比C写出来的词法分析器
- Lex程序长度比C短很多
- C需要花接近三倍于Lex的时间来编写和Debug
- 一些词法用C编写会失败,例如:
/** comment **/
,C编写的词法分析器可能会识别不出这个注释的结尾
2.4 lex.yy.c
中的全局变量和函数
2.4.1 变量
yyout
:FILE*
类型,指向lexer的输出yyin
:FILE*
类型,指向lexer的输入yytext
:匹配到的文本存在这个变量里
2.4.2 函数
-
yylex()
由lex自动生成,从进入这个函数开始分析 -
yywrap()
批量处理文件,可以配合yyin在子程序部分使用,返回1表示文件读完,返回0继续读文件 -
ECHO
输出匹配到的字符串
2.5 flex词法分析产生器实现过程
词法分析器自动生成器的核心是lex编译器,lex编译器的功能是对某语言单词集描述的lex源程序,将其变换为一个能识别该语言单词的词法分析器。而该词法分析器像有限自动机一样取识别处理单词。
基于lex源程序,lex编译器的实现步骤大致是:
- 对lex源程序识别规则中的每个pi构造一个相应的NFA Ni
- 引入唯一初态S,从初态S通过ε弧将所有NFA Ni(i=1,…,n)连接成新的NFA N’
- 对NFA N’确定化,产生DFA N
- DFA N 最小化
- 给出控制程序。控制程序的作用是激活有限自动机,即控制输入字符串在有限自动机上运行,一旦达到终态,即识别出lex源程序模式描述的某个单词,转去调用相应的动作部分就可以了
3 Yacc
生成语法分析器源码y.tab.c
存放LALR分析表
和包含了token的枚举定义的头文件y.tab.h
3.1 Yacc parser分为三部分
每段之间用**%%**隔开
3.1.1定义部分
- 和Lex的一样