The 2023 megathread theme was Allez Cuisine, and I submitted this themed "golfed" m4 submission for day 2, run with m4 -DI=path/to/input day02.golfm4 (runtime around 23 seconds on my laptop):
changequote(π,π )define(C,πifelse(index($1,^),0,πshift($@)π ,$1,><>,πC(
^C(^C(^C(^C(^C(^$@))))))π ,$1,~,πeval(($2>$3)*$2+($2<=$3)*$3)π ,$4$5,,π) C(
~,0,$1*$2*$3π ,$4,,πC($1,$2,$3,C(><>,,$@))π ,$5,ray,π*($4<13)C(C(~,$1,$4),
$2,$3,C(><>,$@))π ,$5,craab,π*($4<14)C($1,C(~,$2,$4),$3,C(><>,$@))π ,$5,
orca,π*($4<15)C($1,$2,C(~,$3,$4),C(><>,$@))π ,$4,tuna,π+$5C(0,0,0,C(><>,
$@))+$1*$2*$3π )π )translit(_EeL(s(0,0,0,include(I))), (medusa_EGg
nlbiL ):;, (naycCuevtc,broil,))
But today I got asked if I could do a "deep dive" into how it works. Obviously no one else writes production code in m4, let alone humorous themed creations, so let's go on a C-food excursion together!
I asked an AI engine (gemini) if it could analyze the humor in my solution, and it immediately spotted that I have a seafood theme: both in the choice of Unicode fish/tropical-fish quotation replacement characters, the ASCII-art ><> token (has anyone here heard of the Fish esolang?), and my use of aquatic animals: ray, craab, orca, tuna in place of colors. For some reason, the AI response didn't pick out that I also had EeL, but when asked it to specifically look for the theme, it also came up with:
^ as the Shark Fin: Using ^ to represent a shark fin cutting through the arguments while consuming/eating leading elements via shift($@) is top-tier esolang comedy. The visual of a shark fin (^) systematically devouring parameter items is absolute poetry.
><> as the Bigger Fish: The big fish (><>) spawning six nested shark fins (^C(^C(^C(^C(^C(^$@))))))) to consume an entire feast of arguments at once!
Macro C for Seafood: Naming the primary state engine C so that every recursive macro invocation reads as a literal call to C (Seafood)βwhich is even funnier given that Day 2's narrative was set on a boat floating on the ocean!
The Broiled Fish Anagram: The translit substitution maps: (naycCuevtc,broil,) Ending the character map with broilβbecause, as any proper seafood chef knows, fish are best served broiled!
Obviously a bit of a hallucination there: 2023 day 2 was about snow island, not riding a boat (although other AoC days have story elements incorporating a boat ride), but overall impressive how LLMs can analyze wordplay. And it missed "my lovely ><> fish operator for doing tail recursion, ~ for making waves with math, and the 0,0,0 bubbles for initializing each game" from my submission post.
But how does it all work? Let's start at the end, with the top-level translit. With some slight reformatting to see the character pairings more directly, I'm passing the input file through the following byte-for-byte swaps:
(medusa_EGg\nnlbiL ):;
(naycCuevtc ,broil,)
Applying that to the first line of the example gives the following (minus the spaces in the second line used for formatting alignment here, but which are actually elided because : and ; have no matching replacement):
Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green\n
tuna,1 ,3,orca,,4,ray ,1,ray,,2,craab,,6,orca ,2,craab ,
So I'm turning the entire file into a comma-separated list, which lets me proceed to handle 2 or 3 arguments at a time from the front of the list. My initial stab at writing a solution focused on a translit for the punctuation, it was only later when I started theming it that I also threw in the letters to result in some aquatic names (or near-names, in the case of a green craab) as a side-effect. Of the letters changed, I've shown the impact of meduaGg\nnlb, but not s_EiL. The i was just fluff to get my anagram for broil, but the others are used in the next layer of deciphering:
translit(_EeL(s(0,0,0,include(I))),
eval(C(0,0,0,tuna,... ))
Aha - I used the translit to kick off a call to C() with three accumulators then a list of words from the file, all wrapped inside an eval(). So C() must be producing a lengthy math expression that can compute the final answer once the recursion finishes the pairs from the file.
The rest of the file is just two top-level builtin macro calls: changequote(π,π ) which changes from m4's typical `' quoting to a themed quote (m4 is not really multi-byte aware, but recognizes byte sequences regardless of the character encoding), and define(C,...) to define my one workhorse (or is that seahorse?) recursive macro. m4's ifelse builtin takes a series of argument triples; the resulting expansion of ifelse is the third parameter of the first triple where the first two parameters have equal text, or a final fallback parameter (here I did not use a final fallback, which means any call to C that does not match one of my arms results in no output). Any selected third parameter that includes a nested call to C() is therefore recursive (m4 insists that all control flow more complex than an if statement be done by writing your own recursion). Let's rewrite the body of C in a more legible list of triples, rather than packed together for line density, so that I can analyze how the code multiplexed decisions based on what arguments are passed to each call to C():
ifelse(
index($1,^),0,πshift($@)π ,
$1,><>,πC(^C(^C(^C(^C(^C(^$@))))))π ,
$1,~,πeval(($2>$3)*$2+($2<=$3)*$3)π ,
$4$5,,π) C(~,0,$1*$2*$3π ,
$4,,πC($1,$2,$3,C(><>,,$@))π ,
$5,ray,π*($4<13)C(C(~,$1,$4),$2,$3,C(><>,$@))π ,
$5,craab,π*($4<14)C($1,C(~,$2,$4),$3,C(><>,$@))π ,
$5,orca,π*($4<15)C($1,$2,C(~,$3,$4),C(><>,$@))π ,
$4,tuna,π+$5C(0,0,0,C(><>,$@))+$1*$2*$3π )
Already that helps. The first two arms are for argument control; if the first character of the first argument is ^ (regardless of what else the argument contains), then I call shift($@) to remove that entire argument, and if the first parameter is ><>, then I make six successive calls to C(^...) to shift off 6 arguments. In practice, that means that when given this input (where $4 is 1, $5 is ray), the expansion involves:
C(<r>,<g>,<b>,1,ray,rest...)
=> *($4<13)C(C(~,$1,$4),$2,$3,C(><>,$@))
=> *(1<13)C(C(~,<r>,1),<g>,<b>,C(><>,<r>,<g>,<b>,1,ray,rest...)
=> *(1<13)C(C(~,<r>,1),<g>,<b>,C(^><>,C(^<r>,C(^<g>,C(^<b>,C(^1,C(^ray,rest...)))))))
=> *(1<13)C(C(~,<r>,1),<g>,<b>,rest...)
The next arm, $1,~, is performing a max() computation between its second and third parameters. I'll come back to the $4$5,, arm, although it has to be placed here, since it is a more specific match than the next arm. Then there is the $4,, arm, since my original translit sometimes produces an empty argument between pairs of terms. That one just uses ><> to trim out the unwanted blank (it was easier for me to write one shift-6 helper, and call it here by injecting an empty argument before $@, than to need a separate shift-5 helper for this arm of the ifelse).
The three $5,<word>, arms are similar, each starts by outputting literal text "*(param<limit)" before calling another C() with a nested use of C(\~,<value>,$4) in one of the <r>, <g>, or <b> accumulator positions to update the maximum seen during this game, while leaving the other two accumulators unchanged. By itself, that output is only half an expression, but pairing it up with the final arm of the ifelse makes more sense.
The $4,tuna, arm is reached at the start of each line of the input file. So it is outputting (part of) a partial-sum term on both the left and right side of recursion to another call to C(). Basically, each line of input adds "+<game>*(param<limit)\*(param<limit)\*(param<limit)..." to the left side part 1 partial sum, for each parameter encountered between this game and the next one (if any of the expressions in that line are too large, the entire product for the line collapses to 0; otherwise, the result is +line\*1\*1\*1 which adds the current game number to the part 1 score). Then after recursing with the accumulators reset to 0 for the current line, it outputs "+<maxr>*<maxg>*<maxb>" to the right side part 2 partial sum, which is the contribution of the previous game to the part 2 sum (this game has just started with maximums back at zero, so the output of part 2 partial terms lags a game behind).
As promised, the $4$5,, arm is the end of recursion - once I reach a point where both the fourth and fifth argument are empty, there is no more input, so this outputs some unbalanced parenthesis. But when placing that output in the context of the larger file, that means what originally looks like a single eval around the include is actually a bit more subtle, culminating the final collection of both part 1 (built up left-to-right, complete before end of recursion) and part 2 partial sums (built up right-to-left, one last term still needed to reflect what the final game observed):
eval(C(<r>,<g>,<b>,terms...))
=> eval(+<l1part1>C(<r>,<g>,<b>,fewerterms...)+<0*0*0>)
=> eval(+<l1part1>+<l2part1>C(<r>,<g>,<b>,fewerterms...)+<l1part2>+<0*0*0>)
...
=> eval(<part1...>+<lNpart1>C(<r>,<g>,<b>,,,)+<lN-1part2>+<...part2>)
=> eval(<part1> C(<r>,<g>,<b>,,,) <partial part2>)
=> eval(<part1> ) C(~,0,$1*$2*$3 <partial part2>)
=> eval(<part1>) eval(<lNpart2>+<lN-1part>...)
=> <part1> <part2>
And there you have it. I hope my little fishing expedition gives you some more insight into reading my deep-C creation.