php的语法分析的主要作用是验证词法分析的基础上将token组成的序列,在php这门语言中是否是一个有效的句子,也可以理解为这些token序列是否匹配设计php这门语言时的语法模型,在匹配的情况下构建具体的程序(组建opcode),以供编译后期使用。

  比如:在设计php语言时,需要设计一套语法规则,通过使用上下文无关方法(主要使用BNF(巴斯科-瑙尔范式)表示法来描述),关于BNF(巴简直斯范式),请猛戳 这里 ,另外 这篇 文章也不错

  比如在有一个功能:我需要打印一些东西,这里主要是echo,不仅要支持echo 变量,也要支持echo 常量 ,也要支持 echo 表达式 ,也要支持 echo 变量,常量 等等这样的,我们不可能用具体的去实现,只能用最抽象的方法去概括

    我简单提取了zend_language_parse.y中关于echo的一些产生式,其中省略了一部分无关的产生式

 unticked_statement:
echo_expr_list ';' echo_expr_list:
echo_expr_list ',' expr { zend_do_echo(&$ TSRMLS_CC); }
| expr { zend_do_echo(&$ TSRMLS_CC); }
; expr:
r_variable { $$ = $; }
| expr_without_variable { $$ = $; }
; r_variable:
variable { zend_do_end_variable_parse(&$, BP_VAR_R, TSRMLS_CC); $$ = $; }
; expr_without_variable:
| scalar { $$ = $; } scalar:
| common_scalar { $$ = $; } common_scalar:
T_LNUMBER { $$ = $; }
| T_DNUMBER { $$ = $; }

  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)      

   

 int zendlex(znode *zendlval TSRMLS_DC) /* {{{ */
{
int retval; if (CG(increment_lineno)) {
CG(zend_lineno)++;
CG(increment_lineno) = ;
} again:
Z_TYPE(zendlval->u.constant) = IS_LONG;
retval = lex_scan(&zendlval->u.constant TSRMLS_CC);
switch (retval) {
case T_COMMENT:
case T_DOC_COMMENT:
case T_OPEN_TAG:
case T_WHITESPACE:
goto again; case T_CLOSE_TAG:
if (LANG_SCNG(yy_text)[LANG_SCNG(yy_leng)-] != '>') {
CG(increment_lineno) = ;
}
if (CG(has_bracketed_namespaces) && !CG(in_namespace)) {
goto again;
}
retval = ';'; /* implicit ; */
break;
case T_OPEN_TAG_WITH_ECHO:
retval = T_ECHO;
break;
case T_END_HEREDOC:
efree(Z_STRVAL(zendlval->u.constant));
break;
} INIT_PZVAL(&zendlval->u.constant);
zendlval->op_type = IS_CONST; //设置为常量,网上资料说是:词法分析阶段识别出来的都是常量,因为不涉及运行
return retval;
}
 typedef struct _znode { /* used only during compilation */
int op_type;
union {
znode_op op;
zval constant; /* replaced by literal/zv */
zend_op_array *op_array;
} u;
zend_uint EA; /* extended attributes */
} 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入符号栈

    

 ZEND_API zend_op_array *compile_file(zend_file_handle *file_handle, int type TSRMLS_DC)
{
zend_lex_state original_lex_state;
zend_op_array *op_array = (zend_op_array *) emalloc(sizeof(zend_op_array));
zend_op_array *original_active_op_array = CG(active_op_array);
zend_op_array *retval=NULL;
int compiler_result;
zend_bool compilation_successful=0;
znode retval_znode;
zend_bool original_in_compilation = CG(in_compilation); retval_znode.op_type = IS_CONST;
retval_znode.u.constant.type = IS_LONG;
retval_znode.u.constant.value.lval = 1;
Z_UNSET_ISREF(retval_znode.u.constant);
Z_SET_REFCOUNT(retval_znode.u.constant, 1); zend_save_lexical_state(&original_lex_state TSRMLS_CC); retval = op_array; /* success oriented */ if (open_file_for_scanning(file_handle TSRMLS_CC)==FAILURE) {
if (type==ZEND_REQUIRE) {
zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, file_handle->filename TSRMLS_CC);
zend_bailout();
} else {
zend_message_dispatcher(ZMSG_FAILED_INCLUDE_FOPEN, file_handle->filename TSRMLS_CC);
}
compilation_successful=0;
} else {
init_op_array(op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE TSRMLS_CC);
CG(in_compilation) = 1;
CG(active_op_array) = op_array;
zend_stack_push(&CG(context_stack), (void *) &CG(context), sizeof(CG(context)));
zend_init_compiler_context(TSRMLS_C);
compiler_result = zendparse(TSRMLS_C);
zend_do_return(&retval_znode, 0 TSRMLS_CC);
CG(in_compilation) = original_in_compilation;
if (compiler_result==1) { /* parser error */
zend_bailout();
}
compilation_successful=1;
} if (retval) {
CG(active_op_array) = original_active_op_array;
if (compilation_successful) {
pass_two(op_array TSRMLS_CC);
zend_release_labels(TSRMLS_C);
} else {
efree(op_array);
retval = NULL;
}
}
zend_restore_lexical_state(&original_lex_state TSRMLS_CC);
return retval;
}

 #define yyparse zendparse

