From 8a2343335cfb3cc083311f917023872825956fb3 Mon Sep 17 00:00:00 2001 From: nat Date: Mon, 12 May 2025 10:05:59 -0700 Subject: [PATCH] add blog posts: dollcode, python-goto --- .../dollcode/dollcode-invocation-memory-log | 39 ++++++ .../dollcode/dollcode-invocation-time | 2 + .../data/thoughts/{ => dollcode}/dollcode.py | 0 .../thoughts/python-goto/evil-fizzbuzz.py | 29 +++++ .../data/thoughts/python-goto/fibonacci.py | 15 +++ www/src/data/thoughts/python-goto/fizzbuzz.py | 25 ++++ www/src/data/thoughts/python-goto/goto-def.py | 27 ++++ .../data/thoughts/python-goto/goto-example.py | 20 +++ .../data/thoughts/python-goto/goto_label.py | 46 +++++++ .../data/thoughts/python-goto/label-def.py | 14 +++ .../data/thoughts/python-goto/limitations.py | 49 ++++++++ www/src/pages/html/thoughts/dollcode.hy | 59 +++++++++ www/src/pages/html/thoughts/python-goto.hy | 117 ++++++++++++++++++ www/src/pages/html/view-thought.hy | 4 +- 14 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 www/src/data/thoughts/dollcode/dollcode-invocation-memory-log create mode 100644 www/src/data/thoughts/dollcode/dollcode-invocation-time rename www/src/data/thoughts/{ => dollcode}/dollcode.py (100%) create mode 100644 www/src/data/thoughts/python-goto/evil-fizzbuzz.py create mode 100644 www/src/data/thoughts/python-goto/fibonacci.py create mode 100644 www/src/data/thoughts/python-goto/fizzbuzz.py create mode 100644 www/src/data/thoughts/python-goto/goto-def.py create mode 100644 www/src/data/thoughts/python-goto/goto-example.py create mode 100644 www/src/data/thoughts/python-goto/goto_label.py create mode 100644 www/src/data/thoughts/python-goto/label-def.py create mode 100644 www/src/data/thoughts/python-goto/limitations.py create mode 100644 www/src/pages/html/thoughts/dollcode.hy create mode 100644 www/src/pages/html/thoughts/python-goto.hy diff --git a/www/src/data/thoughts/dollcode/dollcode-invocation-memory-log b/www/src/data/thoughts/dollcode/dollcode-invocation-memory-log new file mode 100644 index 0000000..3576d76 --- /dev/null +++ b/www/src/data/thoughts/dollcode/dollcode-invocation-memory-log @@ -0,0 +1,39 @@ +~ λ dollcode -a "$(for i in {0..65536}; do echo -ne a; done)" >/dev/null & pid=$!; while kill -0 $pid 2>/dev/null; do pmap -x $pid | tail -n 1 | awk '{print ""$3/1024/1024 " GB"}'; sleep .5; done; wait $pid +[1] 11113 +0.0247803 GB +0.261642 GB +0.625462 GB +0.982468 GB +1.34475 GB +1.69653 GB +2.07035 GB +2.43603 GB +2.80609 GB +3.16292 GB +3.53598 GB +3.90202 GB +4.27491 GB +4.63856 GB +5.02195 GB +5.37629 GB +5.75089 GB +6.13502 GB +6.48058 GB +6.85527 GB +7.24199 GB +7.6222 GB +7.96089 GB +8.33753 GB +8.719 GB +9.0616 GB +9.44017 GB +9.82001 GB +10.1397 GB +10.526 GB +10.9109 GB +11.0703 GB +10.956 GB +10.8826 GB +10.8184 GB +0 GB +[1] + done dollcode -a "$(for i in {0..65536}; do echo -ne a; done)" > /dev/null diff --git a/www/src/data/thoughts/dollcode/dollcode-invocation-time b/www/src/data/thoughts/dollcode/dollcode-invocation-time new file mode 100644 index 0000000..834d2d9 --- /dev/null +++ b/www/src/data/thoughts/dollcode/dollcode-invocation-time @@ -0,0 +1,2 @@ +~ λ time dollcode -a "$(for i in {0..65536}; do echo -ne a; done)" >/dev/null +dollcode -a "$(for i in {0..65536}; do echo -ne a; done)" > /dev/null 14.31s user 7.79s system 99% cpu 22.202 total diff --git a/www/src/data/thoughts/dollcode.py b/www/src/data/thoughts/dollcode/dollcode.py similarity index 100% rename from www/src/data/thoughts/dollcode.py rename to www/src/data/thoughts/dollcode/dollcode.py diff --git a/www/src/data/thoughts/python-goto/evil-fizzbuzz.py b/www/src/data/thoughts/python-goto/evil-fizzbuzz.py new file mode 100644 index 0000000..3944894 --- /dev/null +++ b/www/src/data/thoughts/python-goto/evil-fizzbuzz.py @@ -0,0 +1,29 @@ +from goto_label import * + +def fizzbuzz(n): + count = 0 +#LABEL fizz_start + count += 1 + string = '' + if count == n+1: goto &fizz_end; + goto &label(output.line + int(count%3==0)*4+int(count%5==0)*8); + +#LABEL output + print(string if string else count) + goto &fizz_start + + + string += 'fizz' + goto &output; + + + string += 'buzz' + goto &output; + + + string += 'fizzbuzz' + goto &output; +#LABEL fizz_end + ... + +fizzbuzz(100) diff --git a/www/src/data/thoughts/python-goto/fibonacci.py b/www/src/data/thoughts/python-goto/fibonacci.py new file mode 100644 index 0000000..b9258d4 --- /dev/null +++ b/www/src/data/thoughts/python-goto/fibonacci.py @@ -0,0 +1,15 @@ +from goto_label import * + +def fib(n): + depth = 0 + a = 1 + b = 0 +#LABEL fib_start + print(a) + a, b, depth = b, b + a, depth + 1 + if depth > n+1: goto &fib_end + goto &fib_start +#LABEL fib_end + Ellipsis + +fib(30) diff --git a/www/src/data/thoughts/python-goto/fizzbuzz.py b/www/src/data/thoughts/python-goto/fizzbuzz.py new file mode 100644 index 0000000..683af4b --- /dev/null +++ b/www/src/data/thoughts/python-goto/fizzbuzz.py @@ -0,0 +1,25 @@ +from goto_label import * + +def fizzbuzz(n): + count = 0 +#LABEL fizz_start + str = '' + if n == count: goto &fizz_end; + count += 1 + if count % 3 == 0: goto &fizz; +#LABEL buzz_check + if count % 5 == 0: goto &buzz; + goto &output; + +#LABEL fizz + str += 'fizz' + goto &buzz_check; +#LABEL buzz + str += 'buzz' +#LABEL output + print(str if str else count) + goto &fizz_start; +#LABEL fizz_end + ... + +fizzbuzz(100) diff --git a/www/src/data/thoughts/python-goto/goto-def.py b/www/src/data/thoughts/python-goto/goto-def.py new file mode 100644 index 0000000..f8ffd92 --- /dev/null +++ b/www/src/data/thoughts/python-goto/goto-def.py @@ -0,0 +1,27 @@ +_goto = type('goto', (object,), dict(__and__=lambda _, other: (_goto(other.line)))) + +goto = _goto() + +def _goto(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 + + # it doesn't think we actually need to set the hook for each frame + # while frame: + # frame.f_trace = hook + # frame = frame.f_back + + called_from.f_trace = hook + sys.settrace(hook) diff --git a/www/src/data/thoughts/python-goto/goto-example.py b/www/src/data/thoughts/python-goto/goto-example.py new file mode 100644 index 0000000..b12af72 --- /dev/null +++ b/www/src/data/thoughts/python-goto/goto-example.py @@ -0,0 +1,20 @@ +from goto_label import * +count = 0 +#LABEL lol +print('this prints twice') +count += 1 +goto &lol if (count <= 1) else goto &goto_statements_are_a_perfectly_reasonable_thing_to_have_in_python + +#LABEL last_print +print('this prints last') +goto &stop + +#LABEL ellipsis +print('at least Ellipsis() has some use now, for normal reasons that make sense') +goto &last_print + +#LABEL goto_statements_are_a_perfectly_reasonable_thing_to_have_in_python +print('it is sure this is how the variety of language features it is abusing were intended to be used') +goto &ellipsis +#LABEL stop +... diff --git a/www/src/data/thoughts/python-goto/goto_label.py b/www/src/data/thoughts/python-goto/goto_label.py new file mode 100644 index 0000000..75c701c --- /dev/null +++ b/www/src/data/thoughts/python-goto/goto_label.py @@ -0,0 +1,46 @@ +import sys, inspect + +label = lambda lineno: type( + 'label', + (object,), + dict(line=lineno) +) + +def prepare_labels(fp, scope): + with open(fp, 'r') as f: + lines = f.readlines() + + for lineno, line in enumerate(lines): + if line.startswith('#LABEL'): + # 1 + 1, first to account for 0 index then to account for comment line + scope.__setitem__(line.split(' ')[1].strip(), label(lineno+2)) + +_goto = type('goto', (object,), dict(__and__=lambda _, other: (_goto(other.line)))) + +goto = _goto() + +def _goto(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 + + # it doesn't think we actually need to set the hook for each frame + # while frame: + # frame.f_trace = hook + # frame = frame.f_back + + called_from.f_trace = hook + sys.settrace(hook) + +prepare_labels(inspect.stack()[-1].frame.f_code.co_filename, inspect.stack()[-1].frame.f_globals) diff --git a/www/src/data/thoughts/python-goto/label-def.py b/www/src/data/thoughts/python-goto/label-def.py new file mode 100644 index 0000000..2dd5eb0 --- /dev/null +++ b/www/src/data/thoughts/python-goto/label-def.py @@ -0,0 +1,14 @@ +label = lambda lineno: type( + 'label', + (object,), + dict(line=lineno) +) + +def prepare_labels(fp, scope): + with open(fp, 'r') as f: + lines = f.readlines() + + for lineno, line in enumerate(lines): + if line.startswith('#LABEL'): + # 1 + 1, first to account for 0 index then to account for comment line + scope.__setitem__(line.split(' ')[1].strip(), label(lineno+2)) diff --git a/www/src/data/thoughts/python-goto/limitations.py b/www/src/data/thoughts/python-goto/limitations.py new file mode 100644 index 0000000..dbb9b62 --- /dev/null +++ b/www/src/data/thoughts/python-goto/limitations.py @@ -0,0 +1,49 @@ +from goto_label import * + +gaurd = 0 + +#LABEL say_hi +print('hi') + +#LABEL loop_start +gaurd += 1 + +if gaurd > 2: goto &call_b + +def a(): + global gaurd + goto &a_skip +#LABEL a_label + print('inside a_label') + goto &loop_start + if gaurd: + return +#LABEL a_skip + print('a') + if gaurd < 2: + goto &say_hi + goto &a_label + ... + +def b(): + global gaurd + goto &b_skip +#LABEL b_label + print('inside b_label') + goto &say_hi + goto &a_label + if gaurd > 2: + return +#LABEL b_skip + print('b') + goto &a_label + a() + goto &b_label + ... + +a() +print('done with a()') +goto &say_hi +... +#LABEL call_b +b() diff --git a/www/src/pages/html/thoughts/dollcode.hy b/www/src/pages/html/thoughts/dollcode.hy new file mode 100644 index 0000000..66ac8a0 --- /dev/null +++ b/www/src/pages/html/thoughts/dollcode.hy @@ -0,0 +1,59 @@ +(thought + :title "a moderately cursed dollcode implementation" + :date "2024-10-24 23:18:14" + :tags ["hacks" "computers" "python"] + :description "implementing a dollcode translator in python, very, very badly." + :content `( + (p #[[ + recently, dollcode has become a thing frquently utilized by both this one's friends and other beings it observes. + this is inconvenient, because many of them have written their own transcoders for the format, which can handle arbitrarily large numbers. + at time of writing, the only two translators it has found (]] ~(link "https://noe.sh/dollcode") #[[, ]] ~(link "https://999eagle.moe/dollcode") #[[) + do not support nubbers larger than 64 bit integers. thusly, it can read few of its friends encoded strings.]]) + + (p #[[ + the optimal solution to such a problem is probably either asking the sender of a given message what said message says, or asking them for their transcoder. + this one being this one, it did neither: below, one may find a very good and very reasonable dollcode transcoder. one can tell it is good and reasonable + because it contains the y combinator written in python lambda statements.]]) + + (figure + (figcaption "~/.local/bin/dollcode") + (code + ~(run "syntax-hl data/thoughts/dollcode/dollcode.py"))) + + (p #[[ + python is a very good programming language. this is observable in that it allows integers to have up to 4300 digits]] + ~(run "make-footnote 'or, up to a value specified via sys.set_int_max_str_digits(some_int)'")) + + (p #[[one may wonder: "why is the shebang like that?" the answer to such a question is that it did not want to set the recursion limit inside the actual program + (it did not realize it would hit recursion limit issues until after it was done writing the program), and it decided it would be amusing to have a really weird shebang line.]]) + + (p #[[bafflingly, this program works:]]) + (code #[[ + ~ λ dollcode -a nat
+ ▖▖▖▖▘▖▖▖▖▘▌▖▖▖▘]]) + + (p #[[it is very efficient:]]) + (code (pre + ~(run "cat data/thoughts/dollcode/dollcode-invocation-memory-log"))) + (p #[[the reason for this is interesting: + looking at the the argument passed to the encode function, d, we can see it is an integer such that if the integer were to be represented in base 16 + and split in to groups of two characters, each set of two characters would represent the byte that a character to be encoded is (ex: "nat" -> 0x6e6174 -> 6e, 61, 74 -> n, a, t).]]) + + (p #[[ + after this process of encoding the string to be encoded as a potentially very very big integer, d defines two important internal functions: + we will call them a and b. a takes an argument n, and returns the result of b called on both return values of divmod(n-1, 3) (those being the quotient and modulus of n by 3). + the second function, b, takes two arguments: x, and y. + it then returns str(f(x)) + str(y), where f is defined by the y combinator to be the first function, a.]]) + + (p #[[ + since, before b can return, a((n-1)//3) must first return, which in turn calls b with different arguments, we fill the stack with approximately log3(n) + frames containing numbers smaller than the initial (arbitrarily large) number. not accounting for the overhead of the memory taken by each function, the + number of bytes it takes to store an array of [n, n//3, n//9... 1] is approximately 2.7*n^2. if we plug 65536 in to our equation, we get + 2.7*65536^2 = 11596411699.2. dividing this by 1024^3, we get 10.8, which is very nearly the amount of memory, in gigabytes, this program uses to encode 65536 chars]]) + + (p #[[it is also very fast:]] ~(run "make-footnote 'it is only like this for large inputs, it is near instant for short strings. the python interpreter really does not like this code.'")) + (code (pre + ~(run "cat data/thoughts/dollcode/dollcode-invocation-time"))) + + (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.") + (br))) diff --git a/www/src/pages/html/thoughts/python-goto.hy b/www/src/pages/html/thoughts/python-goto.hy new file mode 100644 index 0000000..7ae8303 --- /dev/null +++ b/www/src/pages/html/thoughts/python-goto.hy @@ -0,0 +1,117 @@ +(thought + :title "implementing goto statements in python (in under 50 lines)" + :date "2024-09-12 08:43:04" + :tags ["hacks" "computers"] + :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.") + (br))) diff --git a/www/src/pages/html/view-thought.hy b/www/src/pages/html/view-thought.hy index fd2e944..8bd2d90 100644 --- a/www/src/pages/html/view-thought.hy +++ b/www/src/pages/html/view-thought.hy @@ -36,7 +36,9 @@ (inherit page-name) (with [thought-registry (open "../data/thought-registry" "a")] (.write thought-registry f"{(.join #[[;]] tags)}\t{(get page-name -1)}\t{title}\t{date}\t{description}\n")) - + + (run "echo > /tmp/footnote_count") + `( ~content ~(run "put-footnotes")