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