python is extremely extensible, though natalie is not sure that is intentional.
firstly, here is the end product of this venture:
from goto_label import *
count = 0
print('this prints twice')
count += 1
goto &lol if (count <= 1) else goto &goto_statements_are_a_perfectly_reasonable_thing_to_have_in_python
print('this prints last')
goto &stop
print('at least Ellipsis() has some use now, for normal reasons that make sense')
goto &last_print
print('it is sure this is how the variety of language features it is abusing were intended to be used')
goto &ellipsis
...
its beautiful.
this outputs the following:
this prints twice
this prints twice
it is sure this is how the variety of language features it is abusing were intended to be used
at least Ellipsis() has some use now, for normal reasons that make sense
this prints last
now then, how does this work?
design decisions
given that evidence suggests python programs can be edited, natalie decided not to use line number based gotos and instead define labels that could be jumped to.
this makes things less annoying to edit and maintain, and developer experience is obviously a very important consideration with this project.
additionally, to maximize ease with which this ""library"" can be utilized, one must only import the file to experience the wonder of python with goto statements.
technical implementation
firstly, labels:
goto_label.py has a function prepare_labels(fp, scope).
this function looks for any labels (defined via /^#LABEL labelname/) in the file at fp, and defines them under the scope scope.
each label is defined as a label object. natalie resents using an object for this (which you can see by its not using the class keyword), but python is python and you cannot override operators without having a class.
it could just not override the operators, but it wanted goto to be called in a unique way that is separate from how a function would be called.
label objects have only one attribute: lineno, which is determined by an extremely rudimentary parser and then offset by 2 to account for the fact that lines are 0 indexed in the "parser" but not in real life and the fact that the label definition comment takes its own line.
the actual goto implementation:
it does not remember how this works because it wrote most of the _goto function like 3 months ago after reading the sys and inspect module docs.
its projects have this fun property wherein they are in one of three states: done; not touched or thought about for 2-36 months; and done.
oh well, the goto function is vaguely self explanatory anyways.
there is some weirdness with operator overloading and how every goto statement is actually just calling what would normally be the bitwise and operator on an instance of the class _goto (not to be confused with the function _goto) and a label object, but that is both very simple and not required to understand this.
also, it does not want to explain that because it is tired.
automatically parsing the importing file on import:
normally, globals are not shared between modules, but python has this cool feature where if there is a general statement to be made about it then this statement can be invalidated using either the sys, inspect, or ctypes library.
this is easily the simplest part as we just grab the filename and global scope of the importing file via the inspect library and pass them to prepare_labels().
goto_label.py
import sys, inspect
debug = False
def prepare_labels(fp, scope):
with open(fp, 'r') as f:
lines = f.readlines()
for lineno, line in enumerate(lines):
if line.startswith('#LABEL'):
scope.__setitem__(line.split(' ')[1].strip(), type(
'label',
(object,),
dict(line=lineno+2)
)())
_goto = type('goto', (object,), dict(__and__=lambda _, other: _goto(other.line)))
goto = _goto()
def _goto(lineno):
if debug: print(lineno)
frame = sys._getframe().f_back.f_back
called_from = frame
def hook(frame, event, ):
if event == 'line' and frame == called_from:
try:
frame.f_lineno = lineno
except ValueError as e:
print("jump failed:", e)
while frame:
frame.f_trace = None
frame = frame.f_back
return None
return hook
while frame:
frame.f_trace = hook
frame = frame.f_back
sys.settrace(hook)
prepare_labels(inspect.stack()[-1].frame.f_code.co_filename, inspect.stack()[-1].frame.f_globals)
natalie finds much enjoyment in the syntax for calling a goto statement being goto &label. it looks like something that thoroughly does not belong in python.
ideas for future improvement:
making label names override builtin function names (unreasonably possible) and making a __call__ method on label objects that jumps to that label.
hopefully no one creates any labels named "print".
random other programs it (badly) implemented using if conditions only for goto statements out of boredom:
fibonacci
from goto_label import *
def fib(n):
depth = 0
a = 1
b = 0
print(a)
a, b, depth = b, b + a, depth + 1
if depth > n+1: goto &fib_end
goto &fib_start
...
fib(20)
fizzbuzz
from goto_label import *
def fizzbuzz(n):
count = 0
str = ''
if n == count: goto &fizz_end;
count += 1
if count % 3 == 0: goto &fizz;
if count % 5 == 0: goto &buzz;
goto &output;
str += 'fizz'
goto &buzz_check;
str += 'buzz'
print(str if str else count)
goto &fizz_start;
...
fizzbuzz(100)
number of times the python interpreter segfaulted today: 7
syntax highlighting here is done via :TOhtml in vim. it should work?