[源码精读系列]PHP2/FI之Echo解释器的实现细节

1.前言

上一篇系列文章,我们简单的演示了一下PHP1的实现效果,因为没有复杂的东西,所以没有进行源码分析。今天,PHP2将引入解释器的概念。没错,是编译原理的东西,编译原理是一个好东西,它可以在现有语法的基础上,衍生出新的创新语言。比如:前端很火的Vue框架,它就是增加了一个编译环节,将定制化的虚拟DOM编译成了HTML的DOM,所以带来了革命性的特性。本文所精读的源码版本是PHP2.0b7,另外,需要读者对lex和yacc有所了解,这里可以阅读这些介绍。

2.main函数的结构

2.1 main函数的分类

PHP2的运行方式有两种,一种是作为Apache的CGI模块的运行方式,另一种,则是作为CLI命令行的运行方式。后者的功能比较简单,输入参数只能是需要解析的脚本文件。下面,我们主要分析后者的实现细节。

2.2 main函数的初始化工作

1995年,编码思想已经有组件化了,下面来看看main函数的部分内容:

#ifndef APACHE
int main(int argc, char **argv) {
	int fd;
	char *s;
	int no_httpd=0;
	long file_size;

#if PHPFASTCGI
	while(FCGI_Accept() >= 0) {
#endif
	s = getenv("PATH_TRANSLATED");
	if(!s) no_httpd=1;

#ifdef HAVE_SETLOCALE
	setlocale(LC_ALL,"");
#endif

	/* Init all components */
	php_init_pool();
	php_init_log();
	php_init_acc();
	php_init_yacc();
	php_init_lex();
	php_init_error();
	php_init_stack();
	php_init_symbol_tree();
	php_init_switch();
	php_init_db();
	php_init_while();
	php_init_msql();
	php_init_pg95();
	php_init_file();
	php_init_head();
	php_init_dir();
#ifdef HAVE_LIBGD
	php_init_gd();
#endif
	php_init_cond();

	if(argc>1) {
		if(!strcasecmp(argv[argc-1],"info")) {
			Info();
			exit(0);
#if ACCESS_CONTROL
		} else if(!strcasecmp(argv[argc-1],"config")) {
			s = getenv("REQUEST_METHOD");
			if(s && !strcasecmp(s,"post")) TreatData(0);  /* POST Data */
			Configuration(argc, argv);	
			exit(0);
#endif
#if TEXT_MAGIC
		} else if(!strcasecmp(argv[argc-1],"text_magic")) {
			set_text_magic(1);
#endif
		}
		if(!getenv("QUERY_STRING")) {
			{
				char *astr=NULL; 
				int ai, al=0;

				for(ai=1;ai<argc;ai++) al+=strlen(argv[ai])+1;
				astr = emalloc(0,al+14);
				strcpy(astr,"QUERY_STRING=");	
				for(ai=1;ai<argc;ai++) {
					strcat(astr,php_urlencode(argv[ai]));
					if(ai<argc-1) strcat(astr,"+");
				}
				putenv(astr);
			}
		}
	}
	s = getenv("REQUEST_METHOD");
	if(s && !strcasecmp(s,"post")) TreatData(0);  /* POST Data */
	TreatData(2); /* Cookie Data */
	TreatData(1); /* GET Data */

	if(no_httpd && argv[1]) fd=OpenFile(argv[1],1,&file_size);
	else fd=OpenFile(NULL,1,&file_size);
	if(fd==-1) return(-1);
	ParserInit(fd,file_size,no_httpd,NULL);	
	yyparse();
	Exit(1);
	php_pool_free(1);
	php_pool_free(2);
	php_pool_free(0);
#if PHPFASTCGI
	}
#endif
	return(0);
}

PHP2里面通用的组件有十几个,其中我们今天会用到比较重要的是php_init_pool(内存池)、php_init_yacc()(词法分析)、php_init_lex()(语法分析)、php_init_stack(表达式堆栈),最后通过yyparse()进行解释器的翻译工作,下面,我们通过几种类型的语法,来尝试跟踪下PHP2的解释器实现细节。

3.解释器的实现细节

3.1 表达式的解释过程

PHP2的语法标签是以<? >形式解析的,这一点,我们可以快速的在示例代码中找到,下面,我们就以<? echo 1+1; > 的表达式,来追踪下解析过程。在这之前,我们需要先介绍下几个涉及到的相关数据结构。

3.1.1 变量的数据结构

/* Variable Tree */
typedef struct VarTree {
	short type;
	int count;
	char *name;
	char *strval;
	char *iname;
	long intval;
	double douval;
	int flag;
	int scope;				/* 0=local to frame, 4=global, 8=static to frame */
	struct VarTree *left;
	struct VarTree *right;
	struct VarTree *next;
	struct VarTree *prev;
	struct VarTree *lacc;
	struct VarTree *lastnode;
	int deleted;
	int allocated;
} VarTree;

变量是由一颗类似B+的矮胖树组成,除了左子树,右子树以外,还有双向链表结构的前指针和后指针,还有其他需要保存的节点和信息。

3.1.2 表达式堆栈的数据结构

/* Expression Stack */
typedef struct Stack {
	short type;
	char *strval;
	long intval;
	double douval;
	VarTree *var;	
	struct Stack *next;
	int flag;
} Stack;

表达式堆栈是一个单链表,其中存放了变量树及表达式运行的中间结果信息。

3.1.3 命令TOKEN的数据结构

typedef struct _cmd_table_t {
	char *cmd;
	unsigned int token;
	void (*fnc)(void);
} cmd_table_t;

/* Command hash table
 * The hash is extremely simplistic and just based on the length of the
 * command.
 */
