您的位置:首页 > 编程语言 > Lua

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

2017-11-05 16:21 232 查看
欢迎关注公众号《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 1
and 2
> < >= <= == ~=3
..5~4
+ -6    
* / %7
^10~9
其中 .. 和 ^ 区分左右优先级,用于实现右结合,比如 "2^1^2",右结合等于2,左结合等于4。其余运算符都是左结合。

本节重点介绍了Lua二元操作符的实现逻辑,作为理解Lua表达式的基础铺垫,敬请后续!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: