luac.c 是编译器 luac 的入口文件。
老规矩,从 main 函数看起,看看这个过程中程序都做了些什么?
int main(int argc, char* argv[]) { char* d="luac.out"; /* default output file */ int i; for (i=1; i<argc; i++) { if (argv[i][0]!='-') /* end of options */ break; else if (IS("-")) /* use stdin */ break; else if (IS("-d")) /* debug */ lua_debug=1; else if (IS("-l")) /* list */ listing=1; else if (IS("-o")) /* output file */ d=argv[++i]; else if (IS("-p")) /* parse only (for timing purposes) */ dumping=0; else if (IS("-v")) /* show version */ printf("%s %s\n(written by %s)\n\n",LUA_VERSION,LUA_COPYRIGHT,LUA_AUTHORS); else /* unknown option */ usage(); } --i; /* fake new argv[0] */ argc-=i; argv+=i; if (argc<2) usage(); for (i=1; i<argc; i++) if (IS(d)) { fprintf(stderr,"luac: will not overwrite input file \"%s\"\n",d); exit(1); } D=(dumping) ? fopen(d,"wb") : stdout; /* must open in binary mode */ if (D==NULL) { fprintf(stderr,"luac: cannot open "); perror(d); exit(1); } for (i=1; i<argc; i++) compile(IS("-")? NULL : argv[i]); fclose(D); return 0; }
看这个代码的时候,最好参考一下 luac 的手册,对比各种选项能看的更清楚点。
程序一开始就定义了一个默认的输出文件,"luac.out"。
接下来,开始遍历命令行的输入,以获得用户从命令行输入的选项。
一旦程序遇到一个不是中划线(减号 '-')打头的选项,遍历结束。
luac 的命令行选项的格式都是中划线后加一个字符,以空白分割,这点和 unix 的传统是一样的。并且和一般的命令行程序操作界面也是一致的。
选项如果只是一个中划线,使用标准输入做为输入文件,遍历结束。
这里的 IS 是一个宏,这是 C 语言里面的一个令代码更有实际意义及更加容易阅读的一个方法,Lua 的源代码里用到了不少的宏,除了可以减少代码量外,最重要的就是让代码更易读,以及更有意义。
'-d' 调试选项,如果打开的话,程序会在生成字节码的时候生成一些调试相关的信息。比如行号和其它一些方便调试的内容。有一些调试接口是只在你打开调试时,它才有意义。
'-l' 是否打印字节码。
'-o' 设置输出文件,输出文件句直接在选项的后面,如果不使用这个选项,则使用上面提到的那个 luac.out 文件做为输出文件。
'-p' 只进行语法分析。
'-v' 显示 Lua 的版本号,版权信息及作者。
否则,如果有错误的选项,调用 usage,打印使用方法。
static void usage(void) { fprintf(stderr,"usage: luac [-dlpv] [-o output] file ...\n"); exit(0); }
命令行选项遍历退出时,说明这时命令行选项应该是到了 Lua 脚本的源代码文件了。
如果参数个数不对,则也同样调用 usage , 打印使用方法。
在对文件进行编译之前,要先检查一下 Lua 脚本文件是否和输出文件同名了,如果同名,打印出错信息并退出。就是这个 for 循环:
for (i=1; i<argc; i++) if (IS(d)) { fprintf(stderr,"luac: will not overwrite input file \"%s\"\n",d); exit(1); }
打开输出文件,如果不需要输出的话,打开标准输出作为输出。如果打开文件出错,则打印错误并退出。
这里的 dumping 标志只影响编译后字节码的输出,其它过程无影响。
D=(dumping) ? fopen(d,"wb") : stdout; /* must open in binary mode */ if (D==NULL) { fprintf(stderr,"luac: cannot open "); perror(d); exit(1); }
最后一个 for 循环,是编译所有的 Lua 脚本文件。
for (i=1; i<argc; i++) compile(IS("-")? NULL : argv[i]);
compile 函数的作用就是打开文件,编译,并关闭文件。
static void compile(char* filename) { if (lua_openfile(filename)==NULL) { fprintf(stderr,"luac: cannot open "); perror(filename); exit(1); } do_compile(); lua_closefile(); }
do_compile 编译,并输出。
static void do_compile(void) { TFunc* tf=new(TFunc); luaI_initTFunc(tf); tf->fileName = lua_parsedfile; lua_parse(tf); do_dump(tf); }
do_dump 看名字可以看出,做实际的 dump 工作的,也就是输出字节码,或者叫转存字节码。
static void do_dump(TFunc* tf) /* only for tf==main */ { if (dumping) DumpHeader(D); while (tf!=NULL) { TFunc* nf; if (listing) PrintFunction(tf); if (dumping) DumpFunction(tf,D); nf=tf->next; /* list only built after first main */ luaI_freefunc(tf); tf=nf; } }
这个文件结束了,不过,这里有好几个东西都没有说,比如,上面的那个打开关闭文件是干什么的,以及为什么要那么做?
do_compile 里的 TFunc 是什么?那个初始化是什么?lua_parser 是什么? do_dump 方法里调的那几个方法又分别是干什么的?
这些东西都会根据这里调用的顺序一点点慢慢的展现出来的。