设为首页 收藏本站
查看: 855|回复: 0

[经验分享] php语法分析

[复制链接]

尚未签到

发表于 2015-8-24 11:28:11 | 显示全部楼层 |阅读模式
    php的语法分析的主要作用是验证词法分析的基础上将token组成的序列,在php这门语言中是否是一个有效的句子,也可以理解为这些token序列是否匹配设计php这门语言时的语法模型,在匹配的情况下构建具体的程序(组建opcode),以供编译后期使用。
    比如:在设计php语言时,需要设计一套语法规则,通过使用上下文无关方法(主要使用BNF(巴斯科-瑙尔范式)表示法来描述),关于BNF(巴简直斯范式),请猛戳 这里 ,另外 这篇 文章也不错
  
    比如在有一个功能:我需要打印一些东西,这里主要是echo,不仅要支持echo 变量,也要支持echo 常量 ,也要支持 echo 表达式 ,也要支持 echo 变量,常量 等等这样的,我们不可能用具体的去实现,只能用最抽象的方法去概括
      我简单提取了zend_language_parse.y中关于echo的一些产生式,其中省略了一部分无关的产生式



1 unticked_statement:
2        echo_expr_list ';'
3
4 echo_expr_list:
5         echo_expr_list ',' expr { zend_do_echo(&$3 TSRMLS_CC); }
6     |    expr                    { zend_do_echo(&$1 TSRMLS_CC); }
7 ;
8
9 expr:
10         r_variable                    { $$ = $1; }
11     |    expr_without_variable        { $$ = $1; }
12 ;
13
14 r_variable:
15     variable { zend_do_end_variable_parse(&$1, BP_VAR_R, 0 TSRMLS_CC); $$ = $1; }
16 ;
17
18 expr_without_variable:
19 |    scalar                { $$ = $1; }
20
21 scalar:
22 |    common_scalar            { $$ = $1; }
23
24
25 common_scalar:
26         T_LNUMBER                     { $$ = $1; }
27     |    T_DNUMBER                     { $$ = $1; }
     BNF是一种描述语言规则的方法 ,可以避免二义性的语法,因为比较直观,在编写的时候就可以规避
    计算机解析BNF写的语法,主要采用LALR(自底向下的方式解析),大概意思是 将用户编写的代码,经过种种计算,推导为最初编写的那些BNF语法, 也就是将我们根据语法编写的语句,逆向推导出产生式的左端,一个非终结符
    LA全称是look-ahead(预读下一个符号) LR中的L 是指对输入的字符串从左到右进行检查, R是指 反向构造成最右推导序列 ,由于语法分析比词法分析要复杂得多,所以绝大多数的分析器都是使用类似yacc,bison这样自动化工具生成的,GCC例外。
  
  语法分析器使用LALR,它 由两个二维数组构成, 一个是ACTION , 一个是GOTO ,但zend_language_parse.c中 yytable代替了action表, yygoto代替了goto,均是一维数组,进行了压缩
    ACTION 指明了动作是移进,归约,接受,还是错误
     GOTO 指明了新的状态
  
  
    语法分析运行方法:
     根据当前状态和向前看符号,执行相应的动作,如果不存在向前看字符,利用yylex获得下一个单词
    移进:将状态压入状态栈, 将向前看字符 压入符号栈中
    规约:将规则左边的非终结符 替换右边的符号(终结符,非终结符),根据语法规则右边的符号的数量决定状态栈要弹出的个数,同时弹出符号栈中相应数量的元素 , 将规则左边的符号(终结符)压入符号栈, 状态栈弹出相应数量的元素后,根据栈顶元素和规则左边那个终结符 在状态表goto中查找,查找出来的状态为新状态,再将此新状态入栈
      
     语法分析 yyparse函数的大概流程:
    使用到的一些变量:
    1)两个栈
      a)状态栈: yytype_int16 yyssa[YYINITDEPTH];# define YYINITDEPTH 200 , yylex词法分析 识别出一个符号后,会返回这个符号的类型 , 这个类型使用yychar来接收
        yyssa是一个short int 类型的数组,初始化时有200个元素,当没有空间放新元素时,会自动扩充# define YYMAXDEPTH 10000,最多存放1W个元素
      b)符号栈: YYSTYPE yyvsa[YYINITDEPTH]; #define YYSTYPE znode  YYSTYPE被定义为znode类型的元素

    2)int yychar; yylex函数返回的符号的类型值
    3)int yytoken; yytoken是yychar在语法分析中的内部形式
    4)YYSTYPE yylval; YYSTYLE是一个宏,#define YYSTYPE znode, yylval用来接收yylex扫描出符号的值
    5)yystate:语法分析中的satate的内部存在形式
    5)yynewstate:归约后产生的新状态值,将此状态压入状态栈中
    6)yyn: 每个规则所对应的索引值
    函数执行过程:
    1)判断yychar是否为空,若为空,执行
       if (yychar == YYEMPTY)
        {
            YYDPRINTF ((stderr, "Reading a token: "));
            yychar = YYLEX;
        }
      
      YYLEX是一个宏,展开后为# define YYLEX yylex (&yylval) ,注意 传入的参数为yylval ,类型是znode,yylex扫描出一个符号后(其实真正工作的是zendlex)      
      



