转自:项目总结之词法分析器

无论是词法分析,还是语法分析,给我的第一感觉就是逻辑要严谨。由于项目有自己一套完整的语言和语法,设计好其对应的词法分析器和语法分析器显得尤为重要。

我们采用flex进行词法分析。flex是一个用来生成扫描器(scanners)的工具,其中扫描器就是可以识别文本中词法模式的程序。具体流程为:flex读取给定的输入文件,或标准输入(当没有给定文件名时)读取信息来生成一个扫描器。信息以正则表达式和C代码组成,这种形式称为规则(rule)。flex生成C源代码文件lex.yy.c,其中定义了一个函数yylex()。这个文件通过编译,并用-lfl 链接生成可执行文件。当可执行文件被执行时,它分析输入中可能存在的符合正则表达的内容。当找到任何一个与正则表达式相匹配内容时,相应的C 代码将被执行。

flex输入文件由三段组成:定义(definitions),规则(rules),用户代码(user code)

一、定义段(definitions)

        定义段包含了简单名称的声明(这些声明可以简化扫描器的说明)和开始条件。在本项目中,定义段中还包含了选项options。现将介绍一些比较常用的options。

flex 提供一个机制用来在扫描器的说明中,而不是在flex 命令中控制选项。在扫描器的说明文件(flex 的输入文件)的第一段中使用%option 指令就可以实现。你可以用单个%option 指令指定多个选项,也可以使用多个%option指令。

%option 7bit,%option 8bit——指示flex生成一个7bit或8bit的扫描器与-7,-8 选项等价。

%option backup——生成一个备份信息到lex.backup,与-b选项等价。

%option caseful,%option case-sensitive——区分大小写,与-i相反。

%option case-insensitive,%option caseless——忽略大小写,与-i选项等价。

%option debug——让生成的扫描器运行在debug模式,与-d选项等价。

%option default,%option nodefault——%default与-s选项相反,后者与其等价。-s选项作用:使不匹配的输入回显到输出设备的rule失去作用。在此种情况下,如果扫描器不能匹配到任何规则rule的输入,它会终止并返回错误。在查找扫描器的规则漏洞时,-s和%option nodefault都非常有用。

%option interactive——指示flex生成一个交互式的扫描器。交互式扫描器就是向前查看下一个匹配的token是什么。结果就是总向前多看了一个字符,即使是在扫描器已经看够了文本已经排除了token 的歧义。但向前查看给了扫描器强大的交互能力。与-I等价。

%option warn——与-w选项相反。%option nowarn与-w选项等价。

%option array——与%array等价。

%option pointer——与%point等价。

以下为%option中定义,但在命令行里没有的特性。

%option always-interactive——指示flex 生成的扫描器总是把它的输入认为是"interactive"。

%option main——指示flex 为扫描器提供一个缺省的main()函数,它只是简单的调用了yylex()。这个选项暗示noyywrap。

 %option never-interactive——flex 生成的扫描器从不认为输入是交互的(不会调用isatty())。这和总是interactive 正好相反。

%option yylineno——flex 生成的扫描器用全局变量yylineno 维护着输入文件的当前行编号。option lex-compat隐含有这个选项。

%option yywrap——如果没有设置(就如%option noyywrap),当扫描器遇到end-of-file 时,不会调用yywrap(),但简单的假定没有更多的文件可以扫描(直到用户把yyin 指向新的文件并再一次调用yylex())。

flex 通过扫描rule 中的action 来判断你是否使用了REJECT 或是yymore 属性。你可用%option reject 表示要使用这个特性而用%option noyymore 表示不使用这个特性。

三个选项使用了字符串值,从'='开始:%option outfile="ABC"等同于-oABC ;%option prefix="XYZ" 等同于-PXYZ;最后,%option yyclass="foo" 只有当生成C++扫描器(-+选项)时才有效。

有些选项可以限制一个例程不出现在生成的扫描器中。下面这些例程如果不被设置(如%option nounput)将不会出现在生成的扫描器中。

input unput yy_push_state yy_pop_sate yy_top_state yy_scan_buffer yy_scan_bytes yy_scan_string

可重入c扫描器(Reentrant C Scanners)

       flex能够生成一个可重入的扫描器。通过定义%option reentrant(与-R选项等价)来实现可重入。所生成的扫描器在一个或多个控制线程中不仅可移植,而且安全性好。可重入扫描器通常应用于多线程应用程序。任何一个线程都可以在不考虑与其他线程同步的情况下创建并执行一个可重入的flex扫描器。

       默认情况下,flex生成一个不可重入的扫描器。本项目为了实现多线程,因而在定义段指定%option reentrant。

