PHP语法分析器:RE2C BISON 总结

在此早先,笔者曾经尝试过三个连串,就是将大家的PHP代码自动生成so扩充,

写在后边的几句废话

言语是人人举行联系和调换的宣布符号,各个语言都有专门项目于自身的标志,表明方式和准则。
就编制程序语言来讲,它也是由特定的暗号,特定的表明方式和法规组成。语言的效劳是关联,不管是自然语言,依旧编制程序语言,它们的分化在于自然语言是人与人里面交换的工具,
而编制程序语言是人与机具之间的联系路子。

编译到PHP中,我叫它 phptoc。

近日在类型的历程中接触了lex 和
yacc,他们得以支持大家来促成团结的园地语言。最卓越的行使正是足以帮助我们来兑现自定义测验脚本的实践器。可是,这里也可能有二个范围,正是测验脚本要做的主干业必需得有现存的C语言库来促成,不然就做不到了;假设基本的操作是用java来做的,那么还能用Antlr,这里不对Antlr做详细介绍。

就PHP言语来讲,它也是生机勃勃组切合自然法则的预订的授命。
在编制程序职员将本人的主见以PHP语言达成后,通过PHP的虚构机(确切的来说应该是PHP的言语引擎Zend)

可是由于各样缘由,暂停了此项目。

lex是什么?

将这一个PHP指令转换成C语言
(能够领略为更底层的风度翩翩种指令集)指令,而C语言又会变动成汇编语言,
最终汇编语言将依靠微电脑的规规矩矩变化成机器码推行。那是三个更加高档次抽象的不仅具体化,不断细化的经过。

写那篇小说一是因为那上头材质太少,二是把团结的获取总结下来,以便以往参照他事他说加以考察,假若能明白PHP语法分析

教材上把lex的作用的效用叫做“词法深入分析 lexical analysis
”,那几个粤语叫法非常令人看不晓得(叫做“符号提取”更贴切),其实从它的印度语印尼语单词lexical上来看他的情趣其实是不行明白的。

从黄金年代种语言到另生机勃勃种语言的转速称之为编写翻译,那三种语言分别能够称之为源语言和指标语言。
这种编写翻译进度通过产生在指标语言比源语言更低端(也许说更底层)。
语言转变的编写翻译进度是由编写翻译器来完毕,
编码器日常被分为大器晚成多种的进度:词法解析、语法解析、语义解析、中间代码生成、代码优化、目的代码生成等。
前边多少个等级(词法解析、语法解析和语义分析)的効用是分析源程序,大家得以称之为编写翻译器的前端。
前边的多少个等第(中间代码生成、代码优化和目的代码生成)的意义是布局指标程序,大家称得上编写翻译器的后端。
黄金时代种语言被叫做编写翻译类语言,平日是出于在程序实行早前有一个翻译的长河,
在这之中关键点是有一个样式上完全两样的也等于程序生成。
而PHP之所以被称作解释类语言,就是因为并不曾这么的多个主次生成,
它生成的是个中代码Opcode,那只是PHP的生龙活虎种内部数据布局。

那对PHP源码的商讨会更上生机勃勃层楼地 ^.^…

lexical,在webster上的分解是:of or relating to words or the vocabulary
of a language as distinguished from its grammar and construction。

二、 PHP代码的实践的进程

自个儿尽量写的老妪能解些。

指的是: 大器晚成种语言中有关词汇、单词的,与之绝对的是这种语言的语法和集体

举例说大家写八个回顾的次序

以此项目思路源于facebook的开源项目 HipHop .

那般来看的话 lexical analysis
的成效就相应是语言中的词汇和单词解析。事实上他的效应便是从言语中提取单词。放到编程语言中的话,他要做的职业实在就是领到编制程序语言占用的种种保留字、操作符等等语言的成分

<?php  
    echo "Hello World!";  
    $a = 1 + 1;  
    echo $a;  
?>  

事实上小编对这几个类其他性质进步八分之四-十分之三持可疑态度,从根本来讲,假设PHP用到APC缓存,它的本性是不是低

之所以她的其余叁个名字scanner其实更形象某个,就是扫描三个文书中的单词。

那些轻巧的次序他实践进度是哪些的呢?其实,施行进度也相比大家前面所说分为4个步骤。(这里只是指PHP语言引擎Zend执行进度,不包涵Web服务器的施行进度。)

于HipHop,小编还还未做测验,不敢断言。

