snoack/python-goto

Jumps out of more than 4 nested blocks are not allowed

Closed this issue · 3 comments

I understand the reason for the limitation - you don't want to have to recompute jumps.

However, there is a simple method of overcoming this that should work: If you don't have enough space, you emit as many POP_BLOCKs as possible and a jump to the end of the buffer (+ 1), append the remaining POP_BLOCKs to the end of the buffer, and then append a jump back to the goto target.

Hopefully that makes sense. Perhaps an example:

def test():
    if a:
        if b:
            if c:
                if d:
                    if e:
                        goto .end
    label .end

This becomes:

  4           0 LOAD_GLOBAL              0 (a)
              3 POP_JUMP_IF_FALSE       52

  5           6 LOAD_GLOBAL              1 (b)
              9 POP_JUMP_IF_FALSE       52

  6          12 LOAD_GLOBAL              2 (c)
             15 POP_JUMP_IF_FALSE       49

  7          18 LOAD_GLOBAL              3 (d)
             21 POP_JUMP_IF_FALSE       46

  8          24 LOAD_GLOBAL              4 (e)
             27 POP_JUMP_IF_FALSE       43

  9          30 LOAD_GLOBAL              5 (goto)
             33 LOAD_ATTR                6 (end)
             36 POP_TOP
             37 JUMP_ABSOLUTE           43
             40 JUMP_ABSOLUTE           46
        >>   43 JUMP_ABSOLUTE           49
        >>   46 JUMP_ABSOLUTE           52
        >>   49 JUMP_FORWARD             0 (to 52)

 10     >>   52 LOAD_GLOBAL              7 (label)
             55 LOAD_ATTR                6 (end)
             58 POP_TOP
             59 LOAD_CONST               0 (None)
             62 RETURN_VALUE

Which you then transform into:

  4           0 LOAD_GLOBAL              0 (a)
              3 POP_JUMP_IF_FALSE       52

  5           6 LOAD_GLOBAL              1 (b)
              9 POP_JUMP_IF_FALSE       52

  6          12 LOAD_GLOBAL              2 (c)
             15 POP_JUMP_IF_FALSE       49

  7          18 LOAD_GLOBAL              3 (d)
             21 POP_JUMP_IF_FALSE       46

  8          24 LOAD_GLOBAL              4 (e)
             27 POP_JUMP_IF_FALSE       43

  9          30 POP_BLOCK
             31 POP_BLOCK
             32 POP_BLOCK
             33 POP_BLOCK
             34 JUMP_ABSOLUTE           63
             37 JUMP_ABSOLUTE           43
             40 JUMP_ABSOLUTE           46
        >>   43 JUMP_ABSOLUTE           49
        >>   46 JUMP_ABSOLUTE           52
        >>   49 JUMP_FORWARD             0 (to 52)

 10     >>   52 NOP
             53 NOP
             54 NOP
             55 NOP
             56 NOP
             57 NOP
             58 NOP
             59 LOAD_CONST               0 (None)
             62 RETURN_VALUE
             63 POP_BLOCK
             64 JUMP_ABSOLUTE           34

Note: I haven't actually tried this, just tossing this out there.

(On an unrelated note: wow Python's bytecode compiler is naive.)

Note that you don't need any POP_BLOCK instructions in your example above, as if-statements don't create blocks. Only loops, try-except, try-finally and with statements do. And I cannot remember having seen any Python code that uses 5 or more nested loops. So I don't think this limitation will be a problem. And therefore I'd rather not make things more complicated as they already are.

I am running into this problem too. The reason why I am using this package is because I am writing fairly complex procedure flows for a reinforcement learning project. Two loop + two with statements resulted in this error:

SyntaxError: Jump out of too many nested blocks