PHP语法分析器:RE2C BISON 总结

澳门新葡亰网址 8

yyparse拿到这个值,不断地translate,

七、文件信息分析。

tset.l分析test.txt文件中的关键词(即test.y中的token标记),遇到token返回给test.y,test.y判断

是否符合一定语法,符合则进行相应动作。

test.l

%{
#include "test.tab.h"

#include <stdio.h>
#include <string.h>
%}
char [A-Za-z]
num [0-9]
eq [=]
name {char}+
age {num}+
%%
{name}      { yylval = strdup(yytext); return NAME; }
{eq}        { return EQ; }
{age}       { yylval = strdup(yytext); return AGE; }
%%
int yywrap()
{
    return 1;
}

test.y

%{
#include <stdio.h>  
#include <stdlib.h> 
typedef char* string;
#define YYSTYPE string
%}
%token NAME EQ AGE
%%
file : record file
    | record
;
record : NAME EQ AGE {
                printf("%s is %s years old!!!n", $1, $3); }
;
%%
int main()
{
    extern FILE* yyin;
    if(!(yyin = fopen("test.txt", "r")))
    {
        perror("cannot open parsefile:");
        return -1;
    }    
    yyparse();
    fclose(yyin);
    return 0;
}
int yyerror(char *msg)
{
    printf("Error encountered: %s n", msg);
}

test.txt

ZhangSan=23
LiSi=34
WangWu=43

编译

澳门新葡亰网址 1

我们写的(f)lex语法规则,比如我们叫他Parse.y

四、The Scanner as Coroutine(协同程序)

即怎样将扫描到的标记给其他程序使用,下面的例子,希望扫描到+ 或
-时做一个特殊输出。

当调用yylex时,若扫描到return对应的标记时,yylex返回,且值就为return后的值;

若没扫描到return对应的标记,yylex继续执行,不返回。

下次调用自动从前一次的扫描位置处开始。

