1. 程式人生 > >高速掌握Lua 5.3 —— 擴展你的程序 (1)

高速掌握Lua 5.3 —— 擴展你的程序 (1)

lac ould key article start markdown max 高速 擴展程序

Q:怎樣在C中將Lua作為配置文件語言使用?

A:
“config.lua”文件裏:

-- window size
width = 200
height = 300

“main.c”文件裏:

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

void error(lua_State *L, const char
*fmt, ...) { // 逐一取出參數並打印到標準錯誤輸出。 va_list argp; va_start(argp, fmt); vfprintf(stderr, fmt, argp); va_end(argp); lua_close(L); // 關閉Lua狀態機。

exit(EXIT_FAILURE); // 以失敗退出。 } void load(char *filename, int *width, int *height) { lua_State *L = luaL_newstate(); luaL_openlibs(L); /* luaL_loadfile(): * 讀取配置文件,並將當中的內容編譯為"chunk"放入虛擬棧中。

* lua_pcall(): * 使用保護模式調用虛擬棧中的函數。

被調用的函數應該遵循下面協議: * 被調用的函數應該被首先入棧。接著把參數按正序入棧(第一個參數首先入棧)。

* 第二個參數代表參數個數;第三個參數代表返回值個數; * 第四個參數代表是否使用自定義的錯誤處理函數。

* 當函數調用完畢之後,全部的參數以及函數本身都會出棧。而函數的返回值則被入棧。 */ if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0)) error(L, "cannot run configuration file: %s"

, lua_tostring(L, -1)); // 獲取配置文件裏的全局變量。 lua_getglobal(L, "width"); // 將Lua環境下名為"width"的全局變量的值入棧。 lua_getglobal(L, "height"); // 將Lua環境下名為"height"的全局變量的值入棧。 // 推斷全局變量的值是否為數值。 if (!lua_isnumber(L, -2)) // 上面"width"先被入棧,所以-2是"width"。

error(L, "`width‘ should be a number\n"); if (!lua_isnumber(L, -1)) // -1是"height"。 error(L, "`height‘ should be a number\n"); // 將全局變量的值轉換為浮點數。 *width = (int)lua_tonumber(L, -2); *height = (int)lua_tonumber(L, -1); lua_close(L); } int main(void) { int width = 0, height = 0; load("config.lua", &width, &height); printf("width = %d, height = %d\n", width, height); return 0; }

prompt> gcc main.c -llua -ldl -lm -Wall
prompt> ./a.out
width = 200, height = 300

看完這個樣例是否會認為使用Lua作為配置文件語言有些不值?只為了獲取兩個變量的值,卻寫了這麽一大段代碼。
可是使用Lua確實有其優勢。

首先Lua承擔了配置文件格式檢查的工作,假設你自己寫解析程序。這一段代碼不會少;其次。你能夠在配置文件裏寫凝視,相同假設你自己寫解析程序,也須要考慮跳過凝視的問題;最後。配置文件裏能夠做很多其它的靈活配置。比方,

-- 依據環境變量,靈活的定義窗體大小。
if os.getenv("DISPLAY") == "small" then
    width = 100
    height = 100
else
    width = 500
    height = 500
end

Q:怎樣在C中使用Lua的”table”?

A:我們在上一個樣例的基礎上繼續擴展程序。
“config.lua”文件裏:

-- window size.
width = 200
height = 300

-- window‘s background color.
-- 配置文件裏能夠自定義顏色。

ORANGE = {r = 255, g = 128, b = 0} --background = ORANGE --background = {r = 128, g = 0, b = 255} background = "BLUE" -- 也能夠直接使用C中定義的顏色。

“main.c”文件裏:

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#define MAX_COLOR       255    // 顏色分量的最大值。

