zoukankan      html  css  js  c++  java
  • php语法分析

      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.
    "));
     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

     }

     

     

     

  • 相关阅读:
    火狐插件火狐黑客插件将Firefox变成黑客工具的七个插件
    memcache安装环境:WINDOWS 7
    PHP正则表达式
    968. 监控二叉树 力扣(困难) dfs 官方说DP
    375. 猜数字大小 II 力扣(中等) 区间动态规划、记忆化搜索
    629. K个逆序对数组 力扣(困难) 区间动态规划
    剑指 Offer 51. 数组中的逆序对 力扣(困难) 巧用归并排序算法
    488. 祖玛游戏 力扣(困难) dfs
    16. 最接近的三数之和 力扣(中等) 双指针
    319. 灯泡开关 力扣(中等) 数论
  • 原文地址:https://www.cnblogs.com/taek/p/3986147.html
Copyright © 2011-2022 走看看