static cmd_table_t cmd_table[22][35] = {
	{ { NULL,0,NULL } },        /* 0 */

	{ { NULL,0,NULL } },        /* 1 */

	{ { "if", IF, NULL },        /* 2 */
	  { NULL,0,NULL } }, 

	{ { "max", INTFUNC1,ArrayMax }, /* 3 */
	  { "min", INTFUNC1,ArrayMin },
	  { "key", KEY,NULL },
	  { "end", END,NULL },
	  { "sin", INTFUNC1,Sin },
	  { "cos", INTFUNC1,Cos },
	  { "tan", INTFUNC1,Tan },
	  { "exp", INTFUNC1,Exp },
	  { "log", INTFUNC1,mathLog },
	  { "abs", INTFUNC1,Abs },
	  { "ord", INTFUNC1,Ord },
	  { "chr", INTFUNC1,Chr },
	  { "pow", INTFUNC2,Pow },
	  { NULL,0,NULL } }, 

	{ { "echo",PHPECHO,NULL },     /* 4 */
	  { "else",ELSE,NULL },
	  { "case",CASE,NULL },
	  { "ereg",EREG,NULL },
	  { "feof",INTFUNC1,Feof },
	  { "msql",INTFUNC2,Msql },
	  { "exit",EXIT,NULL },
	  { "eval",INTFUNC1,Eval },
	  { "exec",EXEC,NULL },
	  { "time",INTFUNC0,UnixTime },
	  { "date",DATE,NULL },
	  { "next",PHPNEXT,NULL },
	  { "prev",PREV,NULL },
	  { "sort",INTFUNC1,Sort },
	  { "rand",INTFUNC0,Rand },
	  { "sqrt",INTFUNC1,Sqrt },
	  { "file",INTFUNC1,File },
	  { "link",INTFUNC2,Link },
	  { NULL,0,NULL } }, 

	{ { "endif",ENDIF,NULL },   /* 5 */
	  { "while",WHILE,NULL },
	  { "break",BREAK,NULL },
	  { "isset",ISSET,NULL },
	  { "count",INTFUNC1,Count },
	  { "eregi",EREGI,NULL },
	  { "crypt",CRYPT,NULL },
	  { "srand",INTFUNC1,Srand },
	  { "sleep",INTFUNC1,Sleep },
	  { "fopen",INTFUNC2,Fopen },
	  { "popen",INTFUNC2,Popen },
	  { "fgets",INTFUNC2,Fgets },
	  { "fputs",INTFUNC2,Fputs },
	  { "fseek",INTFUNC2,Fseek },
	  { "ftell",INTFUNC1,Ftell },
	  { "reset",RESET,NULL },
	  { "chdir",INTFUNC1,ChDir },
	  { "chmod",INTFUNC2,ChMod },
	  { "chown",INTFUNC2,ChOwn },
	  { "chgrp",INTFUNC2,ChGrp },
	  { "mkdir",INTFUNC2,MkDir },
	  { "rmdir",INTFUNC1,RmDir },
	  { "log10",INTFUNC1,mathLog10 },
	  { "unset",UNSET,NULL },
	  { NULL,0,NULL } }, 

	{ { "elseif",ELSEIF,NULL }, /* 6 */
	  { "switch",SWITCH,NULL }, 
	  { "strlen",INTFUNC1,StrLen }, 
	  { "strval",INTFUNC1,StrVal }, 
	  { "intval",INTFUNC1,IntVal }, 
	  { "strtok",STRTOK,NULL }, 
	  { "strstr",INTFUNC2,StrStr }, 
	  { "strchr",INTFUNC2,StrStr }, 
	  { "substr",INTFUNC3,SubStr }, 
	  { "system",SYSTEM,NULL }, 
	  { "header",HEADER,NULL },
	  { "return",RETURN,NULL },
	  { "global",GLOBAL,NULL },
	  { "static",STATIC,NULL },
	  { "gmdate",GMDATE,NULL }, 
	  { "dblist",INTFUNC0,ListSupportedDBs }, 
	  { "unlink",INTFUNC1,Unlink }, 
	  { "rename",INTFUNC2,Rename }, 
	  { "putenv",INTFUNC1,PutEnv }, 
	  { "getenv",INTFUNC1,GetEnv }, 
	  { "mktime",MKTIME,NULL }, 
	  { "fclose",INTFUNC1,Fclose }, 
	  { "pclose",INTFUNC1,Pclose }, 
	  { "rewind",INTFUNC1,Rewind }, 
	  { "bindec",INTFUNC1,BinDec }, 
	  { "decbin",INTFUNC1,DecBin }, 
	  { "hexdec",INTFUNC1,HexDec }, 
	  { "dechex",INTFUNC1,DecHex }, 
	  { "octdec",INTFUNC1,OctDec }, 
	  { "decoct",INTFUNC1,DecOct }, 
	  { "usleep",INTFUNC1,USleep },
	  { "pg_tty",INTFUNC1,PGtty },
	  { "fgetss",INTFUNC2,Fgetss },
	  { NULL,0,NULL } }, 

	{ { "default", DEFAULT,NULL }, /* 7 */
	  { "imagesx", INTFUNC1,ImageSXFN },
	  { "imagesy", INTFUNC1,ImageSYFN },
	  { "include", INCLUDE,NULL },
	  { "dbmopen", INTFUNC2,dbmOpen },
	  { "strrchr", INTFUNC2,StrrChr },
	  { "sprintf", INTFUNC2,Sprintf },
	  { "opendir", INTFUNC1,OpenDir },
	  { "readdir", INTFUNC0,ReadDir },
	  { "tempnam", INTFUNC2,TempNam }, 
	  { "settype", INTFUNC2,SetType }, 
	  { "gettype", INTFUNC1,GetType }, 
	  { "ucfirst", INTFUNC1,UcFirst }, 
	  { "pg_exec", INTFUNC2,PGexec },
	  { "pg_host", INTFUNC1,PGhost },
	  { "pg_port", INTFUNC1,PGport },
	  { "phpinfo", INTFUNC0,Info },
#if APACHE
	  { "virtual", INTFUNC1,Virtual },
#endif
	  { "symlink", INTFUNC2,SymLink },
	  { NULL,0,NULL } },

	{ { "endwhile",ENDWHILE,NULL }, /* 8 */
	  { "function",FUNCTION,NULL },
	  { "dbmclose",INTFUNC1,dbmClose },
	  { "dbmfetch",INTFUNC2,dbmFetch },
	  { "gettotal",INTFUNC0,GetTotal },
	  { "gettoday",INTFUNC0,GetToday },
	  { "closedir",INTFUNC0,CloseDir },
	  { "filesize",FILESIZE,NULL },
	  { "getmyuid",INTFUNC0,GetMyUid },
	  { "getmypid",INTFUNC0,GetMyPid },
	  { "imagegif",IMAGEGIF,NULL },
	  { "imagearc",IMAGEARC,NULL },
	  { "pg_close",INTFUNC1,PGclose },
	  { "passthru",PASSTHRU,NULL },
	  { "readlink",INTFUNC1,ReadLink },
	  { "linkinfo",INTFUNC1,LinkInfo },
	  { NULL,0,NULL } },

	{ { "endswitch", ENDSWITCH,NULL }, /* 9 */
	  { "reg_match", REG_MATCH,NULL },
	  { "dbminsert", INTFUNC3,dbmInsert },
	  { "dbmexists", INTFUNC2,dbmExists },
	  { "dbmdelete", INTFUNC2,dbmDelete },
	  { "rewinddir", INTFUNC0,RewindDir },
	  { "fileperms", FILEPERMS,NULL },
	  { "fileinode", FILEINODE,NULL },
	  { "fileowner", FILEOWNER,NULL },
	  { "filegroup", FILEGROUP,NULL },
	  { "fileatime", FILEATIME,NULL },
	  { "filemtime", FILEMTIME,NULL },
	  { "filectime", FILECTIME,NULL },
	  { "getlogdir", INTFUNC0,GetLogDir },
	  { "getaccdir", INTFUNC0,GetAccDir },
	  { "imageline", INTFUNC6,ImageLine },
	  { "imagefill", INTFUNC4,ImageFill },
	  { "imagechar", IMAGECHAR,NULL },
	  { "doubleval", INTFUNC1,DoubleVal }, 
	  { "securevar", INTFUNC1,SecureVar }, 
	  { "fsockopen", INTFUNC2,FSockOpen },
	  { "microtime", INTFUNC0,MicroTime },
	  { "urlencode", INTFUNC1,UrlEncode },
	  { "urldecode", INTFUNC1,UrlDecode },
	  { "quotemeta", INTFUNC1,QuoteMeta },
	  { "pg_result", INTFUNC3,PG_result },
	  { "pg_dbname", INTFUNC1,PGdbName },
	  { "setcookie", SETCOOKIE,NULL },
	  { NULL,0,NULL } },        

	{ { "strtoupper", INTFUNC1,StrToUpper }, /* 10 */
	  { "strtolower", INTFUNC1,StrToLower },
	  { "reg_search", REG_SEARCH,NULL },
	  { "dbmreplace", INTFUNC3,dbmReplace },
	  { "dbmnextkey", INTFUNC2,dbmNextKey },
	  { "getlogfile", INTFUNC0,GetLogFile },
	  { "getlastref", INTFUNC0,GetLastRef },
	  { "getlastmod", INTFUNC0,GetLastMod },
	  { "getmyinode", INTFUNC0,GetMyInode },
	  { "getrandmax", INTFUNC0,GetRandMax },
	  { "setlogging", INTFUNC1,SetLogging },
	  { "pg_numrows", INTFUNC1,PGnumRows },
	  { "pg_options", INTFUNC1,PGoptions },
	  { "pg_connect", INTFUNC5,PGconnect },
	  { "phpversion", INTFUNC0,PHPVersion },
	  { "addslashes", INTFUNC1,_AddSlashes },
	  { NULL,0,NULL } },

	{ { "msql_result", INTFUNC3,MsqlResult }, /* 11 */
	  { "reg_replace", INTFUNC3,RegReplace },
	  { "dbmfirstkey", INTFUNC1,dbmFirstKey },
	  { "getlasthost", INTFUNC0,GetLastHost },
	  { "imagecreate", INTFUNC2,ImageCreate },
	  { "imagecharup", IMAGECHAR,NULL },
	  { "imagestring", IMAGESTRING,NULL },
	  { "setshowinfo", INTFUNC1,SetShowInfo },
	  { "msql_dbname", INTFUNC2,MsqlDBName },
	  { "msql_dropdb", INTFUNC1,MsqlDropDB },
	  { "pg_fieldnum", INTFUNC2,PGfieldNum },
	  { NULL,0,NULL } },

	{ { "getlastemail", INTFUNC0,GetLastEmail }, /* 12 */
	  { "ereg_replace", INTFUNC3,ERegReplace },
	  { "msql_connect", INTFUNC1,MsqlConnect },
	  { "msql_numrows", INTFUNC1,MsqlNumRows },
	  { "msql_regcase", INTFUNC1,MsqlRegCase },
	  { "imagedestroy", INTFUNC1,ImageDestroy },
	  { "imagepolygon", IMAGEPOLYGON,NULL },
	  { "msql_listdbs", INTFUNC0,MsqlListDBs },
	  { "pg_numfields", INTFUNC1,PGnumFields },
	  { "pg_fieldname", INTFUNC2,PGfieldName },
	  { "pg_fieldtype", INTFUNC2,PGfieldType },
	  { "pg_fieldsize", INTFUNC2,PGfieldSize },
	  { "stripslashes", INTFUNC1,_StripSlashes },
	  { NULL,0,NULL } }, 

	{ { "gethostbyaddr", INTFUNC1,GetHostByAddr }, /* 13 */
	  { "gethostbyname", INTFUNC1,GetHostByName },
	  { "getlastaccess", INTFUNC0,GetLastAccess },
	  { "eregi_replace", INTFUNC3,ERegiReplace },
	  { "msql_fieldlen", MSQL_FIELDLEN,NULL },
	  { "imagesetpixel", INTFUNC4,ImageSetPixel },
	  { "imagestringup", IMAGESTRINGUP,NULL },
	  { "msql_createdb", INTFUNC1,MsqlCreateDB },
	  { "pg_freeresult", INTFUNC1,PGfreeResult },
	  { "pg_getlastoid", INTFUNC0,PGgetlastoid },
	  { NULL,0,NULL } },

	{ { "getlastbrowser", INTFUNC0,GetLastBrowser }, /* 14 */
	  { "msql_fieldname", MSQL_FIELDNAME,NULL },
	  { "msql_fieldtype", MSQL_FIELDTYPE,NULL },
	  { "msql_numfields", INTFUNC1,MsqlNumFields },
	  { "imagerectangle", INTFUNC6,ImageRectangle },
	  { "imageinterlace", INTFUNC2,ImageInterlace },
	  { "msql_tablename", INTFUNC2,MsqlTableName },
	  { "pg_fieldprtlen", INTFUNC3,PGfieldPrtLen },
	  { "escapeshellcmd", INTFUNC1,EscapeShellCmd },
	  { NULL,0,NULL } },

	{ { "msql_freeresult", INTFUNC1,MsqlFreeResult }, /* 15 */
	  { "msql_fieldflags", MSQL_FIELDFLAGS,NULL },
	  { "msql_listtables", INTFUNC1,MsqlListTables },
	  { "msql_listfields", INTFUNC2,MsqlListFields },
	  { "getstartlogging", INTFUNC0,GetStartLogging },
	  { "pg_errormessage", INTFUNC1,PGerrorMessage },
	  { NULL,0,NULL } },

	{ { "htmlspecialchars", INTFUNC1,HtmlSpecialChars }, /* 16 */
	  { "imagecopyresized", IMAGECOPYRESIZED,NULL },
	  { NULL,0,NULL } }, /* 16 */

	{ { "imagefilltoborder", INTFUNC5,ImageFillToBorder }, /* 17 */
	  { "seterrorreporting", INTFUNC1,SetErrorReporting }, 
	  { NULL,0,NULL } }, 

	{ { "imagecolorallocate", INTFUNC4,ImageColorAllocate }, /* 18 */
	  { "imagefilledpolygon", IMAGEFILLEDPOLYGON,NULL },
	  { "imagecreatefromgif", INTFUNC1,ImageCreateFromGif },
	  { NULL,0,NULL } },

	{ { NULL,0,NULL } }, /* 19 */

	{ { "imagefilledrectangle", INTFUNC6,ImageFilledRectangle }, /* 20 */
	  { NULL,0,NULL } }, 

	{ { "imagecolortransparent", INTFUNC2,ImageColorTransparent }, /* 21 */
	  { NULL,0,NULL } }

};

