重磅来袭

来源:互联网 发布:网络延时和rtt的关系 编辑:程序博客网 时间:2024/05/16 19:57

欢迎关注公众号《Lua探索之旅》。


Lua语法短小精悍,简单易学,非开发人员也可以编写一些简单的脚本,有开发经验的人更是分分钟上手。但麻雀虽小五脏俱全,要探索lua内部实现,必须全面掌握其语法。

推荐阅读《lua5.1参考手册》,该手册为官方文档,由云风(游戏圈的大神,梦幻西游技术负责人)翻译。



Lua EBNF范式

代码是程序设计思想的落地,核心在于设计思想,如非必要,不贴源码。

lua的语法规则是通过EBNF范式定义的,理解了EBNF,也就理解了lua的语法分析,事实上lua的语法分析模块就是lua EBNF的代码实现。

lua EBNF如下图所示,从本节开始将完整介绍各个推导式的含义。


chunk -> { stat [`;`] }

{}表示0个或多个, []表示0个或1个,chunk可推导为

chunk -> stat

chunk -> stat; stat; ...


lua的一个执行单元称为chunk(数据块),一个chunk可包含多条语句,比如

a = 1
b = a + 1

每一行表示一条statement(语句),若一行有多条statement,用分号分隔

a = 1; b = a + 1;


chunk可以保存在一个文件里,也可以存入字符串。当一个 chunk 被执行,首先它会被预编译成虚拟机中的指令序列,然后被虚拟机解释运行这些指令。



stat -> ifstat | whilestat | dostat | forstat | repeatstat | funcstat | localstat | retstat | breakstat | exprstat

|表示或,一条statement可以有多种表达形式,比如赋值语句、if语句、for语句、return语句等


每条statement根据第一个token类型决定使用哪种产生式。比如 "a=1" 的第一个token为TK_NAME, "if a>1 then ... end" 的第一个token为TOKEN_IF。

token类型和产生式的对应关系如下:

token产生式例子TK_IF       ifstat          if a>1 then ...TK_WHILE    whilestat       while true do ...TK_DO       dostat          do return endTK_FOR      forstat         for i=1,2 do ...TK_REPEAT   repeatstat      repeat ... until    TK_FUNCTION funcstat        function f() ... endTK_LOCAL    localstat       local a=1TK_RETURN   retstat         return a    TK_BREAK    breakstat       break    其他        exprstat        a=1; a();



exprstat -> func | assignment

除去if、while、local、return等控制语句,exprstat表示一条执行语句,包含赋值和函数调用两类,如 "a=1" 和 "print()"


lua将表达式分为 主表达式 primaryexp 和 任意表达式 explist1

主表达式必须可以作为左值存在,能够被赋值,比如 "a.b=1+2",其中 "a.b"为主表达式,"1+2"不能为主表达式。


在exprstat里,等号'='左边为一个主表达式,用primaryexp解析,有4种表达形式:

exp; exp(exp); exp[exp]; exp.exp

比如: a; a(1+2); a["b"]; a.b


其他复杂形式为这4种形式的递归实现,如

a.b.c = a.exp1
exp1 = b.c

再比如 

a(b.c).d() = exp1.exp2
exp1 = a(exp3)
exp2 = d()
exp3 = b.c


primaryexp先获取一个前缀表达式 prefixexp,如 "a()" 分解为 "a" 和 "()",其中 "a" 即为前缀。

然后根据第2个token决定使用上面4种表达形式的哪一种,如 "a()" 使用 exp(exp) 形式解析。


primaryexp解析完后,判断当前expr是否为函数调用,如:

  • "a()" 是函数调用

  • "a().b" 不是函数调用

  • "a().b()" 是函数调用

若为函数调用,exprstat使用 func 产生式,否则使用 assignment 产生式,作为赋值语句处理。



primaryexp -> prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs }

对应上面的4种表达形式


  • prefixexp.NAME 用于table取key值,如 "a.b" 等同于 "a['b']",这是lua的语法糖

  • prefixexp[exp] 用于table取key值,key可以为一个表达式

  • prefixexp:NAME() 用于table的成员函数调用,另一个语法糖,如 "a:b(c)" 等同于"a.b(a,c)",自动将当前table对象作为第一个参数插入,类似c++的this指针

  • prefixexp() 用于函数调用,如 "a()"



prefixexp -> NAME | `(` expr `)`

前缀表达式可以为标识符,也可以一个表达式


  •  "a()" ,prefixexp使用 NAME 产生式

  • 如 "(a+1).d=1",prefixexp使用 expr 产生式

不要怀疑, "(a+1).d=1",在lua里可以这样写,比如a为table,table又可以定义元方法,重载+运算符返回一个新table就可以了。


本节先介绍这几个EBNF,敬请后续!


原创粉丝点击