php 詞法分析,語法分析

  php的詞法分析 能夠理解爲 經過必定的規則,把輸入的代碼 區分出哪些是 是$開頭的變量, 哪些是 以兩個單引號括起來的字符串,哪些是以兩個雙引號括起來的字符串 等等, 這些區分出來的東西 稱爲token ,token 之間的聯繫 是由語法分析來完成的, 好比 賦值,加減乘除;php

  

  語法分析詳見這裏html

  語法分析的驅動程序 yyparse() 調用yylex()這個函數 , 這個函數 能夠由flex生成,也能夠人爲編寫,在php中,屬於後者;node

每次執行yylex()函數,會返回一個token, 每一個token都會有類型和相應的值 , 類型通常在zend_language_parse.y中的%token表示,這些類型實際上是數值,存放於zend_language_parse.h正則表達式

  token的值經過yylval供bison使用,在php中實際上是zendlval,zendlval的類型是一個zval的結構體數組

#define YYSTYPE zval YYSTYPE yylval; #define yylval  zendlval

  把分析好的yytext , 經過zend_copy_value將其拷貝到zval這個結構體中去less

    zend_language_parse.y中的token 例以下面的ide

%token T_INCLUDE      "include (T_INCLUDE)"

  bison官網有一段關於%token的使用的描述:函數

  yylex函數其實就是zendlex函數fetch

 

這個yyparse會循環調用lex_scan,lex_scan定義在zend_language_scanning.l中 , 這個函數體大部分是一些正則表達式,以區分出代碼中哪些是變量,哪些是字符串,哪些是數組等等flex

  

int lex_scan(zval *zendlval TSRMLS_DC)
{

....

}

 

下面舉例說明:

 

<?php
$name='taek';
?>

 

下面的關於上面 $name='taek'; 的巴斯科範式

start:
top_statement_list { zend_do_end_compilation(TSRMLS_C); }
;

top_statement_list:
top_statement_list { zend_do_extended_info(TSRMLS_C); } top_statement { HANDLE_INTERACTIVE(); }
| /* empty */
;

top_statement:
statement
;

statement:
unticked_statement { DO_TICKS(); }
;

unticked_statement:
| expr ';' { zend_do_free(&$1 TSRMLS_CC); }
;
expr:
r_variable { $$ = $1; }
| expr_without_variable { $$ = $1; }
;

expr_without_variable:
| variable '=' expr { zend_check_writable_variable(&$1); zend_do_assign(&$$, &$1, &$3 TSRMLS_CC); }
| scalar { $$ = $1; }
| expr '.' expr { zend_do_binary_op(ZEND_CONCAT, &$$, &$1, &$3 TSRMLS_CC); }
;

scalar:
| common_scalar { $$ = $1; }
;

common_scalar:
T_LNUMBER { $$ = $1; }
| T_DNUMBER { $$ = $1; }
| T_CONSTANT_ENCAPSED_STRING { $$ = $1; }
;


variable:
| base_variable_with_function_calls { $$ = $1; }
;

base_variable_with_function_calls:
base_variable { $$ = $1; }
;

base_variable:
reference_variable { $$ = $1; $$.EA = ZEND_PARSED_VARIABLE; }
;

reference_variable:
| compound_variable { zend_do_begin_variable_parse(TSRMLS_C); fetch_simple_variable(&$$, &$1, 1 TSRMLS_CC); }
;


compound_variable:
T_VARIABLE { $$ = $1; }
| '$' '{' expr '}' { $$ = $3; }
;

 

  1)發現第一個字符(這裏先不考慮空白)爲$開頭,第二個字符爲n ,符合 LABEL這個正則,接着繼續掃描,直到= ,使用下面的規則 將變量名存儲到zendlval中

 

