Hyper about quines
Befunge-98, infinite order, 54 52 38 36 bytes
Second approach - infinite order, 36 bytes
This program would actually break at the 34th hyperquine since the ASCII value of "
would disrupt the string interpretation (and at 59, ;
), but we offset the storage of that value to a position that will never be executed (i.e. (0, 1)
instead of (0, 0)
).
1+::0*x01pn'!1+:#jr;,kg10@k!:kg10;#"
Try it online: 1, 2, 10, 34, 42
Explanation
INSTRUCTIONS STACK (PYTHON PSEUDOCODE) EXPLANATION
1+ [n] Push n many 1s onto the stack, then sum them up
:: [n]*(at least 3) Duplicate that sum at least twice
0* [n]*(at least 2)+[0] Push a whole lot of zeros, then multiply them all together
x [n]*(at least 1) Pop a vector off the stack (n, 0) and set the IP delta to that; now the IP is only executing every nth character
01p [n]*(at least 1) Place n in the program at coordinates (0, 1); this is just for storage
n [] Clear the stack
'!1+ ['"'] '!' is character 33; one less than 34, or '"'
:#jr ['"'] We duplicate the 34 (all we care is that it's a rather large number), then turn around and skip that many spaces
The IP, having jumped 34*n instructions to the left, is now way in the negatives
Execution resumes on the other side of the program (the following instructions have been reversed for readability
" [the program of order 1] The quote-at-the-end-of-the-program is a common trick for one-liner Befunge quines
#; ... ; Jumps into a loop (when the IP hits one semicolon it skips to the next, restarting the loop)
01gk: [(rest of string), char*(n+2)] This duplicates the letter n+1 times*, leaving n+2 copies on the stack
!k@ If the number on the top of the stack is zero (i.e. we are printing), it will execute '@',
ending the program; otherwise, it will NOT execute '@' and will instead continue normally
Vague* 'k' instruction FTW
10gk, If we aren't done yet, print the character n+1 times* (and restart the loop)
* 'k' is a very strange instruction. It pops a number off the stack; if the number is zero, it skips the command in front of it. If the number is greater than zero,
it will execute the instruction that many times PLUS ONE. This is actually strangely advantageous in this program.
First approach - order 34, 52 bytes (uses introspection, so technically not legal)
For the reason in the above post, this program would break at order 34 (though I haven't tested).
1+::0*x:00p'1\k:00gk,1#;:00g*0g00gk:$00gk,1+:'4-!k@;
Try it online!
><>, infinite order, 178 bytes
The program contains a trailing linefeed.
^
.
*
&
:
&
+
*
2
b
*
*
6
9
$
0
)
*
4
8
:
~
.
*
&
:
&
+
*
2
b
*
*
2
b
$
0
)
i
:
-
1
o
a
&
:
&
o
~
.
*
&
:
&
+
*
7
7
*
*
4
6
$
0
)
0
:
-
1
$
o
:
$
&
:
&
&
,
*
8
b
-
1
l
}
*
3
d
'
Try it online: 1, 2, 3, 10 (That last one takes a while to run.)
Retina script to generate source from linear program.
Explanation
The main idea is to turn the quine vertical, so that the actual control flow isn't affected by the repetition. E.g. the second hyper quine begins like:
^^
..
**
Since we're only moving through the first column, we don't have to worry about repeated characters. Also when we push the majority of the code as a string with '
, this will push a space for each empty line, which gives us a way to determine the number of repetitions. That said, there are some limitations due to these empty lines:
- We can't use
"
to push large numbers as character codes in the main part of the quine, because this would push additional32
s which we don't want. - We can't use
?
or!
because they only skip the next character which would be a space in that case (so they wouldn't actually skip the next command).
Hence, all control flow is done with explicit jumps (2D goto, basically), whose actual offsets we need to calculate based on the number of repetitions.
So let's look at the actual code. We start with ^
so the code is executed bottom up. For easier reading, let's write out the actual code in execution order (and drop the ^
because it's never executed again):
'd3*}l1-b8*,&&:&$:o$1-:0)0$64**77*+&:&*.~o&:&ao1-:i)0$b2**b2*+&:&*.~:84*)0$96**b2*+&:&*.
The '
is the standard quining technique for ><> (and Befunge, I guess). It switches to string mode which means that the encountered characters are pushed onto the stack until the next '
is encountered. Empty lines are implicitly padded with spaces which is why we get all the spaces in between. Empty lines at the end of the program are ignored. So after the IP wraps around and hits the '
again, we've got the first column of the program on the stack, except for the '
itself.
Let's look at how we use this to print the entire program.
d3*} Put a 36 (the ') at the bottom of the stack. Now the stack holds
a representation of the entire first column.
l1- Push the depth of the stack, minus (so minus to ').
b8*, Divide by 88. The original program has 89 lines. If we divide the
depth of the stack (minus 1) by 88, we get the order of the current
hyperquine (due to all the spaces we've pushed).
& Store the order of the hyperquine in the register.
Begin of main loop:
&:& Push a copy of the register onto the stack. Call that N.
Begin of line-printing loop:
$:o$ Print a copy of the top character on the stack.
1- Decrement N.
:0) Check whether it's still positive (gives 0 or 1).
0$ Put a 0 underneath. This will be the x-coordinate of a jump.
64** Multiply the conditional by 24. This is the number of commands
in this inner loop.
77*+ Add this to 49, the offset of the end of the loop.
The result is line we want to jump to in the order-1 hyperquine.
&:&* Multiply by the order of the quine (so that we jump further on
higher quine orders).
. Jump. If N isn't zero yet, this repeats the inner loop. Otherwise
we continue right here.
~ Discard N (now 0).
o Output one last copy of the top character on the stack.
&:& Push a copy of the register onto the stack. Call that N.
Begin of linefeed-printing loop:
ao Print a linefeed.
1- Decrement N.
:i) Check whether it's still non-negative (gives 0 or 1).
The next bit is essentially the same loop structure as above,
but with loop length 22 and offset 22:
0$
b2**
b2*+
&:&*
. Jump. If N isn't -1 yet, this repeats the inner loop. Otherwise
we continue right here.
Begin of space-clearing loop:
~ Discard the top of the stack. On the first iteration this is the
-1 from the previous loop. Afterwards, it's one of the spaces
representing an empty line.
:84*) Check if the top of the stack is a space.
And another loop conditional. This one works the other way round:
the difference is 54, which is the distance between the beginning
of this loop and the main loop. The offset is the beginning
of this loop, at 22 as above.
0$
96**
b2*+
&:&*
. Jump. If the top of the stack is still a space this repeats the
inner loop. Otherwise we continue from the top of the main loop.
The program terminates when the stack is empty and the first inner loop fails to print another character.