亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

characters

詞法解析、語法解析

這一節(jié)我們分析下PHP的解析階段,即 PHP代碼->抽象語法樹(AST) 的過程。

PHP使用re2c、bison完成這個階段的工作:

re2c: 詞法分析器,將輸入分割為一個個有意義的詞塊,稱為token

bison: 語法分析器,確定詞法分析器分割出的token是如何彼此關(guān)聯(lián)的

例如:

$a = 2 + 3;

詞法分析器將上面的語句分解為這些token:$a、=、2、+、3,接著語法分析器確定了2+3是一個表達式,而這個表達式被賦值給了a,我們可以這樣定義詞法解析規(guī)則:

/*!re2c
    LABEL   [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*
    LNUM    [0-9]+
    //規(guī)則
    "$"{LABEL} {return T_VAR;}
    {LNUM} {return T_NUM;}
*/

然后定義語法解析規(guī)則:

//token定義%token T_VAR
%token T_NUM//語法規(guī)則statement:
    T_VAR '=' T_NUM '+' T_NUM {ret = str2int($3) + str2int($5);printf("%d",ret);}
;

上面的語法規(guī)則只能識別兩個數(shù)值相加,假如我們希望支持更復(fù)雜的運算,比如:

$a = 3 + 4 - 6;

則可以配置遞歸規(guī)則:

//語法規(guī)則statement:
    T_VAR '=' expr {}
;
expr:
    T_NUM {...}
    |expr '?' T_NUM {}
;

這樣將支持若干表達式,用語法分析樹表示:


接下來我們看下PHP具體的解析過程,PHP編譯階段流程:



其中 zendparse() 就是詞法、語法解析過程,這個函數(shù)實際就是bison中提供的語法解析函數(shù) yyparse() :

#define yyparse         zendparse

yyparse() 不斷調(diào)用 yylex() 得到token,然后根據(jù)token匹配語法規(guī)則:


#define yylex           zendlex//zend_compile.cint zendlex(zend_parser_stack_elem *elem){
    zval zv;    int retval;
    ...
again:
    ZVAL_UNDEF(&zv);
    retval = lex_scan(&zv);    if (EG(exception)) {        //語法錯誤
        return T_ERROR;
    }
    ...    if (Z_TYPE(zv) != IS_UNDEF) {        //如果在分割token中有zval生成則將其值復(fù)制到zend_ast_zval結(jié)構(gòu)中
        elem->ast = zend_ast_create_zval(&zv);
    }    return retval;
}

這里兩個關(guān)鍵點需要注意:

(1) token值:詞法解析器解析到的token值內(nèi)容就是token值,這些值統(tǒng)一通過 zval 存儲,上面的過程中可以看到調(diào)用lex_scan參數(shù)是是個zval*,在具體的命中規(guī)則總會將解析到的token保存到這個值,從而傳遞給語法解析器使用,比如PHP中的解析變量的規(guī)則:$a;,其詞法解析規(guī)則為:

<ST_IN_SCRIPTING,ST_DOUBLE_QUOTES,ST_HEREDOC,ST_BACKQUOTE,ST_VAR_OFFSET>"$"{LABEL} {    //將匹配到的token值保存在zval中
    zend_copy_value(zendlval, (yytext+1), (yyleng-1)); //只保存{LABEL}內(nèi)容,不包括$,所以是yytext+1
    RETURN_TOKEN(T_VARIABLE);
}

zendlval就是我們傳入的zval*,yytext指向命中的token值起始位置,yyleng為token值的長度。

(2) 語義值類型:bison調(diào)用re2c分割token有兩個含義,第一個是token類型,另一個是token值,token類型一般以yylex的返回值告訴bison,而token值就是語義值,這個值一般定義為固定的類型,這個類型就是語義值類型,默認為int,可以通過 YYSTYPE 定義,而PHP中這個類型是 zend_parser_stack_elem ,這就是為什么zendlex的參數(shù)為zend_parser_stack_elem的原因。

#define YYSTYPE zend_parser_stack_elemtypedef union _zend_parser_stack_elem {
    zend_ast *ast; //抽象語法樹主要結(jié)構(gòu)
    zend_string *str;
    zend_ulong num;
} zend_parser_stack_elem;

