Rasmus Lerdorf 还做一个叫做 Form Interpreter 的项目,可以轻松地将SQL查询嵌入到网页中。它基本上是另一个CGI包装器,可以解析SQL查询,并能在这些查询的基础上轻松创建表单和表格,Rasmus Lerdorf 最终将两个项目合并到了一起,将其合并成为了一个程序,也就是 PHP/FI。现在它已经发展到了嵌入 HTML 文件的简单编程语言的程度,所以代码量飞起,直接涨了接近 20 多倍。
这个阶段的 PHP/FI 此时已经具备了 PHP 的雏形,PHP 完成从“个人主页工具”到 “个人主页建设工具包”。
PHP脚本语言的语法在很多方面与C语言相似。它支持变量、数组、函数调用、不同的变量类型和大多数编写复杂的cgi程序时可能需要的东西。也就是说 PHP 编程语言正式出现了!
在PHP/FI 中引入了 yacc 和 lex 进行词法分析和语法解析,其原理其实还是通过解析 html 中的标记位,然后通过词法解析和语法解析成为C 语言代码进行执行,其核心代码在 lex.c
和 parse.c
两个文件中。
我们先来看一下 PHP/FI 的使用,假设你有一个表单。
<FORM ACTION="/cgi-bin/php.cgi/~userid/display.html" METHOD=POST>。
<INPUT TYPE="text" name="name">
<INPUT TYPE="text" name="age"> <INPUT TYPE="text" name="age" >。
<INPUT TYPE="提交">
</FORM>
然后你的display.html文件可以包含这样的内容。
<?
if($age>50);
echo "Hi $name, you are ancient!<p>";
elseif($age>30);
echo "Hi $name, you are very old!<p>";
else;
echo "Hi $name.";
endif;
>
就是这么简单! PHP/FI为表单中的每个输入字段自动创建一个变量。然后你可以在ACTION URL文件中使用这些变量。
上面的这段例子是来自于 PHP/FI 的文档,其文档地址在压缩包中也存在,目录是 doc。
这个时候的 PHP/FI 还提供了 mysql 数据库:
<?
$name = "bob";
$result = mysql($database,"select * from table where firstname='$name'");
$num = mysql_numrows($result);
echo "$num records found!<p>";
$i=0;
while($i<$num);
echo mysql_result($result,$i,"lcase(fullname)");
echo "<br>";
echo mysql_result($result,$i,"address");
echo "<br>";
$i++;
endwhile;
>
其实到了这一步,我们的 PHP/FI 其实就已经可以完成目前我们的日常业务开发了,也就是 CURD。
同时我们可以通过 changelog 就可以看出,越来越多的人参与到了 PHP 的研发工作当中,也正是因此,PHP 逐渐壮大到了今天这个地步。
目前 PHP/FI 的文档还可以访问 https://www.php.net/manual/phpfi2.php ,有兴趣的朋友可以去看一下。
源码解析
PHP 的本质其实还是 C,这也是大多数脚本语言的本质,我们可以把脚本语言的解释器理解为就是一个特殊的程序,解释器的将其中的各种语法解析、转换成可以执行的 C 代码而已。如果这么理解,那么对于脚本语言的理解就很容易了。
那么我们在定义一个变量,自定义一个函数的时候,其实解释器需要将这些变量、函数存储起来,然后在需要使用的地方进行调用。
接下来,我们就来看一下,PHP/FI中的自定义函数、变量是如何存储的。
PHP 的函数保存在哪里?
内置函数
在了解自定义函数的存储之前,我们先来看一下内置函数是如何进行处理的,在 PHP/FI 中为我们提供了大量的内置函数。
所有的内置函数都会被存储在一个变量名叫做 cmd_table
的 hashtable 中。
typedef struct _cmd_table_t {
char *cmd;
unsigned int token;
void (*fnc)(void);
} cmd_table_t;
- cmd 是函数的名称
- token 是token 的编号
- 最后一个成员是指向函数的指针
自定义函数
PHP 中的变量
支持的变量类型
支持三种类型的变量。长整数、双精度浮点和字符串。
#define DNUMBER 258
#define LNUMBER 259
#define STRING 260
PHP 通过类型推断来判断变量具体的数据类型。
/*
* Determines if 'str' is an integer, real number or a string
*
* Note that leading zeroes automatically force a STRING type
*/
int CheckType(char *str) {
char *s;
int type=LNUMBER;
s = str;
if(*s=='0' && *(s+1) && *(s+1)!='.') return(STRING);
if(*s=='+' || *s=='-' || (*s>='0' && *s <='9') || *s=='.' ) {
if(*s=='.') type=DNUMBER;
s++;
while(*s) {
if(*s>='0' && *s<='9') {
s++;
continue;
}
else if(*s=='.' && type==LNUMBER) { type=DNUMBER; s++; continue; }
else return(STRING);
}
} else return(STRING);
return(type);
} /* CheckType */
表达式的栈管理
/* Expression Stack */
typedef struct Stack {
short type;
char *strval;
long intval;
double douval;
VarTree *var;
struct Stack *next;
int flag;
} Stack;
变量的存储
变量的存储,在这个版本中的 PHP 存储变量使用的并不是 HashTable,而是使用的树。这说明 PHP 的设计也是在不断优化调整的过程。
/* 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;
这个时候的变量的值全部都是存在 tree 中的,根据 type 来判断变量的类型。
socpe 实现了变量的作用域。
参考
- https://baike.baidu.com/item/Yacc/9855057?fr=aladdin
- https://www.php.net/manual/phpfi2.php
- 《深入理解PHP原理之函数(Introspecting PHP Function)》https://www.laruence.com/2008/08/12/164.html