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(名词卡塔尔国正是贰个类token,apple正是归于这些项指标三个切实token。对于某些编制程序语言来讲,token的个数是很有限的,不像Ukraine语这种自然语言中有几十万个单词。

  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本人。

 

从PHP4.2开端提供了叁个函数叫token_get_all,这几个函数就足以将一段PHP代码
Scanning成Tokens;

所以决定重新写一套自身的语法深入分析法规,这些意义就约等于是重写了PHP的语法剖析器,当然会扬弃一些临时用的。

lex和yacc的输入文件格式

小编们用下边包车型大巴代码应用token_get_all函数管理我们最早提到的PHP代码。

re2c &&
yacc/bison,通过援用自个儿的应和文件,然后将她们联合编写翻译成三个*.c文件,最终再gcc编写翻译就能生

 

<?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文

Definition section
%%
Rules section

为了方便明白和查阅,作者利用token_name函数将深入解析器代号改过成了标识名称表达。

件,那些c文件才是真正的我们供给的语法解析程序,小编更乐于叫它
语法生成器。如下图:

%%
C code section

设若有个别童鞋想要看原本的,能够将上边代码中的第10,11行代码注释去掉。

奥门新浦京官方网站 2

 

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

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

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

获取的结果如下:

re2c扫描器,就算大家写的扫描准则文件叫scanner.l,它会将大家写的PHP文件内容,举行围观,然后依照

  • Definition Section 
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语言的各样种种include,define等表明语句,不过要用%{
%}括起来。 

如若是.l文件,可以放预订义的正则表达式:minus
“-” 还要放token的定义,方法是:代号 正则表明式。然后到了,Rules
Section就足以因而{符号} 来援用正则表达式

假定是.y文件,能够放token的定义,如:%token INTEGE奥迪Q5 PLUS
,这里的定二个的每种token都得以在y.tab.h中来看 

我们写的(f卡塔尔lex语法法规,譬如大家叫她Parse.y

  •  Rules section

析那么些重临结果大家得以窥见,源码中的字符串,字符,空格

会通过
yacc/bison编写翻译成二个parse.tab.h,parse.tab.c的文书,parse依据分化的token进行不一样的操作

.l文件在那处放置的rules正是各样正则表明式要相应的动作,日常是回去二个token

.y文件在这里间放置的rules正是知足四个语法描述时要实行的动作

任由是.l文件依旧.y文件这里的动作都以用{}扩起来的,用C语言来说述,那么些代码能够做你任何想要做的政工 

都会原样重回。

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

  •  C code Section

各类源代码中的字符,都会出现在对应的顺序处。

围观此中有三个平整:

main函数,yyerror函数等的概念   

而别的的,比如标签,操作符,语句,都会被调换到多个暗含

"echo" {

return T_ECHO;
 }

lex和yacc能帮我们做什么?

扫描器函数scan会获得”echo
1″字符串,它对这一段代码进行巡回,要是开掘成echo字符串,那么它就视作关键字再次来到token:T_ECHO,

一句话:解释进行自定义语言。有几点要留意: 

部分的

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

  1. 自定义语言的要做的事体必需能够能由此C语言来贯彻。其实任何Computer能做的职业都能够用C语言来完结,lex和yacc存在的意思在于简化语言,让使用者能够以一种用比较简单的言语来落实复杂的操作。举例:对于数据库的查询明确有现存的库能够来产生,但是选用起来相比麻烦,要团结写成语调用API,编写翻译才行。如果我们想实自定义四个简短的语言(譬喻SQL)来落实操作,那时候就足以用lex和yacc。
  2. lex和yacc
    做的事务只是:用C语言来兑现其余一种语言。所以,他不能够贯彻C语言自身,但是足以兑现java、python等。当然你可以经过Antlr来达成C语言的分析和实行,固然你这么做的话,C语言程序首先是因此java来试行,然后java又成为了地方语言(C语言)来试行,什么人叫大家的操作系统都以C语言达成的吧。 

1、Token ID

上面会具体的说一说

选拔lex和yacc大家要做那几件事情? 

讲授器代号

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

  1. 概念各类token类型。他们在.y中定义,那一个token既会被lex使用到,也会被.y文件中的BNF使用到。
  2. 写词汇深入分析代码。那有的代码在.l文件(正是lex的输入文件)中。那块的概念方式是:正则表达式–>对应操作。假使和yacc一同来利用以来,对应的操作平日是回去多个token类型,那几个token的品类要在yacc中提前定义好。
  3. 写BNF。那个事物定义了言语的三纲五常情势。

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

还么有结束,稍后笔者会放上来。

关于BNF

2、源码中的原本的内容

re2c提供了部分宏接口,方面大家应用,作者归纳做了翻译,俄语水平不佳,大概有误,需求原著的能够去地方十一分地方查看。

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

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

接口代码:

 <symbol>
::=
__expression__ 

Parsing, 将Tokens调换来轻巧而有意义的表明式

不像别的的扫描器程序,re2c
不会转移完整的扫描器:顾客必需提供一些接口代码。客商必得定义上面包车型客车宏恐怕是此外相应的布局。

 

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

YYCONDTYPE
用-c
情势你能够应用-to参数用来生成三个文书:使用带有枚举类型的作为条件。种种值都会在准绳会集里面作为标准来利用。
YYCTYPE
用来保障多个输入符号。经常是 char 只怕unsigned char。
YYCTXMARKER
*奥门新浦京官方网站 ,YYCTYPE类型的表明式,生成的代码回溯信息的光景文仲保存在
YYCTXMAPRADOKE奥德赛。若是扫描器准则供给运用上下文中的叁个或几个正则表明式则客商须要定义这么些宏。
YYCURSOR
*YYCTYPE类型的表明式指针指向当前输入的符号,生成的代码作为标识相相配,在开端的地点,YYCU冠道SO大切诺基假定指向当前token的首先个字符。甘休时,YYCU瑞鹰SOTiggo将会指向下一个token的率先个字符。
YYDEBUG(state,current)
本条独有钦定-d标示符的时候才会须要。调用客户定义的函数时得以特别轻巧的调整生成的代码。
其一函数应该有以下具名:void YYDEBUG(int state,char
current卡塔尔(قطر‎。第叁个参数选取 state
,暗许值为-1次之个参数接纳输入的日前任务。
YYFILL(n)
当缓冲器需求填写的时候,生成的代码将会调用YYFILL(nState of Qatar:最少提供n个字符。YYFILL(n卡塔尔(قطر‎将会基于必要调节YYCUKugaSOXC60,YYLIMIT,YYMA奔驰M级KE奥迪Q5和
YYCTXMAEvoqueKEEscort。注意在天下无敌的程序语言在那之中,n等于最长的主要性词的尺寸加一。客户能够在/*!max:re2c*/壹遍定义YYMAXFILL来钦点最长长度。如若运用了-1,YYMAXFILL将会在/*!re2c*/之后调用一遍拥塞。
YYGETCONDITION()
假定运用了-c格局,这几个概念将会在扫描器代码在此以前获得条件集。这一个值,必得初阶化为枚举YYCONDTYPE的门类。
YYGETSTATE()
若果-f情势钦点了,客商就须要定义那几个宏。假使这么,扫描器在起来时为了获取保存的事态,生成的代码将会调用YYGETSTATE(State of Qatar,YYGETSTATE(卡塔尔(قطر‎必得重临多个带符号的整数,那么些值如若是-1,告诉扫描器那是首先次实施,不然这一个值等于此前YYSETSTATE(sState of Qatar保存的场合。不然,扫描器将会卷土而来操作之后立即调用YYFILL(n卡塔尔国。
YYLIMIT
表达式的类型 *YYCTYPE
标识缓冲器的末段(YYLIMIT(-1卡塔尔国是缓冲区的末尾一个字符)。生成的代码将会四处的可比YYCO奇骏SU中华V和 YYLIMIT 以决定 几时填充缓冲区。
YYSETCONDITION(c)
其一宏用来在调换法规中装置规范,它只会在钦点-c格局 和
使用调换准绳时有用。
YYSETSTATE(s)
客商只要求在钦定-f格局时定义那几个宏,借使是这么,生成的代码将会在YYFILL(nState of Qatar此前调用YYSETSTATE(s卡塔尔(قطر‎,YYSETSTATE的参数是一个有号子整型,被称得上独一的标示特定的YYFILL(nState of Qatar实例。
YYMARKER
类型为*YYCTYPE的表达式,生成的代码保存回溯音讯到YYMA奥德赛KE奥迪Q3。一些差非常的少的扫描器大概用不到。

  1. <symbol>
    is
    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 <>.  

接下来将多余的Tokens转换成三个一个的简约的表明式

扫描器,看名就能够猜到其意义,便是对文件扫描,找寻重大代码来。

 

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  

扫描器文件构造:

在yacc中定义的点子实际上是:

Bison是一种通用目标的解析器生成器。它将LALGL450(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();
}

 <symbol> : __expression__ 
{operation}

Bison深入分析器文件是概念了名称叫yyparse並且达成了有些语法的函数的C代码。
那些函数并非贰个能够达成具有的语法解析职务的C程序。
除此那外大家还必需提供额外的有的函数:
如词法解析器、解析器报告错误时调用的错误报告函数等等。
咱们精通二个整机的C程序必须以名称为main的函数开头,假使大家要生成三个可实行文件,并且要运营语法深入分析器,
那么大家就要求有main函数,何况在有些地点直接或直接调用yyparse,否则语法剖判器长久都不会运作。

BEGIN 是概念的宏

|  __expression__ 
{operation}

在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

operation 是
满意语法时要推行的C语言代码,这里的C语言代码能够接收部分变量,他们是:$$
$1
$2等等。$$代表规约的结果,就是表明式__expression__的值,$1代表的是前面 __expression__
中现身的顺序word。举个例子:

  1. Compilation, 将表达式编写翻译成Opocdes

yyparse函数是在yacc 中定义的,

 

后来正是Compilation阶段了,它会把Tokens编译成两个个op_array,
每个op_arrayd包蕴如下5个部分
在PHP达成内部,opcode由如下的布局体表如下:

此中有三个关键宏: YYLEX

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

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()

来自: 

和CPU的指令相通,有一个标识指令的opcode字段,甚至这一个opcode所操作的操作数。
PHP不像汇编那么底层,
在本子实际试行的时候或者还亟需别的越多的新闻,extended_value字段就保存了那类新闻。当中的result域则是保存该指令实行到位后的结果。

它会施行scaner扫描器的yylex

  1. expr2 expr3都以BNF中定义的non-terminal
  2. PLUS和MINUS都以.y中定义的token类
  3. plus和minus 是先行定义好的C语言函数

PHP脚本编译为opcode保存在op_array中,其内部存款和储蓄的结构如下:

想必会有一点点绕,重新缕一缕:

关于yacc中BNF的推理进度援用前面包车型大巴《lex和yacc简明教程》做一下认证:

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

  1. yacc
    在里边维护着七个商旅;多个解析栈和叁个内容栈。剖判栈中保存着终结符和非终结符,况且表示当前剖析状态。内容栈是叁个YYSTYPE
    成分的数组,对于剖判栈中的每三个因素都保存着叁个对应的值。比如,当yylex
    再次来到一个INTEGE哈弗标志时,y acc
    把这些符号移入深入分析栈。同一时间,相应的yylval
    值将会被移入内容栈中。深入分析栈和内容栈的剧情总是同步的,因而从栈中找到相应于贰个标识的值是超轻便实现的。
  2. 对expr: expr ‘+’ expr { $$ = $1 + $3;
    }来讲,在拆解剖判栈中大家实际上用左式代替了右式。在本例中,大家弹出“expr
    ‘+’ expr” 然后压入“expr”。
    大家经过弹出多少个成员,压入贰个成员减少的仓库。在大家的C
    代码中得以用经过相对地址访谈内容栈中的值,“
    $1”代表右式中的第三个分子,“ $2”代表第一个,后边的就那样推算。“ $$
    ”表示降低后的库房的顶上部分。在上头的动作中,把对应三个表达式的值相加,弹出内容栈中的多个分子,然后把造得到的和压入旅社中。那样,解析栈和剧情栈中的源委依然是同台的。

如上边的疏解,opcodes保存在这处,在实行的时候由上边包车型大巴execute函数履行:

将扫描器重回的

来看叁个用lex和yacc完成总括器的例证

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

token再次来到给parse.y,parse依照不相同的token奉行不一的代码.

参谋了下边链接的lex和yacc文件: 

眼前提到每条opcode皆有三个opcode_handler_t的函数指针字段,用于实践该opcode。

举例:

cal.y 

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途锐SOMurano
yyparse();//yyparse() -》yylex()-》yyparse()
return 0;
}

%{
#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);}

PHP暗中同意使用CALL的章程,也正是函数调用的措施,
由于opcode实行是种种PHP程序往往须求开展的操作,

诸有此类贰个简便的扫描器就做成了,

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;

能够接收SWITCH大概GOTO的点子来散发, 常常GOTO的频率相对会高级中学一年级些,

那拆解深入分析器呢?

不过效能是或不是提升信任于分裂的CPU。

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

cal.l

  1. Execution,Zend引擎顺次实施Opcodes
    聊起底一步,也正是Execution,Zend引擎
    顺次实践Opcodes,每一次一条,进而实现PHP脚本的效能,和机器指令运维相符。

至于flex的公文构造:

 %{  

好了,到此地全数PHP代码的施行进度算是写完了,水平有限写的不佳还望海涵,有标题期望我们提议。

%{
/*
C代码段将逐字拷贝到lex编写翻译后产生的C源文件中
能够定义一些全局变量,数组,函数例程等…
*/
#include
#include “scanner.h”
extern int yylex(State of Qatar;//它在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);
}

#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);}    
%% 

参照他事他说加以考查资料以至对她们的蒙恩被德(尽管人家不会鸟大家那个小菜。。。):

在准则段中,start是早前的地点,如若scan识别到PHP最早标签就能再次来到T_OPEN_TAG,然后推行括号的代码,输出start.

 

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

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

动用办法: 

(注:因为鸟哥的博文是08年的,本文的数目就算和鸟哥有个别雷同,PHP发展到今后已经有了广大改造,
为此大家看看鄙人的博文中等射程序运维结果以致相关的阐明与鸟哥的两样,
请不要吃惊,鄙人的结果都以运行验证过的,PHP版本为5.4)
refer
TIPI

yyparse会依据scan重回的标志做switch,然后goto到对应的代码,比如yyparse.y开掘日前的token是T_OPEN_TAG,

yacc -d cal.y 

它会通过宏 #line 映射到 parse.y所对应
21行,T_OPEN_TAG的岗位,然后实施

lex cal.l

画个图来证雅培(AbbottState of Qatar下,
奥门新浦京官方网站 3

g++ -o cal y.tab.c 

那,TOKEN再次来到给yyparse之后做了哪些吗?

运行./cal 然后输入3+4 ctrl+D就可以看出结果了 

为了能直观一些,小编用gdb追踪:

 

奥门新浦京官方网站 4

 

那时候yychar是258,258是如何?

关于lex和yacc中一些预约义的东西 

奥门新浦京官方网站 5

 

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

Lex 变量

继续

yyin FILE* 类型。 它指向 lexer 正在解析的当前文件。
yyout FILE* 类型。 它指向记录 lexer 输出的位置。 缺省情况下,yyin 和 yyout 都指向标准输入和输出。
yytext 匹配模式的文本存储在这一变量中(char*)。
yyleng 给出匹配模式的长度。
yylineno 提供当前的行数信息。 (lexer不一定支持。)

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
};

 Lex 函数

yyparse取得这一个值,不断地translate,

yylex() 这一函数开始分析。 它由 Lex 自动生成。
yywrap() 这一函数在文件(或输入)的末尾调用。 如果函数的返回值是1,就停止解析。 因此它可以用来解析多个文件。 代码可以写在第三段,这就能够解析多个文件。 方法是使用 yyin 文件指针(见上表)指向不同的文件,直到所有的文件都被解析。 最后,yywrap() 可以返回 1 来表示解析的结束。
yyless(int n) 这一函数可以用来送回除了前�n? 个字符外的所有读出标记。
yymore() 这一函数告诉 Lexer 将下一个标记附加到当前标记后。

奥门新浦京官方网站 6

参考资料: 

bison会转移超多用来映射的数组,将最后的translate保存到yyn,

先是推荐《lex and yacc tutorial》
 
上边pdf的粤语版《lex和yacc简明教程》在在:  

这么bison就能够找到token所对应的代码

 

  switch (yyn)
{
case 2:

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

 

不断循环,生成token逐条试行,然后深入深入分析成所对应的zend
函数等,生成对应的op保存在哈希表中,那么些不是本文的显要,

就不细说了。

一个鬼子写的左边教程

到此处,全部的流水生产线就香消玉殒了。。剩下的就是细化的干活,假使有的时候光,作者再持续phptoc
:)

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

 

 

发表评论

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