首页 > 代码库 > Lua2.4 编译器入口 luac.c

Lua2.4 编译器入口 luac.c

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 方法里调的那几个方法又分别是干什么的?

这些东西都会根据这里调用的顺序一点点慢慢的展现出来的。

Lua2.4 编译器入口 luac.c