标签归档:Lex

re2c中文手册

re2c中文手册

在PHP的实现过程中,包括PHP语言本身的词法分析,一共有多达8处的地方使用了re2c,如果我们常用的时间函数、pdo扩展等。对re2c的了解更能促进我们进PHP内核实现的认知。

本手册是re2c官网的manual.html文件翻译稿,仅适用于对re2c的初步了解,更多的资料见re2c项目中lessons目录和doc目录。

Name

re2c – 将正则表达式转化成C/C++代码

Synopsisre2c [-bdDefFghisuvVw1] [-o output] [-c [-t header]] file

Description

re2c是一个将正则表达式转化成基于C语言标识的预处理器。

re2c的输入包含C/C++代码,并且以/*!re2c… */注释的格式将扫描标识交错嵌入到这些代码中。在它的输出中,这些注释将会被生成的代码替换掉,当执行时,它将会查找到下一个token,并且执行用户提供的针对该token的特定代码。
如下示例:

char *scan(char *p)
	{
	/*!re2c
	        re2c:define:YYCTYPE  = "unsigned char";
	        re2c:define:YYCURSOR = p;
	        re2c:yyfill:enable   = 0;
	        re2c:yych:conversion = 1;
	        re2c:indent:top      = 1;
	        [0-9]+          {return p;}
	        [^]             {return (char*)0;}
	*/
	}

re2c将生成如下代码:

/* Generated by re2c on Sat Apr 16 11:40:58 1994 */
	char *scan(char *p)
	{
	    {
	        unsigned char yych;
 
	       yych = (unsigned char)*p;
	        if(yych <= '/') goto yy4;
	        if(yych >= ':') goto yy4;
	        ++p;
	        yych = (unsigned char)*p;
	        goto yy7;
	yy3:
	        {return p;}
	yy4:
	        ++p;
	        yych = (unsigned char)*p;
	        {return char*)0;}
	yy6:
	        ++p;
	        yych = (unsigned char)*p;
	yy7:
	        if(yych <= '/') goto yy3;
	        if(yych <= '9') goto yy6;
	        goto yy3;
	    }
 
	}

你可以通过添加注释:/*!max:re2c/ 来输出一个宏定义 YYMAXFILL 来保存输入解析时字符的最大个数。如果使用了-1, YYMAXFILL 只能在最后的 /*!re2c/ 后触发一次。同时,你也可以使用 /*!ignore:re2c */ 来为扫描代码添加注释文档,它们被输出。

Options

