从php核心代码分析require和include的区别_php技巧_脚本之家

在这之前,我曾经尝试过一个项目,就是将我们的PHP代码自动生成so扩展,

写在前面的几句废话 

深入理解PHP之require/include顺序
//www.jb51.net/article/25867.htm普及在php手册中:

编译到PHP中,我叫它 phptoc。

最近在项目的过程中接触了lex 和
yacc,他们可以帮助我们来实现自己的领域语言。最典型的应用就是可以帮助我们来实现自定义测试脚本的执行器。但是,这里也有一个限制,就是测试脚本要做的基本事情必须有现成的C语言库来实现,否则就做不到了;如果基本的操作是用java来做的,那么还可以用Antlr,这里不对Antlr做详细介绍。

require() is identical to include() except upon failure it will also
produce a fatal E_ERROR level error. In other words, it will halt the
script whereas include() only emits a warning which allows the script
to continue.

但是由于各种原因,暂停了此项目。

lex是什么?

就是说在失败的时候,require是会中止php运行的,而include是可以继续运行的。倒底有什么样的区别呢?我们带着这个疑问来一起进入PHP的核心代码。下面是一个PHP运行过程的图

写这篇文章一是因为这方面资料太少,二是把自己的收获总结下来,以便以后参考,如果能明白PHP语法分析

教科书上把lex的作用的作用叫做“词法分析 lexical
analysis ”,这个中文叫法非常让人看不明白(叫做“符号提取”更合适),其实从它的英文单词lexical上来看他的意思其实是非常清楚的。

补习一下:lex是代码扫描器,扫描代码用的,yacc是Yet Another Compiler
Compiler,作用是把任何一种代码的语法转成yacc语法,yacc就是解析器。lex在c下的后缀是*.l
yacc是*.y

那对PHP源码的研究会更上一层楼地 ^.^…

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

正题下面看操作记录:

我尽可能写的通俗易懂些。

指的是: 一种语言中关于词汇、单词的,与之相对的是这种语言的语法和组织

cc@cc-laptop:/opt/workspace$ svn checkout

php-src-5.3从svn取最新的php源代码。

这个项目思路源于facebook的开源项目 HipHop .

这么来看的话 lexical
analysis
的作用就应该是语言中的词汇和单词分析。事实上他的作用就是从语言中提取单词。放到编程语言中来说,他要做的事情其实就是提取编程语言占用的各种保留字、操作符等等语言的元素

开始深入:

其实我对这个项目的性能提高50%-60%持怀疑态度,从根本来讲,如果PHP用到APC缓存,它的性能是否低

所以他的另外一个名字scanner其实更形象一些,就是扫描一个文本中的单词。

