PHP语法分析器:RE2C BISON 总结

澳门新葡亰娱乐在线 2

在这之前,我曾经尝试过一个项目,就是将我们的PHP代码自动生成so扩展,

语言是人们进行沟通和交流的表达符号,每种语言都有专属于自己的符号,表达方式和规则。
就编程语言来说,它也是由特定的符号,特定的表达方式和规则组成。语言的作用是沟通,不管是自然语言,还是编程语言,它们的区别在于自然语言是人与人之间沟通的工具,
而编程语言是人与机器之间的沟通渠道。

编译到PHP中,我叫它 phptoc。

就PHP语言来说,它也是一组符合一定规则的约定的指令。
在编程人员将自己的想法以PHP语言实现后,通过PHP的虚拟机(确切的来说应该是PHP的语言引擎Zend)

但是由于各种原因,暂停了此项目。

将这些PHP指令转变成C语言
(可以理解为更底层的一种指令集)指令,而C语言又会转变成汇编语言,
最后汇编语言将根据处理器的规则转变成机器码执行。这是一个更高层次抽象的不断具体化,不断细化的过程。

写这篇文章一是因为这方面资料太少,二是把自己的收获总结下来,以便以后参考,如果能明白PHP语法分析

从一种语言到另一种语言的转化称之为编译,这两种语言分别可以称之为源语言和目标语言。
这种编译过程通过发生在目标语言比源语言更低级(或者说更底层)。
语言转化的编译过程是由编译器来完成,
编码器通常被分为一系列的过程:词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成等。
前面几个阶段(词法分析、语法分析和语义分析)的作用是分析源程序,我们可以称之为编译器的前端。
后面的几个阶段(中间代码生成、代码优化和目标代码生成)的作用是构造目标程序,我们可以称之为编译器的后端。
一种语言被称为编译类语言,一般是由于在程序执行之前有一个翻译的过程,
其中关键点是有一个形式上完全不同的等价程序生成。
而PHP之所以被称为解释类语言,就是因为并没有这样的一个程序生成,
它生成的是中间代码Opcode,这只是PHP的一种内部数据结构。

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

二、 PHP代码的执行的过程

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

比如我们写一个简单的程序

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

<?php  
    echo "Hello World!";  
    $a = 1 + 1;  
    echo $a;  
?>  

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

这个简单的程序他执行过程是怎样的呢?其实,执行过程也正如我们前面所说分为4个步骤。(这里只是指PHP语言引擎Zend执行过程,不包含Web服务器的执行过程。)

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

  1. 1.Scanning(Lexing) ,将PHP代码转换为语言片段(Tokens)

  2. 2.Parsing, 将Tokens转换成简单而有意义的表达式

  3. 3.Compilation, 将表达式编译成Opocdes

  4. 4.Execution, 顺次执行Opcodes,每次一条,从而实现PHP脚本的功能。

    注1:Opcode是一种PHP脚本编译后的中间语言,就像Java的ByteCode,或者.NET的MSL

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

注2:现在有的Cache比如APC,可以使得PHP缓存住Opcodes,这样,每次有请求来临的时候,就不需要重复执行前面3步,从而能大幅的提高PHP的执行速度。

它的流程如下,读取PHP文件,解析PHP代码,对其进行语法分析器,生成对应的ZendAPI,编译成扩展。

将PHP代码转换为语言片段(Tokens)

澳门新葡亰娱乐在线 1

那什么是Lexing?
学过编译原理的同学都应该对编译原理中的词法分析步骤有所了解,Lex就是一个词法分析的依据表。

进入正题

对于PHP在开始使用的是Flex,之后改为re2c,
MySQL的词法分析使用的Flex,除此之外还有作为UNIX系统标准词法分析器的Lex等。
这些工具都会读进一个代表词法分析器规则的输入字符串流,然后输出以C语言实做的词法分析器源代码。
这里我们只介绍PHP的现版词法分析器,re2c。

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

在源码目录下的Zend/zend_language_scanner.l 文件是re2c的规则文件,
如果需要修改该规则文件需要安装re2c才能重新编译,生成新的规则文件。

所以,我自然也用到了这个组合。

Zend/zend_language_scanner.c会根据Zend/zend_language_scanner.l,来输入的
PHP代码进行词法分析,从而得到一个一个的“词”。

如果要用PHP的语法分析器就不太现实了,因为需要修改zend_language_parser.y和
zend_language_scanner.l 并重新编译,这难度大不说,还可能影响PHP自身。

