while和break语句
本节介绍while语句,并引入break语句。
while语句
跟if语句的简单形式(不包括elseif和else分支)相比,while语句只是在内部block的末尾增加一个无条件跳转字节码,跳转回语句开头。如下图中左边的跳转所示:
/--->+----------------------+
| | while condition then |---\ 如果condition为假,则跳过block
| +----------------------+ |
| |
| block |
\<---- |
+-----+ |
| end | |
+-----+ |
<--------------------------/
最终生成的字节码序列的格式如下,其中...
代表内部代码块的字节码序列:
/--> Test --\ if分支
| ... |
\--- Jump |
<----/ 整个while语句结尾
语法分析过程和代码,也是在if语句的基础上增加一个无条件跳转字节码,这里略过。需要改造的一个地方是,这里的无条件跳转是向后跳转。之前Jump
字节码的第2个参数是u16
类型,只能向前跳转。现在需要改为i16
类型,用负数表示向后跳转:
pub enum ByteCode {
Jump(i16),
相应的,虚拟机执行部分需要修改为:
// 无条件跳转
ByteCode::Jump(jmp) => {
pc = (pc as isize + jmp as isize) as usize;
}
相对于C语言,Rust的类型管理更加严格,所以看起来比较啰嗦。
break语句
while语句本身非常简单,但引入了另外一个语句:break。break语句本身也很简单,无条件跳转到block结尾处即可,但问题在于并不是所有block都支持break,比如之前介绍的if内部的block就不支持break,只有循环语句的block支持break。准确说,break要跳出的是最近一层的循环的block。比如下面示例:
while 123 do -- 外层循环block,支持break
while true do -- 中层循环block,支持break
a = a + 1
if a < 10 then -- 内层block,不支持break
break -- 跳出 `while true do` 的循环
end
end
end
代码中有3层block,外层和中层的while的block支持break,内层if的block不支持break。此时break就是跳出中层的block。
如果break语句不处于循环block之内,则语法错误。
为实现上述功能,可以在block()
函数中增加一个参数,以便在递归调用时表示最近一次的循环block。由于在生成跳转字节码时block还未结束,还不知道跳转目的地址,所以只能先生成跳转字节码占位,而参数留空;后续在block结束的位置再修复字节码参数。所以block()
函数的参数就是最近一层循环block的break跳转字节码索引列表。调用block()
函数时,
- 如果是循环block,则创建一个新索引列表作为调用参数,在调用结束后,用当前地址(即block结束位置)修复列表中字节码;
- 如果不是循环block,则使用当前列表(也就是当前最近循环block)作为调用参数。
但是block()
函数的递归调用并不是直接递归,而是间接递归。如果要这样传递参数,那么所有的语法分析函数都要加上这个参数了,太复杂。所以把这个索引列表放到全局的ParseProto
中。牺牲了局部性,换来了编码方便。
下面来看具体编码实现。首先在ParseProto
中添加break_blocks
字段,类型是“跳转字节码索引列表”的列表:
pub struct ParseProto<R: Read> {
break_blocks: Vec::<Vec::<usize>>,
在解析while语句时,调用block()
函数前,追加一个列表;调用后,修复列表中的跳转字节码:
fn while_stat(&mut self) {
// 省略条件判断语句处理部分
// 调用block()前,追加一个列表
self.break_blocks.push(Vec::new());
// 调用block()
assert_eq!(self.block(), Token::End);
// 调用block()后,弹出刚才追加的列表,并修复其中的跳转字节码
for i in self.break_blocks.pop().unwrap().into_iter() {
self.byte_codes[i] = ByteCode::Jump((iend - i) as i16);
}
}
在准备好block后,就可以实现break语句了:
fn break_stat(&mut self) {
// 取最近的循环block的字节码列表
if let Some(breaks) = self.break_blocks.last_mut() {
// 生成跳转字节码占位,参数留空
self.byte_codes.push(ByteCode::Jump(0));
// 追加到字节码列表中
breaks.push(self.byte_codes.len() - 1);
} else {
// 如果没有循环block,则语法错误
panic!("break outside loop");
}
}
continue语句?
实现了break语句后,自然就想到continue语句。而且continue的实现跟break类似,区别就是一个跳转到循环结束,一个跳转到循环开头,加上这个功能就是顺手的事情。不过Lua不支持continue语句!其中有一小部分原因跟repeat..until语句有关。我们在下一节介绍完repeat..until语句后再详细讨论continue语句。