%{
enum yytokentype {
    ADD = 259,
    SUB = 260, 
};
%}
myadd   "+"
mysub   "-"
myother .
%%
{myadd}    { return ADD; }
{mysub}    { return SUB; }
{myother}  { printf("Mystery charactern"); }
%%
main(int argc, char **argv)
{
    int tok;
    while(tok = yylex()) {              //yylex的返回值只能是ADD 或 SUB.
        if(tok == ADD || tok == SUB) {printf("meet + or -n");}
        else {printf("this else statement will not be printed, 
            because if yylex return,the retrun value must be ADD or SUB.");}
    }
}

 

澳门新葡亰网址 2

 

我们写的规则,生成不同的token传递给parse。

五、yacc —— unix下是bison

1、yacc语法规则部分和BNF类同,先来看BNF巴克斯范式。

(1)<> 内包含的内容为必选项;

(2)[]  内的包含的内容为可选项;

(3){ } 内包含的为可重复0至无数次的项;

(4) | 表示在其左右两边任选一项,相当于”OR”的意思;

(5)::= 是“被定义为”的意思;

(6)双引号“”内的内容代表这些字符本身;而double _quote用来表示双引号。

(7)BNF范式举例,下面的例子用来定义java中的for语句:

     FOR_STATEMENT ::=

  ”for” “(” ( variable_declaration |

  ( expression “;” ) | “;” )

  [ expression ] “;”

  [ expression ]

  ”)” statement

2、yacc语法。

result: components { /*
        action to be taken in C */ }
        ;

(1)components是根据规则放在一起的终端和非终端符号,后面是{}括起来的执行的动作。

3、语法例子。

param : NAME EQ NAME { 
    printf("tName:%stValue(name):%sn", $1,$3); }         
    | NAME EQ VALUE {
    printf("tName:%stValue(value):%sn",$1,$3);}
    ;

simple_sentence: subject verb object
      |     subject verb object prep_phrase ;
subject:    NOUN
      |     PRONOUN
      |     ADJECTIVE subject ;
verb:       VERB
      |     ADVERB VERB
      |     verb VERB ;
object:     NOUN
      |     ADJECTIVE object ;
prep_phrase:     PREPOSITION NOUN ;

(1)理解 |  的意思,|表示左右两边任选一项,如| subject verb object
prep_phrase ;中|的左边为空,

所以该句表示匹配空或者subject verb object prep_phrase
;而上面还有一句subject verb object ,

所以

simple_sentence: subject verb object

              | subject verb object prep_phrase ;

的意思是匹配subject verb object 或 subject verb object prep_phrase ;

就不细说了。

声明:原创作品,转载注明出处

继续

二、一个简单的lex文件例子

1、来看flex&bison这本书开篇给出的例子:输入几行字符串,输出行数,单词数和字符的个数。

关于yylex即lex中相关变量系列3文章介绍。

/* just like Unix wc */
%{
int chars = 0;
int words = 0;
int lines = 0;
%}
%%
[a-zA-Z]+  { words++; chars += strlen(yytext); }
n         { chars++; lines++; }
.          { chars++; }
%%
main(int argc, char **argv)
{
  yylex();
  printf("%8d%8d%8dn", lines, words, chars);
}

2、按照下面过程编译。

#flex test.l

#gcc lex.yy.c –lfl

#./a.out

 澳门新葡亰网址 3

 

3、分析这个简单的lex文件:

(1)%%把文件分为3段,第一段是c和lex的全局声明,第二段是规则段,第三段是c代码。

(2)第一段的c代码要用%{和%}括起来,第三段的c代码不用。

(3)第二段规则段,[a-zA-Z]+  n   .
是正则表达式,{}内的是c编写的动作。

关于正则表达式系列3文章介绍。

 

4、如果不用-lfl选项,代码可以为下面这样(具体原因见lex的库和函数分析):

int chars = 0;
int words = 0;
int lines = 0;
int yywrap();
%}
%%
[a-zA-Z]+  { words++; chars += strlen(yytext); }
n         { chars++; lines++; }
.          { chars++; }
%%
main(int argc, char **argv)
{
  yylex();
  printf("%8d%8d%8dn", lines, words, chars);
}
int yywrap()
{
    return 1;
}

三、修改第一个例子,将正则表达式放在全局声明中

%{
int chars = 0;
int words = 0;
int lines = 0;
%}
mywords [a-zA-Z]+ 
mylines n 
mychars .  
%%
{mywords}  { words++; chars += strlen(yytext); }
{mylines}  { chars++; lines++; }
{mychars}  { chars++; }
%%
main(int argc, char **argv)
{
  yylex();
  printf("%8d%8d%8dn", lines, words, chars);
}

编译一同上。

我尽可能写的通俗易懂些。

澳门新葡亰网址,一、简介

推荐书籍《flex&bison》.

在UNIX下是flex和bison.网上介绍很多,大部分是写给懂的人看的,初学者一头雾水。这样来理解lex和yacc可能容易些:在linux下,有很多系统配置文件,一些linux下的软件也有配置文件,那么程序是如何读取配置文件中的信息的呢?

首先用到lex词法分析器,读取配置文件中的关键词(后面说到的token标记其实可看做关键词)。然后把关键词

递交给yacc,yacc对一些关键词进行匹配,看是否符合一定语法逻辑,如果符合就进行相应动作。

上面举得例子是分析配置文件内容的,当然可分析其他文件内容。

注:图中a.c是 扫描器生成的最终代码。。

六、flex和bison相结合。

test.l

%{  
#include "test.tab.h"  
#include <stdio.h>  
#include <stdlib.h>  
%}  
%%  
a   {return A_STATE;}  
b   {return B_STATE;}  
c   {return C_STATE;}  
not   {return NOT;}  
%%

test.y

%{  
#include <stdio.h>  
#include <stdlib.h>  
%}  
%token  A_STATE B_STATE C_STATE NOT  
%%  
program :     
    A_STATE B_STATE {  
        printf("1");  
    }  
    c_state_not_token  {  
        printf("2");  
    }  
    |    NOT {   
        printf("3");  
    }  
c_state_not_token : C_STATE {}  
%% 
yyerror(const char *s)
{
    fprintf(stderr, "error: %sn", s);
} 
int main()
{
    yyparse();
    return 0;
}

编译:

澳门新葡亰网址 4

 

 

于HipHop,我还没有做测试,不敢断言。

 

件,这个c文件才是真正的我们需要的语法分析程序,我更愿意叫它
语法生成器。如下图:

补充 :$$ $1 $2….

Each symbol in a bison rule has a value; the value of the target symbol
(the one to the
left of the colon) is called $$ in the action code, and the values on
the right are numbered
$1, $2, and so forth, up to the number of symbols in the rule.

$$——表示冒号的左边符号;$1——冒号右边第一个;$2——冒号右边第二个,依此类推。

如record : NAME EQ AGE { printf(“%s is %s years old!!!n”, $1, $3); } ;

匹配NAME EQ AGE后,$1即NAME所表示的内容,$3即AGE所表示的内容。

lex yacc
入门教程(3)正则表达式和lex变量及函数

 澳门新葡亰网址 5澳门新葡亰网址 6
澳门新葡亰网址 7

参考:



那对PHP源码的研究会更上一层楼地 ^.^…

八、token定义的标记的类型及union的使用。

token定义的标记的类型默认为int 且
默认赋值从258开始。如上面的例子,在生成的头文件

test.tab.h中有如下预编译,

/* Tokens.  */
#ifndef YYTOKENTYPE
# define YYTOKENTYPE
   /* Put the tokens into the symbol table, so that GDB and other debuggers
      know about them.  */
   enum yytokentype {
     NAME = 258,
     EQ = 259,
     AGE = 260
   };
#endif

如果想将token标记定义为其他类型呢?首先将类型定义在联合中,

%union {
   char *str;
   int  num;
   struct { int num1; int num2; } dnum;
}

然后,如下定义,

%token <str> K_HOST K_ERROR
%token <str> WORD PATH STRING
%token <num> NUM 
%token <dnum> DNUM 

会通过
yacc/bison编译成一个parse.tab.h,parse.tab.c的文件,parse根据不同的token进行不同的操作

那,TOKEN返回给yyparse之后做了什么呢?

YYCONDTYPE
用-c
模式你可以使用-to参数用来生成一个文件:使用包含枚举类型的作为条件。每个值都会在规则集合里面作为条件来使用。
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。一些简单的扫描器可能用不到。

编译到PHP中,我叫它 phptoc。

画个图来说明一下,
澳门新葡亰网址 8

关于flex的文件结构:

在规则段中,start是开始的地方,如果
scan识别到PHP开始标签就会返回T_OPEN_TAG,然后执行括号的代码,输出start.

下面会具体的说一说

其实我对这个项目的性能提高50%-60%持怀疑态度,从根本来讲,如果PHP用到APC缓存,它的性能是否低

#define YYCTYPE char   //输入符号的类型
#define STATE(name)     yyc##name
#define BEGIN(n)        YYSETCONDITION(STATE(n))
#define LANG_SCNG(v)    (sc_globals.v)
#define SCNG    LANG_SCNG
#define YYGETCONDITION()        SCNG(yy_state)
#define YYSETCONDITION(s)       SCNG(yy_state)=s

扫描器,顾名思义,就是对文件扫描,找出关键代码来。

BEGIN 是定义的宏

/* #include 文件*/
/*宏定义*/
//扫描函数
int scan(char *p){
/*扫描器规则区*/
}
//执行scan扫描函数,返回token到yacc/bison中。
int yylex(){
        int token;
        char *p=YYCURSOR;//YYCURSOR是一个指针,指向我们的PHP文本内容
        while(token=scan(p)){//这里会移动指针p,一个一个判断是不是我们上面定义好的scanner...
                return token;
        }
}
int main(int argc,char**argv){
        BEGIN(INITIAL);//
        YYCURSOR=argv[1];//YYCURSOR是一个指针,指向我们的PHP文本内容,
        yyparse();
}

解析器我用的是flex和bison。。。

进入正题

可能会有点绕,重新缕一缕:

re2c提供了一些宏接口,方面我们使用,我简单做了翻译,英语水平不好,可能有误,需要原文的可以去上面那个地址查看。

YYTRANSLATE宏接受yychar,然后返回所对应的值

PHPtoc,我只是想把C程序员解放出来,希望能达到,让PHPer用PHP代码就可以写出接近于PHP扩展性能的一个扩展,

"echo" {

return T_ECHO;
 }

成我们自己的程序。所以说,他们从根本来讲不是语法分析程序,他们只是将我们的规则生成一个独立的c文

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

这里最难的就是语法分析器了,大家应该都知道,PHP也有自己的语法分析器,现在版本用到的是re2c
和 Bison。

它会通过宏 #line 映射到 parse.y所对应
21行,T_OPEN_TAG的位置,然后执行

这个时候yychar是258,258是什么?

那解析器呢?

举例:

接口代码:

这个项目思路源于facebook的开源项目 HipHop .

所以决定重新写一套自己的语法分析规则,这个功能就等于是重写了PHP的语法分析器,当然会舍弃一些不常用的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关文章

网站地图xml地图