re2c提供如下的选项:

  • -?
  • -h 帮助
  • -b 当指定-b参数时,-s参数也会被默认同时指定。 使用位向量尝试着从编译器捣鼓出更好的代码。它对于关键字比较多的规则很有用,比如大部分的编程语言。re2c的实现是通过生成256个ascii字符的映射表,直接判断对应的字符串是否应该跳转到下一个字符,从而实现优化。
  • -c 支持类lex或flex的表达式
  • -d 创建一个解析器用来打印当前位置的信息,这对于调试非常有用。如果你要使用它,你需要定义一个供解析器调用的YYDEBUG宏,它像一个函数一样,接受两个参数:void YYDEBUG(int state,char current)。第一个参数是state或者-1,第二个参数是当前所解析的代码位置。在每个++YYCURSOR、不同的goto跳转变化处,re2c自动添加YYDEBUG宏调用。如果在规则文件中没定义YYDEUBG宏,在编译C文件时会出错。
  • -D 输出Graphviz dot 格式的数据,比如可以使用” dot -Tpng input.dot > output.png”来处理生成图片。注意扫描器中如果包含太多的状态可能会让dot程序崩溃
  • -e 从ASCII平台交叉编译EBCDIC
  • -f 生成带可存储状态的扫描器。更多详情见下面的可存储的扫描器小节。
  • -F 部分支持flex语法。当-F标记有效时,flex的变量用大括号括起来,并且在定义时不需要等号,在结束时不需要用分号。否则,名字被认为是直接被引号的字符串。
  • -g 使用GCC的goto特性生成扫描器。当决策复杂时re2c会生成决策跳转表,使用goto针对不同的情况做不同的跳转。仅适用于GCC编译器。注意,这里默认指定了-b参数。re2c的实现中,-g参数会生成yytarget决策跳转表,其实就是一个256个元素的一维数据,针对不同的字符,直接跳转,以优化扫描器。
  • -i 不输出行信息,当你的用户从你的代码编译,而你又不要求他们拥有re2c环境,此时你可以使用CMS工具管理re2c的输出文件时,此参数就有用武之地了。-o参数指定输出文件。
    指在生成的.c文件中不使用#line宏。
  • -r 允许扫描器在每个 ‘/!use:re2c’块后面重用定义的 ‘/!use:re2c’ 块。这些块可以包含适当的配置,特别是 ‘re2c:flags:w’和re2c:flags:u’。这种方法可能会为不同的字符类型,不同的输入机制或不同的输出机制多次创建相同的扫描器。’/!use:re2c’ 块也可以在 ‘/!rules:re2c’中的规则集中包含额外的规则。
  • -s 为一些switch语句生成嵌套的if语句。许多编译器需要这个参数的辅助以便生成更好的代码。
  • -t 生成一个类型定义的头文件,以支持类(f)lex条件,当需要使用-t参数时,需同时指定-c参数,-t参数后面接生成的头文件名称。如果只指定re2c会报错:re2c: error: Can only output a header file when using -c switch
  • -u 生成一个支持Unicode编码的解析器。这意味着生成的代码能处理任何有效的Unicode字符,直到x10FFFF。当需要支持UTF-8或UTF-16时,你需要自己将输入的数据转化成UTF-32编码。
  • -v 查看版本信息。如:re2c 0.13.6
  • -V 以数字格式查看版本信息。如:001306
  • -w 创建支持宽字符格式的解析器,默认指定-s参数,不能和-e参数共存。
  • -1 强制一次生成,它不能和-f组合在一起使用,并且在re2c块结束之前不能禁用YYMAXFILL。
  • –no-generation-date 禁止输出生成日志,所以只会输出re2c的版本信息。
  • –case-insensitive 所有字符串不区分大小写,所以,双引号中的字符和单引号的意义一样。
  • –case-inverted 颠倒单引号和双引号包含的字符中的意思,比如,有了这个开关,单引号内的字符串区分大小写,双引号内的字符串不区分大小写。

Interface Code接口代码

