php語法分析

  php的語法分析的主要做用是驗證詞法分析的基礎上將token組成的序列,在php這門語言中是不是一個有效的句子,也能夠理解爲這些token序列是否匹配設計php這門語言時的語法模型,在匹配的狀況下構建具體的程序(組建opcode),以供編譯後期使用。php

  好比:在設計php語言時,須要設計一套語法規則,經過使用上下文無關方法(主要使用BNF(巴斯科-瑙爾範式)表示法來描述),關於BNF(巴簡直斯範式),請猛戳 這裏 ,另外 這篇 文章也不錯html

 

  好比在有一個功能:我須要打印一些東西,這裏主要是echo,不只要支持echo 變量,也要支持echo 常量 ,也要支持 echo 表達式 ,也要支持 echo 變量,常量 等等這樣的,咱們不可能用具體的去實現,只能用最抽象的方法去歸納node

    我簡單提取了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例外。ui

 

語法分析器使用LALR,它 由兩個二維數組構成, 一個是ACTION , 一個是GOTO ,但zend_language_parse.c中 yytable代替了action表, yygoto代替了goto,均是一維數組,進行了壓縮spa

  ACTION 指明瞭動做是移進,歸約,接受,仍是錯誤.net

   GOTO 指明瞭新的狀態scala

 

 

  語法分析運行方法:

   根據當前狀態和向前看符號,執行相應的動做,若是不存在向前看字符,利用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

 }

相關文章
相關標籤/搜索