性能考虑(performance consideration)

flex的设计目标就是生成一个高性能的扫描器。它已经对处理大量rule 做了优化。除了用-C 选项进行表格压缩之外,还有一些option/action 会影响到扫描器的速度。从最大影响到最弱,有这一些:

REJECT          %option yylineno           arbitrary trailing context

pattern sets that require backing up        %array          %option interactive             %option always-interactive

'^'beginning-of-line operator      yymore()

头三个的开销最大,后两个的开销最小。注意unput()有可能被用例程实现而造成更多操作,而yyless()是一个开销相当低的宏;所以如果只是回放一些你多扫了的文本,可以用yyless()

本项目中也用到了名字定义和开始条件。其中名字定义包括数字、字符、空白符,多行注释,单行注释,引号间的字符串,整数、浮点数、实数,标示符,变量,日期。

数字—digit [0-9],字符—character [a-zA-Z],空白符—space [ \t\r](在制表符前面留有空格表示空格符)

多行注释(以/#开头,中间可以为任意非#非\n字符,也可以为一串#后面紧跟非/非\n字符,最后结尾为1个或多个#后跟/)

comstart   \/\#

comstop \#+\/

cominside ([^#\n]*|#+[^#/\n])

单行注释 line_comment  ^#[^\n]*

引号间的字符串(以双引号开头以双引号结尾。内容为非转义字符和双引号,当遇到转义字符时,进行特殊处理;当遇到双引号时,停止匹配)

dquotes \"

stringstart {dquotes}

stringstop {dquotes}

stringinside [^\\\"]+

注意:在多行注释和引号间的字符串的匹配中,采用了排斥条件(开始条件分为排斥和共享条件)

排斥条件的定义为 %xc(针对多行注释)

%xs(针对引号间的字符串)

整数 integer {digit}+

浮点数 decimal (({digit}+\.{digit}*)|({digit}*\.{digit}+))

decimalfail {digit}+\.\.

实数 real ({integer}|{decimal})[eE][+-]?{digit}+

realfail1 ({integer}|decimal)[eE]

realfail2 ({integer}|decimal)[eE][+-]

标示符 identstart [a-zA-Z\200-\377_]

identcont [a-zA-Z\200-\377_0-9\$]

identifier {identstart}{identcont}*

变量($后跟一个或多个字符) variable \${character}+

日期 date {digit}+\-{digit}+(\-{digit}+)?

datefail1 {digit}+\-{digit}+\-

datefail2 {digit}+\-

二、规则段(rules)

规则段包含模式(pattern)和动作(action),其中模式不能有缩进,而且动作必须在同一行上跟在动作后面。

在规则段可以使用开始条件(start conditions)。flex 提供了一种按条件激活规则rule 的机制。所有模式以"<sc>"为前缀的rule 只有在扫描器是在一个名为"sc"的启动条件时才会被激活。使用BEGIN action 可以激活一个开始条件。直到下一个BEGIN action 被执行,在给出开始条件的rule将被激活并且其他给出其他开始条件的rule 并不会被激活。如果使用的是排他的开始条件,那么只有以开始条件修饰的rule 才会被激活。跟在同一个排他开始条件后的rule 说明在扫描器中,这些rule 是独立于flex 输入中的其他rule。

本项目中涉及到排斥条件的有多行注释、引号间的字符串。

其中MOVELOC,SAVETOKEN为定义段中定义的宏

#define MOVELOC  {yylloc->first_column = yylloc->last_column;\

yylloc->last_column = yylloc->first_column + yyleng;}

#define RESETLOC {yylloc->first_column = yylloc->last_column = 1;\

yylloc->first_line++;\

yylloc->last_line++;}

#define SAVETOKEN yylval->str = new std::string(yytext, yyleng)

多行注释(语句输出省略)

{comstart}       {         MOVELOC;

BEGIN(xc);

}

<xc>{cominside} {     MOVELOC;      }

<xc>\n              {         RESETLOC;     }

<xc>{comstop} {       MOVELOC;

BEGIN(INITIAL);

}

<xc><<EOF>> {       BEGIN(INITIAL);

std::cerr << "unterminated /# comment" << endl;

yyterminate();

}

引号间的字符串(输出语句省略)

{stringstart}         {   MOVELOC;

BEGIN(xs);

SAVETOKEN;

}

<xs>{stringstop} {  MOVELOC;

BEGIN(INITIAL);

*(yylval->str) += yytext;

return QUOTES_STRING;

}

<xs>\n               {       RESETLOC;

*(yylval->str) += yytext;

}

<xs>\\n               {     MOVELOC;

*(yylval->str) += "\n";

}

<xs>\\t               {      MOVELOC;

*(yylval->str) += "\t";

}

<xs>\\r               {      MOVLOC;

*(yylval->str) += "\r";

}

<xs>\\b             {      MOVELOC;

*(yylval->str) += "\b";

}

<xs>\\f               {      MOVELOC;

*(yylval->str) += "\f";

}

<xs>\\.             {       MOVELOC;

*(yylval->str) += yytext[1];

}

<xs>\\\n             {     RESETLOC;

*(yylval->str) += "\n";

}

<xs>{stringinside} {        MOVELOC;

*(yylval->str) += yytext;

}

<xs><<EOF>> {     BEGIN(INITIAL);

std::cerr << "unterminated \"" << endl;

delete yylval->str;

yyterminate();

}

三、用户代码段

用户代码段只会简单的拷贝到lex.yy.c中。这个和扫描器一起,调用扫描器或者被扫描器调用。如果被省略,则第二个%%可以省略。

使用了%option reentrant

1所有的函数都会带一个额外的参数yyscanner。

2所有的全局变量都被它们的宏等价替换。

这些变量包括yytext,yylengyylinenoyyinyyout,yyextrayylval, and yylloc,你可以在action部分安全地使用这些宏(如同使用普通变量一样),但不能够在外部直接使用。以yytext为例,在一个可重入的扫描器中,yytext以及其他类似变量都不是全局变量,因而不能通过action外部或是其他函数来直接访问yytext,而应该使用yyget_text访问器函数来实现对yytext的访问。

      3在使用yylex之前调用yylex_init,在使用之后调用yylex_destroy。

init以及destroy函数

int yylex_init ( yyscan_t * ptr_yy_globals ) ;

int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t * ptr_yy_globals ) ;

int yylex ( yyscan_t yyscanner ) ;

int yylex_destroy ( yyscan_t yyscanner ) ;

函数yylex_init必须在调用任意其他函数之前调用,其参数是一个未初始化的指针地址,并由该函数初始化,这样会覆盖以前的内容。ptr_yy_global中存储的值会传递给yylex和yylex_destroy。flex不会保存传递给yylex_init的变量,因而传递一个局部指针的地址值给yylex_init是很安全的,只要其在调用扫描器到调用yylex_destroy期间一直存在就行。

yylex的可重入版本带一个参数,该参数即为yylex_init通过变量返回的值。

yylex_destroy函数用来释放扫描器使用过的资源。当要重复使用时,就不必destroy。

4获取函数(get或set)提供了访问普通flex变量的途径。

5用户自定义数据可以再yyextra中存储。

在一个可重入的扫描器中,使用全局变量让程序的不同部分通信或是保持状态是不明智的。然而,你需要在action中使用额外的数据或是调用额外的函数。同样,你需要传递信息给你的扫描器。在一个不可重入的扫描器中,实现这的唯一方式就是使用全局变量。flex允许你存储任意的、额外的数据到扫描器中。定义如下:

#define YY_EXTRA_TYPE void*

YY_EXTRA_TYPE yyget_extra ( yyscan_t scanner );

void yyset_extra ( YY_EXTRA_TYPE arbitrary_data , yyscan_t scanner);

项目中最后的代码如下,其中scanner_init初始化yylex,yy_scan_buffer函数(作用是建立输入缓存)从yyext->scanbuf指定的开始位置扫描slen+2个字节,最后两个字节必须是YY_END_OF_BUFFER_CHAR。

  1. yyscan_t
  2. scanner_init(const char *str, inl_yylex_extra *yyext)
  3. {
  4. int             slen = strlen(str);
  5. yyscan_t        scanner;
  6. if(yylex_init(&scanner) != 0)
  7. {
  8. std::cerr << "yylex_init() failed" << std::endl;
  9. exit(1);
  10. }
  11. inl_yyset_extra(yyext, scanner);
  12. yyext->scanbuf = (char *)malloc(slen + 2);
  13. yyext->scanbuflen = slen;
  14. memcpy(yyext->scanbuf, str, slen);
  15. yyext->scanbuf[slen] = yyext->scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR;
  16. yy_scan_buffer(yyext->scanbuf, slen + 2, scanner);
  17. return scanner;
  18. }
  19. void scanner_finish(yyscan_t yyscanner)
  20. {
  21. free((*((inl_yylex_extra**)(yyscanner)))->scanbuf);
  22. inl_yylex_destroy(yyscanner);
  23. }

词法分析器总结--flex&bison的更多相关文章

  1. Flex & Bison 开始

    Flex 与 Bison 是为编译器和解释器的编程人员特别设计的工具: Flex 用于词法分析(lexical analysis,或称 scanning),把输入分割成一个个有意义的词块,称为记号(t ...

  2. OpenCASCADE Expression Interpreter by Flex & Bison

    OpenCASCADE Expression Interpreter by Flex & Bison eryar@163.com Abstract. OpenCASCADE provide d ...

  3. 编译器工具 Flex Bison for Windows 简单入门例子

    最近从事一个系统仿真软件的开发,里面定义了自己的描述性语言MSL, MSL语言经FlexBison转换成C语言,然后用C编译器来编译并计算仿真. 现在领域驱动开发比较热门,有机会定义自己的语言对程序员 ...

  4. Windows下 flex + bison 小例子

    .下载flex和bison,网址是http://gnuwin32.sourceforge.net/packages/flex.htm 和http://gnuwin32.sourceforge.net/ ...

  5. 应注意的Flex&Bison潜规则

    1.Flex的二义性模式 语法分析器匹配输入时匹配尽可能多的字符串 如果两个模式都可以匹配的话,匹配在程序中更早出的模式. 针对这一点的理解,在语法分析文件当中,token的识别,应从特殊到一般的过程 ...

  6. flex&bison 1

    .   {ECHO;}-----单独的flex使用中有效 .   { yyerror();}--------flex和bison交叉使用,即使不调用yyerror函数,也会报错的 error: syn ...

  7. flex/bison 计算器

    flex %{ #include <stdio.h> #include "mycalc.tab.h" ;} %} %% "+" return ADD ...

  8. [flex & bison]编译器杂谈

    flex与bison是编译器设计工具.这里的编译器为广义,其中包括一般的编译器.脚本解析器等,需要进行语言结构解析来得出意义的程序. 当我们需要用一个语言来设计一款编译器时,需要考虑太多设计重心外的东 ...

  9. lex yacc flex bison

    lex与yacc是两个在Unix下的分别作词法分析和语法分析的工具, Linux对应flex与bison. windows:http://sourceforge.net/projects/unxuti ...

随机推荐

  1. SDUT2013级測试赛_D

    题目描写叙述 给出一棵含有n个点的树.每一个点权值为wi.求从根节点到叶子结点权值和最大的那条路经的权值和是多少. 输入 n(1<= n && n <= 10000). 接 ...

  2. IE8 通过Jquery动态修改html不显示的问题

    for (var i = 0; i < _priority_transf.length && i < xmlList.length; i++) { if (textCont ...

  3. wsgi & cgi的一些概念解释

    可以看这里 https://www.zhihu.com/question/19998865 如何理解 CGI, WSGI?修改 写补充说明 举报 添加评论 分享 • 邀请回答 默认排序 按时间排序 1 ...

  4. 设备树(Device Tree)

    设备树介绍: 设备树是一个描述设备硬件资源的文件,该文件是由节点组成的树形结构.如下: / { node1 { a-string-property = "A string"; a- ...

  5. JavaScript:避免代码的重复执行

    我喜欢到一些大型网站上去翻阅它们的原代码,期望能找到一些可以应用到自己的代码中的模式,或发现一些之前从未听说过的工具和技巧.可是,在我查看这些大型网站的源代码时,经常会发现一个问题,那就是重复的代码执 ...

  6. C#中的yield

    一.C#中yield关键字用于遍历循环中,yield语句的两种形式 yield return用于返回IEnumerable<T>, yield break用于终止循环遍历. 二.yield ...

  7. RS报表设计采用Total汇总过滤出错

    错误信息: DMR 子查询计划失败,并产生意外错误.: java.lang.NullPointerException 如图 原因是在RS过滤器中添加了: total([门诊人次] for [明细科室] ...

  8. Install certificates needed for Visual Studio offline installation

    Visual Studio is primarily designed for installation from an internet-connected machine, since many ...

  9. [ES6] 04. The let keyword -- 2 Fiald case

    Fiald case 1: let can work in it's block { let a = 10; var b = 1; } a // ReferenceError: a is not de ...

  10. 架构设计:系统间通信(20)——MQ:消息协议(下)

    (接上文<架构设计:系统间通信(19)--MQ:消息协议(上)>) 上篇文章中我们重点讨论了"协议"的重要性.并为各位读者介绍了Stomp协议和XMPP协议. 这两种协 ...