解决上一篇的问题,上代码了。
C 语言程序的入口为 main 函数,Lua 编译器的入口为 luac.c 文件里的 main 函数。
先来看一下 main 函数:
int main(int argc, const char* argv[]) { Proto** P,*tf; int i=doargs(argc,argv); argc-=i; argv+=i; if (argc<=0) usage("no input files given",NULL); L=lua_open(0); P=luaM_newvector(L,argc,Proto*); for (i=0; i<argc; i++) P[i]=load(IS("-")? NULL : argv[i]); tf=combine(P,argc); if (dumping) luaU_optchunk(tf); if (listing) luaU_printchunk(tf); if (testing) luaU_testchunk(tf); if (dumping) { if (stripping) strip(tf); luaU_dumpchunk(tf,efopen(output,"wb")); } return 0; }
程序上来先声明一个函数原型的二级指针 P(下面用的时候是把它做为函数原型的指针数组来用的),一个函数原型的指针 tf。
函数原型可以简单地认为是一个 lua 文件等价的编译后的东西。
每一个 lua 文件会生成一个函数原型,最后,所有的输入 lua 文件会拼成一个函数原型。
doargs 解析入参。主要是带中划线的那样的参数。就是一系列的 IS 宏调用的判断。这个函数的注释比较清楚。代码如下:
static int doargs(int argc, const char* argv[]) { int i; for (i=1; i<argc; i++) { if (*argv[i]!=‘-‘) /* end of options */ break; else if (IS("-")) /* end of options; use stdin */ return i; else if (IS("-l")) /* list */ listing=1; else if (IS("-o")) /* output file */ { output=argv[++i]; if (output==NULL) usage(NULL,NULL); } else if (IS("-p")) /* parse only */ dumping=0; else if (IS("-s")) /* strip debug information */ stripping=1; else if (IS("-t")) /* test */ { testing=1; dumping=0; } else if (IS("-v")) /* show version */ { printf("%s %s\n",LUA_VERSION,LUA_COPYRIGHT); if (argc==2) exit(0); } else /* unknown option */ usage("unrecognized option `%s‘",argv[i]); } if (i==argc && (listing || testing)) { dumping=0; argv[--i]=OUTPUT; } return i; }
解析好的选项保存在相应的静态变量中。
如下所示:
static int listing=0; /* list bytecodes? */ static int dumping=1; /* dump bytecodes? */ static int stripping=0; /* strip debug information? */ static int testing=0; /* test integrity? */ static const char* output=OUTPUT; /* output file name */
输出的文件名默认为 "luac.out",如果用户指定了,则用用户指定的名字。就是上面的 IS("-o") 这块儿。
如果出错,则调用 usage 提示用户。usage 中列出了各个参数的用户及意义。
参数解析完之后,如果还有剩余的命令行参数的话,都被当作输入的 lua 源代码文件。
新建一个 lua_state ,这就是 lua 中那个无处不在的 L 。
新建函数原型 Proto 指针数组。
为每一个命令行参数调用 load ,把它转化为一个函数原型。代码如下:
for (i=0; i<argc; i++) P[i]=load(IS("-")? NULL : argv[i]);
把这个数组合并为一个函数原型。 tf=combine(P,argc);
最下面的几个 if 判断根据参数选项的不同,进行不同的操作。
dump 代码之前会先对函数原型进行优化。 if (dumping) luaU_optchunk(tf);
luaU_printchunk(tf); 打印函数原型内容。
luaU_dumpchunk : dump 函数原型。
这几个先列个问题,以后细说,虽说和主要的编译过程关系不是太大。
回到编译的问题上来,一个 lua 文件是如何转经为 Proto 的呢?
这里有一个 load 函数,就是做得这个事。
static Proto* load(const char* filename) { Proto* tf; ZIO z; char source[512]; FILE* f; int c,undump; if (filename==NULL) { f=stdin; filename="(stdin)"; } else f=efopen(filename,"r"); c=ungetc(fgetc(f),f); if (ferror(f)) { fprintf(stderr,"luac: cannot read from "); perror(filename); exit(1); } undump=(c==ID_CHUNK); if (undump && f!=stdin) { fclose(f); f=efopen(filename,"rb"); } sprintf(source,"@%.*s",Sizeof(source)-2,filename); luaZ_Fopen(&z,f,source); tf = undump ? luaU_undump(L,&z) : luaY_parser(L,&z); if (f!=stdin) fclose(f); return tf; }
程序上来声明了一个 ZIO 变量,以后读源代码就从它读取了。
如果文件名为空,则从标准输入读取,否则打开文件。
先读文件的第一个字符,如果是 ID_CHUNK 的话,则说明此时打开的文件是一个 lua 字节码文件。
最后根据打开的是否是 lua 字节码文件来决定是进行 undump 操作还是语法解释操作。
combine 把多个 Proto 拼成一个 Proto。
其中有一个需要注意的地方是,一个函数原型是一个 Closure。就是 for 循环里面的东西。
strip 把一些调试信息删掉。
这篇的内容先到这里,等后面几个相关的知识点说完这里自然就清楚了。
看下目前的问题列表,好像已经很多了。
----------------------------------------
到目前为止的问题:
> 函数原型优化 luaU_optchunk
> 打印函数原型 luaU_printchunk
> dump 函数原型 luaU_dumpchunk
> 内存分配 luaM_newvector
> ZIO 是什么
> 语法分析 luaY_parser
----------------------------------------