php语法分析

  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 ,类型是zval,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#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; //搞不懂为什么还要相加
 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
时间: 2024-10-09 20:26:28

php语法分析的相关文章

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

Atitit 表达式原理 语法分析 原理与实践 解析java的dsl  递归下降是现阶段主流的语法分析方法 于是我们可以把上面的语法改写成如下形式:1 合并前缀1 语法分析有自上而下和自下而上两种分析方法2 递归下降是现阶段主流的语法分析方法,2 于是我们可以把上面的语法改写成如下形式: 1)       Operator="+" | "-" | "*" | "/" 2)       Expression=<数字>

编写语法分析程序

编写语法分析程序 Note: Mr.JY的编译原理! 文法改造 1.文法 1) <program>→{<declaration_list><statement_list>} 2) <declaration_list>→<declaration_list><declaration_stat> | ε 3) <declaration_stat>→int ID; 4) <statement_list>→<sta

atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结

atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结 1. 建立AST 抽象语法树 Abstract Syntax Tree,AST) 1 2. 建立AST 语法树----递归下降(recursive descent)法 2 3. 语法分析概念 2 3.1. 上下文无关语言,非终结符(nonterminal symbol),终结符(terminal symbol).注 2 3.2. 最左推导.当然也有最右推导 3 3.3. 分支预测的

语法分析程序

语法分析程序 #include<stdio.h> #include <stdlib.h> typedef struct link //字符链表 { char data; struct link *next; }Link; char special[10]; void specialchar(Link *charhead); main() { char n; Link *charhead; //定义头结点 Link *p,*q; charhead=q=(Link *)malloc(s

编译原理 实验3 语法分析

语法分析 一. 实验目的 算术表达式的文法可以是(你可以根据需要适当改变): E→E+E|E-E|E*E|E/E|(E)|i 根据算符优先分析法,将表达式进行语法分析,判断一个表达式是否正确. 二. 实验环境 操作系统:window xp 编写环境:visual c++ 编写语言:c语言 三. 实验内容 程序输入/输出示例: 如参考C语言的运算符.输入如下表达式(以分号为结束)和输出结果: (1)10; 输出:正确 (2)1+2; 输出:正确 (3)(1+2)/3+4-(5+6/7); 输出:正

Lua1.1 语法分析

无论是 lua_dostring 或者是 lua_dofile,都调用了语法分析 lua_parse.在 lua 里面语法分析器是用 yacc 生成的,就是y.tab.c 文件,lua.stx 就是 yacc 的输入文件.这里说的东西基本上编译原理书里都有介绍,如需要进一步了解,请自行参阅. 在说语法分析之前,说下词法分析.lua 的词法分析是手写的,手写的词法分析性能比较好,这个在 lua1.1 自带的文档里有说明,文件名 lua.ps, 第 8 页 (或者 www.lua.org/semis

Yacc 与 Lex 快速入门(词法分析和语法分析)

我们知道,高级语言,一般的如c,Java等是不能直接运行的,它们需要经过编译成机器认识的语言.即编译器的工作. 编译器工作流程:词法分析.语法分析.语义分析.IR(中间代码,intermediate Representation)产生.IR优化.代码产生.最终优化: 我们这里主要介绍的是语法分析: Lex 代表 Lexical Analyzar.Yacc 代表 Yet Another Compiler Compiler. 让我们从 Lex 开始吧. Lex Lex 是一种生成扫描器的工具.扫描器

实验报告(3)-语法分析

实验三:递归下降语法分析实验 一.    实验目的 编制一个递归下降分析程序. 二.    实验内容和要求 输入:算术表达式: 输出:判断结果(输入正确/错误). 三.实验方法.步骤及结果测试 1.源程序名:递归下降语法分析.c    可执行程序名:递归下降语法分析.exe 2. 原理分析及流程图 采用递归子程序方法进行语法分析,对文法中的每个非终结符号按其产生式结构产生相应的语法分析子程序,完成相应的识别任务.其中终结符产生匹配命令,非终结符则产生调用命令. 流程图: 3. 主要程序段及其解释

完整cmm解释器构造实践(三):语法分析

完整cmm解释器构造实践(三):语法分析 语法树节点 我的语法分析器不仅会判断cmm代码的语法是否正确, 同时会存储分析过程中得到的信息, 完成语法树的构建. 为什么要有语法树呢, 其实还是为了计算机方便做进一步的处理才用的, 语法树里面存储了从代码里面提取的信息, 我们生成语法树之后再通过遍历语法树来得到中间代码. 当然直接遍历语法树并解释执行也是可以的, 我们老师非得让我们有中间代码, 所以我的语法分析只为了生成中间代码而服务的, 虽然我的代码生成器本质上是解释器改造而成的. 那么既然语法树

编译原理实验代码(词法分析,语法分析,中间代码生成)

花了一天写出的程序没有顾及很多层面,但对于理解基本的实验道理和交上实验还是有点帮助的.代码实现了基于有限自动机的词法分析,采用递归下降分析法和EBNF文法实现语法分析并生成中间代码. lexAnalysis.h /* * lexAnalysis.h * * Created on: 2014-12-2 * Author: liuqiushan */ #ifndef LEXANALYSIS_H_ #define LEXANALYSIS_H_ #include <stdio.h> #include