命令token是一个二维数组结构体的hash表,通过对读取到的token进行查表映射作为回调处理。

3.1.3 语法分析过程

bison解释器需要先通过lex语法分析,解析出token,然后再把token交给yacc处理,我们原先的那篇介绍lex和yacc的文章,只是站在配置文件的角度,去实践了一下应用。而PHP2中,它只提供了yacc的模板文件parse.raw,并没有提供lex.c的模板文件,我们下面,来追踪下lex.c中的 yylex函数实现细节。

/* Hand-crafted Lexical analyzer using Bison's Pure_Parser calling convention */
int yylex(YYSTYPE *lvalp) {
	register char c;
	int tokenlen=0;
	char *s, d;
	char temp[8];
	int bs=0, cst, active, ret;
	char *look = NULL;

	php_pool_clear(1);

	cst = GetCurrentState(&active);	
	if(lstate==99) {
#if DEBUG
		Debug("Parser exiting\n");
#endif
		return(0);
	}
	if(ClearIt && LastToken!=RETURN && !cur_func && !eval_mode && cst) { 
		ClearStack(); 
		ClearIt=0; 
	} else ClearIt=0;

	while(1) switch(state) {
		case 0:  /* start of token '<' gets us to state 1 */
			lstate=0;
			c = getnextchar();
			if(!c) { 
				if(outpos) outputline(obuf);
				state=99; break; 
			}
			if(no_httpd && inpos==1 && c=='#') {
				state=80;
				break;
			}
			if(c!='<') {
				obuf[outpos++]=c;
				if(c==10 || c==13 || outpos > LINEBUFSIZE-1) outputline(obuf);
				break;
			}
			if(outpos) outputline(obuf);
			inmarker=inpos-1;
			/* fall-through */

		case 1:	/* '?' or '!?' gets us to state 2 */
			lstate=1;
			c = getnextchar();
			if(!c) { state=99; break; }
			if(c=='!') {
				d = getnextchar();
				if(!d) {
					putback(c);	
					state=99; 
					break; 
				}
				if(d != '?') {
					putback(d);
				} else {
					c = d; /* discard '!' */
				}
			}
			if(c!='?') {
				putback(c);	
				if(output_from_marker() < 0) {
					state=99; break;
				}
				state=0;
				break;
			}	
			NewExpr=1;
			state=2;
			/* fall-through */

		case 2: /* Start of  a command - [a-z][A-Z] gets us to state 3 */
				/* Start of a number  - [0-9] takes us to state 10 */
			lstate=2;
			c = getnextchar();
			if(!c) { state=99; break; }
			if(c==VAR_INIT_CHAR) { 
				state=40; 
				tokenlen=0;
				tokenmarker=inpos;
				break; 
			}
			if(c=='=') { state=15; break; }	
			if(isdigit(c)) {
				state=10;
				tokenlen=1;
				tokenmarker=inpos-1;
				break;
			}
			if(c==';') { 
				NewExpr=1;
				ClearIt=1;
				return(c); 
			}
			if(c=='(') {
				NewExpr=1;
				if(inIf>-1) inIf++;	
				if(inWhile>-1) inWhile++;	
				return(c);
			}
			if(c==')') {
				if(inIf>-1) inIf--;
				if(inIf==0) inIf=-1;
				if(inWhile>-1) inWhile--;
				if(inWhile==0) inWhile=-1;
				NewExpr=0;
				return(c);
			}	
			if(c=='>') { state=20; break; }
			if(c=='<') { state=21; break; }
			if(c==' ' || c=='\t' || c=='\n' || c==10 || c==13) break;
			if(c=='\'') { 
				state = 9;
				tokenlen=0;
				tokenmarker=inpos;
				break;
			}
			if(c=='\"') {
				state=30;
				tokenlen=0;
				tokenmarker=inpos;
				break;
			}	
			if(c=='.') {
				state=11;
				tokenlen=1;
				tokenmarker=inpos-1;
				break;
			}
			if(c=='@') { return(c); }
			if(c=='{') {
				NewExpr=1;
				ClearIt=1;
				return(c);
			}
			if(c=='!') { state=12; break; }
			if(c=='&') { state=13; break; }
			if(c=='|') { state=14; break; }
			if(c=='+') { state=16; break; }
			if(c=='-') { state=17; break; }
			if(c=='/') { state=18; break; }
			if(c=='%') { state=8; break; }
			if(c=='*') { state=19; break; }
			if(c==',') { 
				NewExpr=1;
				return(','); 
			}
			if(c=='}') {
				look = lookaheadword();
				if(strcasecmp(look,"else") && strcasecmp(look,"elseif")) {
					NewExpr=1;
					ret = BraceCheck();
					if(ret) {
						putback(';');
						ClearIt=1;
						return(ret);
					}
					return('}');
				} else {
					break;
				}
			}
			if(c==']') return(c);
			if(c < 32) {
				c = getnextchar();
				if(!c) state=99;
				break;
			}
			if(!isalpha(c) && c != '_') {
				putback(c);	
				if(output_from_marker() < 0) {
					state=99; break;
				}
				state=0;
				break;
			}
			tokenlen=1;
			tokenmarker=inpos-1;
			/* fall-through */

		case 3: /* continue command - non [a-z][A-Z] gets us to state 4 */
			lstate=3;
			while(isalpha((c=getnextchar())) || c=='_' || isdigit(c)) tokenlen++;
			if(!c) { state=99; break; }
			putback(c);
			
		case 4: /* command finished */
			if(active==-5) {
				CondPop(&active);
				CondPush(0,-6);	
				s = (char *) MakeToken(&inbuf[tokenmarker],tokenlen);
				if(s && *s) {
					*lvalp = (YYSTYPE) s;
					lstate=4;
					return(FUNCNAME);
				}
			}
				
			if(tokenlen > MAX_CMD_LEN) {
				/* unrecognized command */
				if(output_from_marker()<0) {
					lstate=4;
					state=99; break;
				}
				state=0;
				break;
			}
			state=2;
			if(tokenlen==2 && !strncasecmp(&inbuf[tokenmarker],"if",2)) {
				inIf++;
			} else if(tokenlen==6 && !strncasecmp(&inbuf[tokenmarker],"elseif",6)) {
				inIf++;		
			} else if(tokenlen==5 && !strncasecmp(&inbuf[tokenmarker],"while",5)) {
				if(iterwhile==SeekPos) { /* Iteration of a previous loop */
					inWhile++; 
				} else {  /* new while loop */
					inWhile++;
					if(GetCurrentState(NULL)) {
						WhilePush(SeekPos,tokenmarker,yylex_linenumber-1);
					}
				}
			} 
			NewExpr=1;
			lstate=4;
			return(CommandLookup(tokenlen,lvalp));
			break;

		case 8: /* % */
			lstate=8;
			state=2;
			NewExpr=1;
			return('%');
			break;

		case 9: /* 'c' */
			lstate=9;
			NewExpr=0;
			while(1) {
				c=getnextchar();
				if(c=='\\') {
					bs=bs?0:1;
					tokenlen++;
					continue;
				}
				if(bs) {
					tokenlen++;
					bs=0;
					continue;
				}	
				if(c=='\'' || !c) break;
				tokenlen++;
			}
			if(!c) { state=99; break; }
			s = (char *) MakeToken(&inbuf[tokenmarker],tokenlen);
			if(s && *s) {
				sprintf((char *)temp,"%d",(int)*s);
			} else strcpy((char *)temp,"0");
			*lvalp = (YYSTYPE) MakeToken(temp,strlen((char *)temp));
			state = 2;
			return(LNUMBER);
			
		case 10: /* LNUMBER */
			lstate=10;
			NewExpr=0;
			while(isdigit((c=getnextchar()))) tokenlen++;
			if(!c) { state=99; break; }
			if(c=='.') { tokenlen++; state=11; break; }
			putback(c);
			*lvalp = (YYSTYPE) MakeToken(&inbuf[tokenmarker],tokenlen);
			state = 2;
			return(LNUMBER);

		case 11: /* Double */
			lstate=11;
			NewExpr=0;
			while(isdigit((c=getnextchar()))) tokenlen++;
			if(!c) { state=99; break; }
			putback(c);
			*lvalp = (YYSTYPE) MakeToken(&inbuf[tokenmarker],tokenlen);
			state = 2;
			return(DNUMBER);

		case 12: /* ! */
			lstate=12;
			NewExpr=1;
			c = getnextchar();
			state=2;
			if(c=='=') return(COND_NE);
			else {
				putback(c);
				return(NOT);
			}
			break;

		case 13: /* & */
			lstate=13;
			NewExpr=1;
			c = getnextchar();
			state=2;
			if(c=='&') return(LOG_AND);
			else if(c=='=') return(ANDEQ);
			else {
				putback(c);
				return('&');
			}
			break;

		case 14: /* | */
			lstate=14;
			NewExpr=1;
			c = getnextchar();
			state=2;
			if(c=='|') return(LOG_OR);
			else if(c=='=') return(OREQ);
			else {
				putback(c);
				return('|');
			}
			break;
			
		case 15: /* = */
			lstate=15;
			NewExpr=1;
			c = getnextchar();
			state=2;
			if(c=='=') return(COND_EQ);
			else {
				putback(c);
				return('=');
			}
			break;

		case 16: /* + */
			lstate=16;
			c = getnextchar();
			state=2;
			if(c=='+') return(INC);
			else if(c=='=') return(PEQ);
			else {
				putback(c);
				NewExpr=1;
				return('+');
			}
			break;

		case 17: /* - */
			lstate=17;
			c = getnextchar();
			state=2;
			if(c=='-') return(DEC);
			else if(c=='=') return(MEQ);
			else {
				putback(c);
				if(NewExpr) {
					NewExpr=0;
					return(NEG);
				} else {
					NewExpr=1;
					return('-');
				}
			}
			break;

		case 18: /* / */
			lstate=18;
			c = getnextchar();
			state=2;
			if(c=='*') {
				while(1) {
					c=getnextchar();
					if((c=='/' && inbuf[inpos-2]=='*') || (!c)) break;
				}
				if(!c) { state=99; break; }
				tokenlen=0;
				tokenmarker=inpos;
				state=2;
				break;
			} else {
				putback(c);
				NewExpr=1;
				return('/');
			}
			break;

		case 19: /* * */
			lstate=19;
			state=2;
			NewExpr=1;
			return('*');
			break;
	
		case 20: /* > */
			lstate=20;
			NewExpr=1;
			if(inIf>-1 || inWhile>-1) {
				state=2;
				c=getnextchar();
				if(c=='=') return(COND_GE);
				putback(c);
				return(COND_GT);
			}
			state=0;
			ClearIt=1;
			return(END_TAG);

		case 21: /* < */
			lstate=21;
			NewExpr=1;
			if(inIf>-1 || inWhile>-1) {
				state=2;
				c=getnextchar();
				if(c=='=') return(COND_LE);
				putback(c);
				return(COND_LT);
			}
			return('<');

		case 30: /* string */
			lstate=30;
			NewExpr=0;
			while(1) {
				c=getnextchar();
				if(c=='\\') {
					bs=bs?0:1;
					tokenlen++;
					continue;
				}
				if(bs) {
					tokenlen++;
					bs=0;
					continue;
				}	
				if(c=='\"' || !c) break;
				tokenlen++;
			}
			if(!c) { state=99; break; }
			*lvalp = (YYSTYPE) MakeToken(&inbuf[tokenmarker],tokenlen);
			state = 2;
			return(STRING);

		case 40: /* Variable */
			lstate=40;
			NewExpr=0;
			while((c=getnextchar()) && ((isalnum(c) || c=='_') || (c==VAR_INIT_CHAR && tokenlen==0))) tokenlen++;
			if(!c) { state=99; break; }
			*lvalp = (YYSTYPE) MakeToken(&inbuf[tokenmarker],tokenlen);
			state = 2;
			while(c==' ' || c=='\t' || c=='\n') c=getnextchar();
			if(c=='[') return(ARRAY);
			putback(c);
			return(VAR);
		case 80: /* Ignore # lines in command line mode */
			state=lstate;
			lstate=80;
			while((c=getnextchar()) && c!=10 && c!=13);
			if(!c) { state=99; break; }
			break;
			
		case 99: /* EOF reached */
			lstate=99;
			state=0;
			pa = FilePop();
			return(END_OF_FILE);		
	}
} /* yylex */

