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. Devexpress VCL Build v2014 vol 14.2.1 beta发布

    已经快到2015 年了. 14.2.1 beta 才出来了. 还好,有一些新东西. 官网地址 VCL Gauge Control Designed to clearly convey informat ...

  2. Linux 随记

    通配符和文件名变量:* ? [] * 查询 $ ls doc1 doc2 document mydoc monday $ ls doc* doc1 doc2 document $ ls  *day m ...

  3. Error creating bean with name 'sessionFactory' defined in class path resource [applicationContext.xml]: Invocation of init method failed; nested exception is org.hibernate.HibernateException: Unable t

    spring与hibernate整合然后出现如下错误: org.springframework.beans.factory.BeanCreationException: Error creating ...

  4. 让tableView的某行移动到tableView的某位置

    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:lineNumber inSection:0]; [lrcTableView selectR ...

  5. AugularJS, Responsive, phonegap, BAE, SAE,GAE, Paas

    http://freewind.me/blog/20121226/1167.html http://88250.b3log.org/bae-sae-gae http://www.ruanyifeng. ...

  6. (KMP)Count the string -- hdu -- 3336

    http://acm.hdu.edu.cn/showproblem.php?pid=3336 Count the string Time Limit: 2000/1000 MS (Java/Other ...

  7. HDU1312 Red and Black(DFS) 2016-07-24 13:49 64人阅读 评论(0) 收藏

    Red and Black Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 65536/32768K (Java/Other) Total ...

  8. ubuntu18.04 编译安装 apache php

    1. apache apache 需要依赖几个模块:apr, apr-util, pcre,也分别源码安装. 1.1 编译安装 1.1.1 apr apr-util 下载地址:http://apr.a ...

  9. java线程一

    我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread).线程(Thread)是一份独立运行的程序,有自己专用的运行栈.线程有可能和其他线程共享一些资源,比 ...

  10. Keil5编译STM32注意事项

    硬件:某STM32开发板,ST-Link/V2 一.硬件相关: 1.引脚连接: pin7 <-> SWIO pin9 <-> SWCLK pin20/pin18 <-&g ...