<ST_IN_SCRIPTING,ST_DOUBLE_QUOTES,ST_HEREDOC,ST_BACKQUOTE,ST_VAR_OFFSET>"$"{LABEL} {
    zend_copy_value(zendlval, (yytext+1), (yyleng-1));
    zendlval->type = IS_STRING;
    return T_VARIABLE;
}

 

  返回token的類型是 T_VARIABLE , 值爲$name放入zendlval中,yylex()返回的yychar就是這個T_VARIABLE,通過處理,分析器通過yytable的返回,這是一個移進操做,便將T_VARIABLE 放入狀態棧,$name放入符號棧 , yyparse()繼續執行yylex()函數,由於這裏返回的是T_VARIABLE,通過yytable查找,發現是歸約操做,即把$name替換爲

  注意:LABEL 是一個正則表達式  LABEL [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*  其中 \x7f-\xff 這個是識別utf8下面漢字的16進制,也就 是說,php的變量名稱裏面能夠有漢字

<?php
$個人名字叫='taek';
echo $個人名字叫;

   上面的代碼是能夠正常運行的,估計是易語言出現後,才增長的功能吧

 

   2)掃描到=這個符號

<ST_IN_SCRIPTING>{TOKENS} { return yytext[0]; }

 此時的yychar接收 = 這個符號的ASICII的值,並放入狀態棧,且將空值放入符號棧

  這裏面的TOKENS也是一個正則表達式  TOKENS [;:,.\[\]()|^&+-/*=%!~$<>?@],[]括號裏的字符只容許出現一次,正好=這個符號在裏面,正好匹配

  

  3)接着掃描到', 這個單引號

 1 <ST_IN_SCRIPTING>b?['] {
 2     register char *s, *t;  3     char *end;  4     int bprefix = (yytext[0] != '\'') ? 1 : 0;  5 
 6     while (1) {  7         if (YYCURSOR < YYLIMIT) {  8             if (*YYCURSOR == '\'') {  9                 YYCURSOR++; 10                 yyleng = YYCURSOR - SCNG(yy_text); 11 
12                 break; 13             } else if (*YYCURSOR++ == '\\' && YYCURSOR < YYLIMIT) { 14                 YYCURSOR++; 15  } 16         } else { 17             yyleng = YYLIMIT - SCNG(yy_text); 18 
19             /* Unclosed single quotes; treat similar to double quotes, but without a separate token 20  * for ' (unrecognized by parser), instead of old flex fallback to "Unexpected character..." 21  * rule, which continued in ST_IN_SCRIPTING state after the quote */
22             return T_ENCAPSED_AND_WHITESPACE; 23  } 24  } 25 
26     zendlval->value.str.val = estrndup(yytext+bprefix+1, yyleng-bprefix-2); 27     zendlval->value.str.len = yyleng-bprefix-2; 28     zendlval->type = IS_STRING; 29 
30     /* convert escape sequences */
31     s = t = zendlval->value.str.val; 32     end = s+zendlval->value.str.len; 33     while (s<end) { 34         if (*s=='\\') { 35             s++; 36 
37             switch(*s) { 38                 case '\\': 39                 case '\'': 40                     *t++ = *s; 41                     zendlval->value.str.len--; 42                     break; 43                 default: 44                     *t++ = '\\'; 45                     *t++ = *s; 46                     break; 47  } 48         } else { 49             *t++ = *s; 50  } 51 
52         if (*s == '\n' || (*s == '\r' && (*(s+1) != '\n'))) { 53             CG(zend_lineno)++; 54  } 55         s++; 56  } 57     *t = 0; 58 
59     if (SCNG(output_filter)) { 60         size_t sz = 0; 61         s = zendlval->value.str.val; 62         SCNG(output_filter)((unsigned char **)&(zendlval->value.str.val), &sz, (unsigned char *)s, (size_t)zendlval->value.str.len TSRMLS_CC); 63         zendlval->value.str.len = sz; 64  efree(s); 65  } 66     return T_CONSTANT_ENCAPSED_STRING; 67 }

 

  上面的YYCURSOR會隨時變化的,當遇到 第二個單引號時,它會認爲掃描過的符號就是字符串了,注意:若是掃描到\'時,會自動跨過,要否則後面的字符會被截掉,最終把字符串放到zendlval中,並返回TOKEN T_CONSTANT_ENCAPSED_STRING

 