不像其他的扫描器程序,re2c 不会生成完整的扫描器:用户必须提供一些接口代码。用户必须定义下面的宏或者是其他相应的配置。

  • YYCONDTYPE 用-c 模式你可以使用-t参数来生成一个包含了会被作为条件使用的枚举类型的文件。枚举类型中的每个值都会在规则集合里面作为条件来使用。
  • YYCTYPE 用来维持一个输入符号。通常是 char 或者unsigned char。
  • YYCTXMARKER *YYCTYPE类型的表达式,生成的代码回溯信息的上下文会保存在
  • YYCTXMARKER。如果扫描器规则需要使用上下文中的一个或多个正则表达式,则用户需要定义这个宏。
  • YYCURSOR *YYCTYPE类型的表达式指针指向当前输入的符号,生成的代码作为符号相匹配,在开始的地方,YYCURSOR假定指向当前token的第一个字符。在结束时,YYCURSOR将会指向下一个token的第一个字符。
  • YYDEBUG(state,current) 这个只有指定-d标记的时候才会需要。调用用户定义的函数时可以非常容易的调试生成的代码。
    这个函数应该有以下签名:void YYDEBUG(int state,char current)。第一个参数接受 state ,默认值为-1第二个参数接受输入的当前位置。
  • YYFILL(n) 当缓冲器需要填充的时候,生成的代码将会调用YYFILL(n):至少提供n个字符。YYFILL(n)将会根据需要调整YYCURSOR,YYLIMIT,YYMARKER 和 YYCTXMARKER。注意在典型的程序语言当中,n等于最长的关键词的长度加一。用户可以在/*!max:re2c/一次定义YYMAXFILL来指定最长长度。如果使用了-1,YYMAXFILL将会在/*!re2c/之后调用一次阻塞。
  • YYGETCONDITION() 如果使用了-c模式,这个定义将会在扫描器代码之前获取条件集。这个值必须初始化为枚举YYCONDTYPE的类型。
  • YYGETSTATE() 如果指定了-f模式,用户就需要定义这个宏。此种情况下,扫描器在开始时为了获取保存的状态,生成的代码将会调用YYGETSTATE()。YYGETSTATE()必须返回一个有符号的整数,这个值如果是-1,告诉扫描器这是第一次执行,否则这个值等于以前YYSETSTATE(s) 保存的状态。否则,扫描器将会恢复操作之后立即调用YYFILL(n)。
  • YYLIMIT 这是一个类型为*YYCTYPE的表达式,它标记了缓冲器的结尾(YYLIMIT[-1]是缓冲区的最后一个字符)。生成的代码将会不断的比较YYCORSUR 和 YYLIMIT 以决定 什么时候填充缓冲区。
  • YYSETCONDITION(c) 这个宏用来在转换规则中设置条件,它只有在指定-c模式和使用转换规则时有用。
  • YYSETSTATE(s) 用户只需要在指定-f模式时定义这个宏,如果是这样,生成的代码将会在YYFILL(n)之前调用YYSETSTATE(s),YYSETSTATE的参数是一个有符号整型,被称为唯一的标示特定的YYFILL(n)实例。
  • YYMARKER 类型为*YYCTYPE的表达式,生成的代码保存回溯信息到YYMARKER。一些简单的扫描器可能用不到。
  • 解析器支持条件 当使用-c参数时,你可以使用正则表达式条件列表。这样re2c会为每个条件生成扫描块,在每一个生成的扫描器都有自己的先决条件。先决条件是定义YYGETCONDETION ,而且类型必须是YYCONDTYPE。
  • YYSETSTATE(s) 用户只需要在指定-f模式时定义这个宏。在此种情况下,生成的代码将会在YYFILL(n)之前调用YYSETSTATE(s),YYSETSTATE的参数是一个有符号整型,被称为唯一的标示特定的YYFILL(n)实例。如果用户希望保存扫描器的状态并用YYFILL(n) 将状态返回给调用 者,他所需要做的是在变量中保存这个唯一的标识。然后,当再次调用扫描器时,它将调用
  • YYGETSTATE()并在恢复到之前离开的地方继续执行。即使禁用了 YYFILL(n) ,生成的代码也会包含YYSETSTATE(s)和YYGETSTATE。

Scanner With Storable States可存储状态的扫描器

当指定-f标记时,re2c会生成一个存储了它当前状态的扫描器,它能精确的恢复到之前离开的位置,并返回给调用者。

re2c的默认行为是拉模式,无论何时需要,它都可以要求额外的输入,然而,这种操作模式是基于扫描器可以控制解析循环这一前提的,而这个前提并不一定会存在。
通常情况下,如果有一个预处理过程或其它相关的源程序数据在扫描器之前先执行,则扫描器无法再要求更多的数据,除非他们都在独立的线程之中。

-f标记刚好可以解决这个问题:它让用户设计的扫描器以拉模式工作,即数据一块一块的输入到扫描器中。当扫描器运行数据时,它仅存储它的状态,并返回给调用者。当更多的输入数据输入到扫描器时,它能很精确的恢复到之前离开的位置。

当re2c使用-f选项时,它不能接收标准输入,因为它必须做两次完整的全局扫描,而两次扫描就需要读取两次。这就意味着,如果不能打开输入两次或第一次输入影响第二次输入,re2c会执行失败。

相对于拉模式,可存储的扫描器有以下不同:

  1. 用户必须提供YYSETSTATE() 宏和YYGETSTATE(state)宏
  2. -f参数禁止了yych和yyaccept的声明。因此用户必须声明这些,并且必须能够保存和恢复他们。在example/push.re文件的示例中,这些都被声明为C++类的字段,因此他们不再需要明确的保存或恢复。对于C语言来说,我们可以通过宏,以参数传递的方式从结构体中获取这些字段。或者,可以将他们声明为局部变量,当它决定返回并将之作为函数的一个项保存在 YYFILL(n)中。此外, 使用YYFILL(n)保存的效率更高,因为可以无条件的调用YYSETSTATE(state)。然而,YYFILL(n) 并不能将state作为参数,因此,我们必须通过YYSETSTATE(state)将state保存到局部变量中。
  3. 如果需要更多的输入,需要修改YYFILL(n) ,使之可以从调用它的函数处返回。
  4. 修改调用者的逻辑,使其在需要更多的输入时做出相应的应答。
  5. 生成的代码中将包含一个选择逻辑块,这个选择逻辑会被用来通过跳转到相应的YYFILL(n)调用处,以恢复最后的状态。这个代码块会在第一个 “/*!re2c */”块收尾的地方自动生成。通过放置 “/*!getstate:re2c */”注释,可能会触发YYGETSTATE() 的生成操作。这对于被包含在循环中的扫描器非常有用。

