BISONFlex.docx

上传人:b****6 文档编号:7065353 上传时间:2023-01-16 格式:DOCX 页数:31 大小:39.20KB
下载 相关 举报
BISONFlex.docx_第1页
第1页 / 共31页
BISONFlex.docx_第2页
第2页 / 共31页
BISONFlex.docx_第3页
第3页 / 共31页
BISONFlex.docx_第4页
第4页 / 共31页
BISONFlex.docx_第5页
第5页 / 共31页
点击查看更多>>
下载资源
资源描述

BISONFlex.docx

《BISONFlex.docx》由会员分享,可在线阅读,更多相关《BISONFlex.docx(31页珍藏版)》请在冰豆网上搜索。

BISONFlex.docx

BISONFlex

FLEX

什么是FLEX?

它是一个自动化工具,可以按照定义好的规则自动生成一个C函数yylex(),也成为扫描器(Scanner)。

这个C函数把文本串作为输入,按照定义好的规则分析文本串中的字符,找到符合规则的一些字符序列后,就执行在规则中定义好的动作(Action)。

例如在规则中可以这样定义:

如果遇到一个换行字符\n,那么就把行计数器的值加一。

Flex文件就是一个文本文件,内容包括定义好的一系列词法规则。

文件的命名习惯上以小写字母l(L)来作为文件后缀。

如果为了清晰,也可以用.flx或者.flex作为文件的后缀名。

Flex文件完成后,就执行下列命令:

$flexexample.flex

这个命令执行后将生成一个C文件,默认文件名为lex.yy.c。

这个C文件主要内容就是函数yylex()的定义。

如果要直接将这个文件编译成为一个可执行程序,还有一些要注意的地方。

如果在Flex文件中没有提供main()函数的定义,那么这个C文件中不会有main()函数。

此时单独编译这个C文件的时候,一定要加上-lfl的连接库参数;若提供了main()函数,就不必要提供这个连接库参数了。

连接库libfl提供了一个缺省的main函数。

缺省的main()函数中只是简单地调用yyflex()函数,而自己提供的main()函数则可以根据需要加入许多其他的处理代码。

Flex文件

词法规范定义文件给出了单词构成规则。

词法文件在习惯上用字母l(即L的小写)来作为后缀。

Flex文件由三个部分组成。

或者说三个段。

三个段之间用两个%%分隔。

定义段(definitions)

%%

规则段(rules)

%%

用户代码段(usercode)

定义段(definitionssection)

定义段包含着一些简单名字的定义(namedefinitions),旨在简化扫描器的规范。

定义名字的方法如下:

namedefinition

名字可以由字母或下划线开头,后跟零个或多个字母、数字、下划线、或短横线。

名字的定义则从其后的第一个非空白字符(non-white-space)开始直到行尾。

下面是一个例子,定义了一个名字DIGIT,其定义就是指一个数字,如下所示:

DIGIT[0-9]

当在后面引用这个名字时,用一对花括号({})括住该名字即可。

它会被展开成一对圆括号括住的该名字的定义,即:

{name}展开成(definition)

例如:

{DIGIT}+"."{DIGIT}*

就等价于:

([0-9])+"."([0-9])*

定义段中还可以加入启动条件(startconditions)的声明。

顾名思义,启动条件就如同C语言中的条件编译一样,根据指定的启动条件去激活一条规则,并用这条规则去匹配读入的字符。

关于启动条件,后面还有更详细的介绍。

规则段(rulessection)

规则由模式(pattern)和动作(action)两个部分组成。

模式就是一个正则表达式,FLEX加入了一些自己的扩展。

而动作一般就是一些C语句。

模式指出了一个单词是如何构成的,当分析出一个符合该规则的单词时,就执行相应的动作。

模式一定要位于一行的开头处,不能有缩进。

而动作的开头一定要与模式在同一行。

当动作是用一对花括号{}括起来时,可以将左花括号放在与规则相同的行,而其余部分则可以从下一行开始。

用户代码段(usercode)

所有用户代码都被原样拷贝到文件lex.yy.c中。

在这里可以定义一些辅助函数或代码,供扫描器yylex()调用,或者调用扫描器(一般来说就是main()了)。

这一部分是可有可无的。

如果没有的话,Flex文件中第二个%%是可以省略的。

在定义段或者规则段中,任何一行有缩进的文本或者包含在一对%{和%}之间的文本,都被原样拷贝到最后生成的C代码文件中(当然%{和%}会被移走)。

在书写时%{和%}都必须在一行的开始处,不能缩进。

