lzio.c 主要有 3 个方法:

  • LUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data)
  • LUAI_FUNC size_t luaZ_read (ZIO* z, void *b, size_t n)
  • LUAI_FUNC int luaZ_fill (ZIO *z)

luaZ_init(lzio.c#38)

void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data) {
  z->L = L;
  z->reader = reader;
  z->data = data;
  z->n = 0;
  z->p = NULL;
}

luaZ_init 主要初始化一个 ZIO 结构体,而这个结构体从参数里面传进来。

typedef struct Zio ZIO;

struct Zio {
  size_t n;     /* bytes still unread */
  const char *p;    /* current position in buffer */
  lua_Reader reader;    /* reader function */
  void *data;     /* additional data */
  lua_State *L;     /* Lua state (for reader) */
};

LuaZ_init 只在 lapi.c 里的 lua_load 方法内被调用;而 lua_load 方法我暂时看到是在 lauxlib.c 里的 luaL_loadfilex 方法和 luaL_loadbufferx 方法中调用。这两个的不同在于,前者是在执行 lua xxx.lua 的时候调用读入一个文件;后者是在执行 lua -e stat 的时候读入这个字符串

LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,
                      const char *chunkname, const char *mode) {
  ZIO z;
  int status;
  lua_lock(L);
  if (!chunkname) chunkname = "?";
  luaZ_init(L, &z, reader, data);
  status = luaD_protectedparser(L, &z, chunkname, mode);
  if (status == LUA_OK) {  /* no errors? */
    LClosure *f = clLvalue(L->top - 1);  /* get newly created function */
    if (f->nupvalues >= 1) {  /* does it have an upvalue? */
      /* get global table from registry */
      Table *reg = hvalue(&G(L)->l_registry);
      const TValue *gt = luaH_getint(reg, LUA_RIDX_GLOBALS);
      /* set global table as 1st upvalue of 'f' (may be LUA_ENV) */
      setobj(L, f->upvals[0]->v, gt);
      luaC_upvalbarrier(L, f->upvals[0]);
    }
  }
  lua_unlock(L);
  return status;
}
LUALIB_API int luaL_loadfilex (lua_State *L, const char *filename,
                                             const char *mode) {
  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, "open", fnameindex);
  }
  if (skipcomment(&lf, &c))  /* read initial portion */
    lf.buff[lf.n++] = '\n';  /* add line to correct line numbers */
  if (c == LUA_SIGNATURE[0] && filename) {  /* binary file? */
    lf.f = freopen(filename, "rb", lf.f);  /* reopen in binary mode */
    if (lf.f == NULL) return errfile(L, "reopen", fnameindex);
    skipcomment(&lf, &c);  /* re-read initial portion */
  }
  if (c != EOF)
    lf.buff[lf.n++] = c;  /* 'c' is the first character of the stream */
  status = lua_load(L, getF, &lf, lua_tostring(L, -1), mode);
  readstatus = ferror(lf.f);
  if (filename) fclose(lf.f);  /* close file (even in case of errors) */
  if (readstatus) {
    lua_settop(L, fnameindex);  /* ignore results from 'lua_load' */
    return errfile(L, "read", fnameindex);
  }
  lua_remove(L, fnameindex);
  return status;
}
LUALIB_API int luaL_loadbufferx (lua_State *L, const char *buff, size_t size,
                                 const char *name, const char *mode) {
  LoadS ls;
  ls.s = buff;
  ls.size = size;
  return lua_load(L, getS, &ls, name, mode);
}

很明显 luaL_loadfilexluaL_loadbufferx 两个方法用不同的参数去调用 lua_load。一个是 getF 作为 lua_Reader readerLoadF lf 作为 void *data;另一个是 getS 作为 lua_Reader readerLoadS ls 作为 void *data。它们的分别定义是这样的:

