有沒有感覺設計一門語言實在是太有意思了,可以自定義語法規則,我的“地盤聽我的”。
腳本語言的功能
本書設計一門純粹的面向對象腳本語言,任何語言都有個名詞,這裏給這個語言起個名字——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返回的時間戳是動態變化的外,其他不變。
鄭鋼 著
本書全面從腳本語言和虛擬機介紹開始,講解了詞法分析的實現、一些底層數據結構的實現、符號表及類的結構符號表,常量存儲,局部變量,模塊變量,方法存儲、虛擬機原理、運行時棧實現、編譯的實現、語法分析和語法制導自頂向下算符優先構造規則、調試、查看指令流、查看運行時棧、給類添加更多的方法、垃圾回收實現、添加命令行支持命令行接口。