请查看 examples/push.re文件中的推模式示例扫描器。它生成的代码可以通过”state:abort”和”state:nextlabel”调整。

Scanner With Condition Support 可判断条件的扫描

当使用-c参数时,你可以在正则表达式之前优先一系统的条件名。在这种情况下,re2c会针对每个条件生成扫描代码块。这些代码块都有它自己的前置条件,这此前置条件都是通过接口定义YYGETCONDITON实现,并且必须为YYCONDTYPE类型。

其中有两个特别的类型,一个是‘*’,它表示满足所有条件;另一个是空条件,它提供一个没有扫描内容的代码块,这意味着不需要任何正则表达式。这个特殊的块始终有一个固定的枚举值0。这些特殊的规则可以被用来初始化一个扫描器。这些特殊的规则并不是必须的,但是有时可以用它来声明一些没有初始化的状态。

非空规则允许指定新的条件,这些条件将导致规则的变化。它会生成定义的YYSETCONDTITION,除此之外再无其它。

还有另一种特殊的规则,它允许在所有的有规则和没有规则代码前添加代码。例如,它可以用来保存扫描的字符串的长度。这个特殊的规则以感叹号开始,后面可以接条件 <! condition, … > 或星号<!*>。当re2c为这个规则生成代码时,如果这个规则的状态没有起始规则或已经在在一个星号规则,那么这个代码将作为起始代码。

Scanner Specifications 扫描器规则

每个扫描器规格都由 规则集、命名定义和配置构成。

规则由正则以及紧跟其后面的C/C++代码构成,当正则匹配时,其后的C/C++代码会被执行。你可以以大括号或:=开始代码。当用大括号开始代码时,re2c会根据大括号判断其尝试并自动结束代码的查找。如果不使用大括号开始代码,则re2c会在第一行不为空时停止查找。

	regular-expression { C/C++ code }
	regular-expression := C/C++ code

如果指定-c参数,则每个正则前面都会有一系列的由逗号分隔的条件名称。除了正常命名的规则以外,有两种特殊的情况。一个规则可能包含一个单独的条件名称’*’和没有条件名称。对于没有条件名称的情况,其后面不能接正则表达式。非空规则可能会进一步指定新的条件。在这种情况下,re2c可能会自动生成必要的代码来改变条件。如上所示代码,其以大括号和’:=’开始代码。更进一步,更多的规则可以使用’:=>’快捷方式来自动生成代码,它不仅仅可以设置新的状态,还可以继续执行新的状态。一个快捷规则不应该在循环中使用,这些循环代码在循环开始和re2c块之间,除非用 re2c:cond:goto使之 ‘continue;’如果一段代码必须放在所有的规则之前,你可以使用<! 伪规则。

    <condition-list> regular-expression { C/C++ code }
    <condition-list> regular-expression := C/C++ code
    <condition-list> regular-expression => condition { C/C++ code }
    <condition-list> regular-expression => condition := C/C++ code
    <condition-list> regular-expression :=> condition
    <*> regular-expression { C/C++ code }
    <*> regular-expression := C/C++ code
    <*> regular-expression => condition { C/C++ code }
    <*> regular-expression => condition := C/C++ code
    <*> regular-expression :=> condition
    <> { C/C++ code }
    <> := C/C++ code
    <> => condition { C/C++ code }
    <> => condition := C/C++ code
    <> :=> condition
    <!condition-list> { C/C++ code }
    <!condition-list> := C/C++ code
    <!*> { C/C++ code }
    <!*> := C/C++ code

命名定义格式如下:

name = regular-expression;

如果使用了-F 模式,可以使用如下命名定义方法:

name regular-expression&lt; /pre&gt;"re2c"开始的命名定义配置如下所示:
<pre lang="c">re2c:name = value;
re2c:name = "value";

