lua原始碼分析3(條件跳轉)

2021-04-21 12:57:33 字數 4609 閱讀 2320

第2章

關於條件編譯

首先,解釋一下,為什麼題目叫做:條件編譯。其實很簡單,現在這一章要分析的是,if、

while

、repeat

、for

語句。這些語句有個什麼特點呢?那就是,都要有條件判斷。根據條件判斷的結果,以決定是否執行,該如何執行。我不知道該怎麼稱呼這樣的語句,就一致稱其為:條件編譯。因為這一章的重點是研究,條件對於中間碼的生成造成的影響,以及如何生成中間碼以實現這種條件判斷。

首先分析

if then elseif then else then end

句型。

這個很簡單,實際上是

lua處理的很簡單。 當

lua遇到了

if這個關鍵字的時候,就開始呼叫

ifstat()

,首先,這是進入乙個塊。這個塊這個東西和函式是相同的重要,因為它關係到乙個變數的作用域的問題。塊由

block()

函式處理。不過在此之前,要注意乙個重要的事情,那就是條件判斷語句。

lua是怎麼處理條件判斷的呢?

首先,lua會跳過

if關鍵字,然後,會進入乙個十分常用的函式:

cond()

,這個函式還是比較複雜的,我現在要再看一遍這個函式,還是心有餘悸的。

cond()

這個函式比較短,我還是貼出來吧:

static

intcond (lexstate *ls)

第一步,用

expr()

這個函式讀取條件表示式,條件表示式是乙個式子,可以是以下幾種情況: 1、

常數:這種情況比較好辦,如果是

false

或nil

就是條件不通過,其他情況都是條件通過; 2、

另一種情況是非常數:也就是說,不是編譯期就能判斷是否為

true

或false

,要到執行期來判斷。

對於這兩種情況,分別分析,當然,首先是簡單的情況,常數。

當遇到常數的時候,

expr()

比較。因為它處理起來比較簡單,也就是通過

******exp()

直接處理。比如,這個常數是個數字

100,

******exp()

就會把這個表示式

v賦值為:

v->k = tk_number

,將v->u.nval = ls->t.seminfo.r

,也就是把

100給

v->u.nval

。如果這個常數是乙個字串,

******exp()

會將這個字串註冊進常量表,並將

v->k = tk_string

,v->u.s.info

賦值為這個字串在常量表中的位置。如果這個常量是

false,nil,true這3

個保留字,

******exp()

就會直接將

v->k

賦值成tk_false

,tk_nil

,tk_true。

然後,lua

會呼叫luak_goiftrue()

函式。這個函式就是生成判斷指令的函式了。

首先,這個函式會把要用到的變數

discharge

到空閒暫存器上去。也就是生成對應的載入變數的指令。由於現在我們研究的是常量,就不需要這一步,直接跳過。

然後,lua會判斷這個常量是

false

呢還是true

呢。如果是

true

,則將pc = no_jump

,這個pc

就是說,如果要生成指令的話,就會用這個

pc指向這個指令。如果是

fasle

,則會生成一條無條件跳轉指令:通過

luak_jump()

。這條指令會跳到哪呢?下面會有兩種情況: 1、

後面是elseif

,這時,就要跳到這條

elseif

前面,來做下一次條件判斷; 2、

後面是else

,這時,就跳到這條

else

前面或後面,因為

else

本身不生成指令。

怎麼實現跳轉到這個地方呢?當解析程式解析到

if語句開始時,如果要跳轉,就要跳過這個

if塊,到達下乙個

elseif

或else

程式塊。但是,解析程式怎麼知道下乙個

elseif

或else

塊的開頭位址在哪呢?當然,剛開始解析程式是不知道的。只有解析程式把這個

if塊解析完了,才能知道原來要跳轉到這裡。所以,之前要把要跳轉的那條指令的位置記錄下來,到解析完了

if塊後,再將那條指令跳轉的位置修正到此處。

lua中就是這麼實現的。

具體就是,

lua會通過

luak_pathtohere()

函式,將要跳轉的那條指令儲存在

fs->jpc

裡,這個

fs->jpc

就是指上次要跳轉的指令。但是,

