設計一種面向物件指令碼語言
有沒有感覺設計一門語言實在是太有意思了,可以自定義語法規則,我的“地盤聽我的”。
指令碼語言的功能
本書設計一門純粹的面向物件指令碼語言,任何語言都有個名詞,這裡給這個語言起個名字——sparrow(麻雀)。它支援的功能如下。
1 變數
支援區域性變數和區域性變數的定義。
變數可引用、賦值。
內部複合資料型別以大寫字元開頭,如System.print()
2 基本資料型別
數值:包括整數和浮點數。
字串:包括普通字元和unicode。
list:列表,如Python中的list。
支援字面量建立,如['a', 'b']和new方法建立。
元素通過下標list'[索引]'獲得。
map:雜湊陣列,如Python中的字典。支援字面量建立和new建立。字面量建立如:
{
'k1':v1
"k2":v2
}
key可以是任何資料型別。同樣支援new方法建立。
value通過下標map'[key]'獲得。
range:用以確定一段整數範圍,用符號..表示。range包括from和to兩個成員,分別表示這段範圍的起和始,用區間表示[from, to],即包括from和to。如range“2..6”,2就是from,6就是to,“2..6”表示2、3、4、5、6。“..”類似於Python中的分片操作符“:”,只不過我們包括了結尾的to,而Python不包括,若用區間表示則to後面的是右小括號“)”。
3 運算
數值:+、−、*、/、%。
邏輯:>、<、==、!=、||、&&、?:、|、&等。
位運算:>>、<<。
方法呼叫:.。
索引:[]。
字串:+、%(字串內的表示式)
4 控制結構
支援if-else選擇。
支援while迴圈。
支援for迴圈。
支援break退出迴圈。
支援continue,跳過本次迴圈體後面的部分,繼續下一輪迴圈。
支援return返回。
5 函式
儘管這是一門面向物件語言,但也支援傳統意義上的函式,用關鍵字fun實現函式定義。
函式也是用類實現。
支援函式過載。
6 類
就是傳統意義上的class,包括類定義和類例項,靜態類。
實現繼承,所有類都是object類的子類。
類成員(也稱域,或欄位)必須先宣告再引用。
方法包括method、getter、setter、subscript、subscriptSetter和建構函式。支援塊引數,塊引數的引數是用“||”括起來的引數列表,以逗號分隔。
支援靜態方法。
7 執行緒
支援執行緒建立及排程。
8 模組
支援執行模組和模組內模組變數的單獨匯入。
9 註釋
行註釋://
塊註釋:/* 塊註釋 */
以上列舉若有遺漏則以實際程式碼為主。
關鍵字
有以下關鍵字被提前徵用了。
var:用於變數定義。
fun:用於函式定義。
if:用於條件判斷。
else:用於條件判斷的else分支。
true:bool值真。
false:bool值假。
while:用於while迴圈。
for:用於for迴圈。
break:用於退出迴圈。
continue:用於結束本次迴圈並進入下一輪迴圈。
return:用於從函式返回。
null:空值。
class:用於類定義。
is:用於判斷類是否為某類的子類,即“is a”。
static:用於設定靜態文法。
this:用於指向本例項。
super:用於指向父類。
import:用於匯入模組。
指令碼的執行方式
我們採用傳統的虛擬機器作為執行方式,即要實現一個虛擬機器。編譯器先把原始碼編譯為opcode,再讓虛擬機器執行opcode。
opcode即操作碼,是自定義的一套專供虛擬機器執行的指令,後面我們在實現虛擬機器時會詳細介紹。
“純手工”的開發環境
既然本著教學的目的我覺得應該拿出教學的誠意,因此這裡所說的“純手工”是指編碼中不想借助STL或其他類似的泛型語言,沒有第三方庫,一切以最基礎最原始的形式展現語言的奧祕,因此選擇了C語言,確定地說是C89,並不是較新的C99標準,誠意滿滿,讓我們純手工去編碼吧。
基礎開發環境是:
宿主系統是Linux,採用CentOS release 6.8 (Final);
編譯器是gcc,版本是gcc version 4.4.7 20120313 (Red Hat 4.4.7-17) (GCC)。
定義sparrow語言的文法
在之前介紹的文法中,我們採用的是大寫字母表示非終結符,小寫字母表示終結符,然而我們也說過了,在現實中為了便於程式設計,一般都用正規文法來定義語言,正規文法說白了就是用正則表示式定義的文法,因此本小節的基礎是正則表示式,為保證容易看懂,我會用最簡單的方式書寫正則表示式。
值得注意的是,正規文法與第0章中介紹的文法有很大的差別,主要是涉及終結符是用''表示,原因是正規文法中會涉及()、[]和{},這些在正規文法中都是元字元,有其特殊含義:()表示成為一組,[]表示範圍,{}表示重複。
但在實際語言它們只是字串字面量(即終結符),比如在語言中()表示函式名後面的括號,也可表示表示式中的小括號,[]表示下標索引,因此為避免衝突,正規文法中用單引號括起的是終結符。
其實語法和傳統語言差不多,只是用文法來描述就顯得生澀了。注意,正規文法中的[]與EBNF中的意義不同,在此表示範圍,其中可以用-表示一段連續的範圍,比如0-9就表示0至9之間(包括0和9)的任何數;a-z同理,表示字母a到字母z之間的任何字母。[]後面一般會接量詞,當然量詞不一定只用在[]之後,但它一定是用在某個字元之後以表示該字元的數量,其前不能沒有字元。
按照數量級別劃分有3種量詞,+表示重複出現1次以上,*表示重複出現0次以上,?表示出現0次或1次,比如可用[\t]*表示0個或多個空白字元,其中\t是tab。
注意此處[]中的空白是空格,為了突顯這裡有個空格就寫了兩個。.表示任意字元,包括控制字元比如回車等,|表示或者、任意其一,比如a|b,表示a和b兩者取其一,注意,|是對兩邊的整體有效,並不是只對緊鄰|的有效。
比如對於ab|cd的意思是ab或者cd,如果想表達abd或acd,可以用分組符號(),就是小括號對兒。()表示作為一組考慮,使相應的正則符號應用於整個組成員。
初次接觸文法的讀者可能對遞迴定義感到“消化不良”,比如非終結符exp是用於定義表示式,exp是由infixExp等非終結符組成,而infixExp又是由exp組成,看上去有點死迴圈出不來了,但你不要忘了,infixExp只是exp的其中一個組成部分,exp還可以由num、id等指代,num和id下面再無遞迴定義,這就是遞迴終止的條件。因此infixExp的組成部分exp也會是num或id等。
下面是具體的樣本。
以下是上述檔案的執行結果,其中的spr是最終的指令碼直譯器(包括編譯器及虛擬機器),spr是sparrow的縮寫。
以上./spr manager.sp就是執行指令碼檔案manager.sp,這與任何指令碼語言的執行方法都是一致的,執行過就是指令碼的輸出,大家有興趣可以核對一下結果,除了System.clock返回的時間戳是動態變化的外,其他不變。
鄭鋼 著
本書全面從指令碼語言和虛擬機器介紹開始,講解了詞法分析的實現、一些底層資料結構的實現、符號表及類的結構符號表,常量儲存,區域性變數,模組變數,方法儲存、虛擬機器原理、執行時棧實現、編譯的實現、語法分析和語法制導自頂向下算符優先構造規則、除錯、檢視指令流、檢視執行時棧、給類新增更多的方法、垃圾回收實現、新增命令列支援命令列介面。
小福利
你認為程式設計師存在的最大價值是什麼,參與話題有機會獲獎哦。截止時間9月7日17時,留言+轉發本活動到朋友圈,小編將抽獎選出3名讀者贈送紙書1本。
掃碼關注我們
點選閱讀原文,直接購買《自制程式語言》
閱讀原文