关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » PHP »PHP语法分析器:RE2C && BISON 总结

    PHP语法分析器:RE2C && BISON 总结

    2014-10-24 00:00:00 出处:Ico_Coco
    分享

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

    编译到PHP中,我叫它 phptoc。

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

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

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

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

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

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

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

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

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

    进入正题

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

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

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

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

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

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

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

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

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

    我们写的规则,生成不同的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编译到一起,就成了。

    下面会具体的说一说

    re2c,关于它的英文文档在 http://re2c.org/manual.html,感兴趣的可以去看看,我也翻译了一个中文版本,

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

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

    接口代码:

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

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

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

    扫描器文件结构:

    /* #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();
    }

    BEGIN 是定义的宏

    #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

    yyparse函数是在yacc 中定义的,

    里面有一个关键宏: YYLEX

    #define YYLEX yylex()

    它会执行scaner扫描器的yylex

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

    在scanner.l中,通过调用parse.y解析器函数yyparse,该函数调用scanner.l的yylex生成关键代码token,yylex

    将扫描器返回的

    token返回给parse.y,parse根据不同的token执行不同的代码.

    举例:

    scanner.l
    #include “scanner.h”
    #include “parse.tab.h”
    int scan(char *p){
    /*!re2c
    <INITIAL>”< php”([ t]|{NEWLINE}) {
    BEGIN(ST_IN_SCRIPTING);
    return T_OPEN_TAG;
    }
    “echo” {

    return T_ECHO;
    }
    [0-9]+ {
    return T_LNUMBER;
    }
    */
    }
    int yylex(){
    int c;

    // return T_STRING;
    int token;
    char *p=YYCURSOR;
    while(token=scan(p)){
    return token;
    }
    }

    int main (int argc,char ** argv){
    BEGIN(INITIAL);//初始化
    YYCURSOR=argv[1];//将用户输入的字符串放到YYCURSOR
    yyparse();//yyparse() -》yylex()-》yyparse()
    return 0;
    }

    这样一个简单的扫描器就做成了,

    那解析器呢?

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

    关于flex的文件结构:

    %{
    /*
    C代码段将逐字拷贝到lex编译后产生的C源文件中
    可以定义一些全局变量,数组,函数例程等…
    */
    #include
    #include “scanner.h”
    extern int yylex();//它在scanner.l中定义的。。
    void yyerror(char *);
    # define YYPARSE_PARAM tsrm_ls
    # define YYLEX_PARAM tsrm_ls
    %}
    {定义段,也就是token定义的地方}
    //这就是关键 token程序是根据这是做switch的。
    %token T_OPEN_TAG
    %token T_ECHO
    %token T_LNUMBER
    %%
    {规则段}
    start:
    T_OPEN_TAG{printf(“startn”); }
    |start statement
    ;
    statement:
    T_ECHO expr {printf(“echo :%sn”,$3)}
    ;
    expr:
    T_LNUMBER {$$=$1;}
    %%
    {用户代码段}
    void yyerror(char *msg){
    printf(“error:%sn”,msg);
    }

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

    在scanner.l中,调用scan的是个while循环,所以它会检查到php代码的末尾,

    yyparse会根据scan返回的标记做switch,然后goto到相应的代码,比如 yyparse.y发现当前的token是T_OPEN_TAG,

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

    画个图来说明一下,

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

    为了能直观一些,我用gdb跟踪:

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

    258是bison自动生成的枚举类型数据。

    继续

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

    #define YYTRANSLATE(YYX)
    ((unsigned int) (YYX) <= YYMAXUTOK yytranslate[YYX] : YYUNDEFTOK)

    /* YYTRANSLATE[YYLEX] — Bison symbol number corresponding to YYLEX. */
    static const yytype_uint8 yytranslate[] =
    {
    0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 27, 2,
    22, 23, 2, 2, 28, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 21,
    2, 26, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 24, 2, 25, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
    5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
    15, 16, 17, 18, 19, 20
    };

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

    bison会生成很多用来映射的数组,将最终的translate保存到yyn,

    这样bison就能找到token所对应的代码

    switch (yyn)
    {
    case 2:

    /* Line 1455 of yacc.c */
    #line 30 “parse.y”
    {printf(“startn”); ;}
    break;

    不断循环,生成token逐条执行,然后解析成所对应的zend 函数等,生成对应的op保存在哈希表中,这些不是本文的重点,

    就不细说了。

    到这里,整体的流程就结束了。。剩下的就是细化的工作,假如有时间,我再继续phptoc :)

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    Destoon 模板存放规则及语法参考

    Destoon系统常量与变量

    Destoon系统目录文件结构说明

    Destoon 系统安装指南

    Destoon会员公司主页模板风格添加方法

    Destoon 二次开发入门

    Microsoft 将于 2026 年 10 月终止对 Windows 11 SE 的支持

    Windows 11 存储感知如何设置?了解Windows 11 存储感知开启的好处

    Windows 11 24H2 更新灾难:系统升级了,SSD固态盘不见了...

    小米路由器买哪款?Miwifi热门路由器型号对比分析

    IT头条

    Synology 对 Office 套件进行重大 AI 更新,增强私有云的生产力和安全性

    01:43

    StorONE 的高效平台将 Storage Guardian 数据中心占用空间减少 80%

    11:03

    年赚千亿的印度能源巨头Nayara 云服务瘫痪,被微软卡了一下脖子

    12:54

    国产6nm GPU新突破!砺算科技官宣:自研TrueGPU架构7月26日发布

    01:57

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    技术热点

    如何删除自带的不常用应用为windows 7减负

    MySQL中多表删除方法

    改进的二值图像像素标记算法及程序实现

    windows 7 32位系统下手动修改磁盘属性例如M盘修改为F盘

    windows 7中怎么样在家庭组互传文件

    Linux应用集成MySQL数据库访问技巧

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。