【PHP7源碼學習】2019-03-20 PHP詞法分析

baiyanphp

所有視頻:https://segmentfault.com/a/11...node

原視頻地址:http://replay.xesv5.com/ll/24...正則表達式

基本概念

  • 在PHP7中,當一個腳本運行請求或到來時,PHP代碼首先會被加載到內存中,隨後進行詞法分析和語法分析並生成抽象語法樹(AST),而後進行深度優先遍歷並生成opcodes,並在zend虛擬機中執行這些opcode,返回最終的執行結果。
  • 詞法分析:它是編譯過程的第一個階段。這個階段會將代碼從左到右按字符掃描並讀入,而後將代碼劃分紅一個又一個的詞法單元(token),方便後續的語法分析以及編譯優化等操做。這就比如用菜刀去切一塊肉,將這塊大肉切分紅一小塊一小塊肉,方便人們後續食用。可使用Re2c、lex等自動化詞法分析工具,來幫助咱們完成。
  • 語法分析:語法分析是編譯過程的一個邏輯階段。語法分析的任務是在詞法分析的基礎上將詞法單元token,組合成各種語法短語,如「程序」,「語句」,「表達式」等等,它可以判斷源程序在結構上是否正確。
  • 在PHP中,使用的詞法分析器是Re2c,語法分析器是Bison。
  • 其實進行詞法分析和語法分析並生成某種數據結構的過程,就是一個解碼的過程。
之因此須要作這種從字符串到數據結構(AST)的轉換,是由於編譯器是沒法直接操做「1+2」這樣的字符串的。實際上,代碼的本質根本就不是字符串,它原本就是一個具備複雜拓撲的數據結構,就像電路同樣。「1+2」這個字符串只是對這種數據結構的一種「編碼」,就像ZIP或者JPEG只是對它們壓縮的數據的編碼同樣。
這種編碼能夠方便你把代碼存到磁盤上,方便你用文本編輯器來修改它們(對人友好,方便人們編寫代碼,可是對編譯器不友好),然而你必須知道,文本並非代碼自己。因此從磁盤讀取了文本以後,你必須先「解碼」,才能方便地操做代碼的數據結構。好比,若是上面的Java代碼生成的AST節點叫node,你就能夠用node.operator來訪問加號ADD,用node.left來訪問1,node.right來訪問2。這是很方便的。對於程序語言,這種解碼的動做就叫作parsing,用於解碼的那段代碼就叫作parser。
  • 關於語法分析與詞法分析的具體概念解釋,這篇文章寫得較好:對 Parser 的誤解
  • 咱們先利用PHP內置函數token_get_all()來取出一段PHP代碼的token:
<?php
$lan = '<?php $a = 1; echo $a';
$tokens = token_get_all($lan);
foreach ($tokens as $token) {
    if (is_array($token)) {
        echo "Line {$token[2]}: ", token_name($token[0]), " ('{$token[1]}')", PHP_EOL;
    }
}
  • 打印結果爲:
Line 1: T_OPEN_TAG ('<?php ')
Line 1: T_VARIABLE ('$a')
Line 1: T_WHITESPACE (' ')
Line 1: T_WHITESPACE (' ')
Line 1: T_LNUMBER ('1')
Line 1: T_WHITESPACE (' ')
Line 1: T_ECHO ('echo')
Line 1: T_WHITESPACE (' ')
Line 1: T_VARIABLE ('$a')
  • 觀察以上結果,能夠看有OPEN_TAG/VARIABLE/WHITESPACE等等詞法單元token。

如何取出token

  • 那麼讓咱們你本身去設計一個算法,從一個字符串中識別並取出token,應該怎麼作?算法

    • 使用兩個指針,一個標記開始位置,一個日後挪,而後回溯。(較麻煩)
    • 使用正則表達式進行匹配
  • 當用較簡單的字符串匹配正則表達式的時候,能夠用人眼很容易地看出來。可是若是用很複雜的字符串(成千上萬行代碼)去匹配一個正則,是至關麻煩而且很是慢的,編譯原理中提出了這樣一個概念用以解決這個問題:有窮狀態機
  • 有窮狀態機:必須有一個起始狀態,用一個箭頭加圓圈表示;也得有一個結束,用兩個圓圈表示。 若是知足某個條件,就會從一個狀態躍遷到另外一個狀態,也用箭頭來表示。

例:觀察下面這個正則表達式:segmentfault

(a|b)*abb
  • 根據這個正則表達式,咱們能夠畫出它的有窮狀態機:

- 對於a,只能到狀態0或者1,不能到達結束的3,因此不匹配
- 對於abb,第一個a可使狀態0躍遷到1,第二個b能夠從1躍遷到2,最後一個b結束,因此匹配
- 對於aabb,第一個a能夠選擇從0躍遷到0,第二個從0躍遷到1,後面兩個b同上,匹配
- 對於cabb,第一個c就沒法知足,不匹配
  • 這裏有個問題,輸入第一個a的時候,能夠從0躍遷到本身,也能夠從0躍遷到1,因此這種狀態機就叫不肯定有窮狀態機(NFA)
  • NFA是有缺陷的,好比aabb,有可能一直從0躍遷到0,共重複了4次這樣的操做,也沒有到達最終的結束狀態3。這就會致使本應該符合匹配要求的字符串,在不肯定有窮狀態機中,錯誤地被斷定爲不符合匹配要求。解決此問題的辦法就是將不肯定有窮狀態機轉化爲肯定有窮狀態機(DFA)。

  • 這樣一來,一個肯定的輸入就對應着一個肯定的輸出(假設如給一個a,必定躍遷到1;給一個b,必定躍遷回0),不存在歧義問題。
  • 可是,將一個NFA轉化成DFA是至關複雜的,因此有工具已經爲咱們作好了這個事情:Re2c。你只須要輸入一個正則表達式,就可以爲你生成一個肯定有窮狀態機(DFA),在Re2c工具中以C/C++代碼體現,詳情見:re2c中文手冊
相關文章
相關標籤/搜索