static const char *getF (lua_State *L, void *ud, size_t *size) {
  LoadF *lf = (LoadF *)ud;
  (void)L;  /* not used */
  if (lf->n > 0) {  /* are there pre-read characters to be read? */
    *size = lf->n;  /* return them (chars already in buffer) */
    lf->n = 0;  /* no more pre-read characters */
  }
  else {  /* read a block from file */
    /* 'fread' can return > 0 *and* set the EOF flag. If next call to
       'getF' called 'fread', it might still wait for user input.
       The next check avoids this problem. */
    if (feof(lf->f)) return NULL;
    *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f);  /* read block */
  }
  return lf->buff;
}
typedef struct LoadF {
  int n;  /* number of pre-read characters */
  FILE *f;  /* file being read */
  char buff[BUFSIZ];  /* area for reading file */
} LoadF;
static const char *getS (lua_State *L, void *ud, size_t *size) {
  LoadS *ls = (LoadS *)ud;
  (void)L;  /* not used */
  if (ls->size == 0) return NULL;
  *size = ls->size;
  ls->size = 0;
  return ls->s;
}
typedef struct LoadS {
  const char *s;
  size_t size;
} LoadS;

这样,我们就可以看出来 lua_load 通过传入的参数调用 luaZ_init,设置 ZIO zL, reader(就是不同的 getFgetS), data(就是不同的 LoadF lfLoadS ls), n(0), p(NULL)。

luaZ_fill(lzio.c#23)

int luaZ_fill (ZIO *z) {
  size_t size;
  lua_State *L = z->L;
  const char *buff;
  lua_unlock(L);
  buff = z->reader(L, z->data, &size);
  lua_lock(L);
  if (buff == NULL || size == 0)
    return EOZ;
  z->n = size - 1;  /* discount char being returned */
  z->p = buff;
  return cast_uchar(*(z->p++));
}

这个方法主要通过 zgetc 这个宏被外界调用。

#define zgetc(z)  (((z)->n--)>0 ?  cast_uchar(*(z)->p++) : luaZ_fill(z))

zgetc 这儿宏会判断 Zio zn 减一能否大于 0,也就是是否还有未处理的字符,如果有的话,就返回这个字符,并把 p 加一,否则的话,就执行 luaZ_fill(z),根据我们之前说到的 luaZ_init,我们知道 n 是一开始就是 0 的,所以需要调用 luaZ_fill。一开始我并不明白为什么这个方法叫 fill,我现在觉得是因为这个方法的职责就是把 reader 读到的数据装到 Zio z 里面,可以看到 buff = z->reader(L, z->data, &size); 这句将读到的数据记录到 buff 里面,最后设置 Zio znsize - 1pbuff 这个指针,最后会自增一,返回当前的第一个字符。

我不清楚大家会不会好奇在什么时候读入了数据,我是好奇的。对于执行命令 lua -e stat 的时候,字符串直接就在 argv 里面了;对于 lua -e xxx.lua 这样的命令,在 getF 里面调用了 fread 来读入文件的内容。

luaZ_read(lzio.c#48)

size_t luaZ_read (ZIO *z, void *b, size_t n) {
  while (n) {
    size_t m;
    if (z->n == 0) {  /* no bytes in buffer? */
      if (luaZ_fill(z) == EOZ)  /* try to read more */
        return n;  /* no more input; return number of missing bytes */
      else {
        z->n++;  /* luaZ_fill consumed first byte; put it back */
        z->p--;
      }
    }
    m = (n <= z->n) ? n : z->n;  /* min. between n and z->n */
    memcpy(b, z->p, m);
    z->n -= m;
    z->p += m;
    b = (char *)b + m;
    n -= m;
  }
  return 0;
}

这个方法会在执行 lua xxx.out 的时候被调用,其中 xxx.out 文件是通过 luac 生成的字节文件。这个方法通过调用 luaZ_fill(z) 来读入内容,不过在外面加入了循环来读入 n 个数据。