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:
```rust, ignore 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:
```rust, ignore
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:
``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
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:
```rust, ignore
// 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;
}