elseif和else分支
上一节支持了if语句。这一节继续完成elseif和else分支。
完整的BNF规范如下:
if exp then block {elseif exp then block} [else block] end
除了if判断外,还可以有连续多个可选的elseif判断分支,最后跟一个可选的else分支。控制结构图如下:
+-------------------+
| if condition then |-------\ 如果condition为假,则跳到下一个elseif分支
+-------------------+ |
|
block |
/<---- |
| +-----------------------+<--/
| | elseif condition then |-----\ 如果condition为假,则跳到下一个elseif分支
| +-----------------------+ |
| |
| block |
+<---- |
| +-----------------------+<----/
| | elseif condition then |-------\ 如果condition为假,则跳到else分支
| +-----------------------+ |
| |
| block |
+<---- |
| +------+ |
| | else | |
| +------+<-----------------------/
|
| block
|
| +-----+
| | end |
| +-----+
\---> 所有block执行完后跳转到这里。
最后一个block执行完后自动到这里,就无需显式跳转。图中最后一个block为else分支。
上图描述了有2个elseif分支和1个else分支的情况。除了右上角if的判断跳转外,其余都是要新增的跳转。跳转分2种:
- 图右侧的条件跳转,由上节新增的
Test
字节码执行; - 图左侧的无条件跳转,需要新增
Jump
字节码,定义如下:
pub enum ByteCode {
// condition structures
Test(u8, u16),
Jump(u16),
语法分析过程如下:
-
对于if判断分支,跟上一节相比,条件跳转的位置不变,还是block的结束位置;但需要在block最后新增一个无条件跳转指令,跳转到整个if语句的最后面;
-
对于elseif分支,跟if分支的处理方式一样。
-
对于else分支,无需处理。
最终生成的字节码序列的格式应该如下,其中...
代表内部代码块的字节码序列:
Test --\ if分支
... |
/<-- Jump |
| /<---/
| Test ----\ 第一个elseif分支
| ... |
+<-- Jump |
| /<-----/
| Test ------\ 第二个elseif分支
| ... |
+<-- Jump |
| /<-------/
| ... else分支
|
\--> 整个语句结尾
语法分析代码如下:
fn if_stat(&mut self) {
let mut jmp_ends = Vec::new();
// if分支
let mut end_token = self.do_if_block(&mut jmp_ends);
// 可选的多个elseif分支
while end_token == Token::Elseif { // 如果上一个block以关键字elseif结尾
end_token = self.do_if_block(&mut jmp_ends);
}
// 可选的一个else分支
if end_token == Token::Else { // 如果上一个block以关键字else结尾
end_token = self.block();
}
assert_eq!(end_token, Token::End); // 语法:最后end结尾
// 修复所有if和elseif分支内block最后的无条件跳转字节码,跳到当前位置
let iend = self.byte_codes.len() - 1;
for i in jmp_ends.into_iter() {
self.byte_codes[i] = ByteCode::Jump((iend - i) as i16);
}
}
其中对if和elseif分之的处理函数do_if_block()
如下:
fn do_if_block(&mut self, jmp_ends: &mut Vec<usize>) -> Token {
let icond = self.exp_discharge_top(); // 读取判断语句
self.lex.expect(Token::Then); // 语法:then关键字
self.byte_codes.push(ByteCode::Test(0, 0)); // 生成Test字节码占位,参数留空
let itest = self.byte_codes.len() - 1;
let end_token = self.block();
// 如果还有elseif或else分支,那么当前block需要追加一个无条件跳转字节码,
// 跳转到整个if语句末尾。由于现在还不知道末尾的位置,所以参数留空,并把
// 字节码索引记录到jmp_ends中。
// 如果没有其他分支,则无需跳转。
if matches!(end_token, Token::Elseif | Token::Else) {
self.byte_codes.push(ByteCode::Jump(0));
jmp_ends.push(self.byte_codes.len() - 1);
}
// 修复之前的Test字节码。
// iend为字节码序列当前位置,itest为Test字节码位置,两者差就是需要跳转的字节码条数。
let iend = self.byte_codes.len() - 1;
self.byte_codes[itest] = ByteCode::Test(icond as u8, (iend - itest) as i16);
return end_token;
}
虚拟机执行
新增的无条件跳转字节码Jump
的执行非常简单。跟之前的条件跳转字节码Test
相比,只是去掉了条件判断即可:
// 条件跳转
ByteCode::Test(icond, jmp) => {
let cond = &self.stack[icond as usize];
if matches!(cond, Value::Nil | Value::Boolean(false)) {
pc += jmp as usize; // jump if false
}
}
// 无条件跳转
ByteCode::Jump(jmp) => {
pc += jmp as usize;
}