lex把各种扫面出来的单词叫统统叫做token,token可以有成都百货上千类。相比自然语言的话,法语中的每一个单词都以token,token有超多类,举个例子non(名词State of Qatar正是三个类token,apple便是归于那个类其余二个现实token。对于有个别编制程序语言来讲,token的个数是很简单的,不像爱尔兰语这种自然语言中有几十万个单词。

  1. 1.Scanning(Lexing卡塔尔(قطر‎ ,将PHP代码调换为语言片段(Tokens卡塔尔(قطر‎

  2. 2.Parsing, 将Tokens转变到不难而有意义的表明式

  3. 3.Compilation, 将表明式编写翻译成Opocdes

  4. 4.Execution, 顺次实行Opcodes,每便一条,进而完结PHP脚本的效果。

    注1:Opcode是生机勃勃种PHP脚本编写翻译后的中间语言,就像Java的ByteCode,大概.NET的MSL

PHPtoc,笔者只是想把C程序员解放出来,希望能达到规定的标准,让PHPer用PHP代码就足以写出相似于PHP扩充质量的二个扩张,

lex工具会帮大家转移叁个yylex函数,yacc通过调用那个函数来获知得到的token是怎么着类型的,不过token的花色是在yacc中定义的。

注2:现在部分Cache比方APC,能够使得PHP缓存住Opcodes,那样,每一回有央浼降临的时候,就不必要再行推行前边3步,进而能大幅度的滋长PHP的试行进度。

它的流程如下,读取PHP文件,深入解析PHP代码,对其举办语法解析器,生成对应的ZendAPI,编写翻译成扩充。

lex的输入文件经常会被取名成 .l文件,通过lex XX.l
大家得到输出的文本是lex.yy.c

将PHP代码调换为语言片段(Tokens)

澳门新浦京电子游戏 1

澳门新浦京电子游戏,yacc是何许吗?

那什么样是Lexing?
学过编写翻译原理的同校都应有对编写翻译原理中的词法深入分析步骤有所理解,Lex正是多个词法深入分析的依靠表。

进去正题

刚刚说罢lex了,那么yacc呢,教科书上把yacc做的行事叫做syntactic
analysis。此次我们翻译未有直译做句法深入分析,而是叫语法解析,这几个翻译能好一点,意思也大约比较清楚。
实则大家最先阶读书乌克兰语的时候老师都会告诉大家日文其实便是“单词+语法”,那几个理念放到编制程序语言中很有分寸,lex提取了单词,那么是多余的大器晚成部分正是何等发挥语法。那么yacc做的职业正是那豆蔻梢头局地(实际应该算得BNF来做的)。

对此PHP在初叶运用的是Flex,之后改为re2c,
MySQL的词法深入分析利用的Flex,除却还应该有作为UNIX系统规范词法剖析器的Lex等。
这么些工具都会读进叁个表示词法分析器法规的输入字符串流,然后输出以C语言实做的词法分析器源代码。
这里大家只介绍PHP的现版词法解析器,re2c。

此间最难的正是语法深入分析器了,我们应该都知晓,PHP也是有协和的语法深入分析器,未来版本采纳的是re2c
和 Bison。

yacc会帮大家调换八个yyparse函数,这几个函数会不断调用下边的yylex函数来收获token的项目。

在源码目录下的Zend/zend_language_scanner.l 文件是re2c的中规中矩文件,
假如需求改进该法规文件须求设置re2c本事再一次编写翻译,生成新的平整文件。

据此,小编自然也应用了那个组合。

yacc的输入文件平日会被命名成 .y文件,通过yacc -d
XX.y我们得到的出口文件是y.tab.h
y.tab.c,后面一个带有了lex需求的token类型定义,供给被include进 .l文件中

Zend/zend_language_scanner.c会根据Zend/zend_language_scanner.l,来输入的
PHP代码进行词法深入分析,从而获取贰个一个的“词”。

比如要用PHP的语法分析器就不太现实了,因为急需改正zend_language_parser.y和
zend_language_scanner.l 并再度编写翻译,那难度大不说,还可能影响PHP自己。

lex和yacc的输入文件格式

从PHP4.2早先提供了一个函数叫token_get_all,这些函数就足以将意气风发段PHP代码
Scanning成Tokens;

进而决定重新写后生可畏套自个儿的语法深入分析准绳,这么些职能就约等于是重写了PHP的语法深入分析器,当然会扬弃一些不经常用的。

Definition section
%%
Rules section

咱俩用上边包车型地铁代码应用token_get_all函数管理大家起首提到的PHP代码。

re2c &&
yacc/bison,通过援引自个儿的应和文件,然后将她们统一编写翻译成叁个*.c文件,最终再gcc编写翻译就可以生

%%
C code section

<?php  
echo "<pre>";  
$phpcode = <<<PHPCODE  
<?php  
    echo "Hello World!";  
    $a = 1 + 1;  
    echo $a;  
?>  
PHPCODE;  
// $tokens = token_get_all($phpcontent);  
// print_r($tokens);  
$tokens = token_get_all($phpcode);   
foreach ($tokens as $key => $token) {  
    $tokens[$key][0] = token_name($token[0]);  
}  
print_r($tokens);  
?>  

成我们休戚相关的前后相继。所以说,他们从根本来说不是语法分析程序,他们只是将大家的法规变化三个独自的c文

.l和.y的文件格式都以分成三段,用%%来划分,多个section的含义是:

为了便于明白和查阅,作者使用token_name函数将深入分析器代号校勘成了标志名称表明。

件,那个c文件才是当真的我们供给的语法解析程序,作者更愿意叫它
语法生成器。如下图:

  • Definition Section

借使有的童鞋想要看原来的,能够将地点代码中的第10,11行代码注释去掉。

澳门新浦京电子游戏 2

那块能够放C语言的各类各样include,define等评释语句,不过要用%{
%}括起来。

比如是.l文件,能够放预约义的正则表达式:minus “-”
还要放token的概念,方法是:代号 正则表明式。然后到了,Rules
Section就足以因此{符号} 来援引正则表达式

如即便.y文件,能够放token的概念,如:%token INTEGE奇骏 PLUS
,这里的定多少个的各样token都能够在y.tab.h中看出

解释器代号列表详见:http://www.php.NET/manual/zh/tokens.php

注:图中a.c是 扫描器生成的终极代码。。

  • Rules section

获取的结果如下:

re2c扫描器,借使我们写的扫描准则文件叫scanner.l,它会将大家写的PHP文件内容,进行围观,然后根据

.l文件在这里边放置的rules便是各类正则表明式要相应的动作,平时是回到一个token

.y文件在此放置的rules正是满意三个语法描述时要推行的动作

无论是是.l文件照旧.y文件这里的动作都以用{}扩起来的,用C语言来说述,这几个代码能够做你任何想要做的政工

Array  
(  
    [0] => Array  
        (  
            [0] => T_OPEN_TAG  
            [1] =>  1  
        )  

    [1] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>     
            [2] => 2  
        )  

    [2] => Array  
        (  
            [0] => T_ECHO  
            [1] => echo  
            [2] => 2  
        )  

    [3] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 2  
        )  

    [4] => Array  
        (  
            [0] => T_CONSTANT_ENCAPSED_STRING  
            [1] => "Hello World!"  
            [2] => 2  
        )  

    [5] =>   
    [6] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>   

            [2] => 2  
        )  

    [7] =>   
    [8] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 3  
        )  

    [9] => Array  
        (  
            [0] => T_LNUMBER  
            [1] => 1  
            [2] => 3  
        )  

    [10] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 3  
        )  

    [11] =>   
    [12] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 3  
        )  

    [13] => Array  
        (  
            [0] => T_LNUMBER  
            [1] => 1  
            [2] => 3  
        )  

    [14] =>   
    [15] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>   

            [2] => 3  
        )  

    [16] => Array  
        (  
            [0] => T_ECHO  
            [1] => echo  
            [2] => 4  
        )  

    [17] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 4  
        )  

    [18] =>   
    [19] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>   

            [2] => 4  
        )  

    [20] => Array  
        (  
            [0] => T_CLOSE_TAG  
            [1] => ?>  
            [2] => 5  
        )  

)  