int yyparse(){

 1#define YYPOPSTACK(N)   (yyvsp -= (N), yyssp -= (N)) 
  
   yybackup:
yyn = yypact[yystate]; //搞不懂yypact这个数组的作用,原来的注释是这样的/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ ,意思是说YYPACK[STATE-NUM]的值是 YYTABL
      if (yyn == YYPACT_NINF)
goto yydefault; if (yychar == YYEMPTY)
{
YYDPRINTF ((stderr, "Reading a token: "));
yychar = YYLEX; //这里调用yylex函数,读取一个符号,YYLEX本身是一个宏
} if (yychar <= YYEOF)
{
yychar = yytoken = YYEOF; //词法分析结束了
YYDPRINTF ((stderr, "Now at end of input.\n"));
}
else
{
yytoken = YYTRANSLATE (yychar); //如果yychar不为空,则使用YYTRANSLATE进行yychar在语法分析中的内部转换
YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
} yyn += yytoken; //yypack可理解为基地址;yytoken可理解为偏移地址;
yyn = yytable[yyn]; //这个yytables是个一维数组,它是一个DNF状态转换表,本身是一个二维数组,但为了减小空间,进行了压缩,详见 这里 ,这里数组肯定做了改进,根据yyn的正负值,可以判断成是移进,还是规约 if (yyn <= )
{
if (yyn == || yyn == YYTABLE_NINF)
goto yyerrlab; //进入错误提示
yyn = -yyn;
goto yyreduce; //进入归约
} if (yyn == YYFINAL)
YYACCEPT; if (yychar != YYEOF)
yychar = YYEMPTY; //将yychar设置为空,为下一次调用yylex()函数作准备

yystate = yyn;
*++yyvsp = yylval; //这里是移进动作,将yylval的值入符号栈,yylval是调用lex_scan,通过引用参数&yylval来传递的,它是一个zval类型的数据
goto yynewstate; yyreduce: //进行归约
/* yyn is the number of a rule to reduce with. */
yylen = yyr2[yyn]; //获得要弹出栈中元素的个数,产生式右端长度,不清楚yyr2怎么计算的

/* If YYLEN is nonzero, implement the default value of the action:
`$$ = $1'. Otherwise, the following line sets YYVAL to garbage.
This behavior is undocumented and Bison
users should not rely upon it. Assigning to YYVAL
unconditionally makes the parser a bit smaller, and it avoids a
GCC warning that YYVAL may be used uninitialized. */
yyval = yyvsp[-yylen]; //这块是一个负数了,不知道具体是什么意思
YY_REDUCE_PRINT (yyn);
switch (yyn)
{             //这里是500多个操作,   
case : { zend_do_end_compilation(TSRMLS_C); }
break;
。。。。。。。
default: break;
}
YYPOPSTACK (yylen); //状态栈和符号栈pop出yylen个元素
yylen = ;
YY_STACK_PRINT (yyss, yyssp); *++yyvsp = yyval; //将规则左边的终结符压入符号栈
/* Now `shift' the result of the reduction. Determine what state
that goes to, based on the state we popped back to and the rule
number reduced by. */ yyn = yyr1[yyn]; yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; //不明白为什么这么计算,计算的结果是一个新的yystate,pop出yylen个元素之后的栈顶元素
if ( <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
yystate = yytable[yystate];
else
yystate = yydefgoto[yyn - YYNTOKENS]; goto yynewstate; yynewstate:
/* In all cases, when you get here, the value and location stacks
have just been pushed. So pushing a state here evens the stacks. */
yyssp++; //状态栈指针加加,以便接收yynewstate,接着进入yysetstate

yysetstate:
*yyssp = yystate; //yystate入栈

。。。。。。。。。。 yyssp = yyss + yysize - ;
yyvsp = yyvs + yysize - ; 。。。。。。。。 goto yybackup; //循环调用 yybackup,读取下一个token

}

php语法分析的更多相关文章

  1. Linux源码Kconfig文件语法分析

    Kconfig是我们进行内核配置的关键文件,用于生成menuconfig的界面并生成最终确定编译选项的.config文件.关于Kconfig文件的编写规则,在Documentation/kbuild/ ...

  2. 跟vczh看实例学编译原理——三:Tinymoe与无歧义语法分析

    文章中引用的代码均来自https://github.com/vczh/tinymoe.   看了前面的三篇文章,大家应该基本对Tinymoe的代码有一个初步的感觉了.在正确分析"print ...

  3. 简单的词法分析和语法分析(C++实现,CodeBlocks+GCC编译)

    说明: 分析的语言是SNL语言,详见<编译程序的设计与实现>( 刘磊.金英.张晶.张荷花.单郸编著) 词法分析就是实现了词法分析的自动机 语法分析使用递归下降法 运行结果: 词法分析 得到 ...

  4. Atitit 表达式原理 语法分析 原理与实践 解析java的dsl  递归下降是现阶段主流的语法分析方法

    Atitit 表达式原理 语法分析 原理与实践 解析java的dsl  递归下降是现阶段主流的语法分析方法 于是我们可以把上面的语法改写成如下形式:1 合并前缀1 语法分析有自上而下和自下而上两种分析 ...

  5. LALR(1)语法分析生成器--xbytes

    0.概述: 看了编译器龙书和虎书后,自己手动写了一个LALR(1)语法分析生成器,使用的语法文件格式和lemon的差不多. 程序里面很多的算法也都是摘录自虎书,龙书虽然讲的很详细,但是真正动手写的时候 ...

  6. JavaCC首页、文档和下载 - 语法分析生成器 - 开源中国社区

    JavaCC首页.文档和下载 - 语法分析生成器 - 开源中国社区

  7. SLR,语法分析表的构建

    太累了,感觉不会再爱了.执行了跟编译原理上的一模一样的例子,输出了正确结果 #include <stdio.h> #include <malloc.h> #include &l ...

  8. LR(1)表驱动语法分析程序

    /* * LR(1) 语法分析 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #inc ...

  9. 实现自己的脚本语言ngscript之二:语法分析

    ngscript的语法分析使用的是我自己的语法分析工具parseroid.与常用cc工具(yacc.bison.javacc.antlr.etc…)不同的是,parseroid生成的不是语法分析器的源 ...

  10. 【编译原理】语法分析LL(1)分析法的FIRST和FOLLOW集

    近来复习编译原理,语法分析中的自上而下LL(1)分析法,需要构造求出一个文法的FIRST和FOLLOW集,然后构造分析表,利用分析表+一个栈来做自上而下的语法分析(递归下降/预测分析),可是这个FIR ...

随机推荐

  1. SVN中检出 和 导出 的区别

    SVN中检出 和 导出 的区别:检出得到的文件夹中,是受SVN客户端控制的,对其进行文件或文件夹的增删改操作都会被SVN客户端识别出来,对其可以进行update.commit操作.其中含有.svn隐藏 ...

  2. Activiti中23张表的含义

    1.与流程定义相关的4张表: 2.与执行任务相关的5张表: 3.与流程变量相关的2张表

  3. 2018.10.14 bzoj1915: 奶牛的跳格子游戏(单调队列优化dp)

    传送门 NOIP练习题. f[i]f[i]f[i]表示去的时候选了iii且回来的时候第一步走的是i−1i-1i−1的最优值. 显然f[i]=maxf[i]=maxf[i]=max{f[j]−sum[j ...

  4. 2018.10.01 NOIP模拟 偷书(状压dp)

    传送门 状压dp经典题. 令f[i][j]f[i][j]f[i][j]表示到第i个,第i−k+1i-k+1i−k+1~iii个物品的状态是j时的最大总和. 然后简单维护一下转移就行了. 由于想皮一下果 ...

  5. UVa 12230 && HDU 3232 Crossing Rivers (数学期望水题)

    题意:你要从A到B去上班,然而这中间有n条河,距离为d.给定这n条河离A的距离p,长度L,和船的移动速度v,求从A到B的时间的数学期望. 并且假设出门前每条船的位置是随机的,如果不是在端点,方向也是不 ...

  6. Linux上查看造成IO高负载的进程

    方法1:使用iotop工具这是一个python脚本工具,使用方法如:iotop -o方法2:使用工具dmesg使用dmesg之前,需要先开启内核的IO监控:echo 1 >/proc/sys/v ...

  7. x13 vs md5

    x13 vs md5 阅读:  评论:  作者:Rybby  日期:  来源:rybby.com 最近在设计巴巴变时想对用户设计的节点模块添加锁定功能,比如你的网站可以让用户发表文章或评论,而你想让用 ...

  8. JavaScript中使用function作为对象键值

    JavaScript的键值只能是string或者number,这一点真是返祖现象啊.现在我面临的问题: var funcs = {}; var funcA = function() { }; var ...

  9. 曲演杂坛--重建索引后,还使用混合分区么?(Are mixed pages removed by an index rebuild?)

    原文来自:http://www.sqlskills.com/blogs/paul/mixed-pages-removed-index-rebuild/ 在SQL SERVER 中,区是管理空间的基本单 ...

  10. telerik:RadGrid 表格中删除数据

    <telerik:RadGrid OnItemCommand=" Height="490px" Culture="zh-CN" CssClass ...