上回说到 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 之路已经打通。
在这之前,先看一下路上的其它风景。