實際這是個union,ast類型用的比較多(其它兩種類型暫時沒發(fā)現(xiàn)有地方在用),這樣可以通過%token、%type將對應(yīng)的值修改為elem.ast,所以在zend_language_parser.y中使用的$$、$1、$2......多數(shù)都是 zend_parser_stack_elem.ast :

%token <ast> T_LNUMBER   "integer number (T_LNUMBER)"%token <ast> T_DNUMBER   "floating-point number (T_DNUMBER)"%token <ast> T_STRING    "identifier (T_STRING)"%token <ast> T_VARIABLE  "variable (T_VARIABLE)"%type <ast> top_statement namespace_name name statement function_declaration_statement
%type <ast> class_declaration_statement trait_declaration_statement
%type <ast> interface_declaration_statement interface_extends_list

語法解析器從start開始調(diào)用,然后層層匹配各個規(guī)則,語法解析器根據(jù)命中的語法規(guī)則創(chuàng)建AST節(jié)點,最后將生成的AST根節(jié)點賦到 CG(ast) :

%% /* Rules */start:
    top_statement_list  { CG(ast) = $1; }
;
top_statement_list:
    top_statement_list top_statement { $$ = zend_ast_list_add($1, $2); }
    |   /* empty */ { $$ = zend_ast_create_list(0, ZEND_AST_STMT_LIST); }
;

首先會創(chuàng)建一個根節(jié)點list,然后將后面不斷命中top_statement生成的ast加到這個list中,zend_ast具體結(jié)構(gòu):

enum _zend_ast_kind {
    ZEND_AST_ZVAL = 1 << ZEND_AST_SPECIAL_SHIFT,
    ZEND_AST_ZNODE,    /* list nodes */
    ZEND_AST_ARG_LIST = 1 << ZEND_AST_IS_LIST_SHIFT,
    ...
};struct _zend_ast {
    zend_ast_kind kind; /* Type of the node (ZEND_AST_* enum constant) */
    zend_ast_attr attr; /* Additional attribute, use depending on node type */
    uint32_t lineno;    /* Line number */
    zend_ast *child[1]; /* Array of children (using struct hack) */};typedef struct _zend_ast_list {
    zend_ast_kind kind;
    zend_ast_attr attr;    uint32_t lineno;    uint32_t children;
    zend_ast *child[1];
} zend_ast_list;

根節(jié)點實際為zend_ast_list,每條語句對應(yīng)的ast保存在child中,使用中zend_ast_list、zend_ast可以相互轉(zhuǎn)化,kind標識的是ast節(jié)點類型,后面會根據(jù)這個值生成具體的opcode,另外函數(shù)、類還會用到另外一種ast節(jié)點結(jié)構(gòu):

typedef struct _zend_ast_decl {
    zend_ast_kind kind;
    zend_ast_attr attr; /* Unused - for structure compatibility */
    uint32_t start_lineno; //開始行號
    uint32_t end_lineno;   //結(jié)束行號
    uint32_t flags;    unsigned char *lex_pos;
    zend_string *doc_comment;
    zend_string *name;
    zend_ast *child[4]; //類中會將繼承的父類、實現(xiàn)的接口以及類中的語句解析保存在child中} zend_ast_decl;

這么看比較難理解,接下來我們從一個簡單的例子看下最終生成的語法樹。

$a = 123;
$b = "hi~";echo $a,$b;

具體解析過程這里不再解釋,有興趣的可以翻下zend_language_parse.y中,這個過程不太容易理解,需要多領(lǐng)悟幾遍,最后生成的ast如下圖:



總結(jié):

這一節(jié)我們主要介紹了PHP詞法、語法解析生成抽象語法樹(AST)的過程,此過程是PHP語法實現(xiàn)的基礎(chǔ),也是zend引擎非常關(guān)鍵的一部分,后續(xù)介紹的內(nèi)容都是基于此過程的產(chǎn)出結(jié)果展開的。這部分內(nèi)容關(guān)鍵在于對re2c、bison的應(yīng)用上,如果是初次接觸它們可能不太容易理解,這里不再對re2c、bison作更多解釋,想要了解更多的推薦看下 《flex與bison》 這本書。


Previous article: Next article: