yyqng

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

6.1 Lua词法

    语言的解析一般是两遍遍历的过程,第一遍生成AST,第二遍将AST翻译为字节码。

    Lua使用一遍扫描代码文件的方式生成字节码,以加快解释执行的速度。但缺点是代码比较难以理解。如

    dostat ->    DO      block    END

    函数    -> 终结符  语法块  终结符

6.2 赋值类指令

--filename中的lua代码
local a = 10

     使用myloadfile(个人测试代码)加载上述 filename 文件中的 lua 代码进行加载和解析:

int myloadfile(const char *filename) {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L); // open all the libs above
    if (luaL_loadfile(L, filename)) {
        error(L, "luaL_loadfile %s", lua_tostring(L, -1));
    }
    lua_close(L);
    return 0;
}

     解释器会依次走过以下函数(箭头左方),右边是其对应的EBNF表示

chunk     -> { stat [';'] }
statement -> localstat
localstat -> LOCAL NAME {',' NAME} [ '=' explist1]
explist1  -> expr {',' expr}
expr      -> subexpr
subexpr   -> simpleexp
simpleexp -> NUMBER

     详细函数调用顺序如下

int myloadfile(const char *filename);
LUALIB_API int luaL_loadfile (lua_State *L, const char *filename);
LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, const char *chunkname);
int luaD_protectedparser (lua_State *L, ZIO *z, const char *name);
int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef);
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud);
static void f_parser (lua_State *L, void *ud);
Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name);
// EBNF 对应函数
static void chunk (LexState *ls);
static int statement (LexState *ls);
static void localstat (LexState *ls);
static int explist1 (LexState *ls, expdesc *v);
static void expr (LexState *ls, expdesc *v);
static BinOpr subexpr (LexState *ls, expdesc *v, unsigned int limit);
static void simpleexp (LexState *ls, expdesc *v);

     EBNF 对应函数中,有一个贯穿始终的用于词法分析的中间临时变量ls(llex.h),结构体为:

typedef struct LexState {
  int current;  /* current character (charint) */
  int linenumber;  /* input line counter */
  int lastline;  /* line of last token `consumed' */
  Token t;  /* current token */
  Token lookahead;  /* look ahead token */
  struct FuncState *fs;  /* `FuncState' is private to the parser */
  struct lua_State *L;
  ZIO *z;  /* input stream */
  Mbuffer *buff;  /* buffer for tokens */
  TString *source;  /* current source name */
  char decpoint;  /* locale decimal point */
} LexState;

      ls->fs->freereg (int型) 存放的就是当前函数栈的下一个可用位置,在每个 chunk 函数中,都会根据当前函数栈存放的变量数量(包括函数的局部变量、函数的参数等)进行调整:

static void chunk (LexState *ls) {
  /* chunk -> { stat [`;'] } */
  int islast = 0;
  enterlevel(ls);
  while (!islast && !block_follow(ls->t.token)) {
    islast = statement(ls); //此函数中将选择进入localstat
    testnext(ls, ';');
    lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg);
    lua_assert(ls->fs->freereg >= ls->fs->nactvar);
    ls->fs->freereg = ls->fs->nactvar;  /* free registers */
  }
  leavelevel(ls);
}

    在函数 localstat(lparser.c) 中,循环调用 new_localvar,将赋值表达式中 = 左边的所有变量都生成一个相应的局部变量,对应的结构体是 LocVar,存放于ls->fs->f->locvars(LocVar *)

typedef struct LocVar {
  TString *varname; /* 变量名 */
  int startpc;  /* first point where variable is active */
  int endpc;    /* first point where variable is dead */
} LocVar;

     localstat用于生成局部变量

static void localstat (LexState *ls) {
  /* stat -> LOCAL NAME {`,' NAME} [`=' explist1] */
  int nvars = 0;
  int nexps;
  expdesc e;
  do {
    // 循环调用 new_localvar,将赋值表达式中 = 左边的所有变量都生成一个相应的局部变量。
    new_localvar(ls, str_checkname(ls), nvars++);
  } while (testnext(ls, ','));
  if (testnext(ls, '='))
    nexps = explist1(ls, &e);
  else {
    e.k = VVOID;
    nexps = 0;
  }
  adjust_assign(ls, nvars, nexps, &e);
  adjustlocalvars(ls, nvars); //用于根据nvars调整ls->fs->nactvar
}

     adjustlocalvars用于调整ls->fs->nactvar