lua不會立即將跳轉指令修改到這裡。因為這個

elseif

或else

塊中可能沒有**,也就是說,就算跳到這裡也是沒有的,不會執行任何**,這時,

lua就進行了優化,只有當

lua在這個塊裡生成**的時候,也就是在

luak_code()

函式裡,呼叫

dischargejpc()

,將儲存在

fs->jpc

裡的指令修改為跳到這個地方。

基本的跳轉原理就是這樣的

了。明白了這些後,再來看複雜條件的跳轉是怎麼實現的。也就是,在編譯期間並不知道條件的結果,到底是真還是假,也就是說,要到執行期間才知道到底要不要跳轉。假設我們現在研究這個例子: 例:

if a > b then end

這裡,就要回到

cond()

函式裡了。看

cond()

函式是怎麼處理

a > b

這種執行時才能出結果的情況。

首先,cond()

會通過呼叫

******exp()讀取a

,然後會讀取下乙個操作符,發現是

>

,這個時候,還不能生成**,因為這裡關係到乙個操作符優先順序的問題,要接著讀取後面的操作符,先把優先順序高的操作符處理後,才能處理低優先順序的操作符,所以,這裡就會進行遞迴讀取。在此之前,還有乙個問題,那就是,以邏輯操作符連線的式子,比如:

and,

or等,這些操作符的話,其實前面就是乙個式子,比如

and,如果是

a and b

,如果a

錯的話,後面的就不用測試了,如果

a 對的話,還要繼續測試

b,所以,在這裡,要為

a生成乙個測試及跳轉的語句,這裡就用到乙個函式

jumponcond()。

這個函式是怎麼實現的呢?這個函式其實是通過呼叫

condjump()

實現的。具體來說,很簡單,

condjump()

生成了2

個指令,第一條指令是判斷指令:

op_test

或者op_testset

,第二條指令是

op_jump

,也就是說,如果測試的結果是

false

,那麼,

op_test

不做任何事情,繼續執行,那麼就會執行到

op_jump

指令了,也就是

false

的時候不執行

if塊,直接跳轉,如果測試成功了呢,

op_test

會執行跳過下一條指令的操作,也就是跳過了

op_jump

,繼續執行

if塊。

這就是條件跳轉的基本實現原理。

現在回到

******exp()

中來,繼續我們的例子:

a > b

。當讀取了

>

好,會先用

luak_infix()

函式進行邏輯

and,

or這兩個邏輯運算的**生成。在這個例子中,沒有邏輯運算,於是,

lua會繼續。這個時候,像前面所說的,要進行操作符優先順序的運算,於是,進行遞迴

******exp()

這個函式,在遞迴的時候,發現後面只有乙個

b,那麼,就會讀取

b ,然後,就進入關鍵的地方了,

luak_posfix()

函式。這個函式會根據操作符的不同,生成操作指令,比如我們現在的

>

,就會通過

codecomp()

函式,生成

op_lt

指令。這個

op_lt

指令的執行其實進行了優化,也就是說,如果測試結果為

false

的話,也就是不通過,理論上是要進入下一條指令跳轉的,在執行期間,這兩條指令會在乙個指令週期裡執行,如果測試不通過,直接從下一條跳轉指令中取出要跳轉的位置,然後跳轉到那裡。如果測試成功的話,就會跳過下一天條跳轉指令,進入

if塊。

這就是條件跳轉**編譯的過程。

3 條件 迴圈

usr bin perl w use feature qw say 列印內容自動換行,不用手動加 n my var ifif var if else if var else if elsif 注意是elsif,不是elif,sv中也是elsif if var 10 elsif var 5 else ...

Lua 原始碼分析 lprefix h

lua 原始碼裡面很小的乙個標頭檔案,沒什麼東西,會預先進行一些設定,每個 c 檔案都會引用.ifndef lprefix h define lprefix h posix xsi if defined lua use c89 有關移植性 if defined xopen source define...

Lua 簡單Lua直譯器原始碼分析

include include include lua.h include lauxlib.h include lualib.h int main void lua close l return 0 lua.h定義了lua提供的基礎函式,包括建立lua環境 呼叫lua函式,它的定義是以lua 開頭的...