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

欢迎关注公众号《后台开发探索之旅》。

 

 

表达式和语句

表达式(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表达式的基础铺垫,敬请后续!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值