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 之路已经打通。

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

时间: 2024-08-01 21:27:57

Lua5.0 语法解析之路的相关文章

Mysql Join语法解析与性能分析详解

一.Join语法概述 join 用于多表中字段之间的联系,语法如下: ... FROM table1 INNER|LEFT|RIGHT JOIN table2 ON conditiona table1:左表:table2:右表. JOIN 按照功能大致分为如下三类: INNER JOIN(内连接,或等值连接):取得两个表中存在连接匹配关系的记录. LEFT JOIN(左连接):取得左表(table1)完全记录,即是右表(table2)并无对应匹配记录. RIGHT JOIN(右连接):与 LEF

工作中的那些坑(2)——语法解析器

工作项目里用到线性回归算法,用于计算账户的分值,表明某账户是否是有风险的账户.其中参数都配好了,代码里直接用逆波兰表达式解析即可.本来事情到这里已经结束,突然来了新的需求:账户算出来的分数较为无序,于是考虑用sigmoid函数将其映射到(0,1)区间内,在乘以系数使其显示更为直观.为了使整个表达式更将通用,要求做到同时能解析sigmoid函数,即:原来单纯解析常量.变量.运算符的逆波兰表达式已经不能直接解析新增的sigmoid函数(即表达式),另外对嵌套的情况也没做处理,所以需要重新设计一个更为

nsis安装包_示例脚本语法解析

以下是代码及解析,其中有底色的部分为脚本内容. 注释.!define.变量.!include.常量 ; Script generated by the HM NIS Edit Script Wizard. ; HM NIS Edit Wizard helper defines !define PRODUCT_NAME "signjing安装示例" !define PRODUCT_VERSION "0.0.0.1" !define PRODUCT_PUBLISHER

MYSQL 源代码 编译原理 AST和解析树 代码语法解析

MYSQL 源代码 编译原理 AST和解析树 代码语法解析 http://blog.csdn.net/wfp458113181wfp/article/details/17082355 使用AST树 分类:             antlr              2013-12-02 22:39     255人阅读     评论(0)     收藏     举报 目录(?)[+] 第五章使用AST树中间结果来计算表达式值 创建ASTS 第五章.使用AST树中间结果来计算表达式值 现在我们已

Android.mk语法解析

Android.mk文件相当于是从Makefile文件中截取的小片段,非常非常的小!可被系统解析一次或者多次!应该尽量少的声明 该文件的一个很重要的组成部分就是模块 1.    - a static library   静态库 2.    - a shared library   动态库 只需要将动态库安装/拷贝到你的应用程序包即可,静态库是用来生成动态库的 你可以定义一个或多个模块,而且同一source file你可以放到多个模块中 编译之前还有一些细节要注意,比如:不需要将头文件或者一些依赖

boost spirit 语法解析

使用spirit能很方便的解析自定义的语法规则,在他的文档中也说明了spirit与regex还有其他库的不同点.灵活,伸缩性好,可以用来搭建小的语法解析器也可以用来开发大型编译器等等. 定义语法规则之前首先要了解一下Extended Backus-Normal Form (EBNF) EBNF可以定义一下生成合法字符串的公式,例如: 例1: rule1 = "0" | "1" | "2" | "3". rule2 = &quo

用java实现一个简易编译器-语法解析

语法和解析树: 举个例子看看,语法解析的过程.句子:"我看到刘德华唱歌".在计算机里,怎么用程序解析它呢.从语法上看,句子的组成是由主语,动词,和谓语从句组成,主语是"我",动词是"看见", 谓语从句是"刘德华唱歌".因此一个句子可以分解成 主语 + 动词 + 谓语从句: 句子-->主语+动词 + 谓语从句 主语是名词,因此有 : 主语->名词 句子里的名词有: "我", "刘德华&q

Generator函数语法解析

转载请注明出处: Generator函数语法解析 Generator函数是ES6提供的一种异步编程解决方案,语法与传统函数完全不同.以下会介绍一下Generator函数. 写下这篇文章的目的其实很简单,是想梳理一下自己对于Generator的理解,同时呢,为学习async函数做一下知识储备. Generator函数 基本概念 yield表达式 next方法 next方法的参数 yield*表达式 与Iterator接口的关系 for...of循环 作为对象属性的Generator函数 Gener

MySQL技术探索01实现SQL语法解析器

本文将介绍如何使用开源的语法和词法分析框架bison和flex来实现SQL解析器.出于技术学习的目的,本文做描述的微型SQL解析器仅能实现对微型SQL的语法解析. 1.MySQL中的SQL解析器 包括JDBC.ODBC.ADO等等关系数据库客户端应用开发框架在内的各种SDK,核心功能是帮助程序员简化各种客户端的数据库操作,同时将SQL语句通过网络形式发送给MySQL等关系数据库的服务器进程.MySQL服务器进行负责解析并执行这些SQL语句.SQL语句中的语法规则多种多样,MySQL服务器是如何实