一篇學(xué)會(huì)好玩的Lua
本文轉(zhuǎn)載自微信公眾號(hào)「編程雜技」,作者 theanarkh 。轉(zhuǎn)載本文請(qǐng)聯(lián)系編程雜技公眾號(hào)。
最近體驗(yàn)了一下Openresty,了解到Openresty里使用lua語(yǔ)言來(lái)增強(qiáng)了Nginx的能力,所以又去了解了一下lua,lua語(yǔ)言小而精悍,lua引擎也值得學(xué)習(xí)。周末看了一下lua引擎的一些實(shí)現(xiàn),也體驗(yàn)了一下lua語(yǔ)言的一些東西,本文簡(jiǎn)單介紹一下,后續(xù)有時(shí)間的話(huà)再寫(xiě)文章分析引擎的實(shí)現(xiàn)。
1 在c語(yǔ)言中嵌入lua引擎
lua引擎本身是一個(gè)庫(kù),類(lèi)似V8一樣,我們可以把它嵌入到其他項(xiàng)目中,我們首先安裝相關(guān)文檔安裝lua(我安裝的是5.1.5)。然后寫(xiě)個(gè)demo體驗(yàn)一下。
- #include <lua.h>
- #include <lualib.h>
- #include <lauxlib.h>
- #include<stdio.h>
- int echo(lua_State *L) {
- printf("world");
- }
- int main(int argc, char *argv[]) {
- int s = 0;
- lua_State *L = lua_open();
- // 注冊(cè)個(gè)自定義的函數(shù)
- lua_register(L,"echo", echo);
- luaL_openlibs(L);
- // 執(zhí)行l(wèi)ua腳本
- luaL_dofile(L, "hello.lua");
- lua_close(L);
- return 1;
- }
編譯上面的代碼
- gcc hello.c -llua -lm -ldl
然后寫(xiě)個(gè)hello.lua腳本。
- print("hello");
- echo();
執(zhí)行./a.out,我們看到輸出了hello world。這個(gè)是個(gè)簡(jiǎn)單的體驗(yàn)demo,和直接使用lua提供的命令行工具類(lèi)似,只不過(guò)我們這里還拓展了一個(gè)自定義的echo函數(shù)給lua腳本調(diào)用。如果我們想動(dòng)態(tài)地執(zhí)行一段腳本,而不是執(zhí)行一個(gè)lua文件,也是可以的。
- #include <lua.h>
- #include <lualib.h>
- #include <lauxlib.h>
- const char * script = "print('hi');";
- int main(int argc, char *argv[]) {
- lua_State *L = lua_open();
- luaL_openlibs(L);
- luaL_dostring(L, script);
- lua_close(L);
- return 1;
- }
編譯執(zhí)行以上代碼我們會(huì)看到輸出hi。以上這些似乎沒(méi)什么大的作用,因?yàn)槲覀儓?zhí)行簡(jiǎn)單地使用lua語(yǔ)言提供的能力。而lua的能力絕不止于此,lua稱(chēng)為膠水語(yǔ)言,除了可以嵌入其他語(yǔ)言中,還支持拓展。下面我們看如果拓展lua的能力。
2 基于lua的demo運(yùn)行時(shí)
雖然這里只是簡(jiǎn)單地拓展lua,但是這里稱(chēng)之為運(yùn)行時(shí)是因?yàn)轭?lèi)似Node.js基于V8一樣,我們也可以通過(guò)拓展lua來(lái)實(shí)現(xiàn)一個(gè)基于lua的運(yùn)行時(shí)。下面我們看看怎么拓展(也就是怎么調(diào)用其他語(yǔ)言的代碼,這里是c)。新建一個(gè)test.c文件。
- #include <lua.h>
- #include <lualib.h
- >#include <lauxlib.h>
- static int test(lua_State* L){
- //取棧第一個(gè)參數(shù)
- const char *a = luaL_checkstring(L, 1);
- //返回值入棧
- lua_pushstring(L, (const char *)"hi");
- return 1;
- }
- static const struct luaL_Reg reg_test[] = {
- {"test", test},
- {NULL, NULL}
- };
- int luaopen_test(lua_State *L) {
- const char* libName = "test";
- luaL_register(L, libName, reg_test);
- return 1;
- }
lua和c是通過(guò)一個(gè)棧進(jìn)行通信的,lua調(diào)用c函數(shù)的時(shí)候,c函數(shù)可以從棧中獲取lua的參數(shù),也可也從棧中返回執(zhí)行結(jié)果給lua。我們把以上代碼編譯成一個(gè)動(dòng)態(tài)庫(kù)。
- gcc test.c -fPIC -shared -o test.so
然后寫(xiě)個(gè)測(cè)試lua demo。
- local test = require "test"
- a = test.test("hello world!")
- print(a)
我們可以看到在lua中成功調(diào)用了test模塊的test函數(shù),并輸出hi。當(dāng)我們r(jià)equire"test"的時(shí)候,lua會(huì)去當(dāng)前目錄找test.o,并且執(zhí)行其中的luaopen_test函數(shù)。luaopen_前綴是約定,test則是模塊名稱(chēng)。當(dāng)前去哪里找需要加載的模塊這個(gè)我們可以設(shè)置。我們分析一下c文件的代碼,看看拓展lua時(shí)的一些內(nèi)容。首先看luaL_register。
- LUALIB_API void (luaL_register) (lua_State *L, const char *libname, const luaL_Reg *l) {
- luaI_openlib(L, libname, l, 0);
- }
我們主要關(guān)注luaL_register的第二第三個(gè)參數(shù)libname和luaL_Reg。因?yàn)橹肋@個(gè)參數(shù)的格式,我們才知道怎么寫(xiě)c代碼。其中name是庫(kù)名稱(chēng),也就是我們r(jià)equire時(shí)傳的字符串。luaL_Reg的定義如下
- typedef int (*lua_CFunction) (lua_State *L);
- typedef struct luaL_Reg {
- const char *name;
- lua_CFunction func;
- } luaL_Reg;
luaL_Reg是封裝了kv的一個(gè)結(jié)構(gòu)體,。name是導(dǎo)出的函數(shù)名稱(chēng),即在lua中可以調(diào)用的函數(shù)。func則是對(duì)應(yīng)的函數(shù),當(dāng)在lua執(zhí)行name函數(shù)時(shí)就會(huì)執(zhí)行func的代碼。
3 lua變量存儲(chǔ)的設(shè)計(jì)
lua是動(dòng)態(tài)類(lèi)型的語(yǔ)言,意味著一個(gè)變量的值的類(lèi)型是可以改變的,下面看一下lua中是如何設(shè)計(jì)底層的存儲(chǔ)的。lua所有變量都使用TValue結(jié)構(gòu)體來(lái)表示。
- #define TValuefields Value value; int tt
- typedef struct lua_TValue {
- TValuefields;
- } TValue;
里面只有兩個(gè)字段。tt是表示變量類(lèi)型。lua的類(lèi)型比較簡(jiǎn)單。如下
- #define LUA_TNIL 0
- #define LUA_TBOOLEAN 1
- #define LUA_TLIGHTUSERDATA 2
- #define LUA_TNUMBER 3
- #define LUA_TSTRING 4
- // 數(shù)組和對(duì)象都使用一種類(lèi)型
- #define LUA_TTABLE 5
- #define LUA_TFUNCTION 6
- #define LUA_TUSERDATA 7
- #define LUA_TTHREAD 8
接下來(lái)我們看看Value的定義。
- typedef union {
- GCObject *gc;
- void *p;
- lua_Number n;
- int b;
- } Value;
Value里分為兩種類(lèi)型,一種是不需要gc的,比如數(shù)字,一種是需要gc的,比如數(shù)組,lua是帶gc的語(yǔ)言。我們繼續(xù)看GCObject。
- union GCObject {
- GCheader gch;
- union TString ts;
- union Udata u;
- union Closure cl;
- struct Table h;
- struct Proto p;
- struct UpVal uv;
- struct lua_State th; /* thread */
- };
我們看到GCObject是一個(gè)聯(lián)合體,可以存儲(chǔ)不同類(lèi)型的變量。我們?cè)倏纯碩String的定義。
- typedef union TString {
- L_Umaxalign dummy; /* 內(nèi)存對(duì)齊,性能優(yōu)化 */
- struct {
- CommonHeader;
- lu_byte reserved;
- unsigned int hash;
- size_t len;
- } tsv;
- } TString;
字符串結(jié)構(gòu)體里面主要的字段時(shí)len和hash,len就是字符串的長(zhǎng)度,hash類(lèi)似一個(gè)索引,lua中的字符串不是存儲(chǔ)在結(jié)構(gòu)體本身的,而是統(tǒng)一管理起來(lái),主要是為了復(fù)用,比如有兩個(gè)變量的值都是同一個(gè)字符串,那么lua中,只會(huì)存儲(chǔ)一個(gè)字符串值,而這兩個(gè)變量都會(huì)通過(guò)hash指向這個(gè)字符串的值。我們可以看一下一段代碼大概了解一下。
- // 新建字符串,如果存在則直接復(fù)用
- TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
- GCObject *o;
- unsigned int h = cast(unsigned int, l); /* seed */
- size_t step = (l>>5)+1;
- size_t l1;
- // 計(jì)算字符串的哈希值
- for (l1=l; l1>=step; l1-=step) /* compute hash */
- h = h ^ ((h<<5)+(h>>2)+cast(unsigned char, str[l1-1]));
- // 判斷是否有一樣的字符串存在了,是則共享,直接返回,否則新建
- for (o = G(L)->strt.hash[lmod(h, G(L)->strt.size)];
- o != NULL;
- o = o->gch.next) {
- TString *ts = rawgco2ts(o);
- if (ts->tsv.len == l && (memcmp(str, getstr(ts), l) == 0)) {
- if (isdead(G(L), o)) changewhite(o);
- return ts;
- }
- }
- // 找不到則新建
- return newlstr(L, str, l, h); /* not found */
- }
我們看到lua的變量存儲(chǔ)設(shè)計(jì)中是一種樹(shù)狀結(jié)構(gòu),通過(guò)上層的變量類(lèi)型,再進(jìn)行不同的存取操作。從而我們也可以了解到動(dòng)態(tài)語(yǔ)言在變量存儲(chǔ)中的一些設(shè)計(jì)思想。
后記:這是周末學(xué)習(xí)lua的一些內(nèi)容,后續(xù)有時(shí)間會(huì)繼續(xù)更新,lua是一個(gè)非常有意思的項(xiàng)目。