cc@cc-laptop:/opt/workspace/php-src-5.3$ find . -type f -name “*.l”
-exec grep -Hn “require_once” {}
;./Zend/zend_language_scanner.l:1093:”require_once”
{寻找lex代码扫描器文件中出现require_once的地方,zend_language_scanner.l的1093行。1093
“require_once” {1094 return T_REQUIRE_ONCE;1095 }

于HipHop,我还没有做测试,不敢断言。

lex把每个扫面出来的单词叫统统叫做token,token可以有很多类。对比自然语言的话,英语中的每个单词都是token,token有很多类,比如non(名词)就是一个类token,apple就是属于这个类型的一个具体token。对于某个编程语言来说,token的个数是很有限的,不像英语这种自然语言中有几十万个单词。

然后再搜一下T_REQUIRE_ONCE,

PHPtoc,我只是想把C程序员解放出来,希望能达到,让PHPer用PHP代码就可以写出接近于PHP扩展性能的一个扩展,

lex工具会帮我们生成一个yylex函数,yacc通过调用这个函数来得知拿到的token是什么类型的,但是token的类型是在yacc中定义的。

cc@cc-laptop:/opt/workspace/php-src-5.3$ find . -type f -name “*.y”
-exec grep -Hn “T_INCLUDE” {}
;./Zend/zend_language_parser.y:52:%left T_INCLUDE
T_INCLUDE_ONCE T_EVAL T_REQUIRE
T_REQUIRE_ONCE./Zend/zend_language_parser.y:985: | T_INCLUDE expr
{ zend_do_include_or_eval(ZEND_INCLUDE, &$$, &$2 TSRMLS_CC);
}./Zend/zend_language_parser.y:986: | T_INCLUDE_ONCE expr {
zend_do_include_or_eval(ZEND_INCLUDE_ONCE, &$$, &$2 TSRMLS_CC);
}

它的流程如下,读取PHP文件,解析PHP代码,对其进行语法分析器,生成对应的ZendAPI,编译成扩展。

lex的输入文件一般会被命名成 .l文件,通过lex XX.l
我们得到输出的文件是lex.yy.c

在985行附近,有这样一群代码:

澳门新浦京电子游戏 1

yacc是什么呢?

internal_functions_in_yacc:T_ISSET ‘’ { $$ = $3; }| T_EMPTY ‘’ {
zend_do_isset_or_isempty(ZEND_ISEMPTY, &$$, &$3 TSRMLS_CC); }|
T_INCLUDE expr { zend_do_include_or_eval(ZEND_INCLUDE, &$$, &$2
TSRMLS_CC); }| T_INCLUDE_ONCE expr {
zend_do_include_or_eval(ZEND_INCLUDE_ONCE, &$$, &$2 TSRMLS_CC);
}| T_EVAL ‘’ { zend_do_include_or_eval(ZEND_EVAL, &$$, &$3
TSRMLS_CC); }| T_REQUIRE expr {
zend_do_include_or_澳门新浦京电子游戏,eval(ZEND_REQUIRE, &$$, &$2 TSRMLS_CC); }|
T_REQUIRE_ONCE expr {
zend_do_include_or_eval(ZEND_REQUIRE_ONCE, &$$, &$2 TSRMLS_CC);
};

进入正题

刚才说完lex了,那么yacc呢,教科书上把yacc做的工作叫做syntactic
analysis。
这次我们翻译没有直译做句法分析,而是叫语法分析,这个翻译能好一点,意思也基本上比较清楚。
其实我们最开始学习英语的时候老师都会告诉我们英语其实就是“单词+语法”,这个观点放到编程语言中很合适,lex提取了单词,那么是剩下的部分就是如何表达语法。那么yacc做的事情就是这一部分(实际应该说是BNF来做的)。

于是乎,我们需要继续深入寻找zend_do_include_or_eval,

这里最难的就是语法分析器了,大家应该都知道,PHP也有自己的语法分析器,现在版本用到的是re2c
和 Bison。

yacc会帮我们生成一个yyparse函数,这个函数会不断调用上面的yylex函数来得到token的类型。

cc@cc-laptop:/opt/workspace/php-src-5.3$ find . -type f -name “*.c”
-exec grep -Hn “zend_do_include_or_eval” {}
;./Zend/zend_compile.c:4317:void zend_do_include_or_eval(int
type, znode *result, const znode *op1 TSRMLS_DC) /* {{{ */

所以,我自然也用到了这个组合。

yacc的输入文件一般会被命名成 .y文件,通过yacc -d
XX.y我们得到的输出文件是y.tab.h
y.tab.c,前者包含了lex需要的token类型定义,需要被include进 .l文件中 

zend_do_include_or_eval中组装了一个结构体,ZEND_INCLUDE_OR_EVAL。

如果要用PHP的语法分析器就不太现实了,因为需要修改zend_language_parser.y和
zend_language_scanner.l 并重新编译,这难度大不说,还可能影响PHP自身。

 

再在zend_vm_def.h中找到ZEND_VM_HANDLER(73, ZEND_INCLUDE_OR_EVAL,
CONST|TMP|VAR|CV, ANY):switch (Z_LVAL(opline->op2.u.constant))
{代码略}

所以决定重新写一套自己的语法分析规则,这个功能就等于是重写了PHP的语法分析器,当然会舍弃一些不常用的。

lex和yacc的输入文件格式

中间关键的一句是:new_op_array =
compile_filename(Z_LVAL(opline->op2.u.constant), inc_filename
TSRMLS_CC);

re2c &&
yacc/bison,通过引用自己的对应文件,然后将他们统一编译成一个*.c文件,最后再gcc编译就会生

 

在zend_complie.h文件中:ZEND_API zend_op_array
*compile_filename(int type, zval *filename TSRMLS_DC);

成我们自己的程序。所以说,他们从根本来讲不是语法分析程序,他们只是将我们的规则生成一个独立的c文

Definition section
%%
Rules section

这个函数定义在zend_language_scaner.l文件中,找出最核心的代码:

件,这个c文件才是真正的我们需要的语法分析程序,我更愿意叫它
语法生成器。如下图:

%%
C code section

if (open_file_for_scanning(file_handle TSRMLS_CC)==FAILURE) {//
require与include的差别:错误信息的显示级别if {
//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 {代码略}

澳门新浦京电子游戏 2

 

继续追踪zend_message_dispatcher可以在main/main.c文件中找到php_message_handler_for_zend函数:

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

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

//include输出错误信息时的级别为:E_WARNINGcase
ZMSG_FAILED_INCLUDE_FOPEN:php_error_docref(“function.include”
TSRMLS_CC, E_WARNING, “Failed opening ‘%s’ for inclusion ”,
php_strip_url_passwd,
STR_PRINT;break;//require输出错误信息时的级别为:E_COMPILE_ERROR代码略

re2c扫描器,假如我们写的扫描规则文件叫scanner.l,它会将我们写的PHP文件内容,进行扫描,然后根据

  • Definition Section 

总结和开头PHP手册所说完全一致,require和include的区别在于,出现错误时,一个是error一个是warning。

我们写的规则,生成不同的token传递给parse。

这块可以放C语言的各种各种include,define等声明语句,但是要用%{
%}括起来。 

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

如果是.y文件,可以放token的定义,如:%token INTEGER 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语言来实现。其实任何计算机能做的事情都可以用C语言来实现,lex和yacc存在的意义在于简化语言,让使用者能够以一种用比较简单的语言来实现复杂的操作。比如:对于数据库的查询肯定有现成的库可以来完成,但是使用起来比较麻烦,要自己写成语调用API,编译才行。如果我们想实自定义一个简单的语言(比如SQL)来实现操作,这个时候就可以用lex和yacc。
  2. lex和yacc
    做的事情只是:用C语言来实现另外一种语言。所以,他没办法实现C语言自己,但是可以实现java、python等。当然你可以通过Antlr来实现C语言的解析和执行,如果你这么做的话,C语言程序首先是通过java来执行,然后java又变成了本地语言(C语言)来执行,谁叫我们的操作系统都是C语言实现的呢。 

下面会具体的说一说

使用lex和yacc我们要做那几件事情? 

re2c,关于它的英文文档在

  1. 定义各种token类型。他们在.y中定义,这些token既会被lex使用到,也会被.y文件中的BNF使用到。
  2. 写词汇分析代码。这部分代码在.l文件(就是lex的输入文件)中。这块的定义方式是:正则表达式–>对应操作。如果和yacc一起来使用的话,对应的操作通常是返回一个token类型,这个token的类型要在yacc中提前定义好。
  3. 写BNF。这些东西定义了语言的规约方式。

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

关于BNF

re2c提供了一些宏接口,方面我们使用,我简单做了翻译,英语水平不好,可能有误,需要原文的可以去上面那个地址查看。

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

接口代码:

 <symbol>
::=
__expression__ 

不像其他的扫描器程序,re2c
不会生成完整的扫描器:用户必须提供一些接口代码。用户必须定义下面的宏或者是其他相应的配置。

 

YYCONDTYPE
用-c
模式你可以使用-to参数用来生成一个文件:使用包含枚举类型的作为条件。每个值都会在规则集合里面作为条件来使用。
YYCTYPE
用来维持一个输入符号。通常是 char 或者unsigned char。
YYCTXMARKER
*YYCTYPE类型的表达式,生成的代码回溯信息的上下文会保存在
YYCTXMARKER。如果扫描器规则需要使用上下文中的一个或多个正则表达式则用户需要定义这个宏。
YYCURSOR
*YYCTYPE类型的表达式指针指向当前输入的符号,生成的代码作为符号相匹配,在开始的地方,YYCURSOR假定指向当前token的第一个字符。结束时,YYCURSOR将会指向下一个token的第一个字符。
YYDEBUG(state,current)
这个只有指定-d标示符的时候才会需要。调用用户定义的函数时可以非常容易的调试生成的代码。
这个函数应该有以下签名:void YYDEBUG(int state,char
current)。第一个参数接受 state
,默认值为-1第二个参数接受输入的当前位置。
YYFILL(n)
当缓冲器需要填充的时候,生成的代码将会调用YYFILL(n):至少提供n个字符。YYFILL(n)将会根据需要调整YYCURSOR,YYLIMIT,YYMARKER

YYCTXMARKER。注意在典型的程序语言当中,n等于最长的关键词的长度加一。用户可以在/*!max:re2c*/一次定义YYMAXFILL来指定最长长度。如果使用了-1,YYMAXFILL将会在/*!re2c*/之后调用一次阻塞。
YYGETCONDITION()
如果使用了-c模式,这个定义将会在扫描器代码之前获取条件集。这个值,必须初始化为枚举YYCONDTYPE的类型。
YYGETSTATE()
如果-f模式指定了,用户就需要定义这个宏。如果这样,扫描器在开始时为了获取保存的状态,生成的代码将会调用YYGETSTATE(),YYGETSTATE()必须返回一个带符号的整数,这个值如果是-1,告诉扫描器这是第一次执行,否则这个值等于以前YYSETSTATE(s)
保存的状态。否则,扫描器将会恢复操作之后立即调用YYFILL(n)。
YYLIMIT
表达式的类型 *YYCTYPE
标记缓冲器的结尾(YYLIMIT(-1)是缓冲区的最后一个字符)。生成的代码将会不断的比较YYCORSUR
和 YYLIMIT 以决定 什么时候填充缓冲区。
YYSETCONDITION(c)
这个宏用来在转换规则中设置条件,它只会在指定-c模式 和
使用转换规则时有用。
YYSETSTATE(s)
用户只需要在指定-f模式时定义这个宏,如果是这样,生成的代码将会在YYFILL(n)之前调用YYSETSTATE(s),YYSETSTATE的参数是一个有符号整型,被称为唯一的标示特定的YYFILL(n)实例。
YYMARKER
类型为*YYCTYPE的表达式,生成的代码保存回溯信息到YYMARKER。一些简单的扫描器可能用不到。

  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 <>.  

扫描器,顾名思义,就是对文件扫描,找出关键代码来。

 

扫描器文件结构:

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

BEGIN 是定义的宏

|  __expression__ 
{operation}

#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。举个例子:

yyparse函数是在yacc 中定义的,

 

里面有一个关键宏: YYLEX

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

#define YYLEX yylex()

来自: 

它会执行scaner扫描器的yylex

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

可能会有点绕,重新缕一缕:

关于yacc中BNF的推导过程引用后面的《lex和yacc简明教程》做一下说明:

在scanner.l中,通过调用parse.y解析器函数yyparse,该函数调用scanner.l的yylex生成关键代码token,yylex

  1. yacc
    在内部维护着两个堆栈;一个分析栈和一个内容栈。分析栈中保存着终结符和非终结符,并且代表当前剖析状态。内容栈是一个YYSTYPE
    元素的数组,对于分析栈中的每一个元素都保存着一个对应的值。例如,当yylex
    返回一个INTEGER标记时,y acc
    把这个标记移入分析栈。同时,相应的yylval
    值将会被移入内容栈中。分析栈和内容栈的内容总是同步的,因此从栈中找到对应于一个标记的值是很容易实现的。
  2. 对expr: expr ‘+’ expr { $$ = $1 + $3;
    }来说,在分析栈中我们其实用左式替代了右式。在本例中,我们弹出“expr
    ‘+’ expr” 然后压入“expr”。
    我们通过弹出三个成员,压入一个成员缩小的堆栈。在我们的C
    代码中可以用通过相对地址访问内容栈中的值,“
    $1”代表右式中的第一个成员,“ $2”代表第二个,后面的以此类推。“ $$
    ”表示缩小后的堆栈的顶部。在上面的动作中,把对应两个表达式的值相加,弹出内容栈中的三个成员,然后把造得到的和压入堆栈中。这样,分析栈和内容栈中的内容依然是同步的。

将扫描器返回的

来看一个用lex和yacc实现计算器的例子

token返回给parse.y,parse根据不同的token执行不同的代码.

参考了下面链接的lex和yacc文件: 

举例:

cal.y 

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];//将用户输入的字符串放到YYCURSOR
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);}

这样一个简单的扫描器就做成了,

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;

那解析器呢?

解析器我用的是flex和bison。。。

cal.l

关于flex的文件结构:

 %{  

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

#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.

 

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

使用方式: 

yyparse会根据scan返回的标记做switch,然后goto到相应的代码,比如
yyparse.y发现当前的token是T_OPEN_TAG,

yacc -d cal.y 

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

lex cal.l

画个图来说明一下,
澳门新浦京电子游戏 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语言解释器

 

 

发表评论

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