这两个函数的定义都位于 ldo.c 中,看看这两个函数都做了什么事儿?
先来看一下 lua_dofile 执行文件
LUA_API int lua_dofile (lua_State *L, const char *filename) { int status = parse_file(L, filename); if (status == 0) /* parse OK? */ status = lua_call(L, 0, LUA_MULTRET); /* call main */ return status; }
先解析文件,如果解析无误,则调用。
由函数名字及下面的调用我们可以猜出,parse_file 应该是做的语法解析。
static int parse_file (lua_State *L, const char *filename) { ZIO z; int status; int bin; /* flag for file mode */ int c; /* look ahead char */ FILE *f = (filename == NULL) ? stdin : fopen(filename, "r"); if (f == NULL) return LUA_ERRFILE; /* unable to open file */ c = fgetc(f); ungetc(c, f); bin = (c == ID_CHUNK); if (bin && f != stdin) { f = freopen(filename, "rb", f); /* set binary mode */ if (f == NULL) return LUA_ERRFILE; /* unable to reopen file */ } lua_pushstring(L, "@"); lua_pushstring(L, (filename == NULL) ? "(stdin)" : filename); lua_concat(L, 2); c = lua_gettop(L); filename = lua_tostring(L, c); /* filename = ‘@‘..filename */ luaZ_Fopen(&z, f, filename); status = protectedparser(L, &z, bin); lua_remove(L, c); /* remove `filename‘ from the stack */ if (f != stdin) fclose(f); return status; }
先根据文件名来判断输入的是什么?
如果文件名为空,则从标准输入读取。
否则从文件名读取。
取得文件的第一个字符,如果是 ID_CHUNK 的话,表示文件是一个已经编译好的 Lua 字节码文件。
bin 标志位就是用来标识这个文件是否为 Lua 字节码文件。
后面对 filename 进行编码,前面添加 ‘@‘ 符号。
luaZ_Fopen 打开缓冲区。
protectedparser 解析。
先不看 protectedparser 里做什么了。
先看下 lua_dostring,因为最后也是调到了 protectedparser 身上。
LUA_API int lua_dostring (lua_State *L, const char *str) { return lua_dobuffer(L, str, strlen(str), str); }
lua_dostring 内部调用 lua_dobuffer 来实现。
可以看出这里调用 lua_dobuffer 时 str 即当 buff 参数,又当 name 参数。
LUA_API int lua_dobuffer (lua_State *L, const char *buff, size_t size, const char *name) { int status = parse_buffer(L, buff, size, name); if (status == 0) /* parse OK? */ status = lua_call(L, 0, LUA_MULTRET); /* call main */ return status; }
同样,和文件类型,也是先语法解析。
解析无误,则调用。
static int parse_buffer (lua_State *L, const char *buff, size_t size, const char *name) { ZIO z; if (!name) name = "?"; luaZ_mopen(&z, buff, size, name); return protectedparser(L, &z, buff[0]==ID_CHUNK); }
可以看到,在解析字符串 buffer 时,最后也是调到了 protectedparser 身上。
到 protectedparser 时,由于缓冲区 ZIO 的作用,已经没有文件或者字符串的区别了。
很不错的设计思想!
这时候,再来看看 protectedparser 。
static int protectedparser (lua_State *L, ZIO *z, int bin) { struct ParserS p; unsigned long old_blocks; int status; p.z = z; p.bin = bin; /* before parsing, give a (good) chance to GC */ if (L->nblocks/8 >= L->GCthreshold/10) luaC_collectgarbage(L); old_blocks = L->nblocks; status = luaD_runprotected(L, f_parser, &p); if (status == 0) { /* add new memory to threshold (as it probably will stay) */ L->GCthreshold += (L->nblocks - old_blocks); } else if (status == LUA_ERRRUN) /* an error occurred: correct error code */ status = LUA_ERRSYNTAX; return status; }
可以看到它里面调用了 f_parser 。
static void f_parser (lua_State *L, void *ud) { struct ParserS *p = (struct ParserS *)ud; Proto *tf = p->bin ? luaU_undump(L, p->z) : luaY_parser(L, p->z); luaV_Lclosure(L, tf, 0); }
f_parser 里调用 luaY_parser 来做具体的语法解析。
如果已经是字节码的文件,就不用语法解析了,直接 luaU_undump 字节码就好。
到这里,就差 lua_call 的执行了。
LUA_API int lua_call (lua_State *L, int nargs, int nresults) { StkId func = L->top - (nargs+1); /* function to be called */ struct CallS c; int status; c.func = func; c.nresults = nresults; status = luaD_runprotected(L, f_call, &c); if (status != 0) /* an error occurred? */ L->top = func; /* remove parameters from the stack */ return status; }
lua_call 里调用 f_call
static void f_call (lua_State *L, void *ud) { struct CallS *c = (struct CallS *)ud; luaD_call(L, c->func, c->nresults); }
f_call 里调用 luaD_call。
然后是 luaV_execute(L, cl, func+1));
这个就是虚拟机执行字节码指令了。
luaV_execute 就是个大大的 switch case,不贴代码了。
翻了下之前的博客,最早的时候有一个虚拟机执行的分析。
这里就不再重复一次这种体力劳动了。
力气应该花在更有意义的地方!
----------------------------------------
到目前为止的问题:
> 无!
----------------------------------------