我是靠谱客的博主 疯狂小虾米,最近开发中收集的这篇文章主要介绍flex & bison 基础概述1. 前言2. 本文目标3. flex & bison4. 后记5. 推荐阅读 & 参考资料,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 本文目标
. 简单介绍 flex 和 bison 的基础使用方法
. 简要分析 flex, bison 生成代码的工作流程
3. flex & bison
3.1 背景
本文所有分析,基于 Ubuntu 16
系统。
3.2 flex
3.1.1 flex 简介
flex
用来生成词法分析器
(lexical analysis
, 或 scanner
),而词法分析器
的作用,简单来讲,就是将输入,按定义的正则表示式
模式,解析分割成一个个记号(token)
。
# 生成词法分析器
flex
XXX.l(词法分析器规则定义文件) ======> 词法分析器
# 通过词法分析器,将输入数据流,解析成一个个记号(tokens)
词法分析器
输入数据流 ===========> 一个个记号(tokens)
3.1.2 flex 使用例子
(1) 初次使用,先运行如下命令安装 flex
:
sudo apt-get install flex
(2) 编写 flex 程序(用来生成词法分析器的规则文件XXX.l)。
我们先来了解一下 flex 程序的编写规则,flex 程序分为3个部分:
定义部分:
包含选项(option),文字块,开始条件,转换等。
本部分中以空白行开头、或包含在%{和%}之间的部分,都会被原封不动的拷贝到C代码中。
%%
规则部分:
包含正则模式行和模式行匹配时执行的C代码。
以空白行开头、或包含在%{和%}之间的部分,都被认为是C代码,它们会被原封不动的拷贝到yylex()函数中。
大多数flex程序都具有二义性,即相同的输入,可能被多种模式不同的正则模式匹配。flex通过两个简单的规则来解决它:
. 词法分析器匹配输入时匹配尽可能多的字符串;
. 如果两个模式都可以匹配的话,匹配更早出现的模式。
%%
用户子程序部分:
这个部分通常包含在模式规则匹配时,执行的C代码调用的函数。
本部分会原封不动的拷贝到C代码中。
这3个部分,用2个%%
分隔,前2个部分是必须的,但它们的内容可以为空,第3部分和它之前的%%
可以省略。
了解 flex 程序的编写规则后,接下来,我们以一个统计字符数、单词数目、行数的 flex 程序为例,来演示一下flex的使用,flex 程序count-words.l
如下:
%{
int chars = 0; /* 字符计数 */
int words = 0; /* 单词计数 */
int lines = 0; /* 行计数 */
%}
%%
[a-zA-Z]+ { words++; chars += strlen(yytext); }
n { chars++; lines++; }
. { chars++; }
%%
int main(int argc, char *argv[])
{
yylex();
printf("%8d%8d%8dn", chars, words, lines);
return 0;
}
编写一个简单的 Makefile 来编译我们的词法分析器:
count-words: count-words.l
flex --noyywrap count-words.l # 生成词法分析器代码 lex.yy.c
gcc -o $@ lex.yy.c # 编译词法分析器 lex.yy.c
clean:
-rm -f lex.yy.c count-words
编译和运行:
make # 编译生成词法分析器程序 count-words
./count-words # 运行词法分析器,按 Ctrl + D 结束数据输入
测试中我们发现,词法分析器从标准输入接收数据,这是默认的行为。如果我们想改变该默认行为,转而将文件作为输入,只需按如下修改 flex 程序count-words.l
就可以达到目标:
%option noyywrap
%{
/* 统计单个文件数据 */
int chars = 0;
int words = 0;
int lines = 0;
/* 统计所有文件数据 */
int total_chars = 0;
int total_words = 0;
int total_lines = 0;
%}
%%
[a-zA-Z]+ { words++; chars += strlen(yytext); }
n { chars++; lines++; }
. { chars++; }
%%
int main(int argc, char *argv[])
{
int i;
if (argc < 2) { /* 没有给定文件列表,仍然从标准输入获取数据 */
yylex();
printf("%8d%8d%8dn", chars, words, lines);
return 0;
}
/* 遍历所有输入文件,统计每一个文件的数据 */
for (i = 1; i < argc; i++) {
FILE *fp = fopen(argv[i], "r");
if (!fp) {
perror(argv[i]);
return -1;
}
/* 复位当前文件的统计数据 */
chars = words = lines = 0;
yyrestart(fp); /* 调用 yyrestart() 接口重置词法分析器的输入流到文件 @argv[i] */
yylex(); /* 调用词法分析器进行数据统计 */
fclose(fp); /* 关闭当前文件 */
printf("%8d%8d%8d %sn", chars, words, lines, argv[i]);
/* 记录当前文件的统计数据 */
total_chars += chars;
total_words += words;
total_lines += lines;
}
if (argc > 1)
printf("%8d%8d%8d totaln", total_chars, total_words, total_lines);
return 0;
}
修改后重新编译运行:
make
./count-words a.txt b.txt # a.txt, b.txt 作为输入
如果不指定输入(具体是修改yyin
全局变量),程序默认使用标准输入;如果要修改输入,我们可以通过yyrestart()
修改。
3.1.3 flex 生成代码流程简析
通常,我们有必要简单的分析下flex的生成代码,以帮助我们理解和更好的使用工具。下面简要分析flex生成代码的工作流程:
/*
* 在分析具体代码前,我们先聊一下 flex 的 fl 库。
* flex工具带有一个微型的 fl 库,它定义了 main(), yywrap() 接口。 其中:
* . main() 函数调用 yylex() 做词法分析;
* . yywrap() 简单地返回 1。yywrap() 的作用是,在yylex()发现到达输入数据末尾时,调用 yywrap(),看是否还有数据,如果有,yywrap() 应该返回0,否则返回1。
*
* 如果我们的flex程序,不自己实现 main() 和 yywrap(),则在编译时,可以给 gcc 指定 -lfl 选项。另外,可以通过给 flex 传递 --noyywrap 选项,或者在 flex 程序中,指定 %option noyywrap 来告诉flex ,我们不调用 yywrap() ,以此来屏蔽编译链接报错。
*/
/* 接下来,进入具体的代码流程分析 */
main()
yylex() /* 进入词法分析器入口 */
/* yylex() 初次调用的初始化。后续 yylex() 调用会在之前的上下文下继续工作。 */
if ( !(yy_init) )
{
(yy_init) = 1;
...
if ( ! (yy_start) )
(yy_start) = 1; /* first start state */
if ( ! yyin ) /* 没有设定输入, 默认将 stdin 作为输入 */
yyin = stdin;
if ( ! yyout ) /* 没有设定输出, 默认将 stdout 作为输入 */
yyout = stdout;
/* 输入缓冲初始化 */
if ( ! YY_CURRENT_BUFFER ) {
yyensure_buffer_stack (); /* 创建 yy_buffer_state 输入缓冲管理对象指针栈 */
YY_CURRENT_BUFFER_LVALUE =
yy_create_buffer(yyin,YY_BUF_SIZE ); /* 创建栈顶 YY_BUF_SIZE 大小的输入缓冲 */
}
/*
* 获取栈顶输入缓冲如下状态:
* . yy_n_chars: 读到栈顶输入缓冲空间的字符个数
* . yytext, yy_c_buf_p: 栈顶输入缓冲空间当前位置指针(char *)
* . yyin: 输入缓冲输入文件
* . yy_hold_char: 栈顶输入缓冲空间当前字符
*/
yy_load_buffer_state( );
}
/* 扫描循环,直到输入结束 */
while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */
{
/* 正则匹配状态机循环 */
yy_current_state = (yy_start);
yy_match:
do {
YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ;
...
yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
++yy_cp;
}
while ( yy_base[yy_current_state] != 17 );
...
/* 一个正则模式匹配完成的后惯例动作:
* yytext: 当前正则模式匹配的内容
* yyleng: 当前正则模式匹配内容长度
* yy_hold_char: 当前正则模式匹配内容的最后一个字符(也即当前字符)
* *yy_cp = '