struct ColorTable {
    char *name;
    unsigned char red, green, blue;
} colortable[] = {    // C中定義的顏色。

{"WHITE", MAX_COLOR, MAX_COLOR, MAX_COLOR}, {"RED", MAX_COLOR, 0, 0}, {"GREEN", 0, MAX_COLOR, 0}, {"BLUE", 0, 0, MAX_COLOR}, {"BLACK", 0, 0, 0}, {NULL, 0, 0, 0} // sentinel }; void error(lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); vfprintf(stderr, fmt, argp); va_end(argp); lua_close(L); exit(EXIT_FAILURE); return ; } void setfield(lua_State *L, const char *index, int value) { lua_pushstring(L, index); //"key"入棧。

lua_pushinteger(L, value); //"value"入棧。 /* 將指定的索引位置(-3)作為"table",將索引位置(-1)作為"value",將索引位置(-2)作為"key", * 做相當於"table[key] = value"的工作。之後將"key""value"出棧。 */ lua_settable(L, -3); return ; } // 獲取"table"中元素"key"的值(此函數假設"table"在棧頂)。

int getfield(lua_State *L, const char *key) { int result = 0; lua_pushstring(L, key); //"key"入棧。

/* 從給定的索引位置(-2)獲得"table",從棧頂位置獲得"key", * 彈出"key",將"table[key]"的結果入棧。 */ lua_gettable(L, -2); if (!lua_isinteger(L, -1)) error(L, "invalid component in background color"); result = lua_tointeger(L, -1); // "table[key]"的結果即是顏色分量值。 lua_pop(L, 1); // 彈出顏色分量值,保證棧的狀態與調用該函數時一樣。 return result; } void setcolor(lua_State *L, struct ColorTable *ct) { lua_newtable(L); // 創建一個"table"並將其入棧。 setfield(L, "r", ct->red); /* table.r = ct->r */ setfield(L, "g", ct->green); /* table.g = ct->g */ setfield(L, "b", ct->blue); /* table.b = ct->b */ // 將棧頂的元素出棧(實際為上面創建的"table"),並使用名為"name"的全局變量存儲。 lua_setglobal(L, ct->name) return ; } void load(lua_State *L, char *filename, int *width, int *height, int *r, int *g, int *b) { if(luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0)) { error(L, "cannot run configuration file: %s", lua_tostring(L, -1)); } lua_getglobal(L, "width"); lua_getglobal(L, "height"); lua_getglobal(L, "background"); // 將Lua環境下名為"background"的全局變量的值入棧。

if(!lua_isnumber(L, -3)) { error(L, "`width‘ should be a number\n"); } *width = (int)lua_tonumber(L, -3); if(!lua_isnumber(L, -2)) { error(L, "`height‘ should be a number\n"); } *height = (int)lua_tonumber(L, -2); // 假設"background"值是個字符串。那麽說明使用C中定義的顏色。 if(lua_isstring(L, -1)) { const char *colorname = lua_tostring(L, -1); int i = 0; // 尋找使用的哪一種顏色。 while((colortable[i].name != NULL) && (strcmp(colorname, colortable[i].name) != 0)) { ++i; } if(colortable[i].name == NULL) { error(L, "invalid color name (%s)", colorname); } else { // 獲取顏色的分量。

*r = colortable[i].red; *g = colortable[i].green; *b = colortable[i].blue; } } // 假設"background"值是個字符串,那麽說明使用Lua中定義的顏色。

else if(lua_istable(L, -1)) { // 獲取顏色的分量。 *r = getfield(L, "r"); *g = getfield(L, "g"); *b = getfield(L, "b"); } else { error(L, "invalid value for `background‘"); } return ; } int main(void) { int i = 0; int width = 0, height = 0; int r = 0, g = 0, b = 0; lua_State *L = luaL_newstate(); luaL_openlibs(L); while(colortable[i].name != NULL) { setcolor(L, &colortable[i++]); // 將C中定義的顏色逐一初始化到Lua的"table"中。 } load(L, "config.lua", &width, &height, &r, &g, &b); lua_close(L); printf("width = %d, height = %d\n" "red = %d, green = %d, blue = %d\n", width, height, r, g, b); return 0; }

prompt> gcc main.c -llua -ldl -lm -Wall
prompt> ./a.out
width = 200, height = 300
red = 0, green = 0, blue = 255

附加:

1、

/* int lua_pcall(lua_State *L, int nargs, int nresults, int msgh)
 * 以保護模式調用具有"nargs"個參數,"nresults"個返回值得函數。

函數在第一個參數的前一個位置。 * 保護模式指的是當調用出錯時不會報錯,而是返回一個錯誤碼同一時候將錯誤信息入棧。

* 當調用成功時,函數返回0。將函數名以及參數出棧,之後將函數的返回值入棧。 * 不管函數返回多少個返回值,Lua會調整為你須要的數量,忽略多余的或者將不夠的補為"nil"。 * 當調用出錯時,函數返回非0值。將函數名以及參數出棧, * 以錯誤信息作為參數,執行虛擬棧中索引"msgh"處的出錯處理函數, * 將出錯處理函數的返回值作為"lua_pcall"的返回值入棧。

* "msgh"0代表沒有錯誤處理函數,錯誤處理函數必須要在被調用函數和其參數入棧之前入棧。

* 典型的使用方法中,錯誤處理函數被用來給錯誤消息加上很多其它的調試信息,比方棧跟蹤信息。

* 這些信息在"lua_pcall"返回後。因為棧已經展開,所以收集不到了。 * lua_pcall 函數會返回下列常數(定義在"lua.h"內)中的一個: LUA_OK (0): 成功。 LUA_ERRRUN: 執行時錯誤(一般錯誤)。

LUA_ERRMEM: 內存分配錯誤(此種情況,Lua不會調用錯誤處理函數)。 LUA_ERRERR: 在執行錯誤處理函數時發生的錯誤(此種情況。Lua不會再次調用錯誤處理函數)。 LUA_ERRGCMM: 在執行"__gc"元方法時發生的錯誤(這個錯誤和被調用的函數無關。)。

*/

高速掌握Lua 5.3 —— 擴展你的程序 (1)