### 3.1.1 詞法解析、語法解析
這一節我們分析下PHP的解析階段,即 __PHP代碼->抽象語法樹(AST)__ 的過程。
PHP使用re2c、bison完成這個階段的工作:
* __re2c:__ 詞法分析器,將輸入分割為一個個有意義的詞塊,稱為token
* __bison:__ 語法分析器,確定詞法分析器分割出的token是如何彼此關聯的
例如:
```php
$a = 2 + 3;
```
詞法分析器將上面的語句分解為這些token:$a、=、2、+、3,接著語法分析器確定了`2+3`是一個表達式,而這個表達式被賦值給了`a`,我們可以這樣定義詞法解析規則:
```c
/*!re2c
LABEL [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*
LNUM [0-9]+
//規則
"$"{LABEL} {return T_VAR;}
{LNUM} {return T_NUM;}
*/
```
然后定義語法解析規則:
```c
//token定義
%token T_VAR
%token T_NUM
//語法規則
statement:
T_VAR '=' T_NUM '+' T_NUM {ret = str2int($3) + str2int($5);printf("%d",ret);}
;
```
上面的語法規則只能識別兩個數值相加,假如我們希望支持更復雜的運算,比如:
```php
$a = 3 + 4 - 6;
```
則可以配置遞歸規則:
```c
//語法規則
statement:
T_VAR '=' expr {}
;
expr:
T_NUM {...}
|expr '?' T_NUM {}
;
```
這樣將支持若干表達式,用語法分析樹表示:

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

其中 __zendparse()__ 就是詞法、語法解析過程,這個函數實際就是bison中提供的語法解析函數 __yyparse()__ :
```c
#define yyparse zendparse
```
__yyparse()__ 不斷調用 __yylex()__ 得到token,然后根據token匹配語法規則:

```c
#define yylex zendlex
//zend_compile.c
int 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生成則將其值復制到zend_ast_zval結構中
elem->ast = zend_ast_create_zval(&zv);
}
return retval;
}
```
這里兩個關鍵點需要注意:
__(1) token值__:詞法解析器解析到的token值內容就是token值,這些值統一通過 __zval__ 存儲,上面的過程中可以看到調用lex_scan參數是是個zval*,在具體的命中規則總會將解析到的token保存到這個值,從而傳遞給語法解析器使用,比如PHP中的解析變量的規則:`$a;`,其詞法解析規則為:
```c
<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}內容,不包括$,所以是yytext+1
RETURN_TOKEN(T_VARIABLE);
}
```
zendlval就是我們傳入的zval*,yytext指向命中的token值起始位置,yyleng為token值的長度。
__(2) 語義值類型__:bison調用re2c分割token有兩個含義,第一個是token類型,另一個是token值,token類型一般以yylex的返回值告訴bison,而token值就是語義值,這個值一般定義為固定的類型,這個類型就是語義值類型,默認為int,可以通過 __YYSTYPE__ 定義,而PHP中這個類型是 __zend_parser_stack_elem__ ,這就是為什么zendlex的參數為`zend_parser_stack_elem`的原因。
```c
#define YYSTYPE zend_parser_stack_elem
typedef union _zend_parser_stack_elem {
zend_ast *ast; //抽象語法樹主要結構
zend_string *str;
zend_ulong num;
} zend_parser_stack_elem;
```
實際這是個union,ast類型用的比較多(其它兩種類型暫時沒發現有地方在用),這樣可以通過%token、%type將對應的值修改為elem.ast,所以在zend_language_parser.y中使用的$$、$1、$2......多數都是 __zend_parser_stack_elem.ast__ :
```c
%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開始調用,然后層層匹配各個規則,語法解析器根據命中的語法規則創建AST節點,最后將生成的AST根節點賦到 __CG(ast)__ :
```c
%% /* 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); }
;
```
首先會創建一個根節點list,然后將后面不斷命中top_statement生成的ast加到這個list中,zend_ast具體結構:
```c
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;
```
根節點實際為zend_ast_list,每條語句對應的ast保存在child中,使用中zend_ast_list、zend_ast可以相互轉化,kind標識的是ast節點類型,后面會根據這個值生成具體的opcode,另外函數、類還會用到另外一種ast節點結構:
```c
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; //結束行號
uint32_t flags;
unsigned char *lex_pos;
zend_string *doc_comment;
zend_string *name;
zend_ast *child[4]; //類中會將繼承的父類、實現的接口以及類中的語句解析保存在child中
} zend_ast_decl;
```
這么看比較難理解,接下來我們從一個簡單的例子看下最終生成的語法樹。
```php
$a = 123;
$b = "hi~";
echo $a,$b;
```
具體解析過程這里不再解釋,有興趣的可以翻下zend_language_parse.y中,這個過程不太容易理解,需要多領悟幾遍,最后生成的ast如下圖:

__總結:__
這一節我們主要介紹了PHP詞法、語法解析生成抽象語法樹(AST)的過程,此過程是PHP語法實現的基礎,也是zend引擎非常關鍵的一部分,后續介紹的內容都是基于此過程的產出結果展開的。這部分內容關鍵在于對re2c、bison的應用上,如果是初次接觸它們可能不太容易理解,這里不再對re2c、bison作更多解釋,想要了解更多的推薦看下 __《flex與bison》__ 這本書。
- 目錄
- 第1章 PHP基本架構
- 1.1 PHP簡介
- 1.2 PHP7的改進
- 1.3 FPM
- 1.4 PHP執行的幾個階段
- 第2章 變量
- 2.1 變量的內部實現
- 2.2 數組
- 2.3 靜態變量
- 2.4 全局變量
- 2.5 常量
- 3.1 PHP代碼的編譯
- 3.1.1 詞法解析、語法解析
- 3.1.2 抽象語法樹編譯流程
- 第3章 Zend虛擬機
- 3.2.1 內部函數
- 3.2.2 用戶函數的實現
- 3.3 Zend引擎執行流程
- 3.3.1 基本結構
- 3.2 函數實現
- 3.3.2 執行流程
- 3.3.3 函數的執行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向對象實現
- 3.4.1 類
- 3.4.2 對象
- 3.4.3 繼承
- 3.4.4 動態屬性
- 3.4.5 魔術方法
- 3.4.6 類的自動加載
- 3.5 運行時緩存
- 3.6 Opcache
- 3.6.1 opcode緩存
- 3.6.2 opcode優化
- 3.6.3 JIT
- 第4章 PHP基礎語法實現
- 4.1 類型轉換
- 4.2 選擇結構
- 4.3 循環結構
- 4.4 中斷及跳轉
- 4.5 include/require
- 4.6 異常處理
- 第5章 內存管理
- 5.1 Zend內存池
- 5.2 垃圾回收
- 第6章 線程安全
- 6.2 線程安全資源管理器
- 第7章 擴展開發
- 7.1 概述
- 6.1 什么是線程安全
- 7.2 擴展的實現原理
- 7.3 擴展的構成及編譯
- 7.4 鉤子函數
- 7.5 運行時配置
- 7.6 函數
- 7.7 zval的操作
- 7.8 常量
- 7.9 面向對象
- 7.9.1 內部類注冊
- 7.9.2 定義成員屬性
- 7.9.3 定義成員方法
- 7.9.4 定義常量
- 7.9.5 類的實例化
- 7.10 資源類型
- 7.11 經典擴展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空間
- 8.2 命名空間的定義
- 8.2.1 定義語法
- 8.2.2 內部實現
- 8.3 命名空間的使用
- 8.3.1 基本用法
- 8.3.2 use導入
- 8.3.3 動態用法
- 附錄
- 附錄1:break/continue按標簽中斷語法實現
- 附錄2:defer推遲函數調用語法的實現
- 8.1 概述