goto
Statement
This section describes the goto
statement.
The goto
statement and label can be used together for more convenient code control. But the goto
statement also has the following restrictions:
- You cannot jump to the label defined by the inner block, but you can jump to the outer block;
- You cannot jump outside the function (note that the above rule has restricted jumping into the function). Since we do not support functions yet, ignore this for now;
- You cannot jump into the scope of local variables, that is, you cannot skip local statements. Note here that the scope ends at the last non-void statement, and the label is considered a void statement. My personal understanding is the statement that does not generate bytecode. For example the following code:
while xx do
if yy then goto continue end
local var = 123
-- some code
::continue::
end
The continue
label is behind the local variable var
, but because it is a void statement, it does not belong to the scope of var, so the above goto
is a legal jump.
The implementation of the goto
statement naturally uses Jump
bytecode. The main task of syntax analysis is to match goto
and label, and generate Jump
bytecode at the place of goto
statement to jump to the corresponding label. Since the goto
statement can jump forward, the definition of the corresponding label may not be encountered when the goto
statement is encountered; it can also jump backward, so when the label statement is encountered, it needs to be saved for subsequent goto
matching. Therefore, two new lists need to be added to ParseProto
to save the goto and label information encountered during syntax analysis:
struct GotoLabel {
name: String, // The label name to jump to/defined
icode: usize, // current bytecode index
nvar: usize, // the current number of local variables, used to determine whether to jump into the scope of local variables
}
pub struct ParseProto<R: Read> {
gotos: Vec<GotoLabel>,
labels: Vec<GotoLabel>,
Both lists have the same member type, GotoLabel
. Among them, nvar
is the current number of local variables. Make sure that the nvar corresponding to the paired goto
statement cannot be smaller than the nvar corresponding to the label statement, otherwise it means that there is a new local variable definition between the goto
and label statements, that is, goto
jump into the scope of the local variable.
There are two implementations of matching goto
statement and lable:
-
One-time match at the end of the block:
- When encountering a
goto
statement, create a newGotoLabel
to join the list, and generate a placeholder Jump bytecode; - When encountering a label statement, create a new
GotoLabel
to add to the list.
Finally, at the end of the block, match once and fix the placeholder bytecode.
- When encountering a
-
Live match:
- When encountering a
goto
statement, try to match from the existing label list, if the match is successful, directly generate a complete Jump bytecode; otherwise create a newGotoLabel
, and generate a placeholder Jump bytecode; - When encountering a label statement, try to match it from the existing
goto
list, and if it matches, repair the corresponding placeholder bytecode; since there may be othergoto
statements adjusted to this point, it is still necessary to create a newGotoLabel
.
At the end of the block, all matches have completed already.
- When encountering a
It can be seen that although real-time matching is a little more complicated, it is more cohesive, and there is no need to execute a final function at the end. But this solution has a big problem: it is difficult to judge non-void statements. For example, in the example at the beginning of this section, when the continue
label is parsed, it cannot be judged whether there are other non-void statements in the future. If there is, it is an illegal jump. It can only be judged after parsing to the end of the block. In the first one-time matching scheme, the matching is done at the end of the block. At this time, it is convenient to judge the non-void statement. Therefore, we choose one-time matching here. It should be noted that when Upvalue is introduced later, it will be found that the one-time matching scheme is flawed.
After introducing the above details, the overall process of syntax analysis is as follows:
- After entering the block, first record the number of
goto
and label before (outer layer); - Parse block, record
goto
and label statement information; - Before the end of the block, match the
goto
statement that appears in this block with all (including the outer layer) label statements: if there is agoto
statement that is not matched, it will still be returned to thegoto
list, because it may be a jump to the block The label defined in the outer layer after exiting; finally delete all the labels defined in the block, because after exiting the block, there should be no other goto statements to jump in. - Before the end of the entire Lua chunk, judge whether the
goto
list is empty. If it is not empty, it means that somegoto
statements have no destination, and an error is reported.
The corresponding code is as follows:
Record the number of goto
and label existing in the outer layer at the beginning of parsing the block; and match and clean up the goto and label defined inside before the end of the block:
fn block_scope(&mut self) -> Token {
let igoto = self.gotos.len(); // Record the number of outer goto before
let ilabel = self.labels.len(); // record the number of outer labels
loop {
// omit other statement analysis
t => { // end of block
// Match goto and label before exiting the block
self.close_goto_labels(igoto, ilabel);
break t;
}
}
}
The specific matching code is as follows:
// The parameters igoto and ilable are the starting positions of goto
// and label defined in the current block
fn close_goto_labels(&mut self, igoto: usize, ilabel: usize) {
// Try to match "goto defined in the block" and "all labels".
let mut no_dsts = Vec::new();
for goto in self. gotos. drain(igoto..) {
if let Some(label) = self.labels.iter().rev().find(|l|l.name == goto.name) { // matches
if label.icode != self.byte_codes.len() && label.nvar > goto.nvar {
// Check whether to jump into the scope of local variables.
// 1. The bytecode corresponding to the label is not the last one,
// indicating that there are non-void statements in the follow-up
// 2. The number of local variables corresponding to the label is
// greater than that of goto, indicating that there are newly
// defined local variables
panic!("goto jump into scope {}", goto.name);
}
let d = (label.icode as isize - goto.icode as isize) as i16;
self.byte_codes[goto.icode] = ByteCode::Jump(d - 1); // fix bytecode
} else {
// If there is no match, put it back
no_dsts.push(goto);
}
}
self. gotos. append(&mut no_dsts);
// Delete the label defined inside the function
self. labels. truncate(ilabel);
}
Finally, before the chunk is parsed, check that all gotos are matched:
fn chunk(&mut self) {
assert_eq!(self. block(), Token::Eos);
if let Some(goto) = self. gotos. first() {
panic!("goto {} no destination", &goto.name);
}
}
This completes the goto
statement.