Summary Of Re2c Regular-expressionsre2c正则表达式小结

  • “foo” 字符串foo。可以使用ANSI-C转义序列。
  • [xyz] 字符集;此种情况匹配字符x,y或z
  • [abj-oZ] 包含区间的字符集,此种情况匹配a,b,j到o之间的任一字符,或z
  • [^class] 字符集否定匹配,匹配没有在方括号中定义的字符。
  • r\s 匹配非s的正则,r和s都必须是可以表示为字符集的正则表达式
  • r* 零次或多次匹配,r是任一正则表达式
  • r+ 一次或多次匹配(至少一次)
  • r? 零次或一次匹配
  • name 这里name就是在前面的定义段给出的名字
  • (r) 匹配规则表达式r,圆括号可以提高其优先级。
  • rs 匹配规则表达式r,其后紧跟着表达式s。这称为联接(concatenation)。
  • r|s 或者匹配规则表达式r,或者匹配表达式s。
  • r/s 匹配模式r,但是要求其后紧跟着模式s。s并不会参与文本的匹配。这种正则表达式的匹配称之为“尾部上下文”
  • r{n} n次匹配
  • r{n,} 至少n次匹配
  • r{n,m} 至少n次,至多m次匹配;匹配除换行符外的任意字符
  • def 当没有使用-F参数时,匹配的命名定义通过def定义。当-F参数指定时,def语名和双引号包含的效果一样,直接匹配def字符串。字符集和字符串可能包含有八进制或十六进制或如下的转义字符 (\n, \t, \v, \b, \r, \f, \a, \)。一个八进制字符由一个反斜杠和紧跟着它的三个八进制数字组成,一个十六进制字符由一个反斜杠,一个小写的x,以及两个十六进制数字组成,或由一个反斜杠,一个大写的X,以及四个十六进制数字组成。re2c进一步会支持更多的C/C++的unicode符号。这些unicode符号由一个反斜杠+u+四个十六进制数字或一个反斜杠+U+八个十六进制的数字组成。然后,仅当-u模式下才能处理这些uincode字符。

在非unicode模式下,大于\X00FF的字符是无法直接匹配的,除非使用”万金油“类型的 (.|”\n”)和[^]正则表达式匹配所有的字符时,包含它们。

如上所示的正则表达式列表按优先级分组,从最上面的最高优先级到最下面的最低优先级。这些组合之间的优先级相同。

Inplace Configuration现场配置

