117 lines
6.5 KiB
Hy
117 lines
6.5 KiB
Hy
(thought
|
|
:title "implementing goto statements in python (in under 50 lines)"
|
|
:date "2024-09-12 08:43:04"
|
|
:tags ["hacks" "programming" "python"]
|
|
:description "alternative title: a fizzbuzz implementation that will instantly fail any job interview."
|
|
:content `(
|
|
(p "please note: this one knows nothing about python. if the reader sees something that it is wrong about, they are encouraged to e-mail it.")
|
|
(hr)
|
|
(p "this is post is really rather self-explanatory, so it supposes it will start by showing the end result of this endeavor:")
|
|
(figure
|
|
(figcaption "goto-example.py")
|
|
(code
|
|
~(run "syntax-hl data/thoughts/python-goto/goto-example.py")))
|
|
|
|
(p "this outputs the following:")
|
|
(code (pre
|
|
~(run "python3 data/thoughts/python-goto/goto-example.py")))
|
|
|
|
(br)
|
|
(h2 "how it works")
|
|
(hr)
|
|
(h3 "design decisions")
|
|
(p ~#[f[
|
|
given that&mdash{";"}according to available data&mdash{";"}python programs can be editted, it decided to implement goto statements based off
|
|
of labels on some line from which a number can be calculated, and not based solely on jumping to line numbers. this allows programs containing
|
|
goto statements to be editted without needing to recalculate each line number to jump to. additionally, for ease of use, simply importing
|
|
* from goto_label.py in any file allows goto statements and labels for them to jump to to be defined and used.]f])
|
|
|
|
(hr)
|
|
(h3 "technical implementation")
|
|
(p "firstly, labels:")
|
|
|
|
(p "labels are defined like so:")
|
|
(code "#LABEL label_name")
|
|
|
|
(p #[-[
|
|
goto_label.py has a function prepare_labels(fp, scope). this function looks for any labels (defined by matching /^#LABEL .*/) in the
|
|
file fp. for each matching string, a variable labelname (whatever was after "#LABEL ") is created in the scope passed to the function,
|
|
with a value of label(lineno+2). the reason we add two to the line number of the label is to account for the fact that the parser
|
|
that creates labels at import time iterates over a list of lines, so we must add one to the index to account for 0-indexing, and
|
|
then add one again to account for the fact that the label definition is its own line.]-])
|
|
|
|
(figure
|
|
(figcaption "functionally, this works out to the following:" ~(run #[[make-footnote "it did not use the class keyword when creating classes in this project. it doesn't really have a reason for this, other than it thought it was funny."]]))
|
|
(code
|
|
~(run "syntax-hl data/thoughts/python-goto/label-def.py")))
|
|
|
|
(p "next, the actual goto implementation:" ~(run #[[make-footnote "the way it named the constructs here is rather confusing. don't code on 0 hours of sleep after having been awake for > 30 hours."]]))
|
|
(p #[-[
|
|
this one wanted the implementation of the goto function to feel very non-pythonic. it thinks it achieved this through making the syntax
|
|
for calling goto be goto &label_name. this is done by simply creating a wrapper object (the first instance of _goto defined) with a custom __and__ method defined: the __and__
|
|
method takes the wrapper class and the label object, and calls the actual goto function on the label object. it simply does nothing with the reference to itself that gets passed to it.]-])
|
|
|
|
(p #[-[
|
|
so, given self-defining labels to jump to, and a function that will operate on those labels, all we need is an actual means of changing what code python is executing.
|
|
this is shockingly simple: we just call sys._getframe() to get the current frame, and then access the f_back field once, to traverse back to the frame
|
|
in which the _goto wrapper object's __and__ method is executing, and then again a second time to traverse back to the frame in which we call the wrapper object's
|
|
__and__ method (read: the place in which goto &label is envoked).]-])
|
|
|
|
(p #[-[
|
|
given the frame which we want to jump to, we define a hook function to be installed as a traceback function for the target frame.
|
|
when this hook function is called, it will attempt to set the line number of the target frame to the line number of the label we wish to jump to.
|
|
if this fails, it will print an error. after modifying the frame's line number, we then remove our trace function from the target frame, and each of its parent frames.
|
|
this results in execution continuing from the line number of the target label, provided no errors occur.]-])
|
|
|
|
(figure
|
|
(figcaption "the code described above works out to be the following:")
|
|
(code
|
|
~(run "syntax-hl data/thoughts/python-goto/goto-def.py")))
|
|
|
|
(p #[-[
|
|
finally, the code that automatically creates labels for any file that imports it is quite simple. prepare_labels() just gets called with the
|
|
filename and scope of the importing file passed as arguments. these values are gotten through similarly cursed stack inspection. all together,
|
|
this yields the following code:]-])
|
|
|
|
(figure
|
|
(figcaption "goto_label.py")
|
|
(code
|
|
~(run "syntax-hl data/thoughts/python-goto/goto_label.py")))
|
|
|
|
(hr)
|
|
(h3 "limitations")
|
|
(figure
|
|
(figcaption "jumping between function scopes raises issues:")
|
|
(code
|
|
~(run "syntax-hl data/thoughts/python-goto/limitations.py"))
|
|
(br)
|
|
(figcaption "output")
|
|
(code (pre
|
|
~(run "python3 data/thoughts/python-goto/limitations.py"))))
|
|
|
|
(p "it is not sure if there is a good way to get around this.")
|
|
|
|
(hr)
|
|
(h2 "various hacks using gotos")
|
|
(p "yes, all of these work. if the reader is ever in a job interview and is asked to write fizzbuzz, this one highly encourages them to use the last one. "
|
|
"it is sure the interviewer would be thrilled.")
|
|
(figure
|
|
(figcaption "fibonacci")
|
|
(code
|
|
~(run "syntax-hl data/thoughts/python-goto/fibonacci.py")))
|
|
(br)
|
|
|
|
(figure
|
|
(figcaption "fizzbuzz (normal)")
|
|
(code
|
|
~(run "syntax-hl data/thoughts/python-goto/fizzbuzz.py")))
|
|
(br)
|
|
|
|
(figure
|
|
(figcaption "fizzbuzz (from hell)" ~(run #[[make-footnote "this works by jumping a different number of lines for each condition by defining new positions to jump to based on the value of count."]]))
|
|
(code
|
|
~(run "syntax-hl data/thoughts/python-goto/evil-fizzbuzz.py")))
|
|
(br)
|
|
|
|
(footnote "this post was rewritten on 2025-05-12, as part of the migration to the new website. the wording has been updated, however it remains similar to the original.")))
|