1.读取语法开始标签(state:0 && 1)

我们看到,这个解析流程就是switch…case 逐行单个字符解析,首先state初始化为0,所以,它的第一步工作就是读取语法开始的标签'<‘。如果是'<‘字符,则switch会进行第二步工作,读取下一个字符是否是语法标签的结构,这里则是'<?’这两个字符。

2.开始语法处理(state:2)

state:2的工作主要是对下一个字符的分类处理,按字母和非字母的处理方式进行分配。这里,我们的表达式是<? echo 1 + 1; > 所以读取到的是字母’e’,然后,流转到了处理字母的state:3环节

3.字母的处理(state:3)

逐步读取下一个字母的处理,会将读取到的字母通过 putback函数存到输入缓冲区作为存储。直到遇到非字母时,进入state:4环节,进行token的解析。这里,当我们读取完echo的时候,就进入了这个环节。

4.命令token的解析(state:4)

解析的过程会把state重置到2的环节,然后通过 CommandLookup(tokenlen,lvalp) 函数,到命令token中去寻找对应的token指令,如下所示:

int CommandLookup(int cmdlen, YYSTYPE *lvalp) {
	register int i=0;

	while(cmd_table[cmdlen][i].cmd) {
		if(!strncasecmp(&inbuf[tokenmarker],cmd_table[cmdlen][i].cmd,cmdlen)) {
			*lvalp = (YYSTYPE) MakeToken(&inbuf[tokenmarker],cmdlen);
			LastToken = cmd_table[cmdlen][i].token;
			return(cmd_table[cmdlen][i].token);
		}	
		i++;
	}
	*lvalp = (YYSTYPE) MakeToken(&inbuf[tokenmarker],cmdlen);
	return(CUSTOMFUNC);
}