我们写的规规矩矩,生成不一致的token传递给parse。

  • C code Section

大家写的(f卡塔尔lex语法规则,譬喻大家叫他Parse.y

main函数,yyerror函数等的定义

析这几个再次来到结果大家得以窥见,源码中的字符串,字符,空格

会透过
yacc/bison编写翻译成二个parse.tab.h,parse.tab.c的公文,parse依据分化的token实行不一致的操作

lex和yacc能帮大家做什么样?

都会原样再次来到。

诸如大家PHP代码是 “echo 1″;

一句话:解释实行自定义语言。有几点要小心:

每一种源代码中的字符,都会并发在相应的各样处。

围观在那之中有一个平整:

  1. 自定义语言的要做的政工必需能够能通过C语言来兑现。其实任何计算机能做的专门的学业都得以用C语言来贯彻,lex和yacc存在的意思在于简化语言,让使用者能够以意气风发种用比较容易的语言来促成复杂的操作。譬如:对于数据库的询问确定有现存的库能够来形成,但是接受起来比较麻烦,要本身写成语调用API,编写翻译才行。若是大家想实自定义叁个简单易行的言语(比方SQL)来促成操作,这时候就能够用lex和yacc。
  2. lex和yacc
    做的事情只是:用C语言来完结此外风华正茂种语言。所以,他无法贯彻C语言自个儿,不过足以达成java、python等。当然你可以因而Antlr来落到实处C语言的深入深入分析和实践,假诺你如此做的话,C语言程序首先是经过java来进行,然后java又形成了本土语言(C语言)来实行,哪个人叫我们的操作系统都以C语言完成的啊。

而其余的,举例标签,操作符,语句,都会被调换来一个包括

"echo" {

return T_ECHO;
 }

运用lex和yacc我们要做那几件事业?

扫描器函数scan会获得”echo
1″字符串,它对那意气风发段代码进行巡回,假若发掘成echo字符串,那么它就视作第一字重临token:T_ECHO,

  1. 概念各样token类型。他们在.y中定义,那一个token既会被lex使用到,也会被.y文件中的BNF使用到。
  2. 写词汇深入分析代码。那有的代码在.l文件(就是lex的输入文件)中。那块的概念情势是:正则表明式–>对应操作。借使和yacc一齐来使用以来,对应的操作平常是回来贰个token类型,那个token的类型要在yacc中提前定义好。
  3. 写BNF。那一个事物定义了语言的轨道格局。

部分的

parse.y和scanner.l会分别生成七个c文件,scanner.c和parse.tab.c,用gcc编写翻译到一齐,就成了。

关于BNF

1、Token ID

下边会实际的说一说

是一种context-free
grammars,请参考:
摘录:

分解器代号

re2c,关于它的Turkey语文书档案在

<symbol> ::= __expression__

(也正是在Zend内部的改Token的对应码,举例,T_ECHO,T_STRING)

还么有收尾,稍后小编会放上来。

  1. <symbol> is a
    nonterminal
  2. __expression__)
    consists of one or more sequences of symbols
  3. more sequences are separated by the vertical
    bar, ‘|’
  4. Symbols that never appear on a left side are
    terminals. On
    the other hand
  5. symbols that appear on a left side are
    non-terminals
    and are always enclosed between the pair <>.

