在前面我們提到語言轉化的編譯過程一般分為詞法分析、語法分析、語義分析、中間代碼生成、代碼優化、目標代碼生成等六個階段。不管是編譯型語言還是解釋型語言,掃描(詞法分析)總是將程序轉化成目標語言的第一步。詞法分析的作用就是將整個源程序分解成一個一個的單詞,這樣做可以在一定程度上減少后面分析工作需要處理的個體數量,為語法分析等做準備。除了拆分工作,更多的時候它還承擔著清洗源程序的過程,比如清除空格,清除注釋等。詞法分析作為編譯過程的第一步,在業界已經有多種成熟工具,如PHP在開始使用的是Flex,之后改為re2c,MySQL的詞法分析使用的Flex,除此之外還有作為UNIX系統標準詞法分析器的Lex等。這些工具都會讀進一個代表詞法分析器規則的輸入字符串流,然后輸出以C語言實做的詞法分析器源代碼。這里我們只介紹PHP的現版詞法分析器,re2c。
[re2c](http://www.re2c.org/)是一個掃描器制作工具,可以創建非常快速靈活的掃描器。它可以產生高效代碼,基于C語言,可以支持C/C++代碼。與其它類似的掃描器不同,它偏重于為正則表達式產生高效代碼(和他的名字一樣)。因此,這比傳統的詞法分析器有更廣泛的應用范圍。你可以在[sourceforge.net](http://sourceforge.net/projects/re2c/)獲取源碼。
PHP在最開始的詞法解析器是使用的是Flex,后來改為使用re2c。在源碼目錄下的Zend/zend_language_scanner.l 文件是re2c的規則文件,如果需要修改該規則文件需要安裝re2c才能重新編譯,生成新的規則文件。
re2c調用方式:
re2c [-bdefFghisuvVw1] [-o output] [-c [-t header]] file
我們通過一個簡單的例子來看下re2c。如下是一個簡單的掃描器,它的作用是判斷所給的字符串是數字/小寫字母/大小字母。當然,這里沒有做一些輸入錯誤判斷等異常操作處理。示例如下:
#include <stdio.h>
?
char *scan(char *p){
#define YYCTYPE char
#define YYCURSOR p
#define YYLIMIT p
#define YYMARKER q
#define YYFILL(n)
/*!re2c [0-9]+ {return "number";} [a-z]+ {return "lower";} [A-Z]+ {return "upper";} [^] {return "unkown";} */
}
?
int main(int argc, char* argv[])
{
printf("%s\n", scan(argv[1]));
?
return 0;
}
如果你是在ubuntu環境下,可以執行下面的命令生成可執行文件。
re2c -o a.c a.l
gcc a.c -o a
chmod +x a
./a 1000
此時程序會輸出number。
我們解釋一下我們用到的幾個re2c約定的宏。
- YYCTYPE 用于保存輸入符號的類型,通常為char型和unsigned char型
- YYCURSOR 指向當前輸入標記, -當開始時,它指向當前標記的第一個字符,當結束時,它指向下一個標記的第一個字符
- YYFILL(n) 當生成的代碼需要重新加載緩存的標記時,則會調用YYFILL(n)。
- YYLIMIT 緩存的最后一個字符,生成的代碼會反復比較YYCURSOR和YYLIMIT,以確定是否需要重新填充緩沖區。
參照如上幾個標識的說明,可以較清楚的理解生成的a.c文件,當然,re2c不會僅僅只有上面代碼所顯示的標記,這只是一個簡單示例,更多的標識說明和幫助信息請移步 [re2c幫助文檔](http://re2c.org/manual.html):[http://re2c.org/manual.html](http://re2c.org/manual.html)。
我們回過頭來看PHP的詞法規則文件zend_language_scanner.l。你會發現前面的簡單示例與它最大的區別在于每個規則前面都會有一個條件表達式。
> NOTE re2c中條件表達式相關的宏為YYSETCONDITION和YYGETCONDITION,分別表示設置條件范圍和獲取條件范圍。 在PHP的詞法規則中共有10種,其全部在zend_language_scanner_def.h文件中。此文件并非手寫, 而是re2c自動生成的。如果需要生成和使用條件表達式,在編譯成c時需要添加-c 和-t參數。
在PHP的詞法解析中,它有一個全局變量:language_scanner_globals,此變量為一結構體,記錄當前re2c解析的狀態,文件信息,解析過程信息等。它在zend_language_scanner.l文件中直接定義如下:
#ifdef ZTS
ZEND_API ts_rsrc_id language_scanner_globals_id;
#else
ZEND_API zend_php_scanner_globals language_scanner_globals;
#endif
在zend_language_scanner.l文件中寫的C代碼在使用re2c生成C代碼時會直接復制到新生成的C代碼文件中。這個變量貫穿了PHP詞法解析的全過程,并且一些re2c的實現也依賴于此,比如前面說到的條件表達式的存儲及獲取,就需要此變量的協助,我們看這兩個宏在PHP詞法中的定義:
// 存在于zend_language_scanner.l文件中
#define YYGETCONDITION() SCNG(yy_state)
#define YYSETCONDITION(s) SCNG(yy_state) = s
#define SCNG LANG_SCNG
?
// 存在于zend_globals_macros.h文件中
# define LANG_SCNG(v) (language_scanner_globals.v)
結合前面的全局變量和條件表達式宏的定義,我們可以知道PHP的詞法解析是通過全局變量在一次解析過程中存在。那么這個條件表達式具體是怎么使用的呢?我們看下面一個例子。這是一個可以識別為結束,識別字符,數字等的簡單字符串識別器。它使用了re2c的條件表達式,代碼如下:
#include <stdio.h>
#include "demo_def.h"
#include "demo.h"
?
Scanner scanner_globals;
?
#define YYCTYPE char
#define YYFILL(n)
#define STATE(name) yyc##name
#define BEGIN(state) YYSETCONDITION(STATE(state))
#define LANG_SCNG(v) (scanner_globals.v)
#define SCNG LANG_SCNG
?
#define YYGETCONDITION() SCNG(yy_state)
#define YYSETCONDITION(s) SCNG(yy_state) = s
#define YYCURSOR SCNG(yy_cursor)
#define YYLIMIT SCNG(yy_limit)
#define YYMARKER SCNG(yy_marker)
?
int scan(){
/*!re2c ? <INITIAL>"<?php" {BEGIN(ST_IN_SCRIPTING); return T_BEGIN;} <ST_IN_SCRIPTING>[0-9]+ {return T_NUMBER;} <ST_IN_SCRIPTING>[ \n\t\r]+ {return T_WHITESPACE;} <ST_IN_SCRIPTING>"exit" { return T_EXIT; } <ST_IN_SCRIPTING>[a-z]+ {return T_LOWER_CHAR;} <ST_IN_SCRIPTING>[A-Z]+ {return T_UPPER_CHAR;} <ST_IN_SCRIPTING>"?>" {return T_END;} ? <ST_IN_SCRIPTING>[^] {return T_UNKNOWN;} <*>[^] {return T_INPUT_ERROR;} */
}
?
void print_token(int token) {
switch (token) {
case T_BEGIN: printf("%s\n", "begin");break;
case T_NUMBER: printf("%s\n", "number");break;
case T_LOWER_CHAR: printf("%s\n", "lower char");break;
case T_UPPER_CHAR: printf("%s\n", "upper char");break;
case T_EXIT: printf("%s\n", "exit");break;
case T_UNKNOWN: printf("%s\n", "unknown");break;
case T_INPUT_ERROR: printf("%s\n", "input error");break;
case T_END: printf("%s\n", "end");break;
}
}
?
int main(int argc, char* argv[])
{
int token;
BEGIN(INITIAL); // 全局初始化,需要放在scan調用之前
scanner_globals.yy_cursor = argv[1]; //將輸入的第一個參數作為要解析的字符串
?
while(token = scan()) {
if (token == T_INPUT_ERROR) {
[printf](http://www.opengroup.org/onlinepubs/009695399/functions/printf.html)("%s\n", "input error");
break;
}
if (token == T_END) {
[printf](http://www.opengroup.org/onlinepubs/009695399/functions/printf.html)("%s\n", "end");
break;
}
print_token(token);
}
?
return 0;
}
和前面的簡單示例一樣,如果你是在linux環境下,可以使用如下命令生成可執行文件
re2c -o demo.c -c -t demo_def.h demo.l
gcc demo.c -o demo -g
chmod +x demo
在使用re2c生成C代碼時我們使用了-c -t demo_def.h參數,這表示我們使用了條件表達式模式,生成條件的定義頭文件。main函數中,在調用scan函數之前我們需要初始化條件狀態,將其設置為INITIAL狀態。然后在掃描過程中會直接識別出INITIAL狀態,然后匹配<?php字符串識別為開始,如果開始不為<?php,則輸出input error。在掃描的正常流程中,當掃描出<?php后,while循環繼續向下走,此時會再次調用scan函數,當前條件狀態為ST_IN_SCRIPTING,此時會跳過INITIAL狀態,直接匹配<ST_IN_SCRIPTING>狀態后的規則。如果所有的<ST_IN_SCRIPTING>后的規則都無法匹配,輸出unkwon。這只是一個簡單的識別示例,但是它是從PHP的詞法掃描器中抽離出來的,其實現過程和原理類似。
那么這種條件狀態是如何實現的呢?我們查看demo.c文件,發現在scan函數開始后有一個跳轉語句:
int scan(){
?
#line 25 "demo.c"
{
YYCTYPE yych;
switch (YYGETCONDITION()) {
case yycINITIAL: goto yyc_INITIAL;
case yycST_IN_SCRIPTING: goto yyc_ST_IN_SCRIPTING;
}
...
}
在zend_language_scanner.c文件的lex_scan函數中也有類型的跳轉過程,只是過程相對這里來說if語句多一些,復雜一些。這就是re2c條件表達式的實現原理。
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和Zend引擎
- 第二節 SAPI概述
- Apache模塊
- 嵌入式
- FastCGI
- 第三節 PHP腳本的執行
- 詞法分析和語法分析
- opcode
- opcode處理函數查找
- 第四節 小結
- 第三章 變量及數據類型
- 第一節 變量的結構和類型
- 哈希表(HashTable)
- PHP的哈希表實現
- 鏈表簡介
- 第二節 常量
- 第三節 預定義變量
- 第四節 靜態變量
- 第五節 類型提示的實現
- 第六節 變量的生命周期
- 變量的賦值和銷毀
- 變量的作用域
- global語句
- 第七節 數據類型轉換
- 第八節 小結
- 第四章 函數的實現
- 第一節 函數的內部結構
- 函數的內部結構
- 函數間的轉換
- 第二節 函數的定義,傳參及返回值
- 函數的定義
- 函數的參數
- 函數的返回值
- 第三節 函數的調用和執行
- 第四節 匿名函數及閉包
- 第五節 小結
- 第五章 類和面向對象
- 第一節 類的結構和實現
- 第二節 類的成員變量及方法
- 第三節 訪問控制的實現
- 第四節 類的繼承,多態及抽象類
- 第五節 魔術方法,延遲綁定及靜態成員
- 第六節 PHP保留類及特殊類
- 第七節 對象
- 第八節 命名空間
- 第九節 標準類
- 第十節 小結
- 第六章 內存管理
- 第一節 內存管理概述
- 第二節 PHP中的內存管理
- 第三節 內存使用:申請和銷毀
- 第四節 垃圾回收
- 新的垃圾回收
- 第五節 內存管理中的緩存
- 第六節 寫時復制(Copy On Write)
- 第七節 內存泄漏
- 第八節 小結
- 第七章 Zend虛擬機
- 第一節 Zend虛擬機概述
- 第二節 語法的實現
- 詞法解析
- 語法分析
- 實現自己的語法
- 第三節 中間代碼的執行
- 第四節 PHP代碼的加密解密
- 第五節 小結
- 第八章 線程安全
- 第二節 線程,進程和并發
- 第三節 PHP中的線程安全
- 第九章 錯誤和異常處理
- 第十章 輸出緩沖
- 第十六章 PHP語言特性的實現
- 第一節 循環語句
- foreach的實現
- 第二十章 怎么樣系列(how to)
- 附錄
- 附錄A PHP及Zend API
- 附錄B PHP的歷史
- 附錄C VLD擴展使用指南
- 附錄D 怎樣為PHP貢獻
- 附錄E phpt測試文件說明
- 附錄F PHP5.4新功能升級解析
- 附錄G:re2c中文手冊