从PHP4.2开始提供了一个函数叫token_get_all,这个函数就可以将一段PHP代码
Scanning成Tokens;

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

我们用下面的代码使用token_get_all函数处理我们开头提到的PHP代码。

re2c &&
yacc/bison,通过引用自己的对应文件,然后将他们统一编译成一个*.c文件,最后再gcc编译就会生

<?php  
echo "<pre>";  
$phpcode = <<<PHPCODE  
<?php  
    echo "Hello World!";  
    $a = 1 + 1;  
    echo $a;  
?>  
PHPCODE;  
// $tokens = token_get_all($phpcontent);  
// print_r($tokens);  
$tokens = token_get_all($phpcode);   
foreach ($tokens as $key => $token) {  
    $tokens[$key][0] = token_name($token[0]);  
}  
print_r($tokens);  
?>  

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

为了便于理解和查看,我使用token_name函数将解析器代号修改成了符号名称说明。

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

如果有的童鞋想要看原始的,可以将上面代码中的第10,11行代码注释去掉。

澳门新葡亰娱乐在线 2

解释器代号列表详见:http://www.php.NET/manual/zh/tokens.php

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

得到的结果如下:

re2c扫描器,假如我们写的扫描规则文件叫scanner.l,它会将我们写的PHP文件内容,进行扫描,然后根据

Array  
(  
    [0] => Array  
        (  
            [0] => T_OPEN_TAG  
            [1] =>  1  
        )  

    [1] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>     
            [2] => 2  
        )  

    [2] => Array  
        (  
            [0] => T_ECHO  
            [1] => echo  
            [2] => 2  
        )  

    [3] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 2  
        )  

    [4] => Array  
        (  
            [0] => T_CONSTANT_ENCAPSED_STRING  
            [1] => "Hello World!"  
            [2] => 2  
        )  

    [5] =>   
    [6] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>   

            [2] => 2  
        )  

    [7] =>   
    [8] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 3  
        )  

    [9] => Array  
        (  
            [0] => T_LNUMBER  
            [1] => 1  
            [2] => 3  
        )  

    [10] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 3  
        )  

    [11] =>   
    [12] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 3  
        )  

    [13] => Array  
        (  
            [0] => T_LNUMBER  
            [1] => 1  
            [2] => 3  
        )  

    [14] =>   
    [15] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>   

            [2] => 3  
        )  

    [16] => Array  
        (  
            [0] => T_ECHO  
            [1] => echo  
            [2] => 4  
        )  

    [17] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 4  
        )  

    [18] =>   
    [19] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>   

            [2] => 4  
        )  

    [20] => Array  
        (  
            [0] => T_CLOSE_TAG  
            [1] => ?>  
            [2] => 5  
        )  

)  

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

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

析这个返回结果我们可以发现,源码中的字符串,字符,空格

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

都会原样返回。

比如我们PHP代码是 “echo 1″;

每个源代码中的字符,都会出现在相应的顺序处。

扫描其中有一个规则:

而其他的,比如标签,操作符,语句,都会被转换成一个包含

"echo" {

return T_ECHO;
 }

扫描器函数scan会拿到”echo
1″字符串,它对这一段代码进行循环,如果发现有echo字符串,那么它就作为关键字返回token:T_ECHO,

部分的

parse.y和scanner.l会分别生成两个c文件,scanner.c和parse.tab.c,用gcc编译到一起,就成了。

1、Token ID

下面会具体的说一说

解释器代号

re2c,关于它的英文文档在

(也就是在Zend内部的改Token的对应码,比如,T_ECHO,T_STRING)

还么有结束,稍后我会放上来。

2、源码中的原来的内容

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

3、该词在源码中是第几行

接口代码:

Parsing, 将Tokens转换成简单而有意义的表达式

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

接下来,就是Parsing阶段了,Parsing首先会丢弃Tokens Array中的多于的空格,

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。一些简单的扫描器可能用不到。

然后将剩余的Tokens转换成一个一个的简单的表达式

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

1.echo a constant string  
2.add two numbers together  
3.store the result of the prior expression to a variable  
4.echo a variable  

扫描器文件结构:

Bison是一种通用目的的分析器生成器。它将LALR(1)上下文无关文法的描述转化成分析该文法的C程序。
使用它可以生成解释器,编译器,协议实现等多种程序。
Bison向上兼容Yacc,所有书写正确的Yacc语法都应该可以不加修改地在Bison下工作。
它不但与Yacc兼容还具有许多Yacc不具备的特性。

发表评论

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

相关文章

网站地图xml地图