static void adjustlocalvars (LexState *ls, int nvars) {
  FuncState *fs = ls->fs;
  fs->nactvar = cast_byte(fs->nactvar + nvars);
  for (; nvars; nvars--) {
    getlocvar(fs, fs->nactvar - nvars).startpc = fs->pc;
  }
}
#define getlocvar(fs, i)    ((fs)->f->locvars[(fs)->actvar[i]])

     下面介绍右边表达式结果的存储

    解析表达式的结果会存储在一个临时数据结构 expdesc (lparser.h)中:

/*
** Expression descriptor
*/

typedef enum {
  VVOID,    /* no value */
  VNIL,
  VTRUE,
  VFALSE,
  VK,        /* info = index of constant in `k' */
  VKNUM,    /* nval = numerical value */
  VLOCAL,    /* info = local register */
  VUPVAL,       /* info = index of upvalue in `upvalues' */
  VGLOBAL,    /* info = index of table; aux = index of global name in `k' */
  VINDEXED,    /* info = table register; aux = index register (or `k') */
  VJMP,        /* info = instruction pc */
  VRELOCABLE,    /* info = instruction pc */
  VNONRELOC,    /* info = result register */
  VCALL,    /* info = instruction pc */
  VVARARG    /* info = instruction pc */
} expkind;

typedef struct expdesc {
  expkind k; /* 具体类型 */
  union {
    struct { int info, aux; } s;
    lua_Number nval;
  } u;
  int t;  /* patch list of `exit when true' */
  int f;  /* patch list of `exit when false' */
} expdesc;

     localstat会调用explist1(lparser.c),将结果缓存在 expdesc 中

static int explist1 (LexState *ls, expdesc *v) {
  /* explist1 -> expr { `,' expr } */
  int n = 1;  /* at least one expression */
  expr(ls, v); //解析表达式
  while (testnext(ls, ',')) {
    //将表达式存入当前函数的下一个可用寄存器中
    luaK_exp2nextreg(ls->fs, v);
    expr(ls, v); //解析表达式
    n++;
  }
  return n;
}

    expr->subexpr ->simpleexp,赋值表达式右边为10,最终走入函数simpleexp(lparser.c)

