文档章节

Lua5.0 语法解析之路

晓寒
 晓寒
发布于 2015/08/31 13:16
字数 1116
阅读 294
收藏 1

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

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


© 著作权归作者所有

上一篇: Lua5.0 词法分析
下一篇: Lua5.0 编译器入口
晓寒
粉丝 35
博文 119
码字总数 133745
作品 0
海淀
私信 提问
Lua5.0 语法分析

写着写着,又来到了这里。 这次是写还是不写,嗯,这是一个问题。 先说点题外话,没准也算是题内。 经过这段时间的代码阅读,分析,调试,感觉自己的代码控制力提高了一些。 当然了,不单是 ...

晓寒
2015/09/06
137
2
Nodejs监听日志文件的变化

需求 最近有在做日志文件的分析,其中有一个需求:A服务器项目需要用Nodejs监听日志文件的变化,当项目产生了新的日志信息,将新的部分通过socket传输到B服务器项目。socket暂时不做分析。 ...

qiufeihong2018
08/03
0
0
Lua5.0 第零篇

Lua5.0 版本从这里下载: www.lua.org/ftp/lua-5.0.tar.gz 它的在线文档在这里: http://www.lua.org/manual/5.0/ 很好,终于进入版本五时代了。 打开下载下来的压缩包,好多的目录和文件。不...

晓寒
2015/08/29
206
0
Lua5.0 示例程序抽样

Lua5.0 的 test 目录里是一些 Lua 示例程序,有些挺有意思的,这里拿出来看看。 简单的介绍在 README 文件里有描述。 这些程序应该就是原作者的团队写的,有的程序的含金量很高,也很有意思。...

晓寒
2015/09/08
147
0
Python大牛的必学成长之路(20个经典学习资料)

想要成为Python大师,首先要从最基础知识开始学起,了解掌握Python最基础的知识点,编程意识从无都有,才能在Python 的道路上走得更远。 第一章 python基础 3课时 1小时5分钟 1、python 入门...

让往事随风
2015/12/23
53
0

没有更多内容

加载失败,请刷新页面

加载更多

32位与64位Linux系统下各类型长度对比

64 位的优点:64 位的应用程序可以直接访问 4EB 的内存和文件大小最大达到4 EB(2 的 63 次幂);可以访问大型数据库。本文介绍的是64位下C语言开发程序注意事项。 1. 32 位和 64 位C数据类型...

mskk
34分钟前
6
0
Vue 实现点击空白处隐藏某节点(三种方式:指令、普通、遮罩)

在项目中往往会有这样的需求: 弹出框(或Popover)在 show 后,点击空白处可以将其 hide。 针对此需求,整理了三种实现方式,大家按实际情况选择。 当然,我们做项目肯定会用到 UI 框架,常...

张兴华ZHero
40分钟前
7
0
SpringBoot激活profiles你知道几种方式?

多环境是最常见的配置隔离方式之一,可以根据不同的运行环境提供不同的配置信息来应对不同的业务场景,在SpringBoot内支持了多种配置隔离的方式,可以激活单个或者多个配置文件。 激活Profi...

恒宇少年
42分钟前
8
0
PDF修改文字的方法有哪些?怎么修改PDF文件中的文字

PDF修改文字一直以来都是一个难以解决的问题,很多的办公族在办公的时候会有修改PDF文件中的文字的需要,可是PDF文件一般是不能进行编辑和修改的,难道就没有什么办法解决这个问题了嘛?不要...

趣味办公社
45分钟前
5
0
企业组织中采用服务网格的挑战

作者:Christian Posta 译者:罗广明 原文:https://blog.christianposta.com/challenges-of-adopting-service-mesh-in-enterprise-organizations/ 编者按 本文作者介绍了企业组织采用服务网...

jimmysong
54分钟前
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部