一步步寫lua直譯器--語法分析
當我們從原始碼中讀取到一個個單詞token之後,就需要將這些token轉換為實際的語句了。一門語言的產生是有一定的道理,是用來解決實際問題的,沒人會吃飽了沒事幹發明一門語言。例如在學數學時要求一個一元一次方程的結果即解方程ax + b = c,我們可以寫如下程式碼:
local f = funciton(a, b, c)
if a ~= 0 then
local x = (c - b ) / a
return x
else
...
end
end
t = {a=5, b = 6, c = 7}
f(t.a, t.b, t.c)
除了迴圈,以上程式碼基本上包含了一門簡單語言必有的語句:變數宣告,賦值,if else語句,運算,函式呼叫等等。當解析器遇到這麼多各不相同的語句時,是怎樣管理這些語句,這些語句最終又是怎麼樣變成可執行的程式碼的呢?一臺既可以打遊戲又可以上網聽歌的電腦是怎麼生產出來的呢?答案是由一個個零件組裝起來的。
程式碼也是,再大再複雜的程式碼也可以分解成為基本的模組。例如賦值語句x = (c-b)/a可以分解為賦值左邊變數x和右邊表示式(c-b)/a;表示式又是兩種基本運算的組合:減法和除法;if語句有邏輯判斷部分和要選擇執行的模組程式碼;函式的構成部分為:函式名,引數列表和函式體模組。總之,再複雜的語句都可以分解為幾個基本的語句,再複雜的程式碼都可以轉化為賦值,運算,邏輯比較等指令。
我們再來分析如何解析這些程式碼語句。拿函式來舉例,一個函式有函式名,函式引數,函式體。這些可以看做是函式語句的子語句。而函式體又有語句,且語句的數量不確定。我們把函式體語句當做函式體的子語句也可以,但是由於不知道有多少個子語句,處理起來不太好,而且這樣細分子語句會導致越來越複雜。一個較好的方法是將函式體的子語句就當做函式體,也就是說函式體就是函式子語句列表的形式。舉個簡單例子:
f = function(a, b)
local x = a + b
x = x * 2
reurn x
end
分解後類似於如下結構:
函式 :
名字 :f
引數 :a -> b
函式體 :local x=a+b -> x=x* 2 -> return x
函式體就是這三個語句的列表,而不是他再有三個子語句。我們把這三個語句叫做兄弟語句,而函式體相對函式來說是子語句。
所以我們要構造一個結構,即能包含幾個子結構,也能擁有兄弟結構。所有結構我們可以用一個base結構的指標來表示。相信有c語言基礎的朋友都能構建出來:
class { protected: TreeNodeBase* _child[Child_Num_Const]; TreeNodeBase* _pParent; TreeNodeBase* _pNext; ...
其他語句也基本這樣來構造,再舉個多值賦值的語句:
local a, b, c = 1, 2+3, f()
賦值語句:
變數左值列表: a -> b -> c
表示式右值列表: 1 -> 2+3 -> f()
還有幾種語句需要特別的處理,例如:
t.a.b.c = 1
那究竟t.a.b.c該如何來解析呢?先說簡單的t.a,我們可以把他看做是一個表訪問語句,子語句分別為t和a。t.a.b.c可以看做是有3個巢狀的語句:
t.a.b和c,t.a.b又分為t.a,b,t.a分為t,a。為什麼不反過來分為t,a.b.c呢?這個後面講table的時候專門會講到。
同理if elseif elseif ... else 也是這樣類似的巢狀解析,這個後面也會講到。
好了,相信大家明白怎麼樣來進行語法分析了,所有的語句都可以按照以上的方法來進行解析。一個lua檔案的所有程式碼可以看做是一個大的塊,塊包含語句列表。最終,一個lua檔案會生成一個樹狀的大型結構體。
解析完語句下一步就要生成程式碼了,我們拭目以待。有問題可以在後面留言,或者加入QQ群 858791125 討論。