1 int zendlex(znode *zendlval TSRMLS_DC) /* {{{ */
2 {
3     int retval;
4
5     if (CG(increment_lineno)) {
6         CG(zend_lineno)++;
7         CG(increment_lineno) = 0;
8     }
9
10 again:
11     Z_TYPE(zendlval->u.constant) = IS_LONG;
12     retval = lex_scan(&zendlval->u.constant TSRMLS_CC);
13     switch (retval) {
14         case T_COMMENT:
15         case T_DOC_COMMENT:
16         case T_OPEN_TAG:
17         case T_WHITESPACE:
18             goto again;
19
20         case T_CLOSE_TAG:
21             if (LANG_SCNG(yy_text)[LANG_SCNG(yy_leng)-1] != '>') {
22                 CG(increment_lineno) = 1;
23             }
24             if (CG(has_bracketed_namespaces) && !CG(in_namespace)) {
25                 goto again;               
26             }
27             retval = ';'; /* implicit ; */
28             break;
29         case T_OPEN_TAG_WITH_ECHO:
30             retval = T_ECHO;
31             break;
32         case T_END_HEREDOC:
33             efree(Z_STRVAL(zendlval->u.constant));
34             break;
35     }
36
37     INIT_PZVAL(&zendlval->u.constant);
38     zendlval->op_type = IS_CONST;  //设置为常量,网上资料说是:词法分析阶段识别出来的都是常量,因为不涉及运行
39     return retval;
40 }
  
  



1 typedef struct _znode { /* used only during compilation */
2         int op_type;
3         union {
4                 znode_op op;
5                 zval constant; /* replaced by literal/zv */
6                 zend_op_array *op_array;
7         } u;
8         zend_uint EA;      /* extended attributes */
9 } znode;
  
    这里znode的定义,仔细看第一条注释:只是在编译阶段使用
  
   
     2) yychar不为空,执行 yytoken = YYTRANSLATE (yychar); YYTRANSLATE是个宏函数,查找出yychar在语法分析中内在的值 yytoken
    #define YYTRANSLATE(YYX) \
       ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
    3)将yytoken 赋值给yyn,然后执行 yyn = yytable[yyn];  yytable这个具体是如何生成,我也不知道,它是一个超级大数组,有5W多个数字,
      这些数字如果为正数,则表明要执行移进动作, 如果是负数,则要执行归约动作, 将yyn赋值给yystate , yylval入符号栈
      