static void simpleexp (LexState *ls, expdesc *v) {
  /* simpleexp -> NUMBER | STRING | NIL | true | false | ... |
                  constructor | FUNCTION body | primaryexp */
  switch (ls->t.token) {
    case TK_NUMBER: {
      init_exp(v, VKNUM, 0); //VKNUM表示数字常量
      //union u 的数据根据不同的类型会存储不同的信息,这里将10赋值给nval
      v->u.nval = ls->t.seminfo.r; 
      break;
    }
    现在这个表达式的信息已经存放在 expdesc 结构体中,接下来根据它生成对应的字节码。
    localstat -> adjust_assign -> luaK_exp2nextreg(lcode.c)
void luaK_exp2nextreg (FuncState *fs, expdesc *e) {
  // 根据变量所在的不同作用域( local, global, upvalue )来决定这个变量是否需要重定向。
  luaK_dischargevars(fs, e);
  freeexp(fs, e);
  // 分配可用的函数寄存器空间,得到这个空间对应的寄存器索引。有了空间,才能存储变量。
  luaK_reserveregs(fs, 1);
  // 把表达式的数据放入寄存器空间。最终又会调用 discharge2reg 函数,
  // 这个函数式根据不同的表达式类型( NIL ,布尔表达式,数字等) 来生成存取表达式的值到寄存器的字节码。
  exp2reg(fs, e, fs->freereg - 1); 
}

    luaK_exp2nextreg -> discharge2reg(lcode.c)

static void discharge2reg (FuncState *fs, expdesc *e, int reg) {
  luaK_dischargevars(fs, e);
  switch (e->k) {
    // ... ...
    case VKNUM: {
      luaK_codeABx(fs, OP_LOADK, reg, luaK_numberK(fs, e->u.nval));
      break;
    }

    设置 fs->f->code[fs->pc] = i;  // i 为 Instruction

int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) {
  lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx);
  lua_assert(getCMode(o) == OpArgN);
  return luaK_code(fs, CREATE_ABx(o, a, bc), fs->ls->lastline);
}

    如果左边的变量变成下面这样 

--filename中的lua代码
local a = 10

     回头看看前面的 localstat 函数最后两行代码

static void localstat (LexState *ls) {
  // ......
  adjust_assign(ls, nvars, nexps, &e); // 根据等号两边变量和表达式的数量来调整赋值,b会赋值为nil
  adjustlocalvars(ls, nvars); //用于根据nvars调整ls->fs->nactvar,并记录这些局部变量的 startpc值
}

     再次修改示例

--filename中的lua代码
local a = 10
local b = a

     Chunk:Spy反编译的指令如下:

; source chunk: c_call_lua/a.lua
; x86 standard (32-bit, little endian, doubles)

; function [0] definition (level 1)
; 0 upvalues, 0 params, 2 stacks
.function  0 0 2 2
.local  "a"  ; 0
.local  "b"  ; 1
.const  1  ; 0
[1] loadk      0   0        ; 1
[2] move       1   0      
[3] return     0   1      
; end of function

     对于loadk,前面已经介绍过了。对于move指令,解释器会依次走过以下函数(箭头左方),右边是其对应的EBNF表示

chunk     -> { stat [';'] }
statement -> localstat
localstat -> LOCAL NAME {',' NAME} [ '=' explist1]
explist1  -> expr {',' expr}
expr      -> subexpr
subexpr   -> simpleexp
simpleexp -> primaryexp
primaryexp-> prefixexp
prefixexp -> NAME

    与前面的区别是,这里走入了 primaryexp,然后在 prefixexp中判断这是一个变量时,会调用 singlevar(lparser.c) 进行变量查找

static void singlevar (LexState *ls, expdesc *var) {
  TString *varname = str_checkname(ls);
  FuncState *fs = ls->fs;
  if (singlevaraux(fs, varname, var, 1) == VGLOBAL)
    var->u.s.info = luaK_stringK(fs, varname);  /* info points to global name */
}

     singlevar会调用singlevaraux进行递归查找

static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
  if (fs == NULL) {  /* no more levels? */
    init_exp(var, VGLOBAL, NO_REG);  /* default is global variable */
    return VGLOBAL;
  }
  else {
    int v = searchvar(fs, n);  /* look up at current level */
    if (v >= 0) {
      init_exp(var, VLOCAL, v);
      if (!base)
        markupval(fs, v);  /* local will be used as an upval */
      return VLOCAL;
    }
    else {  /* not found at current level; try upper one */
      if (singlevaraux(fs->prev, n, var, 0) == VGLOBAL)
        return VGLOBAL;
      var->u.s.info = indexupvalue(fs, n, var);  /* else was LOCAL or UPVAL */
      var->k = VUPVAL;  /* upvalue in this level */
      return VUPVAL;
    }
  }
}

     localstat -> adjust_assign -> luaK_exp2nextreg->luaK_dischargevars(lcode.c)根据三种不同的类型,有三种不同的操作 

void luaK_dischargevars (FuncState *fs, expdesc *e) {
  switch (e->k) {
    case VLOCAL: { //局部变量不需要重定向,也无需额外的语句把这个值加载进来
      e->k = VNONRELOC;
      break;
    }

     localstat -> adjust_assign -> luaK_exp2nextreg -> exp2reg -> discharge2reg(lcode.c)

static void discharge2reg (FuncState *fs, expdesc *e, int reg) {
  luaK_dischargevars(fs, e);
  switch (e->k) {
    // ......
    case VNONRELOC: {// 不需要重定位,那么直接生成MOVE 指令来完成变量的赋值
      if (reg != e->u.s.info)
        luaK_codeABC(fs, OP_MOVE, reg, e->u.s.info, 0);
      break;
    }
    default: {
      lua_assert(e->k == VVOID || e->k == VJMP);
      return;  /* nothing to do... */
    }
  }
  e->u.s.info = reg;
  e->k = VNONRELOC;
}

 

    赋值元源数据是局部变量,使用MOVE指令完成赋值。

 

posted on 2021-04-14 22:25  zziii  阅读(137)  评论(0编辑  收藏  举报