当输入缓冲区中的命令能够在hash表中找到时,会生成token,交给yyparse()函数去处理,这里,我们找到了echo 指令,然后,让我们看看yyparse是怎么处理的。

parse.raw

为了便于理解,我们这里直接阅读yacc的模板配置文件,对于我们的 echo 1+1; 这种语法,它属于 PHPECHO expr eoc 这条指令,而expr 是由val定义的,而val中我们的数字属于LNUMBER,所以,它会继续往下读取,我们接着进行state:2的处理

5.语法处理(state:2)

我们读取到了数字1,这个时候,我们将进行state:10环节的解析。

6.数字的处理(state:10)

数字处理阶段,每次都会重置到state:2的状态,并且返回LNUMBER的TOKEN,而LNUMBER的处理如上图所示:会通过PUSH操作,将数字压入表达式堆栈,这里的$1是反向引用LNUMBER的数值。PUSH操作如下:

void Push(char *value, int type) {
	Stack *new, *s=NULL;
	VarTree *t;
	int next=0;

	if(!value) return;
	if(type==ARRAY) {
		s = Pop();
		if(!s) {
			Error("Stack Error in Push");
			return;
		}
	} else if(type== -ARRAY) { type=ARRAY; next=1; }
	new = emalloc(2,sizeof(Stack));
	new->type   = type;
	new->next   = NULL;
	new->strval = NULL;
	new->var = NULL;
	if(type==DNUMBER || type==LNUMBER) {
		new->strval = estrdup(2,value);
		new->intval = atol(value);
		new->douval = atof(value);
	} else if(type==STRING) {
		new->strval = SubVar(estrdup(2,value));
		new->intval = atol(new->strval);
		new->douval = atof(new->strval);
	} else if(type==VAR) {
		t = GetVar(value,NULL,0);
		if(!t) {
#if DEBUG
			Debug("Undefined variable: %s\n",value);
#endif
			new->strval = estrdup(2,"");
			new->intval = 0;
			new->douval = 0;
			new->type = STRING;
		} else {
			new->strval = estrdup(2,t->strval);
			new->intval = t->intval;
			new->douval = t->douval;
			new->type = t->type;
			new->var = t;
			new->flag = 1;
		}
	} else if(type==ARRAY) {
		if(!next) t = GetVar(value,s->strval,0);
		else t = GetVar(value,NULL,1);
		if(!t) {
#if DEBUG
			Debug("Undefined array variable: %s\n",value);
#endif
			new->strval = estrdup(2,"");
			new->intval = 0;
			new->douval = 0;
			new->type = STRING;
		} else {
			new->strval = estrdup(2,t->strval);
			new->intval = t->intval;
			new->douval = t->douval;
			new->type = t->type;
			new->var = t;
			new->flag = 0;
		}
	}
	new->next = top;
	top = new;
}

