Self-replicating Code (in javascript)
Self-replicating code (Quine) is a program that can print its own source code when run. There are many C language and C# versions online, and I’m going to write a JavaScript version.
Digression: eval function/infinite recursion
JavaScript is an interpreted language, and it has a powerful function eval()
: it can execute the received string as JavaScript code. I came up with this piece of code:
s="eval(s)";eval(s);
We can do this experiment in any browser: just type javascript:s="eval(s)";eval(s);
into the address bar. (Go and try it..)
After running it in the Chrome console for a while, you get this error: <span style=”color:#F00”>Uncaught RangeError: Maximum call stack size exceeded</span><span style=”color:#999>(…)</span>
<hr/>
Here’s another interesting infinite recursion experiment: execute these statements in the Chrome console:
A = {};
B = {};
A.a = B;
B.b = A;
Then, output A, and we get an infinitely nested object:
(It’s not actually infinitely nested, but rather two mutually pointing pointers.)
Main Topic: Self-printing Programs
Let’s first look at the popular C language version online:
char*s="char*s=%c%s%c;main(){printf(s,34,s,34);}";main(){printf(s,34,s,34);}
The final printf function outputs the source code of the entire program. The ‘34’ in printf is the ASCII code for the double quote “ character, which is used to avoid issues with writing quotes within quotes. Cleverly using %s
to represent the entire string in formatted output is the core of this self-replicating program: only by self-referencing in this way can the program output itself with limited code.
Referencing the online C language version, I got an idea for writing it in JavaScript: a method must be used to represent the entire string. However, JavaScript doesn’t seem to have a command for formatted output, so we must find a way to use a very short string to represent the entire string. I thought of using a random character (like “@”) as a placeholder in the string, and then using the string’s split
function (for string splitting and finding) to replace “@” with the entire string for output. So, let’s first write a function to do this:
function r(s){
j = String.fromCharCode(34); //j represents a double quote
k = '@';
q = s.split(k);
return q[0]+k+q[1]+j+s+j+q[2] //Returns the string where '@' in 's' is replaced by 's', e.g., "abc@d" becomes "abc@d" + "abc@d" + "d" (meaning "abc" + s + "d")
}
Next, we need to write that string. We don’t know what that string looks like yet, so let’s look at the next step, which is also the final step – outputting the string: We use the console.log
function to output the final result, so the last line of code is console.log(r(s))
, meaning to output the replaced string. Our code now looks like this:
function r(s){
j = String.fromCharCode(34);
k = '@';
q = s.split(k);
return q[0]+k+q[1]+j+s+j+q[2]
}
s = .....
console.log(r(s))
Now we know what string s
should be equal to, right? It should be equal to all the code text above (because our goal is to output all the code). The ellipses above are placeholders for “@”, which will allow the code to describe itself. So the final code is as follows:
function r(s){j=String.fromCharCode(34);k='@';q=s.split(k);return q[0]+k+q[1]+j+s+j+q[2]}s="function r(s){j=String.fromCharCode(34);k='@';q=s.split(k);return q[0]+k+q[1]+j+s+j+q[2]}s=@;console.log(r(s))";console.log(r(s))
We are aiming for brevity, so there’s no need for a separate function. Optimizing it, we get:
s="s=@;j=String.fromCharCode(34);k='@';q=s.split(k);z=q[0]+j+s+j+q[1]+k+q[2];console.log(z)";j=String.fromCharCode(34);k='@';q=s.split(k);z=q[0]+j+s+j+q[1]+k+q[2];console.log(z)
To fully leverage JavaScript’s features to make the code shorter, we can also use the eval function mentioned earlier. We see a large amount of code appearing twice – first as the code itself, and second inside the string s
. We actually only need to write this string content once: one copy becomes the code itself using eval
, and the other is assigned to string s
. Simplified, it’s as follows:
x="j=String.fromCharCode(34)";y="k='@';q=s.split(k);z='x='+j+x+j+';y='+j+y+j+';eval(x);s='+j+q[0]+k+q[1]+j+q[1];console.log(z);";eval(x);s="s=@;eval(y)";eval(y)
Further optimization:
j=String.fromCharCode(34);y="k='@';q=s.split(k);z='j=String.fromCharCode(34);y='+j+y+j+';s='+j+q[0]+k+q[1]+j+q[1];console.log(z);";s="s=@;eval(y)";eval(y)
It still doesn’t feel short enough. JavaScript has another advantage we haven’t utilized yet: the nested use of single and double quotes. If we use it well, we won’t need such long code like String.fromCharCode(34)
to represent quotes. The optimized result is as follows:
x="'";v='"';y="k='@';q=s.split(k);z='x='+j+x+j+';v='+x+v+x+';y='+j+y+j+';s='+j+q[0]+k+q[1]+j+q[1];console.log(z);";s="s=@;eval(y);";eval(y);
We also don’t need to specifically create a symbol “@”; we can just use an existing single quote as a marker, saving a few characters:
x="'";v='"';y="q=s.split(x);z='x='+v+x+v+';v='+x+v+x+';y='+v+y+v+';s='+v+q[0]+x+q[1]+v+q[1];console.log(z);";s="s=';eval(y)";eval(y)
The single quote in the string s=';eval(y)
is found by s.split(x)
and then replaced. Of course, we can also use the more readily available replace
function to replace strings. But then I was stunned when I saw the shortest JavaScript code on this website:
function a(){console.log(a+"a()")}a()
Its principle is very simple: directly console.log
the function variable a
, and then JavaScript automatically converts a
into a string, which directly becomes the source code of function a
. Of course, some say this is a cheating method… In fact, using the eval
function itself might be considered cheating…