在规则段中,第一条规则之前的任何未缩进的文本或者在%{和%}之间的文本,可以用来为扫描器声明一些本地变量和代码。

一旦进入扫描器的代码,这些代码就会被执行。

规则段内其他的缩进的文本或者%{和%}之间的文本还是被原样拷贝输出,但是他们的含义是尚未有明确定义,很可能引起编译时(compile-time)错误(这一特性是为了与POSIX兼容而提供的)。

在定义段中,没有缩进的注释也会被原样拷贝到最后生成的C代码文件中,例如以/*开始的一行注释,直到遇到*/,这中间的文本会被原样拷贝输出。

模式及其分类

模式采用正则表达式来书写。

正则表达式大致可以分为如下几类(从上到下,优先级依次递减):

(1)单字符匹配

*‘x’匹配字符x。

*‘.’匹配任意一个字符(字节),除了换行符。

*‘[xyz]’匹配单个字符,这个字符是方括号中给出的字符类(characterclass)中的一个。

*‘[abj-oZ]’匹配单个字符,这个字符是方括号中给出的字符类中的一个。

与上一方式的区别是指定字符类时用到了一个范围表示法:

j-o,这表示按照26个英文字母的顺序,从字母j开始一直到字母o共6个字母。

这里减号(-)表示范围。

如果减号本身也要作为一个匹配字符时,最好用转义字符(\)去除其特殊含义。

由于花括号({})在模式中用来引用名字,以及作为模式定义之后的动作(Action)定义块的首尾界定符,因此如果要在字符类中匹配花括号,必须用转义字符(\)去除其特殊含义。

下面这个例子定义了一个所有可打印字符的字符类:

[[:

alnum:

][:

blank:

]]\t+\-*/&!

_'?

@^`~$\\()%|.;[\]\{\}:

#<>=]

*‘[^A-Z]’匹配单个字符,这个字符必须是方括号中给定字符类以外的字符。

在方括号内开始处的特殊符号(^)表示否定。

当字符^不在字符类的开始处时,并不具有特殊含义,而是一个普通字符。

*‘[^A-Z\n]’匹配单个字符,这个字符不可以是方括号中给出的字符类中的字符。

与上一方式的不同在于,这里多了一个换行符,也就是说所匹配的字符不能是26个大写字母,也不能是换行符。

根据上面的描述,在表达字符分类时,除了直接用字符以及字符范围来表达外,还有一种叫做字符类表达式的,也有同样的作用,常见的一些表达式如下:

[:

alnum:

][:

alpha:

][:

blank:

][:

cntrl:

][:

digit:

][:

graph:

]

[:

lower:

][:

print:

][:

punct:

][:

space:

][:

upper:

][:

xdigit:

]

每一个表达式都指示了一个字符分类,而且其名称与标准C函数isXXXX的名字对应。

例如,[:

alnum:

]就指示了那些经由函数isalnum()检查后返回true的字符,也就是任何的字母或者数字。

注意,有些系统上没有给出C函数isblank()的定义,所以flex自己定义了[:

blank:

]为一个空格或者一个tab。

下面所举的几个例子,都是等价的:

[[:

alnum:

]]

[[:

alpha:

][:

digit:

]]

[[:

alpha:

]0-9]

[a-zA-Z0-9]

应该注意字符类表达式的写法。

一个字符类表达式是由一对[:

和:

]包住的,作为一个整体,在书写时不可与外层的[]混淆。

(2)重复模式的匹配

*‘r*’r是一个正则表达式,特殊字符`*'表示0个或多个。

因此这个模式表示匹配0个或多个r。

*‘r+’r是一个正则表达式,特殊字符`+'表示1个或多个。

因此这个模式表示匹配1个或多个r。

*‘r?

’r是一个正则表达式,特殊字符`?

'表示0个或1个。

因此这个模式表示匹配0个或1个r。

(从另一个角度看,就是说模式r是可选的)

*‘r{2,5}’r是一个正则表达式,{2,5}表示2个到5个。

因此这个模式表示匹配2个到5个r。

也就是说可以匹配`rr',`rrr',`rrrr',`rrrrr'四种重复的模式。

*‘r{2,}’r是一个正则表达式,{2,}省略了第二个数字,表示至少2个,不设上限。

因此这个模式表示匹配2个及以上个r。

也就是说至少可以匹配`rr',还可以匹配`rrr',`rrrr'等无限多种重复的模式。

*‘r{4}’r是一个正则表达式,{4}只有一个数字,表示4个。

因此这个模式确切地匹配4个r,即`rrrr'。

