廣義而言,語言是一套采用共同符號、表達方式與處理規則。就編程語言而言,編程語言也是特定規則的符號,用來傳達特定的信息,自然語言是人與人之間溝通的渠道,而編程語言則是機器之間,人與機器之間的溝通渠道。人有非常復雜的語言能力,語言本身也在不斷的進化,人之間能夠理解復雜的語言規則,而計算機并沒有這么復雜的系統,它們只能接受指令執行操作,編程語言則是機器和人(準確說是程序員)之間的橋梁,編程語言的作用就是將語言的特定符號和處理規則進行翻譯,由編程語言來處理這些規則,
目前有非常多的編程語言,不管是靜態語言還是動態語言都有固定的工作需要做:將代碼編譯為目標指令,而編譯過程就是根據語言的語法規則來進行翻譯,我們可以選擇手動對代碼進行解析,但這是一個非常枯燥而容易出錯的工作,尤其是對于一個完備的編程語言而言,由此就出現了像lex/yacc這類的編譯器生成器。
編程語言的編譯器(compiler)或解釋器(interpreter)一般包括兩大部分:
1. 讀取源程序,并處理語言結構。
1. 處理語言結構并生成目標程序。
Lex和Yacc可以解決第一個問題。第一個部分也可以分為兩個部分:
1. 將代碼切分為一個個的標記(token)。
1. 處理程序的層級結構(hierarchical structure)。
很多編程語言都使用lex/yacc或他們的變體(flex/bison)來作為語言的詞法語法分析生成器,比如PHP、Ruby、Python以及MySQL的SQL語言實現。
Lex和Yacc是Unix下的兩個文本處理工具, 主要用于編寫編譯器, 也可以做其他用途。
- Lex(詞法分析生成器:A Lexical Analyzer Generator)。
- Yacc(Yet Another Compiler-Compiler)
### Lex/Flex
Lex讀取詞法規則文件,生成詞法分析器。目前通常使用Flex以及Bison來完成同樣的工作, Flex和lex之間并不兼容,Bison則是兼容Yacc的實現。
詞法規則文件一般以.l作為擴展名,flex文件由三個部分組成,三部分之間用%%分割:
定義段
%%
規則段
%%
用戶代碼段
例如以下一個用于統計文件字符、詞以及行數的例子:
%option noyywrap
%{
int chars = 0;
int words = 0;
int lines = 0;
%}
?
%%
[a-zA-Z]+ { words++; chars += strlen(yytext); }
\n { chars++; lines++; }
. { chars++; }
%%
?
main(int argc, char **argv)
{
if(argc > 1) {
if(!(yyin = fopen(argv[1], "r"))) {
perror(argv[1]);
return (1);
}
yylex();
printf("%8d%8d%8d\n", lines, words, chars);
}
}
該解釋器讀取文件內容, 根據規則段定義的規則進行處理, 規則后面大括號中包含的是動作, 也就是匹配到該規則程序執行的動作,這個例子中的匹配動作時記錄下文件的字符,詞以及行數信息并打印出來。其中的規則使用正則表達式描述。
回到PHP的實現,PHP以前使用的是flex,[后來](http://blog.somabo.de/2008/02/php-on-re2c.html)PHP的詞法解析改為使用[re2c](http://re2c.org/),$PHP_SRC/Zend/zend_language_scanner.l 文件是re2c的規則文件, 所以如果修改該規則文件需要安裝re2c才能重新編譯。
### Yacc/Bison
> PHP在后續的版本中[可能會使用Lemon作為語法分析器](http://wiki.php.net/rfc/lemon), [Lemon](http://www.sqlite.org/src/doc/trunk/doc/lemon.html)是SQLite作者為SQLite中SQL所編寫的詞法分析器。 Lemon具有線程安全以及可重入等特點,也能提供更直觀的錯誤提示信息。
Bison和Flex類似,也是使用%%作為分界不過Bison接受的是標記(token)序列,根據定義的語法規則,來執行一些動作,Bison使用巴科斯范式([BNF](http://baike.baidu.com/view/1137652.htm))來描述語法。
下面以php中echo語句的編譯為例:echo可以接受多個參數,這幾個參數之間可以使用逗號分隔,在PHP的語法規則如下:
echo_expr_list:
echo_expr_list ',' expr { zend_do_echo(&$3 TSRMLS_CC); }
| expr { zend_do_echo(&$1 TSRMLS_CC); }
;
其中echo_expr_list規則為一個遞歸規則,這樣就允許接受多個表達式作為參數。在上例中當匹配到echo時會執行zend_do_echo函數,函數中的參數可能看起來比較奇怪, 其中的$3 表示前面規則的第三個定義,也就是expr這個表達式的值,zend_do_echo函數則根據表達式的信息編譯opcode,其他的語法規則也類似。這和C語言或者Java的編譯器類似,不過GCC等編譯器時將代碼編譯為機器碼,Java編譯器將代碼編譯為字節碼。
> 更多關于lex/yacc的內容請參考[Yacc 與Lex 快速入門](http://www.ibm.com/developerworks/cn/linux/sdk/lex/index.html)
下面將介紹PHP中的opcode。
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和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中文手冊