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:

  1. 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:

  1. +-------------------+
  2. | if condition then |-------\ jump to the next `elseif` branch if $condition is false
  3. +-------------------+ |
  4. |
  5. block |
  6. /<---- |
  7. | +-----------------------+<--/
  8. | | elseif condition then |-----\ jump to the next `elseif` branch if $condition is false
  9. | +-----------------------+ |
  10. | |
  11. | block |
  12. +<---- |
  13. | +-----------------------+<----/
  14. | | elseif condition then |-------\ jump to the `else` branch if $condition is false
  15. | +-----------------------+ |
  16. | |
  17. | block |
  18. +<---- |
  19. | +------+ |
  20. | | else | |
  21. | +------+<-----------------------/
  22. |
  23. | block
  24. |
  25. | +-----+
  26. | | end |
  27. | +-----+
  28. \---> All block jump here.
  29. 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:

```rust, ignore pub enum ByteCode { // condition structures Test(u8, u16), Jump(u16),

  1. The syntax analysis process is as follows:
  2. - 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;
  3. - For the `elseif` branch, it is handled in the same way as the `if` branch.
  4. - For the `else` branch, no processing is required.
  5. The format of the final generated bytecode sequence should be as follows, where `...` represents the bytecode sequence of the inner code block:
  1. Test --\ `if` branch
  2. ... |

/<— Jump | | /<—-/ | Test ——\ elseif branch | … | +<— Jump | | /<——-/ | Test ———\ elseif branch | … | +<— Jump | | /<———-/ | … else branch | --> end of all

  1. The syntax analysis code is as follows:
  2. ```rust, ignore
  3. fn if_stat(&mut self) {
  4. let mut jmp_ends = Vec::new();
  5. // `if` branch
  6. let mut end_token = self. do_if_block(&mut jmp_ends);
  7. // optional multiple `elseif` branches
  8. while end_token == Token::Elseif { // If the previous block ends with the keyword `elseif`
  9. end_token = self.do_if_block(&mut jmp_ends);
  10. }
  11. // optional `else` branch
  12. if end_token == Token::Else { // If the previous block ends with the keyword `else`
  13. end_token = self. block();
  14. }
  15. assert_eq!(end_token, Token::End); // Syntax: `end` at the end
  16. // Repair the unconditional jump bytecode at the end of the
  17. // block in all `if` and `elseif` branches, and jump to the
  18. // current position
  19. let iend = self.byte_codes.len() - 1;
  20. for i in jmp_ends.into_iter() {
  21. self.byte_codes[i] = ByteCode::Jump((iend - i) as i16);
  22. }
  23. }

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

``rust, ignore 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

  1. self.byte_codes.push(ByteCode::Test(0, 0)); // generate Test bytecode placeholder, leave the parameter blank
  2. let itest = self.byte_codes.len() - 1;
  3. let end_token = self. block();
  4. // If there is an `elseif` or `else` branch, then the current
  5. // block needs to add an unconditional jump bytecode, to jump
  6. // to the end of the entire `if` statement. Since the position
  7. // of the end is not known yet, the parameter is left blank and the
  8. // The bytecode index is recorded into `jmp_ends`.
  9. // No need to jump if there are no other branches.
  10. if matches!(end_token, Token::Elseif | Token::Else) {
  11. self.byte_codes.push(ByteCode::Jump(0));
  12. jmp_ends.push(self.byte_codes.len() - 1);
  13. }
  14. // Fix the previous Test bytecode.
  15. // `iend` is the current position of the bytecode sequence,
  16. // `itest` is the position of the Test bytecode, and the difference
  17. // between the two is the number of bytecodes that need to be jumped.
  18. let iend = self.byte_codes.len() - 1;
  19. self.byte_codes[itest] = ByteCode::Test(icond as u8, (iend - itest) as i16);
  20. return end_token;
  21. }
  1. ## Virtual Machine Execution
  2. 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:
  3. ```rust, ignore
  4. // conditional jump
  5. ByteCode::Test(icond, jmp) => {
  6. let cond = &self. stack[icond as usize];
  7. if matches!(cond, Value::Nil | Value::Boolean(false)) {
  8. pc += jmp as usize; // jump if false
  9. }
  10. }
  11. // unconditional jump
  12. ByteCode::Jump(jmp) => {
  13. pc += jmp as usize;
  14. }