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:
  1. while xx do
  2. if yy then goto continue end
  3. local var = 123
  4. -- some code
  5. ::continue::
  6. 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:

```rust, ignore 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 { gotos: Vec, labels: Vec,

  1. 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.
  2. There are two implementations of matching `goto` statement and lable:
  3. - One-time match at the end of the block:
  4. - When encountering a `goto` statement, create a new `GotoLabel` to join the list, and generate a placeholder Jump bytecode;
  5. - When encountering a label statement, create a new `GotoLabel` to add to the list.
  6. Finally, at the end of the block, match once and fix the placeholder bytecode.
  7. - Live match:
  8. - 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 new `GotoLabel`, and generate a placeholder Jump bytecode;
  9. - 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 other `goto` statements adjusted to this point, it is still necessary to create a new `GotoLabel`.
  10. At the end of the block, all matches have completed already.
  11. 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.
  12. After introducing the above details, the overall process of syntax analysis is as follows:
  13. - After entering the block, first record the number of `goto` and label before (outer layer);
  14. - Parse block, record `goto` and label statement information;
  15. - 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 a `goto` statement that is not matched, it will still be returned to the `goto` 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.
  16. - Before the end of the entire Lua chunk, judge whether the `goto` list is empty. If it is not empty, it means that some `goto` statements have no destination, and an error is reported.
  17. The corresponding code is as follows:
  18. 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:
  19. ```rust, ignore
  20. fn block_scope(&mut self) -> Token {
  21. let igoto = self.gotos.len(); // Record the number of outer goto before
  22. let ilabel = self.labels.len(); // record the number of outer labels
  23. loop {
  24. // omit other statement analysis
  25. t => { // end of block
  26. // Match goto and label before exiting the block
  27. self.close_goto_labels(igoto, ilabel);
  28. break t;
  29. }
  30. }
  31. }

The specific matching code is as follows:

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

  1. // Delete the label defined inside the function
  2. self. labels. truncate(ilabel);
  3. }
  1. Finally, before the chunk is parsed, check that all gotos are matched:
  2. ```rust, ignore
  3. fn chunk(&mut self) {
  4. assert_eq!(self. block(), Token::Eos);
  5. if let Some(goto) = self. gotos. first() {
  6. panic!("goto {} no destination", &goto.name);
  7. }
  8. }

This completes the goto statement.