它可能在re2c块中配置并生成代码,如下所示为可用的配置项:

  • re2c:condprefix = yyc_ ;
    允许指定条件标签的前缀。它将在生成的输出文件中的所有条件标签前添加指定的前缀。
  • re2c:condenumprefix = yyc ;
    允许指定条件值的前缀。它将在生成的输出文件中的所有条件枚举值前添加指定的前缀。
  • re2c:cond:divider = “/* *********************************** */” ;
    允许为条件块自定义分隔符。你可以使用’@@’输出条件的名字或使用
  • re2c:cond:divider@cond = @@ ;
    指定即将被 re2c:cond:divider中的条件名替换的占位符。
  • re2c:cond:goto = “goto @@;” ;
    允许使用 ‘:=>’ 规则自定义条件跳转语句。你可以使用’@@’输出条件的名字或使用re2c:cond:divider@cond自定义占位符,同时你也可以使用此语句继续下一个循环周期,这个循环周期包括循环开始到re2c块之间的任何代码。
  • re2c:cond:goto@cond = @@ ;
    指定即将在 re2c:cond:goto语句中被替换的条件标签占位符
  • re2c:indent:top = 0 ;
    指定最小的缩进,大于或等于0
  • re2c:indent:string = “\t” ;
    指定缩进用的字符串。除非你想使用外部工具,否则就需要只包含空白字符串。最简单的方法就是用单引号或双引号包含它们。如果你不需要任何缩进,直接使用””即可。
  • re2c:yych:conversion = 0 ;
    当此设置非零时,re2c会在读取yych时自动生成转换代码。此时的类型必须使用re2c:define:YYCTYPE定义。
  • re2c:yych:emit = 1 ;
    设置为0可以禁止yych的生成。
  • re2c:yybm:hex = 0 ;
    如果设置为0,则生成一个十进制表格,否则将生成一个十六进制表格
  • re2c:yyfill:enable = 1 ;
    将此设置为0可以禁止YYFILL(n)的生成。当使用它时请确认生成的扫描器在输入之后不再读取。允许此行为将给你的程序引入服务安全问题。
  • re2c:yyfill:check = 1 ;
    当YYLIMIT + max(YYFILL)一直可用时,把此设置为0可以禁止使用YYCURSOR和YYLIMIT的先决条件的输出。
  • re2c:yyfill:parameter = 1 ;
    允许禁止YYFILL调用的参数传递。如果设置为0,将没有任何参数传递到YYFILL。然而,define:YYFILL@LEN允许指定一个字符串替换实际字符中的长度。如果设置为非0,除非设置re2c:define:YYFILL:naked,否则YYFILL将使用紧跟其后的大括号内的所要求的字符数。其它请参照:re2c:define:YYFILL:naked和re2c:define:YYFILL@LEN.
  • re2c:startlabel = 0 ;如果设置为0的整数,即使没有扫描器本身,下一个扫描块的开始标签也会被生成。否则仅在需要的时候生成常规的yy0开始标签。如果设置为一个文本值,不管常规的开始标签生成是否,包含当前文本的标签都会被生成。在开始标签生成后,当前设置会被重置为0。
  • re2c:labelprefix = yy ;
    允许修改数字标签的前缀,默认为yy,任何有效的标签都是可以的。
  • re2c:state:abort = 0 ;
    当设置为非零,并且开启-f模式时,YYGETSTATE 块会包含一个默认的情况,初始化时设置为-1
  • re2c:state:nextlabel = 0 ;
    当开启-f模式时,使用此设置可以控制是否在YYGETSTATE块后面接yyNext标签行。通常,你可以用startlabel配置强制指定开始标签或用默认的yy0作为开始标签,而不是用yyNext。通常我们通过放置”/*!getstate:re2c */” 注释来分隔实际扫描器的YYGETSTATE 代码,而不是专用的标签。
  • re2c:cgoto:threshold = 9 ;
    当启用-g模式时,这个值指定生成的跳转表的复杂度阈值,而不是使用嵌套的if语句和决策位字段。
  • re2c:yych:conversion = 0 ;
    当输入使用有符号字符时,并且开启-s和-b械时,re2c会自动将其转化为无符号类型。当设置为0时会禁用空字符串转化。设置为非零时,转化将在YYCTYPE处进行。如果这个值通过现场配置,则使用该值。否则,将会变成(YYCTYPE),并且不能再修改成配置。当设置为一个字符串时,必须用括号括起来。现在,假设你的输入为char*并且使用上述的设置,你可以设置YYCTYPE为unsigned char,并且当前值设置为1或者”(unsigned char)”
  • re2c:define:define:YYCONDTYPE = YYCONDTYPE ;枚举用于支持-c模式的条件
  • re2c:define:YYCTXMARKER = YYCTXMARKER ;
    允许覆盖定义的YYCTXMARKER ,从而避免将其设置为实际所需的代码。
  • re2c:define:YYCTYPE = YYCTYPE ;
    允许覆盖定义的YYCTYPE ,从而避免将其设置为实际所需的代码。
  • re2c:define:YYCURSOR = YYCURSOR ;
    允许覆盖定义的YYCURSOR ,从而避免将其设置为实际所需的代码。
  • re2c:define:YYDEBUG = YYDEBUG ;
    允许覆盖定义的YYDEBUG ,从而避免将其设置为实际所需的代码。
  • re2c:define:YYFILL = YYFILL ;
    允许覆盖定义的YYFILL ,从而避免将其设置为实际所需的代码。
  • re2c:define:YYFILL:naked = 0 ;
    当设置为1时,括号、参数、分号都会被发出。
  • re2c:define:YYFILL@len = @@ ;
    当使用 re2c:define:YYFILL 时,并且re2c:yyfill:parameter 为0时,YYFILL 中的任何文本将会被新的实际的长度值替换。
  • re2c:define:YYGETCONDITION = YYGETCONDITION ;
    允许覆盖定义的YYGETCONDITION
  • re2c:define:YYGETCONDITION:naked = ;
    当设置为1时,括号、参数、分号都会被发出。
  • re2c:define:YYGETSTATE = YYGETSTATE ;
    允许覆盖定义的YYGETSTATE ,从而避免将其设置为实际所需的代码。
  • re2c:define:YYGETSTATE:naked = 0 ;
    当设置为1时,括号、参数、分号都会被发出。
  • re2c:define:YYLIMIT = YYLIMIT ;
    允许覆盖定义的YYLIMIT ,从而避免将其设置为实际所需的代码。
  • re2c:define:YYMARKER = YYMARKER ;
    允许覆盖定义YYMARKER,从而避免将其设置为实际所需的代码。
  • re2c:define:YYSETCONDITION = YYSETCONDITION ;
    允许覆盖定义的YYSETCONDITION
  • re2c:define:YYSETCONDITION@cond = @@ ;
    当使用 re2c:define:YYSETCONDITION时,YYSETCONDITION中的任何文本将会被新的实际的
    条件值替换。
  • re2c:define:YYSETSTATE = YYSETSTATE ;
    允许覆盖定义的YYSETSTATE,从而避免将其设置为实际所需的代码。
  • re2c:define:YYSETSTATE:naked = 0 ;
    当设置为1时,括号、参数、分号都会被发出。
  • re2c:define:YYSETSTATE@state = @@ ;
    当使用re2c:define:YYSETSTATE时,YYSETCONDITION中的任何文本将会被新的实际的状态值替换
  • re2c:label:yyFillLabel = yyFillLabel ;
    允许覆盖标签yyFillLabel,即可以自定义生成的yyFillLabel 变量名。
  • re2c:label:yyNext = yyNext ;
    允许覆盖标签yyNext ,即可以自定义生成的yyNext变量名。
  • re2c:variable:yyaccept = yyaccept ;
    允许覆盖变量yyaccept,即可以自定义生成的yyaccept变量名。
  • re2c:variable:yybm = yybm ;
    允许覆盖变量yybm,即可以自定义生成的yybm变量名。
  • re2c:variable:yych = yych ;
    允许覆盖变量yych,即可以自定义生成的yych变量名。
  • re2c:variable:yyctable = yyctable ;
    当指定-c参数和-g参数时,re2c会使用此变量为YYGETCONDITION生成静态跳转表。
  • re2c:variable:yystable = yystable ;
    当指定-f参数和-g参数时,re2c会使用此变量为YYGETSTATE生成静态跳转表。
  • re2c:variable:yytarget = yytarget ;
    允许覆盖变量yytarget,即可以自定义生成的yytarget变量名。

