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
}