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

新聞熱點
疑難解答
圖片精選