(3)名字替换

*‘{name}’这里name就是在前面的定义段给出的名字。

这个模式将用这个名字的定义来匹配。

(4)平凡(plain)文本串的匹配

*‘“[xyz]\″foo”’这个模式用来确切地匹配文本串:

[xyz]\″foo。

注意最外层的单引号所包含的是整个模式表达式,也就是说,当希望匹配字串[xyz]\″foo时,在书写规则时该字串必须用双引号括住。

(5)特殊单字符的匹配

*‘\x’当x是一个`a',`b',`f',`n',`r',`t'或`v'时,它就解释为ANSI-C中的\x。

否则就仍然作为一个普通字符x(一般用于诸如`*'字符的转义字符)。

*‘\0’匹配一个NUL字符(ASCII码值为0)。

*‘\123’匹配一个字符,其值用八进制表示为123。

*‘\x2a’匹配一个字符,其值用十六进制表示为2a。

(6)组合模式的匹配

*‘(r)’匹配规则表达式r,圆括号可以提高其优先级。

*‘rs’匹配规则表达式r,其后紧跟着表达式s。

这称为联接(concatenation)。

*‘r|s’或者匹配规则表达式r,或者匹配表达式s。

*‘r/s’匹配模式r,但是要求其后紧跟着模式s。

当需要判断本次匹配是否为“最长匹配(longestmatch)时,模式s匹配的文本也会被包括进来,但完成判断后开始执行对应的动作(action)之前,这些与模式s相配的文本会被返还给输入。

所以动作(action)只能看到模式r匹配到的文本。

这种模式类型叫做尾部上下文(trailingcontext)。

