php -s是高亮源代码的命令,所谓高亮源代码其实就是对词素进行一个颜色高亮,我们通过入口文件分析到在$PHPSRC/sapi/cli/php_cli.c中的do_cli函数里边接收了命令行的参数输入。-s的输入对应的是高亮源码。
紧接着,便是调用了Zend引擎的代码高亮的函数:zend_highlight。
在$PHPSRC/Zend/zend_highlight.c中,我们找到了zend_highlight的定义,zend_highlight()调用的就是词法分析器lex_scan来获取Token,然后加入对应的颜色。
到了这里,就真正进入词法分析的流程了。
其表达的意思就是:当我们词法解析器处于ST_IN_SCRIPTING这个状态时,遇到"exit"这个字符串就返回一个T_EXIT的Token标志(在Zend引擎中Token的宏都是以T_开头,其实际对应是一个数字)。你可以经常从语法错误提示信息中看到T_开头的提示信息,例如在:echo "Hello" World!/n";字符串中加多了一个双引号,运行时就会出现编译错误,这里边就有一个T_STRING的Token错误:

关键字Token回到lex词法描述文件上,前边说到词法扫描的入口在zend_language_scanner.l的第999行int lex_scan(zval *zendlval TSRMLS_DC)里。先定义一些前置的正则匹配:
对于一些无需复杂处理的关键字,我们扫描到对应的关键字,直接生成对应的Token标志即可,例如:
在lex文件中可以看到很多这样的规则声明,<ST_IN_SCRIPTING>是指扫描到这个关键字的前置条件是词法解析器要处于ST_IN_SCRIPTING这个状态,在lex文件里边有以下几种方式可以设置当前的词法解析器状态
当扫描到<?php时,在1790行设置了当前词法解析器的状态为ST_IN_SCRIPTING,其中HANDLE_NEWLINE是为了递增当前的zend_lineno,这个变量是用来记录当前解析到第几行。最后return一个T_OPEN_TAG出去。当遇到短标签<?=时,会先检查全局属性里边的short_tags有没有打开,没有的话就goto到inline_char_handler去处理,inline_char_handler对应的就是扫描不在PHP标签里边的字符了。
在1732行行定义了另外一种PHP语法打开标签,就是:<script language="php">echo 2;</script>
可以通过这个规则看出,如果在script里边加入其他属性就会导致这条规则失效,例如:<script language="php">echo 2;</script>就不会进行PHP语法解析了。
可以看出,PHP是支持#以及//两种方式的单行注释。处于ST_IN_SCRIPTING状态下,遇到"#"|"//",变触发了单行注释的扫描,从当前字符开始一直扫描到流缓冲区的末尾(也即是while(YYCURSOR < YYLIMIT))。遇到/r/n以及/n时,递增记录当前解析的行(zend_lineno++),为了更好容错性,PHP还兼容了//?>这样的语法,也即是说当行注释是不会注释到?>的,可以从case '?'这个分支看出Zend的处理,先让当前指针YYCURSOR--,回到?>前一个字符,然后跳出循环,这样才不会吃掉"?>"导致后边认不到PHP的关闭标签。多行注释的规则稍微复杂那么一点点:
首先可以看到/**是对应PHP文档声明的解析(在文档中是可以书写PHP变量,在变量解析那里可以看到这个问题),紧接着一个while循环扫描到*/的位置,如果一直到文件结尾都没扫到*/,那就zend_error一个Waring错误,但是不会影响接下去的解析。
其实对于代码来说,数字其实也是字符,词法分析器扫描到这5个规则的时候,需要把当前的zendlval对应的解析成数字存起来,同时返回一个数字类型的Token标志,看最简单的LNUM规则处理:
首先检查一下当前的字符串是否超出C语言的long类型长度,如果不超过,直接接调用strtol把字符串转换成long int类型。如果超出了long的范围,Zend还是尝试看看能不能转,如果发生溢出(error == ERANGE)那就把当前数字转成double类型。至于DNUM、BNUM等就不占篇幅了。
有三种变量的声明调用方式,$var, $var->PRop, $var["key"]。注意到yyless调用,yyless的宏定义声明在69行:
因为词法扫描的时候已经吃掉了"$var->",而我们只需要提取出变量名"var",因此我们需要让YYCURSOR指针重新回到"var->"的"-"位置,因此调用了yyless(yyleng-3)。紧接着都是通过zend_copy_value拷贝变量名到zendlval里边记录起来供之后语法解析阶段插入到符号表里边去。这里再讨论一个关于$var->prop的规则,
我们留意到1193行有个奇怪的规则,为什么在ST_LOOKING_FOR_PROPERTY下还可以再有->呢,研究了一下,原来这里是为了检验$var->prop1->prop2这第2+个的->。
首先留意到b?['],字符串前边能加上b声明?但是在之后的代码中压根没看出这个b的声明对字符串有什么影响。在http://php.net/manual/zh/language.types.string.php里边有这样一句描述:
原来这b是为了声明一个二进制字符串用的。再留意到2022行,为什么遇到'//'要让YYCURSOR++呢?因为在字符串中/后边带的是转义字符,这里让YYCURSOR++的目的就是为了跳过下一个字符,例如:'/'',如果不跳过第二个单引号的话,我们扫描到第二个引号就会认为字符串结束了。接下去的处理就比较简单了,从输入流中取出字符串的内容,返回一个T_CONSTANT_ENCAPSED_STRING的Token标志。双引号的字符串处理就复杂一点了:
双引号里边是支持变量的!$hello = "Hello"; $str = "${hello} World";留意到2085行,如果双引号字符串里边没有变量,直接就返回一个字符串了,从这里看出,其实双引号字符串在没有包含$的情况下的效率跟单引号字符串是差不多的。如果遇到了变量!这个时候就要切换到ST_DOUBLE_QUOTES状态了:
现在又回到了寻找变量的规则,其他的规则就不占篇幅了,讨论一个细节,我们回到1871行:
注意到扫描到"$var["这种情况的时候,会压入一个新的状态ST_VAR_OFFSET,同时在1889这条规则里边有前置条件ST_VAR_OFFSET的存在,这个是为了扫描到$var[$key][$key]这样的情况,细心点还可以留意到字符串里边的数组变量的key是不允许用->的,例如:$str = "$var[$a->s]";这样是不符合语法的,会出现一个解析错误:Parse error: syntax error, unexpected '-', expecting ']' in xxx.php

新闻热点
疑难解答
图片精选