php语法分析
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语法分析的更多相关文章
- Linux源码Kconfig文件语法分析
Kconfig是我们进行内核配置的关键文件,用于生成menuconfig的界面并生成最终确定编译选项的.config文件.关于Kconfig文件的编写规则,在Documentation/kbuild/ ...
- 跟vczh看实例学编译原理——三:Tinymoe与无歧义语法分析
文章中引用的代码均来自https://github.com/vczh/tinymoe. 看了前面的三篇文章,大家应该基本对Tinymoe的代码有一个初步的感觉了.在正确分析"print ...
- 简单的词法分析和语法分析(C++实现,CodeBlocks+GCC编译)
说明: 分析的语言是SNL语言,详见<编译程序的设计与实现>( 刘磊.金英.张晶.张荷花.单郸编著) 词法分析就是实现了词法分析的自动机 语法分析使用递归下降法 运行结果: 词法分析 得到 ...
- Atitit 表达式原理 语法分析 原理与实践 解析java的dsl 递归下降是现阶段主流的语法分析方法
Atitit 表达式原理 语法分析 原理与实践 解析java的dsl 递归下降是现阶段主流的语法分析方法 于是我们可以把上面的语法改写成如下形式:1 合并前缀1 语法分析有自上而下和自下而上两种分析 ...
- LALR(1)语法分析生成器--xbytes
0.概述: 看了编译器龙书和虎书后,自己手动写了一个LALR(1)语法分析生成器,使用的语法文件格式和lemon的差不多. 程序里面很多的算法也都是摘录自虎书,龙书虽然讲的很详细,但是真正动手写的时候 ...
- JavaCC首页、文档和下载 - 语法分析生成器 - 开源中国社区
JavaCC首页.文档和下载 - 语法分析生成器 - 开源中国社区
- SLR,语法分析表的构建
太累了,感觉不会再爱了.执行了跟编译原理上的一模一样的例子,输出了正确结果 #include <stdio.h> #include <malloc.h> #include &l ...
- LR(1)表驱动语法分析程序
/* * LR(1) 语法分析 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #inc ...
- 实现自己的脚本语言ngscript之二:语法分析
ngscript的语法分析使用的是我自己的语法分析工具parseroid.与常用cc工具(yacc.bison.javacc.antlr.etc…)不同的是,parseroid生成的不是语法分析器的源 ...
- 【编译原理】语法分析LL(1)分析法的FIRST和FOLLOW集
近来复习编译原理,语法分析中的自上而下LL(1)分析法,需要构造求出一个文法的FIRST和FOLLOW集,然后构造分析表,利用分析表+一个栈来做自上而下的语法分析(递归下降/预测分析),可是这个FIR ...
随机推荐
- Devexpress VCL Build v2014 vol 14.2.1 beta发布
已经快到2015 年了. 14.2.1 beta 才出来了. 还好,有一些新东西. 官网地址 VCL Gauge Control Designed to clearly convey informat ...
- Linux 随记
通配符和文件名变量:* ? [] * 查询 $ ls doc1 doc2 document mydoc monday $ ls doc* doc1 doc2 document $ ls *day m ...
- 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 ...
- 让tableView的某行移动到tableView的某位置
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:lineNumber inSection:0]; [lrcTableView selectR ...
- AugularJS, Responsive, phonegap, BAE, SAE,GAE, Paas
http://freewind.me/blog/20121226/1167.html http://88250.b3log.org/bae-sae-gae http://www.ruanyifeng. ...
- (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 ...
- 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 ...
- ubuntu18.04 编译安装 apache php
1. apache apache 需要依赖几个模块:apr, apr-util, pcre,也分别源码安装. 1.1 编译安装 1.1.1 apr apr-util 下载地址:http://apr.a ...
- java线程一
我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread).线程(Thread)是一份独立运行的程序,有自己专用的运行栈.线程有可能和其他线程共享一些资源,比 ...
- Keil5编译STM32注意事项
硬件:某STM32开发板,ST-Link/V2 一.硬件相关: 1.引脚连接: pin7 <-> SWIO pin9 <-> SWCLK pin20/pin18 <-&g ...