Understanding Re2c 理解re2c

re2c的子目录中包含各种例子教你一步一步的如何开启re2c的世界,所有的例子都是可编译运行的。

Features特点

re2c不提供默认的动作:生成的代码假定输入包含一系列token。通常,可以通过添加一条规则实现,例如上面示例中的异常字符

因为re2c不提供结束表达式,所以用户必须安排一个输入结束符并让一个规则匹配并捕获它。
如果来源是一个以空字符串结尾的字符串,则匹配一个空字符串就可以了。如果来源是一个文件,你可以在文件后添加一个换行(或其它不会出现的标记);通过识别这个字符,以检查这是否为一个标记点并执行相应的操作。同样,你也可以使用YYFILL(n)判断是否没有足够的字符可用时结束扫描。

BugsDifference only works for character sets.
The re2c internal algorithms need documentation.
See Alsoflex(1), lex(1).
More information on re2c can be found here:

http://re2c.org/

Authors

Peter Bumbulis peter@csg.uwaterloo.ca
Brian Young bayoung@acm.org
Dan Nuffer nuffer@users.sourceforge.net
Marcus Boerger helly@users.sourceforge.net
Hartmut Kaiser hkaiser@users.sourceforge.net
Emmanuel Mogenet mgix@mgix.com added storable state

