Lua語法分析(1)
歡迎關注公眾號《Lua探索之旅》。
Lua語法短小精悍,簡單易學,非開發人員也可以編寫一些簡單的指令碼,有開發經驗的人更是分分鐘上手。但麻雀雖小五臟俱全,要探索lua內部實現,必須全面掌握其語法。
推薦閱讀《lua5.1參考手冊》,該手冊為官方文件,由雲風(遊戲圈的大神,夢幻西遊技術負責人)翻譯。
Lua EBNF正規化
程式碼是程式設計思想的落地,核心在於設計思想,如非必要,不貼原始碼。
lua的語法規則是通過EBNF正規化定義的,理解了EBNF,也就理解了lua的語法分析,事實上lua的語法分析模組就是lua EBNF的程式碼實現。
lua EBNF如下圖所示,從本節開始將完整介紹各個推導式的含義。
chunk -> { stat [`;`] }
{}表示0個或多個, []表示0個或1個,chunk可推導為
chunk -> stat
chunk -> stat; stat; ...
lua的一個執行單元稱為chunk(資料塊),一個chunk可包含多條語句,比如
a = 1
b = a + 1
每一行表示一條statement(語句),若一行有多條statement,用分號分隔
a = 1; b = a + 1;
chunk可以儲存在一個檔案裡,也可以存入字串。當一個 chunk 被執行,首先它會被預編譯成虛擬機器中的指令序列,然後被虛擬機器解釋執行這些指令。
stat -> ifstat | whilestat | dostat | forstat | repeatstat | funcstat | localstat | retstat | breakstat | exprstat
|表示或,一條statement可以有多種表達形式,比如賦值語句、if語句、for語句、return語句等
每條statement根據第一個token型別決定使用哪種產生式。比如 "a=1" 的第一個token為TK_NAME, "if a>1 then ... end" 的第一個token為TOKEN_IF。
token型別和產生式的對應關係如下:
token | 產生式 | 例子 |
---|---|---|
TK_IF | ifstat | if a>1 then ... |
TK_WHILE | whilestat | while true do ... |
TK_DO | dostat | do return end |
TK_FOR | forstat | for i=1,2 do ... |
TK_REPEAT | repeatstat | repeat ... until |
TK_FUNCTION | funcstat | function f() ... end |
TK_LOCAL | localstat | local a=1 |
TK_RETURN | retstat | return a |
TK_BREAK | breakstat | break |
其他 | exprstat | a=1; a(); |
exprstat -> func | assignment
除去if、while、local、return等控制語句,exprstat表示一條執行語句,包含賦值和函式呼叫兩類,如 "a=1" 和 "print()"
lua將表示式分為 主表示式 primaryexp 和 任意表達式 explist1。
主表示式必須可以作為左值存在,能夠被賦值,比如 "a.b=1+2",其中 "a.b"為主表示式,"1+2"不能為主表示式。
在exprstat裡,等號'='左邊為一個主表示式,用primaryexp解析,有4種表達形式:
exp; exp(exp); exp[exp]; exp.exp
比如: a; a(1+2); a["b"]; a.b
其他複雜形式為這4種形式的遞迴實現,如
a.b.c = a.exp1
exp1 = b.c
再比如
a(b.c).d() = exp1.exp2
exp1 = a(exp3)
exp2 = d()
exp3 = b.c
primaryexp先獲取一個字首表示式 prefixexp,如 "a()" 分解為 "a" 和 "()",其中 "a" 即為字首。
然後根據第2個token決定使用上面4種表達形式的哪一種,如 "a()" 使用 exp(exp) 形式解析。
primaryexp解析完後,判斷當前expr是否為函式呼叫,如:
-
"a()" 是函式呼叫
-
"a().b" 不是函式呼叫
-
"a().b()" 是函式呼叫
若為函式呼叫,exprstat使用 func 產生式,否則使用 assignment 產生式,作為賦值語句處理。
primaryexp -> prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs }
對應上面的4種表達形式
-
prefixexp.NAME 用於table取key值,如 "a.b" 等同於 "a['b']",這是lua的語法糖
-
prefixexp[exp] 用於table取key值,key可以為一個表示式
-
prefixexp:NAME() 用於table的成員函式呼叫,另一個語法糖,如 "a:b(c)" 等同於"a.b(a,c)",自動將當前table物件作為第一個引數插入,類似c++的this指標
-
prefixexp() 用於函式呼叫,如 "a()"
prefixexp -> NAME | `(` expr `)`
字首表示式可以為識別符號,也可以一個表示式
-
如 "a()" ,prefixexp使用 NAME 產生式
-
如 "(a+1).d=1",prefixexp使用 expr 產生式
不要懷疑, "(a+1).d=1",在lua裡可以這樣寫,比如a為table,table又可以定義元方法,過載+運算子返回一個新table就可以了。
本節先介紹這幾個EBNF,敬請後續!