(有些‘r/s’组合是flex不能识别的;请参看后面deficiencies/bugs一节中的dangeroustrailingcontext的内容。

*‘^r’匹配模式r,但是这个模式只出现在一行的开始处。

也就是说,刚开始扫描时遇到的,或者说在刚扫描完一个换行字符后紧接着遇到的。

*‘r$’匹配模式r,但是这个模式只在一行的尾部。

也就是说,该模式就出现在换行之前。

这个模式等价于r/\n。

注意,flex中的换行(newline)的概念,就是C编译器中所使用的\n,flex也采用同样的符号和解释。

在DOS系统中,可能必须由你自己滤除输入中的\r,或者明确地在模式中写成r/\r\n来代替r$。

(在unix系统中换行是用一个字节\n表示的,而DOS/Windows则采用两个字节\r\n来表示换行。

(7)有启动条件(StartCondition)的模式匹配

*‘r’匹配模式r,但需要启动条件s(后面后关于启动条件的讨论)。

模式‘r’是类似的,匹配模式r,只要有三个启动条件s1,s2,s3中的任一个即可。

(启动条件简单来说,类似于C语言中的条件编译,满足了某个条件才启动这个模式参与匹配,否则不会启动该模式参与匹配。

*‘<*>r’匹配模式r,在任何启动条件下都参与匹配,即使是排斥性的条件。

[上述还需要从实践中体会其含义]

(8)文件尾匹配

*‘<>’匹配文件尾,即遇到了文件尾部。

一般说来,都应该在模式中加入文件尾模式。

这样可以有机会在文件扫描完成时增加一些额外的处理。

*‘<>’在有启动条件s1或者s2的情况下,匹配文件尾部。

一些常见规则的编写(待续)

(1)双引号字符串。

[\"]({SAFECHAR}|{RESTCHAR}|[_])*[\"]

这里需要注意的地方是中间的重复模式的写法:

(r)*。

r可以是一个组合模式。

中间的两个名称SAFECHAR和RESTCHAR是在定义段给出的两个字符类。

[此处应在实用中不断添加]

=========================================

创建一个简单的扫描器

下列例子来自于Flex的手册。

并在Windows+Cygwin+bison+flex+gcc的环境下编译运行。

(1) 编辑Flex语法文件。

/*name:

example.flex*/

intnum_lines=0,num_chars=0;

%%

\n++num_lines;++num_chars;

.++num_chars;

%%

intmain()

{

yylex();

printf("#oflines=%d,#ofchars=%d\n",num_lines,num_chars);

return0;

}

(2) 生成扫描器的C文件。

$flexexample.flex

Theoutputislex.yy.c

(3) 编译生成的C文件。

编译时失败,出现了如下的问题:

#gcc-g-Wall-lfl-oscanlex.yy.c

lex.yy.c:

959:

warning:

'yyunput'definedbutnotused

/cygdrive/c/DOCUME~1/ADMINI~1.78B/LOCALS~1/Temp/ccHwCWNb.o:

Infunction`main':

/cygdrive/c/home/sandbox/flex_exam_1/example.l:

9:

multipledefinitionof`_main'

/usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../libfl.a(libmain.o):

(.text+0x0):

firstdefinedhere

/cygdrive/c/DOCUME~1/ADMINI~1.78B/LOCALS~1/Temp/ccHwCWNb.o:

Infunction`yylex':

/cygdrive/c/home/sandbox/flex_exam_1/lex.yy.c:

692:

undefinedreferenceto`_yywrap'

/cygdrive/c/DOCUME~1/ADMINI~1.78B/LOCALS~1/Temp/ccHwCWNb.o:

Infunction`input':

/cygdrive/c/home/sandbox/flex_exam_1/lex.yy.c:

1041:

undefinedreferenceto`_yywrap'

collect2:

ldreturned1exitstatus

上述消息指出两个问题:

(1)函数yywrap没有定义。

(2)自定义函数main与连接库fl中的定义冲突了。

第一个问题的解决办法是在第一段(定义段)中加上一个选项指令:

%optionnoyywrap

第二个问题的解决办法就是用gcc编译时不连接fl库,如下所示:

#flexexample.flex

#ls

example.flexlex.yy.c

#gcc-g-Wall-oscanlex.yy.c

lex.yy.c:

977:

warning:

'yyunput'definedbutnotused

#ls

example.flexlex.yy.cscan.exe

#./scan.exe

789

234

345#oflines=2,#ofchars=11

修改过的代码如下:

%optionnoyywrap<====防止出现yywrap的问题

%{

intnum_lines=0,num_chars=0;

%}

%%

\n++num_lines;++num_chars;

.++num_chars;

%%

intmain()

{

yylex();

printf("#oflines=%d,#ofchars=%d\n",

num_lines,num_chars);

return0;

}

更改扫描器yylex()的名字

我们还可以更改Flex自动生成的词法分析函数yylex()的名字、参数以及返回值,也就是说yylex这个名字仅仅是一个默认的名称,是可以改成其他名称的。

方法很简单,只需要对宏YY_DECL做一个重定义即可:

#defineYY_DECLfloatlexscan(floata,floatb)

上述的宏定义就表明:

当运行Flex生成C代码时,词法分析函数的名字叫做lexscan(不再是yylex了),有两个浮点型参数a和b,函数的返回值是浮点型。

如果与Bison联用的话,还是不要更改的好,因为Bison要求词法分析函数的名称是yylex。

[应该也是可以改的,但其实际的方法还需在实践中得来。

]

词法分析函数yylex()会使用全局变量yyin读取字符。

一些思考

(1)在H248协议的BNF文本中,需要分析很多的数字,有十六进制的,有十进制的,有长的数字也有短的数字。

虽然在H248协议看来,各种不同的数字有着不同的意义,但是在Flex词法扫描器看来,它们有什么不同呢?

特别是同样的一个0xab这样的只有两位数字的十六进制数,在H248协议和BISON看来,其有不同的含义和类型,但是在Flex看来却没有什么不同。

假设Bison分别将其定义为Token_A和Token_B,那么当Flex分析出这么一个单词时,返回给Bison的数字类型是A还是B?

(2)在H248协议中,有一种表达式是由多个参数组成的,其中每个参数至多出现一次,且参数间次序是任意的。

此外其中有两个参数是必须的。

这种情况下如何给出Bison文法规则定义呢?

文法分析概览

利用BNF写出的文法规则,可以用来对输入的文本进行文法分析。

一条BNF文法规则,左边是一个非终结符(Symbol或者non-terminal),右边则定义该非终结符是如何构成的,也称为产生式(Production),产生式中可能包含非终结符,也可能包含终结符(terminal),也可能二者都有。

在所有文法规则中,必有一个开始的规则,该规则左边的部分叫做开始符号(startsymbol)。

一个规则的写法如下:

Symbol:

=Production

下面是一个BNF文法定义的例子。

FN是fractionalnumber的意思,DL是digitlist的意思,S是startsymbol。

S:

='-'FN|FN

FN:

=DL|DL'.'DL

DL:

=D|DDL

D:

='0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'

一个非终结符可能有多个产生式,相互间用竖线(|)隔开。

每一条BNF产生式,都有自己的启动集(startset)。

启动集里的元素就是每个Production中的第一个部分,比如上例S规则的启动集就是{'-'}以及{FN}。

利用BNF文法来分析目标文本,其分析方法比较流行的有几种,下面作一概述[Garshol03]。

LL(k)分析

LL分析又称为自顶向下的分析(top-downparsing),也有叫递归下降分析(recursive-descentparsing)。

也是最简单的一种分析方式。

它工作的方式类似于找出一个产生式可以从哪一个终结符开始。

当分析时,从起始符号开始,比较输入中的第一个终结符和启动集,看哪一个产生式规则被使用了。

当然,两个启动集之间不能拥有同一个终结符。

如果有的话,就没有办法决定选择哪个产生式规则了。

Ll文法通常用数字来分类,比如LL

(1),LL(0)等。

这个数字告诉你,在一个文法规则中的任何点可以允许一次察看的终结符的最大数量。

LL(0)就不需要看任何终结符,分析器总是可以选择正确的产生式规则。

它只适用于所有的非终结符都只有一个产生规则。

只有一个产生规则意味着只有一个字符串。

[不用看当前的终结符是什么就可以决定是哪一个产生规则,说明这个规则是为一个固定的字符串所写的。

]这种文法是没有什么意义的。

最常见也是比较有用的事LL

(1)文法。

它只需要看一个终结符,然后就可以决定使用哪一个产生规则。

而LL

(2)则可以查看两个终结符,还有LL(k)文法等等。

对于某个固定的k值,也存在着根本不是LL(k)的文法,而且还很普遍。

下面来分析一下本章开头给出的例子。

首先看下面这条规则:

D:

='0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'

上述规则有十个产生式,每个产生式的启动集是一个数字终结符构成的集合{'0'}、{'1'}、……、{'9'}。

这是一个很好的LL

(1)文法,因为我们只要看一个终结符,就可以选择一个正确的产生式。

例如,如果看到一个终结符,其内容是3,那么就采用上面第四个产生式,即D:

='3'。

接下来分析DL规则。

DL:

=D|DDL

上述规则有两个产生式,启动集是{D},{D}。

很不幸,两个产生式的启动集相同。

这就表示只看第一个输入中的第一个终结符不能选择正确的产生式。

然而可以通过欺骗来绕过这个问题:

如果输入中第二个终结符不是一个数字,那么就选择第一个产生式,但如果两者都是数字就必须选择第二个产生式。

换句话说,这意味着这是一条好的LL

(2)文法规则。

实际上这里有些东西被简化了。

再分析下FN规则吧。

它的情况更糟糕。

FN:

=DL|DL'.'DL

它有两条产生式,而且启动集相同,均为{DL}。

然而这次不像DL规则那么幸运了。

咋一看,似乎通过LL

(2)可以分辨应该使用哪一个产生式。

但是很不幸,我们无法确定在读到终结符('.')之前,需要读多少个数字才算是DL符号的最后一个数字。

[想想吧,分析器这么工作着:

读入第一个终结符,一看是相同的DL符号,那么就读第二个终结符吧;读入第二个终结符,两者合起来一看,还是一样的DL符号;读入第三个终结符,前三个终结符合起来看,仍然是相同的DL符号。

但是DL符号表指示数字表示没有长度限制的。

]没有任何一个给定的k值,这都不符合LL(k)文法,因为数字表总能突破这个k的长度。

最后看看启动符号规则。

有点意外,它产生规则的选择很简单。

S:

='-'FN|FN

它有两个产生规则,两者的启动集是{'-'}和{FN}。

因此,如果输入中第一个终结符是'-',那么就选择第一个产生式,否则选择第二个产生式。

所以这是一个LL

(1)文法。

从上述的LL分析看,只有FN和DL规则引起了问题。

但是不必绝望。

大部分的非LL(k)文法都可以容易地转换为LL

(1)文法。

下面以当前的这个例子来看看如何转换有问题的FN和DL。

对于FN符号来说,它的两个产生式都开始于DL,但是第二个产生式其后续的是一个小数点终结符('.'),以及另外一个数字表。

那么这很容易解决:

可以将FN改变为一个产生式,其以DL开始,后跟一个FP(fractionalpart)符号。

而FP符号则定义成或者为空,或者为小数点后跟着一个数字表,如下所示:

FN:

=DLFP

FP:

=@|'.'DL

上述@符号表示为空。

现在FN文法没有任何问题了,因为它现在只有一个产生式。

而FP也不会有问题,因为它的两个产生式的启动集是不同的:

前者是输入的尾端,后者是小数点终结符。

DL符

展开阅读全文
相关搜索

当前位置:首页 > 总结汇报 > 其它

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1