表达式堆栈,其本质是随机动态内存通过单链表连接,栈顶指针指向栈尾,如下图所示:

POP出栈的时候,就是通过移动Top指针,来将数值弹出堆栈处理。将中间结果再进行压栈存储。下面,我们继续执行state:2的环节。

7.语法处理(state:2)

这个时候,我们读取到的是’+’,进行state:16的加号处理。

8.加号处理(state:16)

加号处理,每次一样都会重置到state:2,我们这里返回’+’的token处理,’+’的parse处理是声明在结合性这里,左结合,如下图所示:

然后,我们继续回到了state:2,读取数字,直到满足expr+expr时,会调用Calc函数进行表达式计算。

int Calc(int op) {
	Stack *s, a, b;
	char temp[1024];
	static char *atemp=NULL;
	int l;
	long intval;
	double douval=0.0;

	a.strval = NULL;
	b.strval = NULL;
	s = Pop();
	if(!s) {
		Error("Stack Error");
		return(0);
	}
	b.type = s->type;
	b.intval = s->intval;
	b.douval = s->douval;
	if(s->strval) b.strval = estrdup(0,s->strval);

	s = Pop();
	if(!s) {
		Error("Stack Error");
		return(0);
	}
	a.type = s->type;
	a.intval = s->intval;
	a.douval = s->douval;
	if(s->strval) a.strval = estrdup(0,s->strval);

	switch(a.type) {
		case LNUMBER:
			switch(op) {
			case '+':
				intval = a.intval + b.intval;
				sprintf(temp,"%ld",intval);
				Push(temp,LNUMBER);
				break;
			case '-':
				intval = a.intval - b.intval;
				sprintf(temp,"%ld",intval);
				Push(temp,LNUMBER);
				break;
			case '*':
				intval = a.intval * b.intval;
				sprintf(temp,"%ld",intval);
				Push(temp,LNUMBER);
				break;
			case '/':
				if(b.intval != 0) {
					intval = a.intval / b.intval;
					sprintf(temp,"%ld",intval);
					Push(temp,LNUMBER);
				} else {
					Push("undefined",STRING);
				}
				break;
			case '%':
				intval = a.intval % b.intval;
				sprintf(temp,"%ld",intval);
				Push(temp,LNUMBER);
				break;
			case '&':
				intval = a.intval & b.intval;
				sprintf(temp,"%ld",intval);
				Push(temp,LNUMBER);
				break;
			case '|':
				intval = a.intval | b.intval;
				sprintf(temp,"%ld",intval);
				Push(temp,LNUMBER);
				break;
			case '^':
				intval = a.intval ^ b.intval;
				sprintf(temp,"%ld",intval);
				Push(temp,LNUMBER);
				break;
			}
			break;
		case DNUMBER:
			switch(op) {
			case '+':
				douval = a.douval + b.douval;
				sprintf(temp,"%.10f",douval);
				Push(temp,DNUMBER);
				break;
			case '-':
				douval = a.douval - b.douval;
				sprintf(temp,"%.10f",douval);
				Push(temp,DNUMBER);
				break;
			case '*':
				douval = a.douval * b.douval;
				sprintf(temp,"%.10f",douval);
				Push(temp,DNUMBER);
				break;
			case '/':
				if(b.douval != 0) {
					douval = a.douval / b.douval;
					sprintf(temp,"%.10f",douval);
					Push(temp,DNUMBER);
				} else {
					Push("undefined",STRING);
				}
				break;
			case '%':
				douval = (double)((int)a.douval % (int)b.douval);
				sprintf(temp,"%ld",(long)douval);
				Push(temp,DNUMBER);
				break;
			case '&':
				douval = (double)((int)a.douval & (int)b.douval);
				sprintf(temp,"%ld",(long)douval);
				Push(temp,DNUMBER);
				break;
			case '|':
				douval = (double)((int)a.douval | (int)b.douval);
				sprintf(temp,"%ld",(long)douval);
				Push(temp,DNUMBER);
				break;
			case '^':
				douval = (double)((int)a.douval ^ (int)b.douval);
				sprintf(temp,"%ld",(long)douval);
				Push(temp,DNUMBER);
				break;
			}
			break;
		case STRING:
			switch(op) {
			case '+': 
				if((l=strlen(a.strval)+strlen(b.strval)) > 1023) {
					atemp = emalloc(0,l+1);
					sprintf(atemp,"%s%s",a.strval,b.strval);
					Push(atemp,STRING);
				} else {
					sprintf(temp,"%s%s",a.strval,b.strval);
					Push(temp,STRING);
				}
				break;
			case '-':
				if(strlen(b.strval) && strlen(a.strval)) {
					Push(b.strval,STRING);
					Push("",STRING);
					Push(a.strval,STRING);
					RegReplace();
				} else if(strlen(b.strval)) Push(b.strval,STRING);
				else Push(a.strval,STRING);
				break;
			case '*':
				/* Multiplying strings? */
				break;
			case '/':
				/* Dividing strings? */
				break;
			case '%':
				/* Modding strings? */
				break;
			}
			break;
	}
	return(0);
}

