上回说到 luaL_loadfile ,这次把它的调用展开到语法分析器 parser.
先一下它的函数定义
LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) {
LoadF lf;
int status, readstatus;
int c;
int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */
if (filename == NULL) {
lua_pushliteral(L, "=stdin");
lf.f = stdin;
}
else {
lua_pushfstring(L, "@%s", filename);
lf.f = fopen(filename, "r");
}
if (lf.f == NULL) return errfile(L, fnameindex); /* unable to open file */
c = ungetc(getc(lf.f), lf.f);
if (!(isspace(c) || isprint(c)) && lf.f != stdin) { /* binary file? */
fclose(lf.f);
lf.f = fopen(filename, "rb"); /* reopen in binary mode */
if (lf.f == NULL) return errfile(L, fnameindex); /* unable to reopen file */
}
status = lua_load(L, getF, &lf, lua_tostring(L, -1));
readstatus = ferror(lf.f);
if (lf.f != stdin) fclose(lf.f); /* close file (even in case of errors) */
if (readstatus) {
lua_settop(L, fnameindex); /* ignore results from `lua_load' */
return errfile(L, fnameindex);
}
lua_remove(L, fnameindex);
return status;
}
上来先定义一个 LoadF 数据结构。
typedef struct LoadF {
FILE *f;
char buff[LUAL_BUFFERSIZE];
} LoadF;
可以看到,里面一个 FILE 指针,一个字符串 buffer。
这会儿还看不出来这个结构体是干什么的。
接下来给 LoadF 结构体 lf 变量赋值。
如果 filename 为空,表示从 stdin 输入。
否则,打开文件 filename。
这里可以看到,lf 变量的 f 字段被赋值为打开的文件 FILE 。
之后读文件的第一个字符,判断文件是否为二进制文件,也就是已经编译了的 Lua 字节码文件。
如果是二进制文件,把原来的文件关闭。以二进制格式打开。
然后就是调用 lua_load 进行解析。
注意它的第二个和第三个参数分别为 getF, lf,一会儿下面会用到。
LUA_API int lua_load (lua_State *L, lua_Chunkreader reader, void *data,
const char *chunkname) {
ZIO z;
int status;
int c;
lua_lock(L);
if (!chunkname) chunkname = "?";
luaZ_init(&z, reader, data, chunkname);
c = luaZ_lookahead(&z);
status = luaD_protectedparser(L, &z, (c == LUA_SIGNATURE[0]));
lua_unlock(L);
return status;
}
lua_load 的第二个参数是个函数指针,用来进行块读取。
函数指针的签名如下:
typedef const char * (*lua_Chunkreader) (lua_State *L, void *ud, size_t *sz);
这里传入的是 getF。
static const char *getF (lua_State *L, void *ud, size_t *size) {
LoadF *lf = (LoadF *)ud;
(void)L;
if (feof(lf->f)) return NULL;
*size = fread(lf->buff, 1, LUAL_BUFFERSIZE, lf->f);
return (*size > 0) ? lf->buff : NULL;
}
这个函数做的事情就是从 LoadF 结构体的 FILE 指针 f 中读取一个块到其 buff 中。
这里的 ud 就是上面 lua_load 传入的第三个参数。
回到 lua_load 函数。
看到里面有 lua_lock, lua_unlock,这是做线程同步的,现在不用管它。
接着调用 luaZ_init 来初始化 ZIO 结构体。
void luaZ_init (ZIO *z, lua_Chunkreader reader, void *data, const char *name) {
z->reader = reader;
z->data = data;
z->name = name;
z->n = 0;
z->p = NULL;
}
可以看到,在调用 luaZ_int 的时候把 reader 和 data 传过去了。
reader 和 data 就是 getF 和 lf。
在 luaZ_fill 可以看到一个 reader 的调用,
int luaZ_fill (ZIO *z) {
size_t size;
const char *buff = z->reader(NULL, z->data, &size);
if (buff == NULL || size == 0) return EOZ;
z->n = size - 1;
z->p = buff;
return char2int(*(z->p++));
}
这里通过 reader 来获取一个 buff。
这个 z->reader(NULL, z->data, &size) 的调用就相当于
getF(NULL, lf, &size)。
读数据这块就通了。
回到 lua_load 函数。
c = luaZ_lookahead(&z);
取第一个字符。
status = luaD_protectedparser(L, &z, (c == LUA_SIGNATURE[0]));
这里取第一个字符后,就是用来和保存的字节码的第一个字符进行对比。
编译器 dump 出来的字节码的头几个字符是 LUA_SIGNATURE
#define LUA_SIGNATURE "\033Lua" /* binary files start with "<esc>Lua" */
看下 luaD_protectedparser
int luaD_protectedparser (lua_State *L, ZIO *z, int bin) {
struct SParser p;
int status;
ptrdiff_t oldtopr = savestack(L, L->top); /* save current top */
p.z = z; p.bin = bin;
luaZ_initbuffer(L, &p.buff);
status = luaD_rawrunprotected(L, f_parser, &p);
luaZ_freebuffer(L, &p.buff);
if (status != 0) { /* error? */
StkId oldtop = restorestack(L, oldtopr);
seterrorobj(L, status, oldtop);
}
return status;
}
这里上来定义了结构体变量 p。
/*
** Execute a protected parser.
*/
struct SParser { /* data to `f_parser' */
ZIO *z;
Mbuffer buff; /* buffer to be used by the scanner */
int bin;
};
接下来给结构体赋值。
luaZ_initbuffer(L, &p.buff); // 初始化 buff。
status = luaD_rawrunprotected(L, f_parser, &p); // 调用
luaD_rawrunprotected 的定义如下:
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
struct lua_longjmp lj;
lj.status = 0;
lj.previous = L->errorJmp; /* chain new error handler */
L->errorJmp = &lj;
if (setjmp(lj.b) == 0)
(*f)(L, ud);
L->errorJmp = lj.previous; /* restore old error handler */
return lj.status;
}
这个主要是为把调用放到一个 C 语言版的 try catch 里去。
(*f)(L, ud); // 调用 f_parser
static void f_parser (lua_State *L, void *ud) {
struct SParser *p;
Proto *tf;
Closure *cl;
luaC_checkGC(L);
p = cast(struct SParser *, ud);
tf = p->bin ? luaU_undump(L, p->z, &p->buff) : luaY_parser(L, p->z, &p->buff);
cl = luaF_newLclosure(L, 0, gt(L));
cl->l.p = tf;
setclvalue(L->top, cl);
incr_top(L);
}
通过判断传入的是否为二进制格式而进行不同的处理:
如果是二进制则 luaU_undump
否则调用语法解析 luaY_parser 。
解析完后,放到闭包里,压栈。
通向语法解析 luaY_parser 之路已经打通。
在这之前,先看一下路上的其它风景。