Chapter 25: Extending Your Application
Lua 的一种重要应用是作为一种配置语言。这一章我们将展示如何用Lua 来配置一个应用程序。
25.1 The Basics
加载lua配置文件初始化应用程序
-- config.lua
-- define window size
width = 200
height = 300
---------------
#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
/* 4 error function */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
static void stackDump (lua_State *L) {
int i;
int top = lua_gettop(L);
for (i = 1; i <= top; i++) { /* repeat for each level */
int t = lua_type(L, i);
switch (t) {
case LUA_TSTRING: { /* strings */
printf("'%s'", lua_tostring(L, i));
break;
}
case LUA_TBOOLEAN: { /* booleans */
printf(lua_toboolean(L, i) ? "true" : "false");
break;
}
case LUA_TNUMBER: { /* numbers */
printf("%g", lua_tonumber(L, i));
break;
}
default: { /* other values */
printf("%s", lua_typename(L, t));
break;
}
}
printf(" "); /* put a separator */
}
printf("/n"); /* end the listing */
}
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);
}
void load (lua_State *L, const char *fname, int *w, int *h) {
if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0))
error(L, "cannot run config. file: %s", lua_tostring(L, -1));
lua_getglobal(L, "width");
lua_getglobal(L, "height");
if (!lua_isnumber(L, -2))
error(L, "'width' should be a number/n");
if (!lua_isnumber(L, -1))
error(L, "'height' should be a number/n");
*w = lua_tointeger(L, -2);
*h = lua_tointeger(L, -1);
}
#define FILE_NAME "./src/config.lua"
int width;
int height;
int main (void) {
char buff[256];
int error;
lua_State *L = luaL_newstate(); /* opens Lua */
luaL_openlibs(L); /* opens the standard libraries */
load(L, FILE_NAME, &width, &height);
printf("width:%d height:%d/n", width, height);
printf("stack dump:/n");
stackDump(L);
while (fgets(buff, sizeof(buff), stdin) != NULL) {
error = luaL_loadbuffer(L, buff, strlen(buff), "line") ||
lua_pcall(L, 0, 0, 0);
if (error) {
fprintf(stderr, "%s", lua_tostring(L, -1));
lua_pop(L, 1); /* pop error message from the stack */
}
}
lua_close(L);
return 0;
}
程序中使用luaL_loadfile 函数加载文件,然后用lua_pcall 运行已编译过的chunk。如遇到错误(如语法错误),此函数将错误消息压入栈中,并返回非零错误码。我们的程序以-1 索引作为参数调用lua_tostring 从栈顶获得错误消息。
调用lua_getglobal 两次,获取全局变量的值,每一次调用都会将相应的全局值压入栈中。所以,width 在栈中的索引是-2,height 的索引是-1(位于栈顶)(因为这个栈之前是空的,你可以从栈底开始索引,1 是第一个值,2 是第二个值)。
这个任务使用Lua 来完成是否值得?对这种简单的任务,一个只有两数值的简单文件可能比使用Lua 更方便。既便如此,使用Lua 会获得一些好处。首先,Lua 为你处理所有语法细节;你的配置文件甚至可以有注释!其次,用户已经可以用它来做更复杂的配置。例如,脚本可以给用户一些提示信息,或它可以查询一个环境变量以选择一个合适的size:
-- configuration file
if getenv("DISPLAY") == ":0.0" then
width = 300; height = 300
else
width = 200; height = 200
end
最后一个原因是,现在可以容易的为你的程序添加新配置。
25.2 Table Manipulation
现在,我们也想给窗体配置背景色。我们假设颜色是由三个数组成,既RGB。C 中,这些数是整数,其范围类似[0, 255]。Lua 中,因为所有数都是实数,我们可以使用更自然的范围[0, 1]。
一种方法是让用户去设置每一个不同的全局变量:
-- configuration file
width = 200
height = 300
background_red = 0.30
background_green = 0.10
background_blue = 0
这种方法有两个缺点:它太冗长了(对窗体背景,前景,菜单等,真实的程序可能需要一大堆不同的颜色);并且这里没有办法预定义通用颜色,使得用户可以简单的这样写background=WHITE。为解决这个问题,我们将使用表来表达颜色:
background = {r=0.30, g=0.10, b=0}
现在可以方便的为用户(或为应用程序)在配置文件中预定义颜色:
-- config.lua
-- configuration file
width = 200
height = 300
BLUE = {r=0, g=0, b=1}
--<other color de[1]nitions>
background = BLUE
------------------------------
/* main.c */
/* 加载配置文件,运行整个chunk */
if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0))
print_error(L, "cannot run config. file: %s", lua_tostring(L, -1));
/* 将全局变量background 压栈 */
lua_getglobal(L, "background");
/* 确定栈顶是表 */
if (!lua_istable(L, -1))
print_error(L, "'background' is not a table");
red = getfield(L, "r");
green = getfield(L, "g");
blue = getfield(L, "b");
/* 函数定义 */
/* assume that table is on the stack top */
int getfield (lua_State *L, const char *key) {
int result;
lua_pushstring(L, key);
lua_gettable(L, -2); /* get background[key] */
if (!lua_isnumber(L, -1))
print_error(L, "invalid component in background color");
result = (int)lua_tonumber(L, -1) * MAX_COLOR;
lua_pop(L, 1); /* remove number */
return result;
}
以表在栈中的位置调用lua_gettable 函数,然后它从栈中取得key(我们事先将key 压入栈顶,lua_gettable 将key 弹出),接着又将相应用value 压栈。
因为以一个stirng key 对表进行索引是很常见的操作,lua 5.1 为这种任务提供一个特别版的lua_gettable:lua_getfield。
lua_pushstring(L, key);
lua_gettable(L, -2); /* get background[key] */
使用这个函数,我们可以将上面这两行代码重写为:
lua_getfield(L, -1, key);
进一步扩展我们的例子,并引入颜色名。用户仍然可以使用颜色表,但对那些常见颜色她也可以使用预定义名字。为实现这个特性,我们需要在C 程序中加一个颜色表:
struct ColorTable {
char *name;
unsigned char red, green, blue;
} colortable[] = {
{"WHITE", MAX_COLOR, MAX_COLOR, MAX_COLOR},
{"RED", MAX_COLOR, 0, 0},
{"GREEN", 0, MAX_COLOR, 0},
{"BLUE", 0, 0, MAX_COLOR},
/* <other colors> */
{NULL, 0, 0, 0} /* sentinel */
};
lua_setfield
/* assume that table is at the top */
void setfield (lua_State *L, const char *index, int value) {
lua_pushstring(L, index);
lua_pushnumber(L, (double)value/MAX_COLOR);
lua_settable(L, -3);
}
lua_setfield 将上面的代码简化为:
void setfield (lua_State *L, const char *index, int value) {
lua_pushnumber(L, (double)value/MAX_COLOR);
lua_setfield(L, -2, index);
}
setcolor 函数定义单个颜色(预定义颜色是在配置文件创建,setcolor是在代码中创建颜色)。它创建新表,设置合适的域,并将这个表赋值给相应的全局变量:
void setcolor (lua_State *L, struct ColorTable *ct) {
lua_newtable(L); /* creates a 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 */
lua_setglobal(L, ct->name); /* 'name' = table */
}
lua_newtable 创建出的表在栈顶,setfield 给它添加了三个域,分别是r,g,b。
最后lua_setglobal 弹出由secolor 创建的表,并把把这个表设置成全局变量。
从C 颜色表创建lua 颜色表
int i = 0;
while (colortable[i].name != NULL)
setcolor(L, &colortable[i++]);
让配置文件中的颜色既可以是stirng,也可以是表
lua_getglobal(L, "background");
if (lua_isstring(L, -1)) { /* value is a string? */
const char *name = lua_tostring(L, -1); /* get string */
int i; /* search the color table */
for (i = 0; colortable[i].name != NULL; i++) {
if (strcmp(colorname, colortable[i].name) == 0)
break;
}
if (colortable[i].name == NULL) /* string not found? */
error(L, "invalid color name (%s)", colorname);
else { /* use colortable[i] */
red = colortable[i].red;
green = colortable[i].green;
blue = colortable[i].blue;
}
} else if (lua_istable(L, -1)) {
red = getfield(L, "r");
green = getfield(L, "g");
blue = getfield(L, "b");
} else
error(L, "invalid value for 'background'");
让string 来标记选项不是良好实践,因为编译器不能检测拼写错误。
--config.lua
-- configuration file
width = 200
height = 300
BLUE = {r=0, g=0, b=1}
WHITE = {r=1, g=1, b=1}
RED = {r=1, g=0, b=0}
background = BLUE
-----------------------
main.c
#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
/* 4 error function */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
static void stackDump (lua_State *L) {
int i;
int top = lua_gettop(L);
for (i = 1; i <= top; i++) { /* repeat for each level */
int t = lua_type(L, i);
switch (t) {
case LUA_TSTRING: { /* strings */
printf("'%s'", lua_tostring(L, i));
break;
}
case LUA_TBOOLEAN: { /* booleans */
printf(lua_toboolean(L, i) ? "true" : "false");
break;
}
case LUA_TNUMBER: { /* numbers */
printf("%g", lua_tonumber(L, i));
break;
}
default: { /* other values */
printf("%s", lua_typename(L, t));
break;
}
}
printf(" "); /* put a separator */
}
printf("/n"); /* end the listing */
}
void print_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);
}
void load (lua_State *L, const char *fname, int *w, int *h) {
if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0))
print_error(L, "cannot run config. file: %s", lua_tostring(L, -1));
lua_getglobal(L, "width");
lua_getglobal(L, "height");
if (!lua_isnumber(L, -2))
print_error(L, "'width' should be a number/n");
if (!lua_isnumber(L, -1))
print_error(L, "'height' should be a number/n");
*w = lua_tointeger(L, -2);
*h = lua_tointeger(L, -1);
}
#define MAX_COLOR 255
struct ColorTable {
char *name;
unsigned char red, green, blue;
} colortable[] = {
{"WHITE", MAX_COLOR, MAX_COLOR, MAX_COLOR},
{"RED", MAX_COLOR, 0, 0},
{"GREEN", 0, MAX_COLOR, 0},
{"BLUE", 0, 0, MAX_COLOR},
/* <other colors> */
{NULL, 0, 0, 0} /* sentinel */
};
/* assume that table is on the stack top */
int getfield (lua_State *L, const char *key) {
int result;
lua_pushstring(L, key);
lua_gettable(L, -2); /* get background[key] */
if (!lua_isnumber(L, -1))
print_error(L, "invalid component in background color");
result = (int)lua_tonumber(L, -1) * MAX_COLOR;
lua_pop(L, 1); /* remove number */
return result;
}
/* assume that table is at the top */
void setfield (lua_State *L, const char *index, int value) {
lua_pushnumber(L, (double)value/MAX_COLOR);
lua_setfield(L, -2, index);
}
void setcolor (lua_State *L, struct ColorTable *ct) {
lua_newtable(L); /* creates a 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 */
lua_setglobal(L, ct->name); /* 'name' = table */
}
#define FILE_NAME "./src/config.lua"
int width;
int height;
int red;
int green;
int blue;
int i = 0;
int main (void) {
char buff[256];
int error;
lua_State *L = luaL_newstate(); /* opens Lua */
luaL_openlibs(L); /* opens the standard libraries */
/* setcolor 后那些颜色表就成lua 环境中的全局变量了*/
while (colortable[i].name != NULL)
setcolor(L, &colortable[i++]);
/* load 以后配置文件中的表会加载到栈中,但还不是全局变量?*/
load(L, FILE_NAME, &width, &height);
printf("width:%d height:%d/n", width, height);
lua_getglobal(L, "background");
if (!lua_istable(L, -1))
print_error(L, "'background' is not a table");
red = getfield(L, "r");
green = getfield(L, "g");
blue = getfield(L, "b");
printf("stack dump:/n");
stackDump(L);
while (fgets(buff, sizeof(buff), stdin) != NULL) {
error = luaL_loadbuffer(L, buff, strlen(buff), "line") ||
lua_pcall(L, 0, 0, 0);
if (error) {
fprintf(stderr, "%s", lua_tostring(L, -1));
lua_pop(L, 1); /* pop error message from the stack */
}
}
lua_close(L);
return 0;
}
25.3 Calling Lua Functions
C 代码中调用lua 函数
#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#define ASSERT(value) if (!(value)) { _asm{int 3};}
#define FILENAME "./src/config.lua"
lua_State *L = NULL;
/* call a function 'f' defined in Lua */
double f (double x, double y) {
double z;
/* push functions and arguments */
lua_getglobal(L, "f"); /* function to be called */
lua_pushnumber(L, x); /* push 1st argument */
lua_pushnumber(L, y); /* push 2nd argument */
/* do the call (2 arguments, 1 result) */
if (lua_pcall(L, 2, 1, 0) != 0) ASSERT(0);
/* retrieve result */
if (!lua_isnumber(L, -1)) ASSERT(0);
z = lua_tonumber(L, -1);
lua_pop(L, 1); /* pop returned value */
return z;
}
int main (void) {
char buff[256];
int error;
L = luaL_newstate(); /* opens Lua */
luaL_openlibs(L); /* opens the standard libraries */
if (luaL_loadfile(L, FILENAME) || lua_pcall(L, 0, 0, 0)) ASSERT(0);
printf("%lf/n", f(2.0, 3.0)) ;
while (fgets(buff, sizeof(buff), stdin) != NULL) {
error = luaL_loadbuffer(L, buff, strlen(buff), "line") ||
lua_pcall(L, 0, 0, 0);
if (error) {
fprintf(stderr, "%s", lua_tostring(L, -1));
lua_pop(L, 1); /* pop error message from the stack */
}
}
lua_close(L);
return 0;
}
你要少返回值,lua_pcall 就返回多少给你,不够就push nil,多就丢弃:
/* 2 表示有两个参数,1 个返回值 */
if (lua_pcall(L, 2, 1, 0) != 0) ASSERT(0);
从栈顶往下数,前两个是参数,第三个就是要调用的函数。
lua_pcall 在压入返回值之前会从栈中移除刚才调用的函数及其参数。
如果有多个返回值,第一个返回值先压栈。例如有三个返回值,第一个在栈中的索引是-3,最后一个-1。
如果lua_pcall 运行期间出错,它返回一个非0 值,将错误消息压栈,将函数及其参数从栈中移除。在压入错误消息之前,它调用错误处理函数,如果有的话。第三个参数为0 意思是没有错误处理函数;这样,最终的错误消息就是原始消息。第三个参数如果不是0,参数值就是错误处理函数在栈中的索引,而且,它比函数及其参数更早被压入。
对普通错误,lua_pcall 返回错误码LUA_ERRRUN。对内存分配错误,返回LUA_ERRMEM。如果是错误处理器本身出错,这种情况下立既返回LUA_ERRRUN。
25.4 A Generic Call Function
对lua 函数调用做一个封装:
call_va("f", "dd>d", x, y, &z);
其中f 是欲调用的函数名,dd 表示两个类型为double 的参数,> 是分隔符,d 表示有一返回值类型是double,x, y 是传给f 的参数,z 的意议很明显。 如果函数没有返回值,‘>’ 可以省略。
大部分代码和之前类似,但是这里有些微妙的地方。首先,它不检查func 是否是一个函数。lua_pcall 会引发所有错误。第二,因为它压入任意数量的参数,所以它必须检查栈空间。第三,因为函数可能返回strings,call_va 不能从栈中弹出结果,在调用者获取临时字符串的结果之后(拷贝到其他的buffer),由调用都来决定弹出它们。
封装对lua 的函数调用
--config.lua
function f (x, y)
return (x^2 * math.sin(y))/(1 - x)
end
-----------------
-- main.c
#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#define ASSERT(value) if (!(value)) { _asm{int 3};}
#define FILENAME "./src/config.lua"
lua_State *L = NULL;
#include <stdarg.h>
void call_va (const char *func, const char *sig, ...) {
va_list vl;
int narg, nres; /* number of arguments and results */
va_start(vl, sig);
lua_getglobal(L, func); /* push function */
for (narg = 0; *sig; narg++) { /* repeat for each argument */
/* check stack space */
luaL_checkstack(L, 1, "too many arguments");
switch (*sig++) {
case 'd': /* double argument */
lua_pushnumber(L, va_arg(vl, double));
break;
case 'i': /* int argument */
lua_pushinteger(L, va_arg(vl, int));
break;
case 's': /* string argument */
lua_pushstring(L, va_arg(vl, char *));
break;
case '>': /* end of arguments */
goto endargs;
default:
ASSERT(0); //error(L, "invalid option (%c)", *(sig - 1));
}
}
endargs:
nres = strlen(sig); /* number of expected results */
/* do the call */
if (lua_pcall(L, narg, nres, 0) != 0) /* do the call */
ASSERT(0); // error(L, "error calling '%s': %s", func,
nres = -nres; /* stack index of first result */
while (*sig) { /* repeat for each result */
switch (*sig++) {
case 'd': /* double result */
if (!lua_isnumber(L, nres))
ASSERT(0); //error(L, "wrong result type");
*va_arg(vl, double *) = lua_tonumber(L, nres);
break;
case 'i': /* int result */
if (!lua_isnumber(L, nres))
ASSERT(0); //error(L, "wrong result type");
*va_arg(vl, int *) = lua_tointeger(L, nres);
break;
case 's': /* string result */
if (!lua_isstring(L, nres))
ASSERT(0); // error(L, "wrong result type");
*va_arg(vl, const char **) = lua_tostring(L, nres);
break;
default:
ASSERT(0);//error(L, "invalid option (%c)", *(sig - 1));
}
nres++;
}
va_end(vl);
}
/* call a function 'f' defined in Lua */
double f (double x, double y) {
double z;
/* push functions and arguments */
lua_getglobal(L, "f"); /* function to be called */
lua_pushnumber(L, x); /* push 1st argument */
lua_pushnumber(L, y); /* push 2nd argument */
/* do the call (2 arguments, 1 result) */
if (lua_pcall(L, 2, 1, 0) != 0) ASSERT(0);
/* retrieve result */
if (!lua_isnumber(L, -1)) ASSERT(0);
z = lua_tonumber(L, -1);
lua_pop(L, 1); /* pop returned value */
return z;
}
int main (void) {
char buff[256];
int error;
double x, y, z;
L = luaL_newstate(); /* opens Lua */
luaL_openlibs(L); /* opens the standard libraries */
if (luaL_loadfile(L, FILENAME) || lua_pcall(L, 0, 0, 0)) ASSERT(0);
//printf("%lf/n", f(2.0, 3.0)) ;
x=2.0;
y=3.0;
call_va("f", "dd>d", x, y, &z);
printf("%lf/n", z) ;
while (fgets(buff, sizeof(buff), stdin) != NULL) {
error = luaL_loadbuffer(L, buff, strlen(buff), "line") ||
lua_pcall(L, 0, 0, 0);
if (error) {
fprintf(stderr, "%s", lua_tostring(L, -1));
lua_pop(L, 1); /* pop error message from the stack */
}
}
lua_close(L);
return 0;
}