elseif和else分支

上一节支持了if语句。这一节继续完成elseif和else分支。

完整的BNF规范如下:

  1. if exp then block {elseif exp then block} [else block] end

除了if判断外,还可以有连续多个可选的elseif判断分支,最后跟一个可选的else分支。控制结构图如下:

  1. +-------------------+
  2. | if condition then |-------\ 如果condition为假,则跳到下一个elseif分支
  3. +-------------------+ |
  4. |
  5. block |
  6. /<---- |
  7. | +-----------------------+<--/
  8. | | elseif condition then |-----\ 如果condition为假,则跳到下一个elseif分支
  9. | +-----------------------+ |
  10. | |
  11. | block |
  12. +<---- |
  13. | +-----------------------+<----/
  14. | | elseif condition then |-------\ 如果condition为假,则跳到else分支
  15. | +-----------------------+ |
  16. | |
  17. | block |
  18. +<---- |
  19. | +------+ |
  20. | | else | |
  21. | +------+<-----------------------/
  22. |
  23. | block
  24. |
  25. | +-----+
  26. | | end |
  27. | +-----+
  28. \---> 所有block执行完后跳转到这里。
  29. 最后一个block执行完后自动到这里,就无需显式跳转。图中最后一个blockelse分支。

上图描述了有2个elseif分支和1个else分支的情况。除了右上角if的判断跳转外,其余都是要新增的跳转。跳转分2种:

  • 图右侧的条件跳转,由上节新增的Test字节码执行;
  • 图左侧的无条件跳转,需要新增Jump字节码,定义如下:
  1. pub enum ByteCode {
  2. // condition structures
  3. Test(u8, u16),
  4. Jump(u16),

语法分析过程如下:

  • 对于if判断分支,跟上一节相比,条件跳转的位置不变,还是block的结束位置;但需要在block最后新增一个无条件跳转指令,跳转到整个if语句的最后面;

  • 对于elseif分支,跟if分支的处理方式一样。

  • 对于else分支,无需处理。

最终生成的字节码序列的格式应该如下,其中...代表内部代码块的字节码序列:

  1. Test --\ if分支
  2. ... |
  3. /<-- Jump |
  4. | /<---/
  5. | Test ----\ 第一个elseif分支
  6. | ... |
  7. +<-- Jump |
  8. | /<-----/
  9. | Test ------\ 第二个elseif分支
  10. | ... |
  11. +<-- Jump |
  12. | /<-------/
  13. | ... else分支
  14. |
  15. \--> 整个语句结尾

语法分析代码如下:

  1. fn if_stat(&mut self) {
  2. let mut jmp_ends = Vec::new();
  3. // if分支
  4. let mut end_token = self.do_if_block(&mut jmp_ends);
  5. // 可选的多个elseif分支
  6. while end_token == Token::Elseif { // 如果上一个block以关键字elseif结尾
  7. end_token = self.do_if_block(&mut jmp_ends);
  8. }
  9. // 可选的一个else分支
  10. if end_token == Token::Else { // 如果上一个block以关键字else结尾
  11. end_token = self.block();
  12. }
  13. assert_eq!(end_token, Token::End); // 语法:最后end结尾
  14. // 修复所有if和elseif分支内block最后的无条件跳转字节码,跳到当前位置
  15. let iend = self.byte_codes.len() - 1;
  16. for i in jmp_ends.into_iter() {
  17. self.byte_codes[i] = ByteCode::Jump((iend - i) as i16);
  18. }
  19. }

其中对if和elseif分之的处理函数do_if_block()如下:

  1. fn do_if_block(&mut self, jmp_ends: &mut Vec<usize>) -> Token {
  2. let icond = self.exp_discharge_top(); // 读取判断语句
  3. self.lex.expect(Token::Then); // 语法:then关键字
  4. self.byte_codes.push(ByteCode::Test(0, 0)); // 生成Test字节码占位,参数留空
  5. let itest = self.byte_codes.len() - 1;
  6. let end_token = self.block();
  7. // 如果还有elseif或else分支,那么当前block需要追加一个无条件跳转字节码,
  8. // 跳转到整个if语句末尾。由于现在还不知道末尾的位置,所以参数留空,并把
  9. // 字节码索引记录到jmp_ends中。
  10. // 如果没有其他分支,则无需跳转。
  11. if matches!(end_token, Token::Elseif | Token::Else) {
  12. self.byte_codes.push(ByteCode::Jump(0));
  13. jmp_ends.push(self.byte_codes.len() - 1);
  14. }
  15. // 修复之前的Test字节码。
  16. // iend为字节码序列当前位置,itest为Test字节码位置,两者差就是需要跳转的字节码条数。
  17. let iend = self.byte_codes.len() - 1;
  18. self.byte_codes[itest] = ByteCode::Test(icond as u8, (iend - itest) as i16);
  19. return end_token;
  20. }

虚拟机执行

新增的无条件跳转字节码Jump的执行非常简单。跟之前的条件跳转字节码Test相比,只是去掉了条件判断即可:

  1. // 条件跳转
  2. ByteCode::Test(icond, jmp) => {
  3. let cond = &self.stack[icond as usize];
  4. if matches!(cond, Value::Nil | Value::Boolean(false)) {
  5. pc += jmp as usize; // jump if false
  6. }
  7. }
  8. // 无条件跳转
  9. ByteCode::Jump(jmp) => {
  10. pc += jmp as usize;
  11. }