php的语法分析的主要作用是验证词法分析的基础上将token组成的序列,在php这门语言中是否是一个有效的句子,也可以理解为这些token序列是否匹配设计php这门语言时的语法模型,在匹配的情况下构建具体的程序(组建opcode),以供编译后期使用。
比如:在设计php语言时,需要设计一套语法规则,通过使用上下文无关方法(主要使用BNF(巴斯科-瑙尔范式)表示法来描述),关于BNF(巴简直斯范式),请猛戳 ,另外 文章也不错
比如在有一个功能:我需要打印一些东西,这里主要是echo,不仅要支持echo 变量,也要支持echo 常量 ,也要支持 echo 表达式 ,也要支持 echo 变量,常量 等等这样的,我们不可能用具体的去实现,只能用最抽象的方法去概括
我简单提取了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例外。
语法分析器使用LALR,它 由两个二维数组构成, 一个是ACTION , 一个是GOTO ,但zend_language_parse.c中 yytable代替了action表, yygoto代替了goto,均是一维数组,进行了压缩
ACTION 指明了动作是移进,归约,接受,还是错误
GOTO 指明了新的状态
语法分析运行方法:
根据当前状态和向前看符号,执行相应的动作,如果不存在向前看字符,利用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
}