计算的过程,会将表达式堆栈进行POP操作,我们上面也讲过了,然后进行计算,将最后的结果再次入栈,比如1 + 1 + 2,会被优化成: 2 + 2,这种形式。然后,继续读取到’;’,才满足我们的词法分析条件进行Echo的处理:

void Echo(char *format, int args) {
	Stack *s=NULL;
	Stack sarg[5]; /* Max 5 args to keep things simple in the parser */
#if APACHE
	char *buf;
#endif
	int num=args, done=0, type;
	char *t,*st,*beg,*fmt;

#if DEBUG
	Debug("Calling php_header from Echo\n");
#endif
	php_header(0,NULL);
	while(num) {	
		s = Pop();
		if(!s) {
			return;
		}
		num--;
		memcpy(&(sarg[num]),s,sizeof(Stack));
		if(s->strval) sarg[num].strval = estrdup(1,s->strval);
	}
	if(!format) {
		for(num=0; num<args; num++) {
			if(sarg[num].strval) {
				ParseEscapes(sarg[num].strval);
				StripSlashes(sarg[num].strval);
				if(PUTS(sarg[num].strval) < 0) Exit(0);
			}
		}
		return;
	}
	st = t;
	ParseEscapes(format);
	t = format;
	num=0;
	while(num<args && !done) {	
		st = t;
		type = FormatCheck(&t,&beg,&fmt);	
		if(type==0 || type==-1) break;
		if(beg && *beg) {
			if(PUTS(beg)<0) { Exit(0); break; }
		}
		if(type==1) {
			if(PUTS("%")<0) { Exit(0); break; }
			continue;
		}
		switch(type) {
		case LNUMBER:
			ParseEscapes(fmt);
			StripSlashes(fmt);
#if APACHE
			buf = emalloc(1,strlen(fmt)+strlen(sarg[num].strval)+ECHO_BUF);
			sprintf(buf,fmt,sarg[num].intval);
			if(PUTS(buf)<0) { Exit(0); done=1; }
#else
			if(printf(fmt,sarg[num].intval)<0) { Exit(0); done=1; }
#endif
			num++;
			break;
		case DNUMBER:
			ParseEscapes(fmt);
			StripSlashes(fmt);
#if APACHE
			buf = emalloc(1,strlen(fmt)+strlen(sarg[num].strval)+ECHO_BUF);
			sprintf(buf,fmt,sarg[num].douval);
			if(PUTS(buf)<0) { Exit(0); done=1; }
#else
			if(printf(fmt,sarg[num].douval) < 0) { Exit(0); done=1; }
#endif
			num++;
			break;
		case STRING:
			ParseEscapes(fmt);
			StripSlashes(fmt);
			ParseEscapes(sarg[num].strval);
			StripSlashes(sarg[num].strval);
#if APACHE
			buf = emalloc(1,strlen(fmt)+strlen(sarg[num].strval)+ECHO_BUF);
			sprintf(buf,fmt,sarg[num].strval);
			if(PUTS(buf)<0) Exit(0);
#else
			if(printf(fmt,sarg[num].strval) < 0) Exit(0);
#endif
			num++;
			break;
		}
	}
	if(t && *t) {
			if(PUTS(t)<0) Exit(0);
	}
}

Echo函数通过弹出表达式堆栈的数值,在经过字符的安全预处理后,通过PUTS方法输出结果。然后,继续读取’>’标签结束,跳转到state:20这个环节。

9.标签结束处理(state:20)

state重置到0环节,然后返回END_TAG的token处理,如下:

通过YYACCEPT结束yyparse的解析并成功返回。

3.2 系统函数的调用

在经过上面一轮基础的表达式解释过程后,我们对整个解释流程已经基本熟悉,所以,我们直接写一些重点流程,这里我们按phpinfo()函数为例。在进行命令token,hash解析的时候,会返回INTFUNC0,token。而yyarse的处理如下:

我们会调用IntFunc()来回调的处理token:

void IntFunc(char *fnc_name) {
	int i=0;
	int cmdlen = strlen(fnc_name);

	while(cmd_table[cmdlen][i].cmd) {
		if(!strncasecmp(fnc_name,cmd_table[cmdlen][i].cmd,cmdlen)) {
			cmd_table[cmdlen][i].fnc();
			break;
		}	
		i++;
	}
}

然后调用回调函数Info:

void Info(void) {
	struct stat sb;
	char *path;
	struct passwd *pw;
	struct group *gr;
	char buf[512];
#if APACHE
	array_header *env_arr;
	table_entry *env;
	int i;
	VarTree *var;
#endif	
	FILE *fp;

	php_header(0,NULL);
	sprintf(buf,"<html><head><title>PHP/FI</title></head><body><h1>PHP/FI Version %s</h1>by Rasmus Lerdorf (<a href=\"mailto:rasmus@vex.net\">rasmus@vex.net</a>)<p>The PHP/FI Web Site is at <a href=\"http://www.vex.net/php\">http://www.vex.net/php</a><p>\n",PHP_VERSION);
	PUTS(buf);
	PUTS("This program is free software; you can redistribute it and/or modify\n");
	PUTS("it under the terms of the GNU General Public License as published by\n");
	PUTS("the Free Software Foundation; either version 2 of the License, or\n");
	PUTS("(at your option) any later version.<p>\n");
	PUTS("This program is distributed in the hope that it will be useful,\n");
	PUTS("but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
	PUTS("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n");
	PUTS("GNU General Public License for more details.<p>\n");
	PUTS("You should have received a copy of the GNU General Public License\n");
	PUTS("along with this program; if not, write to the Free Software\n");
	PUTS("Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.<p>\n");

	PUTS("<hr><b><i>Unix version:</i></b> ");
	fp = popen("uname -a","r");
	if(fp) {
		while(fgets(buf,255,fp)) {
			PUTS(buf);	
		}
		pclose(fp);
	}
	
	PUTS("<hr><b><i>Environment:</i></b><pre>");
	fp = popen("env","r");
	if(fp) {
		while(fgets(buf,255,fp)) {
			PUTS(buf);	
		}
		pclose(fp);
	}
#ifndef APACHE
	{
		char *sn, *pi;
		sn = getenv("SCRIPT_NAME"); pi = getenv("PATH_INFO");
		sprintf(buf,"PHP_SELF=%s%s\n",sn?sn:"",pi?pi:"");
		PUTS(buf);	
	}
#endif
	PUTS("</pre>\n");
#if APACHE
	PUTS("<hr><b><i>Apache defined variables available to PHP/FI:</i></b><p>\n");
	env_arr = table_elts(php_rqst->subprocess_env);
	env = (table_entry *)env_arr->elts;
	for(i = 0; i < env_arr->nelts; ++i) {
		if(!env[i].key) continue;
		if(strcasecmp(env[i].key,"SCRIPT_NAME")) {
			sprintf(buf,"%s = %s<br>\n",env[i].key,env[i].val);
			PUTS(buf);
			if(!strcasecmp(env[i].key,"SCRIPT_FILENAME")) {
				sprintf(buf,"PATH_TRANSLATED = %s<br>\n",env[i].val);
				PUTS(buf);
			}
		}
    }
	sprintf(buf,"PATH_INFO = %s<br>\n",php_rqst->uri);  /* Faked by GetVar */
	PUTS(buf);
	sprintf(buf,"PHP_SELF = %s<br>\n",php_rqst->uri);  /* Faked by GetVar */
	PUTS(buf);
#if APACHE
	var = GetVar("PHP_AUTH_USER",0,0);
	if(var) {
		sprintf(buf,"PHP_AUTH_USER = %s<br>\n",var->strval);
		PUTS(buf);
	}
	var = GetVar("PHP_AUTH_PW",0,0);
	if(var) {
		sprintf(buf,"PHP_AUTH_PW = %s<br>\n",var->strval);
		PUTS(buf);
	}
	var = GetVar("PHP_AUTH_TYPE",0,0);
	if(var) {
		sprintf(buf,"PHP_AUTH_TYPE = %s<br>\n",var->strval);
		PUTS(buf);
	}
#endif
	PUTS("<hr><b><i>Request HTTP Headers:</i></b><p>\n");
	env_arr = table_elts(php_rqst->headers_in);
	env = (table_entry *)env_arr->elts;
	for(i = 0; i < env_arr->nelts; ++i) {
		if(!env[i].key) continue;
		sprintf(buf,"%s:%s<br>\n",env[i].key,env[i].val);
		PUTS(buf);
    }
	PUTS("<hr><b><i>Send HTTP Headers:</i></b><p>\n");
	env_arr = table_elts(php_rqst->headers_out);
	env = (table_entry *)env_arr->elts;
	for(i = 0; i < env_arr->nelts; ++i) {
		if(!env[i].key) continue;
		sprintf(buf,"%s:%s<br>\n",env[i].key,env[i].val);
		PUTS(buf);
    }
#endif
#if APACHE
	path = php_rqst->filename;
#else	
	path = getenv("PATH_TRANSLATED");
#endif
	if(path) {
#ifndef APACHE
		if(stat(path,&sb)!=-1) {
#endif
			PUTS("<hr><font size=+1>Information on <i>");
			PUTS(path);
			PUTS("</i></font><br>\n");
#if APACHE
			switch(php_rqst->finfo.st_mode&S_IFMT) {
#else
			switch(sb.st_mode&S_IFMT) {
#endif
			case S_IFIFO:
				PUTS("<b>Type:</b> <i>fifo special</i><br>\n");
				break;
        	case S_IFCHR:
				PUTS("<b>Type:</b> <i>character special</i><br>\n");
				break;
        	case S_IFDIR:
				PUTS("<b>Type:</b> <i>directory</i><br>\n");
				break;
        	case S_IFBLK:
				PUTS("<b>Type:</b> <i>block special</i><br>\n");
				break;
        	case S_IFREG:
				PUTS("<b>Type:</b> <i>ordinary file</i><br>\n");
				break;
			default:
				PUTS("<b>Type:</b> <i>unknown</i><br>\n");
				break;
			}
#if APACHE
			sprintf(buf,"<b>Permission Bits:</b> <i>%04lo</i><br>\n",(long)php_rqst->finfo.st_mode&07777);
#else
			sprintf(buf,"<b>Permission Bits:</b> <i>%04lo</i><br>\n",(long)sb.st_mode&07777);
#endif
			PUTS(buf);
			sprintf(buf,"<b>Number of Links:</b> <i>%ld</i><br>\n",(long)sb.st_nlink);
			PUTS(buf);
#if APACHE
			pw = getpwuid(php_rqst->finfo.st_uid);
#else
			pw = getpwuid(sb.st_uid);
#endif
			if(pw) {
				gr = getgrgid(pw->pw_gid);
				sprintf(buf,"<b>Owner:</b> <i>%s</i> <b>From Group:</b> <i>%s</i><br>\n",pw->pw_name,gr->gr_name);
				PUTS(buf);
			}
#if APACHE
			gr = getgrgid(php_rqst->finfo.st_gid);
#else
			gr = getgrgid(sb.st_gid);
#endif
			if(gr) {
				sprintf(buf,"<b>Group:</b> <i>%s</i><br>\n",gr->gr_name);
				PUTS(buf);
			}
			sprintf(buf,"<b>Size:</b> <i>%ld</i><br>\n",
#if APACHE
				(long)php_rqst->finfo.st_size);
#else
				(long)sb.st_size);
#endif
			PUTS(buf);
			sprintf(buf,"<b>Last Access:</b> <i>%s</i><br>",
#if APACHE
				ctime(&php_rqst->finfo.st_atime));
#else
				ctime(&sb.st_atime));
#endif
			PUTS(buf);
			sprintf(buf,"<b>Last Modification:</b> <i>%s</i><br>",
#if APACHE
				ctime(&php_rqst->finfo.st_mtime));
#else
				ctime(&sb.st_mtime));
#endif
			PUTS(buf);
			sprintf(buf,"<b>Last Status Change:</b> <i>%s</i><br>",
#if APACHE
				ctime(&php_rqst->finfo.st_ctime));
#else
				ctime(&sb.st_ctime));
#endif
			PUTS(buf);
#ifndef APACHE
		}
#endif
	}
	path = getcwd(NULL,1024);
	if(path) {
		sprintf(buf,"<hr><b>Working Directory:</b> <i>%s</i><br>\n",path);
		PUTS(buf);
		free(path);
	}
#if ACCESS_CONTROL
	sprintf(buf,"<b>Access Control enabled using:</b> <i>%s</i><br>\n",getaccdir());
	PUTS(buf);
#endif
#if LOGGING
	sprintf(buf,"<b>Access Logging enabled using:</b> <i>%s</i><br>\n",getlogdir());
	PUTS(buf);
#endif
#if MSQLLOGGING
	sprintf(buf,"<b>Access Logging enabled using mSQL in db:</b> <i>%s</i><br>\n",getlogdir());
	PUTS(buf);
#endif
#ifdef HAVE_LIBMSQL
	PUTS("<b>mSQL support enabled</b><br>\n");
#endif
#ifdef HAVE_LIBPQ
	PUTS("<B>Postgres95 support enabled</b><br>\n");
#endif
#ifdef GDBM
	PUTS("<b>GDBM support enabled</b><br>\n");
#else
#ifdef NDBM
	PUTS("<b>NDBM support enabled</b><br>\n");
#else
	PUTS("<b>FlatFile support enabled</b><br>\n");
#endif
#endif
#if APACHE
	PUTS("<b>Apache module support enabled</b><br>\n");
#endif
#ifdef HAVE_LIBGD
	PUTS("<b>GD support enabled</b><br>\n");
#endif
#if PHPFASTCGI
	PUTS("<b>FastCGI support enabled</b><br>\n");
#endif
	PUTS("</body></html>");
}

这就是系统函数的调用流程。

4.结语

上面,我们追踪了PHP2/CI解释器对于表达式和系统函数的主要解释流程,剩下的比如条件判断、自定义函数、等等,大家有兴趣也可以去阅读下细节。

5.参考

PHP2

https://museum.php.net/

如无特殊说明,文章均为本站原创,转载请注明出处。如发现有什么不对的地方,希望得到您的指点。

发表评论

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