Hacker News new | ask | show | jobs
by tveita 3928 days ago
This is cute but of course horribly unsafe since it doesn't respect control statements.

Some examples you'd expect to work that will crash the interpreter:

  @with_goto
  def fun1():
   while True:
    for x in [1]:
     goto .next
    label .next
  
  @with_goto
  def fun2():
   goto .next
   for x in [1]:
    label .next
  
  @with_goto
  def fun3():
   while True:
    try:
     goto .next
    finally:
     print('skipped')
    label .next
3 comments

Note how Lisp's TAGBODY/GO doesn't have this problem.

  (loop
    (tagbody
      (loop for x in '(1 2 3)
            do (go next))
    next
      (print 'out)))
(OUT is printed repeatedly.)

How it works is that tagbody contains a bunch of labels mixed with forms. The tagbody establishes an exit point for GO expressions used in these forms. When a (go label) occurs, it works by abandoning the current sub-form being evaluated, and initiating a control transfer to the exit point, which then transfers control to the appropriate label.

The upshot is that whatever form is interrupted will cleanly unwind, as necessary:

  (tagbody
    (unwind-protect (progn 
                      (go out)
                      (progn 'never-printed))
      (print 'bye-bye))
   out)
Note that you can only have labels and GO in certain forms like TAGBODY and PROG, not just willy nilly in any construct. The labels of a given TAGBODY are all on the same level of nesting: immediate children of the TAGBODY form. SO this isn't possible:

  (tagbody
     (go impossible)
     (let ((x 42))
       impossible x))
Here, impossible is not considered a label associated with the tagbody, so the (go impossible) is branching to a nonexistent label. If it were allowed, of course it would create the problem of what becomes of the initialization of x.

Thus, compilers only have to reason about GO within specific constructs, and rely on those GO's to only be performing abandonment followed by a simple lateral move.

Fixed: https://github.com/snoack/python-goto/commit/a46dbe57e9cfa4f...

Jumps out of loops and other blocks work now. However, there are some limitations:

1. As I only have 4 bytes left to inject POP_BLOCK instructions, you can't exit more than 4 blocks with a goto. But this is handled and results into a SyntaxError rather than crashing Python.

2. Jumps into loops still doesn't work. However, it's handled now, and causes a SyntaxError as well.

3. When jumping out of a try- or with-block the finalizer is skipped.

How would you expect the second one to work?
I guess similarly to C:

  for i in foo:
    print(42)
would be considered equivalent to something like

  it = iter(foo)
  while True:
   try:
    i = next(it)
   catch StopIteration:
    break
   print(42)
and jumping to print(42) would skip the init and first iteration expression.

Of course that's non-obvious behaviour, so in practice you would make it a compile time error. A careful language designer might even decide to not support goto at all. :)