Lua语法分析(3)- 二元操作符

来源:互联网 发布:微信模板消息 java 编辑:程序博客网 时间:2024/05/18 18:15

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


表达式和语句

表达式(expression)和语句(statement)是两个不同的概念,表达式可以返回一个值,语句是一条执行命令,没有返回值,每类编程语言的处理方法不尽相同。


比如 "a=1" ,在C语言里既可以作为语句,也可以作为表达式,因此可以连续赋值。如"b=a=1;",先执行 "a=1",返回值为 a,再执行 "b=a"


但在Lua里 "a=1" 属于赋值语句,不是表达式,所以 "b=a=1" 会语法解析错误。


在Lua里唯一既可以作为表达式,也可以作为语句的是函数调用(function call)。作为表达式时,有函数返回值;作为语句时,忽略函数返回值。



二元操作符

作为表达式的纽带,二元操作符将各个子表达式连接起来,构成复杂表达式。

Lua有如下几类二元操作符:

  • 数学操作符:包括'+'、 '-'、 '*'、 '/'、 '%'

  • 比较操作符:包括'=='、 '~='、 '>'、 '<'、 '>='、 '<='

  • 逻辑操作符:包括 and、or

  • 其他运算符:如幂运算 '^'、字符串连接 '..'


操作符有优先级的概念,'*' 的优先级等于 '/',大于 '+'、'-',高优先级的操作符优先计算。

比如 "1+2*3/3-2",有如下计算步骤:

2 * 3 = 6
6 / 3 = 2
1 + 2 = 3
3 - 2 = 1


人脑可以通过分析整个表达式,找出优先级最高的操作符,一步步演算得到结果。对程序而言,它只能从头开始扫描整个表达式,并不能预知后面的操作符,该如何解析表达式?



优先级递归解析

在《递归下降算法》小节里介绍过利用EBNF范式解析表达式的方法,这次介绍一种根据优先级递归解析的方法,下面分步骤介绍。


(1)操作符和优先级

为简单起见,设定只有数字、加、减、乘、除这5类Token,枚举如下:

enum TOKEN { NUMBER, ADD, SUB, 
 MUL, DIV, };
struct token{
 TOKEN type;
 int value;
};


优先级

int priority(token& tk) {
 switch (tk.type) {
 case ADD: return 6;
 case SUB: return 6;
 case MUL: return 7;
 case DIV: return 7;
 default: break;
 }
 return 0;
}

加、减的优先级为6,乘、除的优先级为7,值越大表示优先级越高。


(2)递归函数

struct expdesc {
 int value;
 token op;
};
expdesc nextNoHigherExp(int limit);


nextNoHigherExp函数,简称nextExp,每次返回下一个优先级不高于limit的子表达式,参数limit为当前优先级,其逻辑如下:

  • 每次先取出一个数value 和后面的二元操作符 op。

  • 若 op优先级 > limit,递归调用nextExp,传入op的优先级作为新的limit,并根据nextExp的返回值进行计算。

  • 若 op优先级 <= limit,结束递归,返回 value和op。


以 "1+2*3/3-2" 为例,初始limit为0。

(1)优先级从0升到6(加号),再升到7(乘号),遇到"3/"返回:

nextExp(limit=0) -> "1+"
nextExp(limit=6) -> "2*"
nextExp(limit=7) -> "3/" <-返回


"2*" 和 "3/" 计算得到 "6/":

nextExp(limit=0) -> "1+"
nextExp(limit=6) -> "6/"


(2)"6/"的优先级大于6,再次升到7,遇到"3-"返回

nextExp(0) -> "1+"
nextExp(6) -> "6/"
nextExp(7) -> "3-" <-返回


"6/" 和 "3-" 计算得到 "2-":

nextExp(0) -> "1+" -> 
nextExp(6) -> "2-"


(3)"2-"的优先级等于6,nextExp(6)返回


"1+" 和 "2-" 计算得到 "3-"

nextExp(0) -> "3-"


(4)"3-"的优先级大于0,优先级升到6,遇到"2"返回

nextExp(0) -> "3-"
nextExp(6) -> "2"  <-返回


计算得到 "1"

nextExp(0) -> "1"



程序实现

(1)计算函数

int calc(token op, int v1, int v2) {
 int v = 0;
 switch (op.type) {
 case ADD: v = v1 + v2; break;
 case SUB: v = v1 - v2; break;
 case MUL: v = v1 * v2; break;
 case DIV: v = v1 / v2; break;
 default:  return 0;
 }
 return v;
}


(2)递归函数

expdesc nextExp(int limit) {
 token tk = next_token();

 /*取出下一个数和后面的操作符*/
 expdesc exp;
 exp.value = tk.value;
 exp.op = next_token();

 /*更高优先级的操作符递归调用*/
 if (priority(exp.op) > limit) {
   do {
     expdesc exp2 =
       nextExp(priority(exp.op));
     exp.value = calc(exp.op,
       exp.value, exp2.value);
     exp.op = exp2.op;
   }
   while (priority(exp.op) > limit);
 }
 return exp;
}


nextExp返回下一个优先级不高于limit的 exp2,并和当前exp进行calc计算,并将exp2的op赋给exp。

比如 exp为 "2*",exp2为 "3/",先用exp的op计算,exp更新为 "6/"。



Lua实现

上述例子即为Lua源码中 subexpr函数的简易实现,在Lua里运算符的优先级如下:

操作符
优先级or 1and 2> < >= <= == ~=3..5~4+ -6    * / %7^10~9


其中 .. 和 ^ 区分左右优先级,用于实现右结合,比如 "2^1^2",右结合等于2,左结合等于4。其余运算符都是左结合。


本节重点介绍了Lua二元操作符的实现逻辑,作为理解Lua表达式的基础铺垫,敬请后续!


原创粉丝点击