英文原地址:http://re2c.org/manual.html
译者:胖子(http://www.phppan.com/)
友情协助:吴帅(http://www.imsiren.com/)
校验:reeze(http://www.reeze.cn)
特别鸣谢:老婆大人的亲自指点和崽崽的听话。

使用Bison和re2c构建词法分析和语法分析器

使用说明: 本文需要读者对C语言有一定的基础,对于re2c和bison有一些了解,最好也熟悉linux命令

我们在前面介绍了PHP的语法分析器-Bison入门PHP的词法解析器:re2c,那么如何将re2c与bison集成在一起的呢? 我们以一个从PHP源码中剥离出来的示例来说明整个过程。这个示例的功能与词法分析器的示例类似,作用都是识别输入参数中的字符串类型。 本示例是在其基础上添加了语法解析过程。 首先我们看这个示例的语法文件:demo.y

%{
#include <stdio.h>
#include "demo_scanner.h"
extern int yylex(znode *zendlval);
void yyerror(char const *);

#define YYSTYPE znode   //关键点一,znode定义在demo_scanner.h   
%}

%pure_parser    //  关键点二

%token T_BEGIN
%token T_NUMBER
%token T_LOWER_CHAR
%token T_UPPER_CHAR
%token T_EXIT
%token T_UNKNOWN
%token T_INPUT_ERROR
%token T_END
%token T_WHITESPACE

%%

begin: T_BEGIN {printf("begin:\ntoken=%d\n", $1.op_type);}
     | begin variable {
        printf("token=%d ", $2.op_type);
        if ($2.constant.value.str.len > 0) {
            printf("text=%s", $2.constant.value.str.val);
        }
        printf("\n");
}

variable: T_NUMBER {$$ = $1;}
|T_LOWER_CHAR {$$ = $1;}
|T_UPPER_CHAR {$$ = $1;}
|T_EXIT {$$ = $1;}
|T_UNKNOWN {$$ = $1;}
|T_INPUT_ERROR {$$ = $1;}
|T_END {$$ = $1;}
|T_WHITESPACE {$$ = $1;}

%%

void yyerror(char const *s) {
    printf("%s\n", s);
}

这个语法文件有两个关键点:

1、znode是复制PHP源码中的znode,只是这里我们只保留了两个字段,其结构如下:

typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
} zvalue_value;

typedef struct _zval_struct {
    /* Variable information */
    zvalue_value value;     /* value */
    int type;    /* active type */
}zval;

typedef struct _znode {
    int op_type;
    zval constant;
}znode;

这里我们同样也复制了PHP的zval结构,但是我们也只取了关于整型,浮点型和字符串型的结构。 op_type用于记录操作的类型,constant记录分析过程获取的数据。 一般来说,在一个简单的程序中,对所有的语言结构的语义值使用同一个数据类型就足够用了。比如在前一小节的逆波兰记号计算器示例就只有double类型。 而且Bison默认是对于所有语义值使用int类型。如果要指明其它的类型,可以像我们示例一样将YYSTYPE定义成一个宏:

#define YYSTYPE znode

2、%pure_parser 在Bison中声明%pure_parse表明你要产生一个可重入(reentrant)的分析器。默认情况下Bison调用的词法分析函数名为yylex,并且其参数为void,如果定义了YYLEX_PARAM,则使用YYLEX_PARAM为参数, 这种情况我们可以在Bison生成的.c文件中发现其是使用#ifdef实现。

如果声明了%pure_parser,通信变量yylval和yylloc则变为yyparse函数中的局部变量,变量yynerrs也变为在yyparse中的局部变量,而yyparse自己的调用方式并没有改变。比如在我们的示例中我们声明了可重入,并且使用zval类型的变更作为yylex函数的第一个参数,则在生成的.c文件中,我们可以看到yylval的类型变成

一个可重入(reentrant)程序是在执行过程中不变更的程序;换句话说,它全部由纯(pure)(只读)代码构成。 当可异步执行的时候,可重入特性非常重要。例如,从一个句柄调用不可重入程序可能是不安全的。 在带有多线程控制的系统中,一个非可重入程序必须只能被互锁(interlocks)调用。

通过声明可重入函数和使用znode参数,我们可以记录分析过程中获取的值和词法分析过程产生的token。 在yyparse调用过程中会调用yylex函数,在本示例中的yylex函数是借助re2c生成的。 在demo_scanner.l文件中定义了词法的规则。大部分规则是借用了上一小节的示例, 在此基础上我们增加了新的yylex函数,并且将zendlval作为通信变量,把词法分析过程中的字符串和token传递回来。 而与此相关的增加的操作为:

SCNG(yy_text) = YYCURSOR;   //  记录当前字符串所在位置
/*!re2c
  <!*> {yyleng = YYCURSOR - SCNG(yy_text);} //  记录字符串长度

main函数发生了一些改变:

int main(int argc, char* argv[])
{
    BEGIN(INITIAL); //  全局初始化,需要放在scan调用之前
    scanner_globals.yy_cursor = argv[1];    //将输入的第一个参数作为要解析的字符串

    yyparse();
    return 0;
}

在新的main函数中,我们新增加了yyparse函数的调用,此函数在执行过程中会自动调用yylex函数。

如果需要运行这个程序,则需要执行下面的命令:

re2c -o demo_scanner.c -c -t demo_scanner_def.h demo_scanner.l
bison -d demo.y
gcc -o t demo.tab.c demo_scanner.c
chmod +x t
./t "<?php tipi2011"

相关代码下载请移步TIPI项目