2、源码中的原本的内容

re2c提供了部分宏接口,方面大家运用,作者归纳做了翻译,意大利语水平不佳,或者有误,要求原来的书文的能够去地点十二分地点查看。

在yacc中定义的措施实际是:

3、该词在源码中是第几行

接口代码:

<symbol> : __expression__ {operation}

Parsing, 将Tokens转变来简单而有意义的表明式

不像任何的扫描器程序,re2c
不会扭转完整的扫描器:顾客必得提供部分接口代码。顾客必需定义上面包车型地铁宏或许是其余相应的布署。

| __expression__ {operation}

接下去,正是Parsing阶段了,Parsing首先会扬弃Tokens Array中的多于的空格,

YYCONDTYPE
用-c
情势你能够采取-to参数用来生成一个文件:使用带有枚举类型的充任规范。每一个值都会在法则集结里面作为基准来利用。
YYCTYPE
用来保持三个输入符号。日常是 char 可能unsigned char。
YYCTXMARKER
*YYCTYPE类型的表明式,生成的代码回溯音讯的上下文少禽保存在
YYCTXMA中华VKERAV4。要是扫描器法则须求运用上下文中的三个或八个正则表达式则顾客须求定义这几个宏。
YYCURSOR
*YYCTYPE类型的表明式指针指向当前输入的标识,生成的代码作为标识相匹配,在开班的位置,YYCU中华VSO君越假定指向当前token的第叁个字符。停止时,YYCU兰德瑞虎SOGL450将会针对下多少个token的首先个字符。
YYDEBUG(state,current)
其一头有钦定-d标示符的时候才会供给。调用客商定义的函数时能够特别轻巧的调整生成的代码。
本条函数应该有以下签字:void YYDEBUG(int state,char
current卡塔尔(قطر‎。第八个参数接受 state
,暗许值为-1次之个参数选拔输入的眼无业位。
YYFILL(n)
当缓冲器要求填写的时候,生成的代码将会调用YYFILL(nState of Qatar:最少提供n个字符。YYFILL(n卡塔尔(قطر‎将会基于供给调节YYCU奥迪Q3SOPRADO,YYLIMIT,YYMAQashqaiKEGL450和
YYCTXMAPAJEROKEHaval。注意在天下无敌的程序语言个中,n等于最长的关键词的尺寸加生龙活虎。客户能够在/*!max:re2c*/一遍定义YYMAXFILL来钦赐最长长度。倘诺运用了-1,YYMAXFILL将会在/*!re2c*/之后调用一回堵塞。
YYGETCONDITION()
假若运用了-c形式,这些概念将会在扫描器代码以前拿到条件集。这么些值,必得伊始化为枚举YYCONDTYPE的体系。
YYGETSTATE()
风流倜傥经-f方式钦定了,客商就要求定义那个宏。假诺如此,扫描器在重三时为了获取保存的境况,生成的代码将会调用YYGETSTATE(卡塔尔(قطر‎,YYGETSTATE(卡塔尔(قطر‎必需再次来到两个带符号的莫西干发型,这一个值若是是-1,告诉扫描器那是首先次施行,不然那些值等于在此以前YYSETSTATE(s卡塔尔国保存的场合。不然,扫描器将会还原操作之后即刻调用YYFILL(n卡塔尔(قطر‎。
YYLIMIT
表明式的类别 *YYCTYPE
标识缓冲器的结尾(YYLIMIT(-1卡塔尔国是缓冲区的结尾一个字符)。生成的代码将会处处的相比YYCOSportageSUTucson和 YYLIMIT 以决定 什么日期填充缓冲区。
YYSETCONDITION(c)
那个宏用来在转变法规中设置条件,它只会在内定-c格局 和
使用转变法规时有用。
YYSETSTATE(s)
客户只须要在内定-f形式时定义这么些宏,假如是那样,生成的代码将会在YYFILL(n卡塔尔(قطر‎从前调用YYSETSTATE(s卡塔尔,YYSETSTATE的参数是叁个有标识整型,被称为唯生机勃勃的标示特定的YYFILL(n卡塔尔(قطر‎实例。
YYMARKER
类型为*YYCTYPE的表达式,生成的代码保存回溯音讯到YYMAEscortKE酷威。一些总结的扫描器大概用不到。

operation 是
满意语法时要施行的C语言代码,这里的C语言代码能够动用部分变量,他们是:$$
$1
$2等等。$$代表规约的结果,正是表明式__expression__的值,$1代表的是前面
__expression__ 中现身的种种word。比如:

下一场将余下的Tokens转变来一个一个的简易的表明式

扫描器,顾名思义,就是对文本扫描,搜索关键代码来。

expr2:
expr3 { $$ == $1; }
| expr2 PLUS expr3 { $$ = plus($1, $3); }
| expr2 MINUS expr3 { $$ = minus($1, $3); }
;

1.echo a constant string  
2.add two numbers together  
3.store the result of the prior expression to a variable  
4.echo a variable  

扫描器文件结构:

来自:

Bison是后生可畏种通用指标的剖析器生成器。它将LALMurano(1State of Qatar上下文非亲非故文法的描述转产生剖判该文法的C程序。
使用它能够生成解释器,编写翻译器,合同贯彻等多种顺序。
Bison向上宽容Yacc,全数书写准确的Yacc语法都应该能够不加改进地在Bison下办事。
它不止与Yacc宽容还会有着大多Yacc不辜负有的性状。

/* #include 文件*/
/*宏定义*/
//扫描函数
int scan(char *p){
/*扫描器规则区*/
}
//执行scan扫描函数,返回token到yacc/bison中。
int yylex(){
        int token;
        char *p=YYCURSOR;//YYCURSOR是一个指针,指向我们的PHP文本内容
        while(token=scan(p)){//这里会移动指针p,一个一个判断是不是我们上面定义好的scanner...
                return token;
        }
}
int main(int argc,char**argv){
        BEGIN(INITIAL);//
        YYCURSOR=argv[1];//YYCURSOR是一个指针,指向我们的PHP文本内容,
        yyparse();
}
  1. expr2 expr3都以BNF中定义的non-terminal
  2. PLUS和MINUS都以.y中定义的token类
  3. plus和minus 是先行定义好的C语言函数

Bison深入分析器文件是概念了名称为yyparse而且达成了有些语法的函数的C代码。
这几个函数并不是三个方可做到全部的语法深入分析义务的C程序。
除此那外大家还必得提供额外的某些函数:
如词法分析器、深入分析器报告错误时调用的错误报告函数等等。
大家精晓八个安然无事的C程序必须以名称为main的函数开始,如若大家要生成一个可实施文件,而且要运营语法解析器,
那么大家就供给有main函数,况兼在有个别地点一向或直接调用yyparse,否则语法解析器恒久都不会运营。

BEGIN 是概念的宏

关于yacc中BNF的演绎进度引用后边的《lex和yacc简明教程》做一下表达:

在PHP源码中,词法深入分析器的最后是调用re2c准绳定义的lex_scan函数,而提须求Bison的函数则为zendlex。
而yyparse被zendparse取代。

#define YYCTYPE char   //输入符号的连串
#define STATE(name)     yyc##name
#define BEGIN(n)        YYSETCONDITION(STATE(n))
#define LANG_SCNG(v)    (sc_globals.v)
#define SCNG    LANG_SCNG
#define YYGETCONDITION()        SCNG(yy_state)
#define YYSETCONDITION(s)       SCNG(yy_state)=s

  1. yacc
    在里头维护着八个旅社;多个剖判栈和叁个内容栈。深入分析栈中保存着终结符和非终结符,并且表示当前深入分析状态。内容栈是二个YYSTYPE
    成分的数组,对于深入分析栈中的每贰个成分都保留着一个应和的值。譬喻,当yylex
    再次回到三个INTEGECR-V标志时,y acc
    把这些标识移入分析栈。同一时间,相应的yylval
    值将会被移入内容栈中。解析栈和内容栈的开始和结果总是同步的,因而从栈中找到相应于三个标志的值是超级轻松达成的。
  2. 对expr: expr ‘+’ expr { $$ = $1 + $3;
    }来讲,在言之有序栈中大家实际上用左式代替了右式。在本例中,大家弹出“expr
    ‘+’ expr” 然后压入“expr”。
    大家经过弹出三个分子,压入贰个成员收缩的饭馆。在大家的C
    代码中得以用经过相对地址访谈内容栈中的值,“
    $1”代表右式中的第一个成员,“ $2”代表第二个,后面包车型地铁就那样类推。“ $$
    ”表示减弱后的仓库的最上端。在上头的动作中,把对应多个表达式的值相加,弹出内容栈中的多少个成员,然后把造获得的和压入饭馆中。那样,深入分析栈和内容栈中的剧情照旧是联合签名的。
  1. Compilation, 将表达式编写翻译成Opocdes

yyparse函数是在yacc 中定义的,

来看二个用lex和yacc达成计算器的例子

然后便是Compilation阶段了,它会把Tokens编写翻译成叁个个op_array,
每个op_arrayd饱含如下5个部分
在PHP达成内部,opcode由如下的布局体表如下:

里头有八个关键宏: YYLEX

参谋了上面链接的lex和yacc文件:

struct _zend_op {  
opcode_handler_t handler; // 执行该opcode时调用的处理函数  
znode result;  
znode op1;  
znode op2;  
ulong extended_value;  
uint lineno;  
zend_uchar opcode; // opcode代码  
};  

#define YYLEX yylex()

cal.y

和CPU的命令相通,有八个标识指令的opcode字段,甚至那些opcode所操作的操作数。
PHP不像汇编那么底层,
在剧本实际实行的时候只怕还亟需任何越多的新闻,extended_value字段就保存了那类音讯。当中的result域则是保留该指令施行到位后的结果。

它会进行scaner扫描器的yylex

%{
#include <stdio.h>
#include "lex.yy.c"
#define YYSTYPE int 
int yyparse(void);
%}
%token INTEGER PLUS MINUS TIMES DIVIDE LP RP
%%
command : exp {printf("%d/n",$1);}
exp: exp PLUS term {$$ = $1 + $3;}
|exp MINUS term {$$ = $1 - $3;}
|term {$$ = $1;}
;
term : term TIMES factor {$$ = $1 * $3;}
|term DIVIDE factor {$$ = $1/$3;}
|factor {$$ = $1;}
;
factor : INTEGER {$$ = $1;}
| LP exp RP {$$ = $2;}
;
%%
int main()
{
return yyparse();
}
void yyerror(char* s)
{
fprintf(stderr,"%s",s);
}
int yywrap()
{
return 1; 
} 

PHP脚本编写翻译为opcode保存在op_array中,当中间存款和储蓄的布局如下:

莫不会有一点绕,重新缕后生可畏缕:

cal.l

struct _zend_op_array {  
    /* Common elements */  
    zend_uchar type;  
    char *function_name; // 如果是用户定义的函数则,这里将保存函数的名字  
    zend_class_entry *scope;  
    zend_uint fn_flags;  
    union _zend_function *prototype;  
    zend_uint num_args;  
    zend_uint required_num_args;  
    zend_arg_info *arg_info;  
    zend_bool pass_rest_by_reference;  
    unsigned char return_reference;  
    /* END of common elements */  
    zend_bool done_pass_two;  
    zend_uint *refcount;  
    zend_op *opcodes; // opcode数组  
    zend_uint last,size;  
    zend_compiled_variable *vars;  
    int last_var,size_var;  
    // ...  
}  

在scanner.l中,通过调用parse.y深入剖析器函数yyparse,该函数调用scanner.l的yylex生成重要代码token,yylex

%{ 
#include<string.h>
#include "y.tab.h" 
extern int yylval; 
%} 
numbers ([0-9])+ 
plus "+" 
minus "-" 
times "*" 
divide "/" 
lp "(" 
rp ")" 
delim [ /n/t] 
ws {delim}* 
%% 
{numbers} {sscanf(yytext, "%d", &yylval); return INTEGER;} 
{plus} {return PLUS;} 
{minus} {return MINUS;} 
{times} {return TIMES;} 
{divide} {return DIVIDE;} 
{lp} {return LP;} 
{rp} {return RP;} 
{ws} ; 
. {printf("Error");exit(1);} 
%% 

如下面的注释,opcodes保存在此边,在实施的时候由上边包车型客车execute函数推行:

将扫描器再次回到的

应用办法:

ZEND_API void execute(zend_op_array *op_array TSRMLS_DC)  
{  
    // ... 循环执行op_array中的opcode或者执行其他op_array中的opcode  
}  

token再次来到给parse.y,parse遵照分裂的token实行分化的代码.

yacc -d cal.y

前面提到每条opcode都有叁个opcode_handler_t的函数指针字段,用于执行该opcode。

举例:

lex cal.l

PHP有二种办法来展开opcode的管理:CALL,SWITCH和GOTO。

scanner.l
#include “scanner.h”
#include “parse.tab.h”
int scan(char *p){
/*!re2c
<INITIAL>”<?php”([ t]|{NEWLINE})? {
BEGIN(ST_IN_SCRIPTING);
return T_OPEN_TAG;
}
“echo” {

return T_ECHO;
}
[0-9]+ {
return T_LNUMBER;
}
*/
}
int yylex(){
int c;

//       return T_STRING;
int token;
char *p=YYCURSOR;
while(token=scan(p)){
return token;
}
}

int main (int argc,char ** argv){
BEGIN(INITIAL);//初始化
YYCURSOR=argv[1];//将客户输入的字符串放到YYCU揽胜SOCR-V
yyparse();//yyparse() -》yylex()-》yyparse()
return 0;
}

g++ -o cal y.tab.c

PHP暗许使用CALL的章程,也正是函数调用的章程,
由于opcode实施是每种PHP程序往往需求开展的操作,

那样二个粗略的扫描器就做成了,

运转./cal 然后输入3+4 ctrl+D就能够观望结果了

还不错SWITCH只怕GOTO的章程来散发, 常常GOTO的频率相对会高级中学一年级些,

这解析器呢?

有关lex和yacc中某个预订义的东西

唯独作用是不是进步依赖于区别的CPU。

深入分析器作者用的是flex和bison。。。

Lex 变量

  1. Execution,Zend引擎顺次实践Opcodes
    末尾一步,也正是Execution,Zend引擎
    顺次执行Opcodes,每便一条,进而实现PHP脚本的效应,和机器指令运转相仿。

关于flex的文本结构:

yyin
FILE* 类型。 它指向 lexer 正在剖判的当下文件。

好了,到那边整个PHP代码的推行进度算是写完了,水平有限写的不佳还望海涵,不不荒谬期望我们提议。

%{
/*
C代码段将逐字拷贝到lex编写翻译后发生的C源文件中
能够定义一些全局变量,数组,函数例程等…
*/
#include
#include “scanner.h”
extern int yylex(卡塔尔国;//它在scanner.l中定义的。。
void yyerror(char *);
# define YYPARSE_PARAM tsrm_ls
# define YYLEX_PARAM tsrm_ls
%}
{定义段,也正是token定义的地点}
//那就是关键  token程序是基于那是做switch的。
%token T_OPEN_TAG
%token T_ECHO
%token T_LNUMBER
%%
{规则段}
start:
T_OPEN_TAG{printf(“startn”); }
|start statement
;
statement:
T_ECHO expr {printf(“echo :%sn”,$3)}
;
expr:
T_LNUMBER {$$=$1;}
%%
{客商代码段}
void yyerror(char *msg){
printf(“error:%sn”,msg);
}

yyout
FILE* 类型。 它指向记录 lexer 输出的岗位。 缺省事态下,yyin 和 yyout
都指向专门的事业输入和出口。

参考资料甚至对他们的多谢(尽管人家不会鸟大家那几个小菜。。。):

在法规段中,start是从头的地点,倘使scan识别到PHP发轫标签就能够再次回到T_OPEN_TAG,然后施行括号的代码,输出start.

yytext
特别格局的文书存款和储蓄在这里生机勃勃变量中(char*)。

鸟哥:http://www.laruence.com/2008/06/18/221.html

在scanner.l中,调用scan的是个while循环,所以它会检讨到php代码的结尾,

yyleng
交付相配情势的尺寸。

(注:因为鸟哥的博文是08年的,本文的数目固然和鸟哥有个别相同,PHP发展到现行反革命风流倜傥度有了许多退换,
进而大家看看鄙人的博文中程序运维结果甚至有关的注解与鸟哥的分化,
请不要吃惊,鄙人的结果都以运维验证过的,PHP版本为5.4)
refer
TIPI

yyparse会依据scan再次回到的标志做switch,然后goto到对应的代码,比如yyparse.y开采脚下的token是T_OPEN_TAG,

yylineno
提供当前的行数音讯。 (lexer不一定帮衬。)

它会由此宏 #line 映射到 parse.y所对应
21行,T_OPEN_TAG的地点,然后实践

Lex 函数

画个图来讲雅培(Abbott卡塔尔下,
澳门新浦京电子游戏 3

yylex()
那生机勃勃函数早先解析。 它由 Lex 自动生成。

那,TOKEN重返给yyparse之后做了什么样吧?

yywrap()
那黄金年代函数在文件(或输入)的末梢调用。 假使函数的重回值是1,就告意气风发段落深入剖析。
因而它能够用来剖判多少个公文。 代码能够写在第三段,那就能够分析多个文件。
方法是利用 yyin
文件指针(见上表)指向分歧的文书,直到全部的文本都被解析。
最终,yywrap(State of Qatar 能够回到 1 来代表分析的截至。

为了能直观一些,作者用gdb追踪:

yyless(int n)
那风流罗曼蒂克函数能够用来送回除了前�n? 个字符外的保有读出标识。

澳门新浦京电子游戏 4

yymore()
那生龙活虎函数告诉 Lexer 将下一个标识附加到眼下标识后。

本条时候yychar是258,258是什么样?

仿照效法资料:

澳门新浦京电子游戏 5

先是推荐《lex and yacc tutorial》

上边pdf的中文版《lex和yacc简明教程》在在:

258是bison自动生成的枚举类型数据。

继续

YYTRANSLATE宏选取yychar,然后回到所对应的值

#define
YYTRANSLATE(YYX)                                               
((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] :
YYUNDEFTOK)

/* YYTRANSLATE[YYLEX] — Bison symbol number corresponding to
YYLEX.  */
static const yytype_uint8 yytranslate[] =
{
0,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,    27,     2,
22,    23,     2,     2,    28,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,    21,
2,    26,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,    24,     2,    25,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
2,     2,     2,     2,     2,     2,     1,     2,     3,     4,
5,     6,     7,     8,     9,    10,    11,    12,    13,    14,
15,    16,    17,    18,    19,    20
};

二个鬼子写的左侧教程

yyparse获得这几个值,不断地translate,

澳门新浦京电子游戏 6

bison会转移比非常多用来映射的数组,将最终的translate保存到yyn,

那七个用 lex 和 yacc完结了 c语言解释器

那般bison就能够找到token所对应的代码

  switch (yyn)
{
case 2:

/* Line 1455 of yacc.c  */
#line 30 “parse.y”
{printf(“startn”); ;}
break;

接踵而来循环,生成token逐一施行,然后拆解解析成所对应的zend
函数等,生成对应的op保存在哈希表中,那么些不是本文的主要,

就不细说了。

到这里,全部的流水生产线就终止了。。剩下的正是细化的做事,假使不时间,笔者再持续phptoc
:)

发表评论

电子邮件地址不会被公开。 必填项已用*标注