elseif and else branches

The previous section supported the if statement. This section continues with the elseif and else branches.

The complete BNF specification is as follows:

     if exp then block {else if exp then block} [else block] end

In addition to the if judgment, there can also be multiple optional elseif judgment branches in a row, followed by an optional else branch at the end. The control structure diagram is as follows:

     +-------------------+
     | if condition then |-------\ jump to the next `elseif` branch if $condition is false
     +-------------------+       |
                                 |
         block                   |
/<----                           |
|    +-----------------------+<--/
|    | elseif condition then |-----\ jump to the next `elseif` branch if $condition is false
|    +-----------------------+     |
|                                  |
|        block                     |
+<----                             |
|    +-----------------------+<----/
|    | elseif condition then |-------\ jump to the `else` branch if $condition is false
|    +-----------------------+       |
|                                    |
|        block                       |
+<----                               |
|    +------+                        |
|    | else |                        |
|    +------+<-----------------------/
|
|        block
|
|    +-----+
|    | end |
|    +-----+
\---> All block jump here.
      The last block gets here without jump.

The above diagram depicts the situation where there are 2 elseif branches and 1 else branch. Except for the judgment jump of if in the upper right corner, the rest are jumps to be added. There are 2 types of jumps:

  • The conditional jump on the right side of the figure is executed by the Test bytecode added in the previous section;
  • The unconditional jump on the left side of the figure needs to add Jump bytecode, which is defined as follows:
pub enum ByteCode {
     // condition structures
     Test(u8, u16),
     Jump(u16),

The syntax analysis process is as follows:

  • For the if judgment branch, compared with the previous section, the position of the conditional jump remains unchanged, and it is still the end position of the block; however, an unconditional jump instruction needs to be added at the end of the block to jump to the end of the entire if statement;

  • For the elseif branch, it is handled in the same way as the if branch.

  • For the else branch, no processing is required.

The format of the final generated bytecode sequence should be as follows, where ... represents the bytecode sequence of the inner code block:

     Test --\  `if` branch
     ...    |
/<-- Jump   |
|      /<---/
|    Test ----\  `elseif` branch
|    ...      |
+<-- Jump     |
|      /<-----/
|    Test ------\  `elseif` branch
|    ...        |
+<-- Jump       |
|      /<-------/
|    ...   `else` branch
|
\--> end of all

The syntax analysis code is as follows:

     fn if_stat(&mut self) {
         let mut jmp_ends = Vec::new();

         // `if` branch
         let mut end_token = self. do_if_block(&mut jmp_ends);

         // optional multiple `elseif` branches
         while end_token == Token::Elseif { // If the previous block ends with the keyword `elseif`
             end_token = self.do_if_block(&mut jmp_ends);
         }

         // optional `else` branch
         if end_token == Token::Else { // If the previous block ends with the keyword `else`
             end_token = self. block();
         }

         assert_eq!(end_token, Token::End); // Syntax: `end` at the end

         // Repair the unconditional jump bytecode at the end of the 
         // block in all `if` and `elseif` branches, and jump to the
         // current position
         let iend = self.byte_codes.len() - 1;
         for i in jmp_ends.into_iter() {
             self.byte_codes[i] = ByteCode::Jump((iend - i) as i16);
         }
     }

The processing function do_if_block() for if and elseif is as follows:

     fn do_if_block(&mut self, jmp_ends: &mut Vec<usize>) -> Token {
         let icond = self.exp_discharge_top(); // read judgment statement
         self.lex.expect(Token::Then); // Syntax: `then` keyword

         self.byte_codes.push(ByteCode::Test(0, 0)); // generate Test bytecode placeholder, leave the parameter blank
         let itest = self.byte_codes.len() - 1;

         let end_token = self. block();

         // If there is an `elseif` or `else` branch, then the current
         // block needs to add an unconditional jump bytecode, to jump
         // to the end of the entire `if` statement. Since the position
         // of the end is not known yet, the parameter is left blank and the
         // The bytecode index is recorded into `jmp_ends`.
         // No need to jump if there are no other branches.
         if matches!(end_token, Token::Elseif | Token::Else) {
             self.byte_codes.push(ByteCode::Jump(0));
             jmp_ends.push(self.byte_codes.len() - 1);
         }

         // Fix the previous Test bytecode.
         // `iend` is the current position of the bytecode sequence,
         // `itest` is the position of the Test bytecode, and the difference
         // between the two is the number of bytecodes that need to be jumped.
         let iend = self.byte_codes.len() - 1;
         self.byte_codes[itest] = ByteCode::Test(icond as u8, (iend - itest) as i16);

         return end_token;
     }

Virtual Machine Execution

The implementation of the newly added unconditional jump bytecode Jump is very simple. Compared with the previous conditional jump bytecode Test, only the conditional judgment is removed:

                 // conditional jump
                 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
                     }
                 }

                 // unconditional jump
                 ByteCode::Jump(jmp) => {
                     pc += jmp as usize;
                 }