Lua5.0 语法解析之路

上回说到 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 之路已经打通。

在这之前,先看一下路上的其它风景。


你可能感兴趣的:(lua,Lua5.0)