<ST_IN_SCRIPTING>b?['] {
    register char *s, *t; char *end; int bprefix = (yytext[0] != '\'') ? 1 : 0; while (1) { if (YYCURSOR < YYLIMIT) { if (*YYCURSOR == '\'') { YYCURSOR++; yyleng = YYCURSOR - SCNG(yy_text); break; } else if (*YYCURSOR++ == '\\' && YYCURSOR < YYLIMIT) { YYCURSOR++; } } else { yyleng = YYLIMIT - SCNG(yy_text); /* Unclosed single quotes; treat similar to double quotes, but without a separate token * for ' (unrecognized by parser), instead of old flex fallback to "Unexpected character..." * rule, which continued in ST_IN_SCRIPTING state after the quote */
            return T_ENCAPSED_AND_WHITESPACE; } } zendlval->value.str.val = estrndup(yytext+bprefix+1, yyleng-bprefix-2); zendlval->value.str.len = yyleng-bprefix-2; zendlval->type = IS_STRING; /* convert escape sequences */ s = t = zendlval->value.str.val; end = s+zendlval->value.str.len; while (s<end) { if (*s=='\\') { s++; switch(*s) { case '\\': case '\'': *t++ = *s; zendlval->value.str.len--; break; default: *t++ = '\\'; *t++ = *s; break; } } else { *t++ = *s; } if (*s == '\n' || (*s == '\r' && (*(s+1) != '\n'))) { CG(zend_lineno)++; } s++; } *t = 0; if (SCNG(output_filter)) { size_t sz = 0; s = zendlval->value.str.val; SCNG(output_filter)((unsigned char **)&(zendlval->value.str.val), &sz, (unsigned char *)s, (size_t)zendlval->value.str.len TSRMLS_CC); zendlval->value.str.len = sz; efree(s); } return T_CONSTANT_ENCAPSED_STRING; }
View Code

 

 

<ST_IN_SCRIPTING>b?['] { 中的b ,全稱爲binary ,便可定義 二進制的字符串,見下圖

 

 

 

我在php 5.4.12 版本中 ,是能夠運行的,不像上面黑框中所說的effect on of PHP 6.0.0, 但不知道什麼狀況下會定義一個二進制的字符串?

 

if (*YYCURSOR == '\'') {
YYCURSOR++;
yyleng = YYCURSOR - SCNG(yy_text);

break;
}

這段代碼的意思是 若是 碰上第二個單引號,那麼跳過,否則這就意味着結束了

 1)yylex掃描 $name後,yychar 值爲 T_VARIABLE 這個token , 通過yytable運算,執行移進操做,將獲得的狀態值放入狀態棧,同時將$name放入符號棧

  而後根據 產生式

  compound_variable:
  |  T_VARIABLE { $$ = $1; }
  | '$' '{' expr '}' { $$ = $3; }
  ;

  進行歸約,先把狀態棧pop出一個元素,也就是T_VARIABLE,接着符號棧pop出一個元素,也就是$name, 最後yygogo這個數組計算出新的狀態,這個狀態就是上面產生式的編號,將此編號放入着狀態棧,$name再入符號棧

  接着通過下列幾個產生式的歸約

  reference_variable:
  | compound_variable { zend_do_begin_variable_parse(TSRMLS_C); fetch_simple_variable(&$$, &$1, 1 TSRMLS_CC); }
  ;

  base_variable:
    reference_variable { $$ = $1; $$.EA = ZEND_PARSED_VARIABLE; }
  ;

  base_variable_with_function_calls:
    base_variable { $$ = $1; }
  ;

  variable:

  | base_variable_with_function_calls { $$ = $1; }
  ;

  最終將variable這個產生式的編號入狀態棧   

 4)yylex掃描'taek'後,yychar 的值爲T_CONSTANT_ENCAPSED_STRING 這個token,經yytable數組計算出的值,執行移進操做,此值入狀態棧 , 將'taek' 放入 符號棧 ,程序又回到yybackup:執行這裏的代碼