1 ZEND_API zend_op_array *compile_file(zend_file_handle *file_handle, int type TSRMLS_DC)
2 {
3     zend_lex_state original_lex_state;
4     zend_op_array *op_array = (zend_op_array *) emalloc(sizeof(zend_op_array));
5     zend_op_array *original_active_op_array = CG(active_op_array);
6     zend_op_array *retval=NULL;
7     int compiler_result;
8     zend_bool compilation_successful=0;
9     znode retval_znode;
10     zend_bool original_in_compilation = CG(in_compilation);
11
12     retval_znode.op_type = IS_CONST;
13     retval_znode.u.constant.type = IS_LONG;
14     retval_znode.u.constant.value.lval = 1;
15     Z_UNSET_ISREF(retval_znode.u.constant);
16     Z_SET_REFCOUNT(retval_znode.u.constant, 1);
17
18     zend_save_lexical_state(&original_lex_state TSRMLS_CC);
19
20     retval = op_array; /* success oriented */
21
22     if (open_file_for_scanning(file_handle TSRMLS_CC)==FAILURE) {
23         if (type==ZEND_REQUIRE) {
24             zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, file_handle->filename TSRMLS_CC);
25             zend_bailout();
26         } else {
27             zend_message_dispatcher(ZMSG_FAILED_INCLUDE_FOPEN, file_handle->filename TSRMLS_CC);
28         }
29         compilation_successful=0;
30     } else {
31         init_op_array(op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE TSRMLS_CC);
32         CG(in_compilation) = 1;
33         CG(active_op_array) = op_array;
34         zend_stack_push(&CG(context_stack), (void *) &CG(context), sizeof(CG(context)));
35         zend_init_compiler_context(TSRMLS_C);
36         compiler_result = zendparse(TSRMLS_C);
37         zend_do_return(&retval_znode, 0 TSRMLS_CC);
38         CG(in_compilation) = original_in_compilation;
39         if (compiler_result==1) { /* parser error */
40             zend_bailout();
41         }
42         compilation_successful=1;
43     }
44
45     if (retval) {
46         CG(active_op_array) = original_active_op_array;
47         if (compilation_successful) {
48             pass_two(op_array TSRMLS_CC);
49             zend_release_labels(TSRMLS_C);
50         } else {
51             efree(op_array);
52             retval = NULL;
53         }
54     }
55     zend_restore_lexical_state(&original_lex_state TSRMLS_CC);
56     return retval;
57 }
  
   #define yyparse zendparse
  
   int yyparse(){



 1#define YYPOPSTACK(N)   (yyvsp -= (N), yyssp -= (N))
  
   yybackup:
  2      yyn = yypact[yystate]; //搞不懂yypact这个数组的作用,原来的注释是这样的/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM.  */ ,意思是说YYPACK[STATE-NUM]的值是 YYTABL

  3      if (yyn == YYPACT_NINF)
  4         goto yydefault;
  5      
  6  if (yychar == YYEMPTY)
  7     {
  8       YYDPRINTF ((stderr, "Reading a token: "));
  9       yychar = YYLEX; //这里调用yylex函数,读取一个符号,YYLEX本身是一个宏
10     }
11
12   if (yychar <= YYEOF)
13     {
14       yychar = yytoken = YYEOF; //词法分析结束了
15       YYDPRINTF ((stderr, "Now at end of input.\n"));
16     }
17   else
18     {
19       yytoken = YYTRANSLATE (yychar); //如果yychar不为空,则使用YYTRANSLATE进行yychar在语法分析中的内部转换
20       YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
21     }
22
23
24       yyn += yytoken; //yypack可理解为基地址;yytoken可理解为偏移地址;
25       yyn = yytable[yyn]; //这个yytables是个一维数组,它是一个DNF状态转换表,本身是一个二维数组,但为了减小空间,进行了压缩,详见 这里 ,这里数组肯定做了改进,根据yyn的正负值,可以判断成是移进,还是规约
26 if (yyn <= 0)
27     {
28       if (yyn == 0 || yyn == YYTABLE_NINF)
29     goto yyerrlab; //进入错误提示
30       yyn = -yyn;
31       goto yyreduce; //进入归约
32     }
33
34   if (yyn == YYFINAL)
35     YYACCEPT;
36
37  if (yychar != YYEOF)
38     yychar = YYEMPTY; //将yychar设置为空,为下一次调用yylex()函数作准备
39
40       yystate = yyn;
41       *++yyvsp = yylval; //这里是移进动作,将yylval的值入符号栈,yylval是调用lex_scan,通过引用参数&yylval来传递的,它是一个zval类型的数据
42
43
44       goto yynewstate;
45
46 yyreduce: //进行归约
47   /* yyn is the number of a rule to reduce with.  */
48   yylen = yyr2[yyn]; //获得要弹出栈中元素的个数,产生式右端长度,不清楚yyr2怎么计算的
49
50   /* If YYLEN is nonzero, implement the default value of the action:
51      `$$ = $1'.
52
53      Otherwise, the following line sets YYVAL to garbage.
54      This behavior is undocumented and Bison
55      users should not rely upon it.  Assigning to YYVAL
56      unconditionally makes the parser a bit smaller, and it avoids a
57      GCC warning that YYVAL may be used uninitialized.  */
58   yyval = yyvsp[1-yylen]; //这块是一个负数了,不知道具体是什么意思
61   YY_REDUCE_PRINT (yyn);
62   switch (yyn)
63     {             //这里是500多个操作,   
64         case 2:
65
66     { zend_do_end_compilation(TSRMLS_C); }
67     break;
68     。。。。。。。
69       default: break;
70     }
73   YYPOPSTACK (yylen); //状态栈和符号栈pop出yylen个元素
74   yylen = 0;
75   YY_STACK_PRINT (yyss, yyssp);
76
77   *++yyvsp = yyval;  //将规则左边的终结符压入符号栈
78
79
80   /* Now `shift' the result of the reduction.  Determine what state
81      that goes to, based on the state we popped back to and the rule
82      number reduced by.  */
83
84   yyn = yyr1[yyn];
85
86   yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; //不明白为什么这么计算,计算的结果是一个新的yystate,pop出yylen个元素之后的栈顶元素
87   if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
88     yystate = yytable[yystate];
89   else
90     yystate = yydefgoto[yyn - YYNTOKENS];
91
92   goto yynewstate;
93
94
95 yynewstate:
96   /* In all cases, when you get here, the value and location stacks
97      have just been pushed.  So pushing a state here evens the stacks.  */
98   yyssp++; //状态栈指针加加,以便接收yynewstate,接着进入yysetstate
99
100  yysetstate:
101   *yyssp = yystate; //yystate入栈
102
103 。。。。。。。。。。
104
105   yyssp = yyss + yysize - 1;
106       yyvsp = yyvs + yysize - 1;
107
108 。。。。。。。。
109
110  goto yybackup; //循环调用 yybackup,读取下一个token
   }
  
  
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-103450-1-1.html 上篇帖子: 欧飞手机充值接口php源代码v0.1 下篇帖子: PHP三层结构(上)——简单三层结构
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表