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;
                }