Lua源码解析之二:parser

本文探讨了Lua的parser部分,详细分析了从function解析到end的整个过程,包括function name、function body和end的处理。Lua的parser不是严格区分词法分析和语法分析,而是在解析过程中同时构建临时的语法树并生成代码。
摘要由CSDN通过智能技术生成

Lua中的parser

目录

1. 前言

上一章介绍了Lua的词法分析,本章论述lua语法分析,但是纵观lua源代码,发现语法分析和词法分析区分得并不明显,尽管看起来词法分析是放在llex.c,语法分析是放在lparser.c。

Lua在load一段代码或者一个lua文件时(统称为buffer),首先open一个function,设置好function的参数和upval,然后将buffer里面的内容作为函数体内的代码去解析,以下代码引用自lparser.c中,解析一个文件buffer时调用的mainfunc:

/*
** compiles the main function, which is a regular vararg function with an
** upvalue named LUA_ENV
*/
static void mainfunc (LexState *ls, FuncState *fs) {
  BlockCnt bl;
  expdesc v;
  open_func(ls, fs, &bl);
  fs->f->is_vararg = 2;             /* main function 永远是变参 */
  init_exp(&v, VLOCAL, 0);          /* create and... */
  newupvalue(fs, ls->envn, &v);     /* ...set environment upvalue, 设置环境变量为upvalue,它指向偏移为0的stack */
  luaX_next(ls);                    /* read first token */
  statlist(ls);                     /* parse main body */
  check(ls, TK_EOS);
  close_func(ls);
}
  1. 调用open_func,设定好parser过程中用到的FuncState和BlockCnt;
  2. 为当前的FuncState创建name为envn的upvalue,它偏移为0的栈
  3. 解析‘函数体内’的表达式
  4. 解析完buffer,close_func。解析到此为止

lua代码由一条条语句(statement)组成,这里和c语言略不同:一个function的定义,可以看作语句;一个require语句,也可看作语句,总而言之,所有的lua代码都可以看作表达式。
通过解析statment,采用自顶向下语法分析,会生成一棵语法树,但lua源代码中很难看到清晰的语法树结构,仔细阅读lparser.c就会发现,parser实质上一边生成临时的语法树,一边调用lcode.c生成code(生成code的过程不在本章中讲解)。

2. extended BNF

言归正传,本章重点讲解parser,据说最早的lua parser是用YACC生成的,后来改成手写的lparser了,这样的可读性会好很多,lua parser严格按照extend BNF表述的语法进行解析:

chunk ::= block

    block ::= {stat} [retstat]

    stat ::=  ‘;’ | 
         varlist ‘=’ explist | 
         functioncall | 
         label | 
         break | 
         goto Name | 
         do block end | 
         while exp do block end | 
         repeat block until exp | 
         if exp then block {elseif exp then block} [else block] end | 
         for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end | 
         for namelist in explist do block end | 
         function funcname funcbody | 
         local function Name funcbody | 
         local namelist [‘=’ explist] 

    retstat ::= return [explist] [‘;’]

    label ::= ‘::’ Name ‘::’

    funcname ::= Name {‘.’ Name} [‘:’ Name]

    varlist ::= var {‘,’ var}

    var ::=  Name | prefixexp ‘[’ exp ‘]’ | prefixexp ‘.’ Name 

    namelist ::= Name {‘,’ Name}

    explist ::= exp {‘,’ exp}

    exp ::=  nil | false | true | Numeral | LiteralString | ‘...’ | functiondef | 
         prefixexp | tableconstructor | exp binop exp | unop exp 

    prefixexp ::= var | functioncall | ‘(’ exp ‘)’

    functioncall ::=  prefixexp args | prefixexp ‘:’ Name args 

    args ::=  ‘(’ [explist] ‘)’ | tableconstructor | LiteralString 

    functiondef ::= function funcbody

    funcbody ::= ‘(’ [parlist] ‘)’ block end

    parlist ::= namelist [‘,’ ‘...’] | ‘...’

    tableconstructor ::= ‘{’ [fieldlist] ‘}’

    fieldlist ::= field {fieldsep field} [fieldsep]

    field ::= ‘[’ exp ‘]’ ‘=’ exp | Name ‘=’ exp | exp

    fieldsep ::= ‘,’ | ‘;’

    binop ::=  ‘+’ | ‘-’ | ‘*’ | ‘/’ | ‘//’ | ‘^’ | ‘%’ | 
         ‘&’ | ‘~’ | ‘|’ | ‘>>’ | ‘<<’ | ‘..’ | 
         ‘<’ | ‘<=’ | ‘>’ | ‘>=’ | ‘==’ | ‘~=’ | 
         and | or

    unop ::= ‘-’ | not | ‘#’ | ‘~’

这里有必要介绍上述BNF中常用的几个推导符号:

符号 含义
::= 推导
{} 一个或者多个
[] 出现0次或者1次
| 或者

以函数推导为例:

functiondef ::= function funcbody
    funcbody ::= ‘(’ [parlist] ‘)’ block end
        parlist ::= namelist [‘,’ ‘...’] | ‘...’
        block ::= {stat} [retstat]

一个函数的定义由function关键词开始,接着是参数列表,然后block语句 以end关键词结束
function(a,b) block end 而,block本身也是由若干语句组成(又是递归)
在下一小节中,将以解析一个函数为例,来分析lparser的源代码。

3. parse function

如何完整的解析一个函数呢,请看下面的代码:

static void funcstat (LexState *ls, int line) {
  /* funcstat -> FUNCTION funcname body */
  int ismethod;
  expdesc v, b;
  luaX_next(ls);                    /* skip FUNCTION */
  ismethod = funcname(ls, &v);      /* 解析函数名,保存结果到v */
  body(ls, &b, ismethod, line);     /* 解析函数体,保存结果到b */
  luaK_storevar(ls->fs, &v, &b);    /* 编码生成赋值语句 */
  luaK_fixline(ls->fs, line);       /* definition "happens" in the first line */
}

expdesc是用于描述 表达式 的结构体,函数名可以用它来表达;这很好理解,而函数体也可以用它来表达,可以想象一下,函数体被编码好之后,放在某个角落,然后expdesc指向这个角落: ),具体分析代码的话,涉及到的细节比较多,不过这里分析的细节对于解析其它的BNF,也是有帮助的。

3.1 function name

函数名如何解析呢?
先以合法的function name为例,一般有以下几种形式:

function A()        end
function A.a()      end
function A.a.b()    end
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值