yyn = yypact[yystate];
if (yyn == YYPACT_NINF)
goto yydefault;

yydefault:
yyn = yydefact[yystate];
if (yyn == 0)
goto yyerrlab;
goto yyreduce;

若是yyn不爲0,則由產生式

common_scalar:
T_LNUMBER { $$ = $1; }
| T_DNUMBER { $$ = $1; }
| T_CONSTANT_ENCAPSED_STRING { $$ = $1; }
;

進行歸約,將狀態棧,符號棧pop出若干元素,再將該產生式編號 放入狀態棧,'taek'經處理(可能)後,再放入符合棧,接着再次進行歸約,經過下面幾個產生式

expr:
r_variable { $$ = $1; }
| expr_without_variable { $$ = $1; }
;

expr_without_variable:
| variable '=' expr { zend_check_writable_variable(&$1); zend_do_assign(&$$, &$1, &$3 TSRMLS_CC); }
| scalar { $$ = $1; }
| expr '.' expr { zend_do_binary_op(ZEND_CONCAT, &$$, &$1, &$3 TSRMLS_CC); }
;

scalar:
| common_scalar { $$ = $1; }
;

進行歸約,最終將expr這個併產生式的編號入狀態棧, 至此'taek'歸約成功

 

5)yylex掃描 . 這個鏈接符,並將其ASICII返回給zendparse中的yychar, 執行移進操做, 將ASICII值放入狀態棧中,符號棧放一個空元素

6)  yylex接着掃描 'world' ,流程跟4同樣,符號棧中的'world',在狀態棧 對應的是expr(數字而已)

7)再次歸約,也就是說expr . expr  可由產生式

expr_without_variable:
| variable '=' expr { zend_check_writable_variable(&$1); zend_do_assign(&$$, &$1, &$3 TSRMLS_CC); }
| scalar { $$ = $1; }
| expr '.' expr { zend_do_binary_op(ZEND_CONCAT, &$$, &$1, &$3 TSRMLS_CC); }
;

進行歸約,狀態棧,符號棧 pop出三個元素, expr_without_variable 這個產生式

8)  variable = expr ,由產生式

  expr_without_variable:
  | variable '=' expr { zend_check_writable_variable(&$1); zend_do_assign(&$$, &$1, &$3 TSRMLS_CC); }
  | scalar { $$ = $1; }
  | expr '.' expr { zend_do_binary_op(ZEND_CONCAT, &$$, &$1, &$3 TSRMLS_CC); }
  ;

  進行歸約,將狀態棧,符號棧pop出若干元素, 再將該產生式編號入狀態棧,同時將yylval入符號棧,

8) yylex掃描到;這個符號,將其ASICII值傳給yychar, 入狀態棧,符號棧入一個空元素

9)expr ; 由產生式

unticked_statement:
| expr ';' { zend_do_free(&$1 TSRMLS_CC); }
;

進行歸約,狀態棧,符合棧pop出若干個元素, 再將該產生式編號 放入狀態棧,同時將

8)

common_scalar

 

 4) 接着是分號的asicII進入狀態棧,空值進入符號棧, 此時進行歸約操做,併產生opcode代碼,

 5) 結束

雙引號:

<ST_IN_SCRIPTING>b?["] {
    int bprefix = (yytext[0] != '"') ? 1 : 0;

    while (YYCURSOR < YYLIMIT) {
        switch (*YYCURSOR++) {
            case '"':
                yyleng = YYCURSOR - SCNG(yy_text);
                zend_scan_escape_string(zendlval, yytext+bprefix+1, yyleng-bprefix-2, '"' TSRMLS_CC);
                return T_CONSTANT_ENCAPSED_STRING;
            case '$':
                if (IS_LABEL_START(*YYCURSOR) || *YYCURSOR == '{') {
                    break;
                }
                continue;
            case '{':
                if (*YYCURSOR == '$') {
                    break;
                }
                continue;
            case '\\':
                if (YYCURSOR < YYLIMIT) {
                    YYCURSOR++;
                }
                /* fall through */
            default:
                continue;
        }

        YYCURSOR--;
        break;
    }

    /* Remember how much was scanned to save rescanning */
    SET_DOUBLE_QUOTES_SCANNED_LENGTH(YYCURSOR - SCNG(yy_text) - yyleng);

    YYCURSOR = SCNG(yy_text) + yyleng;

    BEGIN(ST_DOUBLE_QUOTES);
    return '"';
}

 

 

 

 

對於雙引號括起來的字符串,會對裏面的字符進行判斷

1)發現 $符號,記錄已掃描過字符的位置,而後設置新條件:BEGIN(ST_DOUBLE_QUOTES);能夠理解爲在ST_DOUBLE_QUOTES的規則中尋找一個恰當的規則 ,接着 繼續 執行lex_scan,最終找到的是

  

<ST_DOUBLE_QUOTES>{ANY_CHAR} { if (GET_DOUBLE_QUOTES_SCANNED_LENGTH()) { YYCURSOR += GET_DOUBLE_QUOTES_SCANNED_LENGTH() - 1; SET_DOUBLE_QUOTES_SCANNED_LENGTH(0); goto double_quotes_scan_done; } if (YYCURSOR > YYLIMIT) { return 0; } if (yytext[0] == '\\' && YYCURSOR < YYLIMIT) { YYCURSOR++; } while (YYCURSOR < YYLIMIT) { switch (*YYCURSOR++) { case '"': break; case '$': if (IS_LABEL_START(*YYCURSOR) || *YYCURSOR == '{') { break; } continue; case '{': if (*YYCURSOR == '$') { break; } continue; case '\\': if (YYCURSOR < YYLIMIT) { YYCURSOR++; } /* fall through */
            default: continue; } YYCURSOR--; break; } double_quotes_scan_done: yyleng = YYCURSOR - SCNG(yy_text); zend_scan_escape_string(zendlval, yytext, yyleng, '"' TSRMLS_CC); return T_ENCAPSED_AND_WHITESPACE; }
View Code

 

最終返回T_ENCAPSED_AND_WHITESPACE 標籤,並將 字符串放入zendval中

2)再次循環執行lex_scan , 由於全局變量已保存了前面掃描過的字符了,因此從那以後進行掃描 ,

  若是 發現 $, 若是$後面的字符在 a-z , A-Z , _ , \x7f-\xff 的範圍內,或者$後面是一個左括號 { , 那麼說明$後面的將是一個變量,跳轉到ST_DOUBLE_QUOTES,進入

  

<ST_IN_SCRIPTING,ST_DOUBLE_QUOTES,ST_HEREDOC,ST_BACKQUOTE,ST_VAR_OFFSET>"$"{LABEL} {   zend_copy_value(zendlval, (yytext+1), (yyleng-1));   zendlval->type = IS_STRING;   return T_VARIABLE; }

  詳見這個例子

<?php $ABC='ABC'; $name="taek$ABC#"; echo $name;

 

結果是 taekABC#

PHP記法分析把 $ABC看成一個變量,而沒有把$ABC#看成變量,由於ABC#不符合 LABEL [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* 這個規則

 

2)發現{,若是緊接其後是一個$,則

  

<ST_DOUBLE_QUOTES,ST_BACKQUOTE,ST_HEREDOC>"{$" { zendlval->value.lval = (long) '{'; yy_push_state(ST_IN_SCRIPTING TSRMLS_CC); yyless(1); return T_CURLY_OPEN; }

 

#define IS_LABEL_START(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || (c) == '_' || (c) >= 0x7F)

相關文章
相關標籤/搜索