Esta es una guía para escribir un programa que, al ejecutarlo, imprime su propio código. (No hablo de que sale por una impresora en una hoja, imprime en pantalla...)
¿Es posible hacer esto? Sí. Por algo te hice esta guía. Empecemos.
Introducción
Este tipo de programas (llamados quine, o ouroboros en inglés) se puede hacer de muchas formas. Hay gente muy loca que inventa todo tipo de formatos para llevarlos a cabo, variando entre lenguajes, por supuesto.
En Python se puede ver un ejemplo muy, muy corto de un programa que se autorreplica al ejecutarse:
s='s=%r;print(s%%s)';print(s%s)
Ese programa de una sola línea es código perfectamente válido en Python, y al
ejecutarlo imprime en pantalla exactamente los mismos caracteres que conforman
su código fuente.
Puede ser un poco raro de entender, pero su funcionamiento es en realidad
sencillo (valiéndose de algunas curiosidades sintácticas) y sigue los mismos
principios que voy a comentar en la guía.
No voy a explicar ese programa, sin embargo.
Idea principal
Todo quine típicamente debería tener estos componentes, de alguna
manera:
- Su código
- Una representación de su código como texto
- Una instrucción que imprima esa representación
En el caso anterior en Python, la variable s es una cadena
(string) que contiene el código fuente del programa, no de forma
exacta, sino de una forma que al printear (con ciertas
modificaciones) producirá el código fuente.
Veamos cómo replicar esto en un programa en C.
Pasos a seguir
Voy a dar una serie de pasos para recrear esto en el lenguaje C. Es
relativamente sencillo y mecánico si consideramos algunas cosas.
- Escribir un programa que haga algo. Nos vamos a dar la libertad de hacer uno que no haga nada, excepto printearse a sí mismo, en esta ocasión.
- Crear una variable que contenga TODO el código fuente, reemplazando las nuevas líneas, tabulaciones, comillas dobles por %c, y a sí misma por %s. Manejaremos esto posteriormente.
- Agregar una línea de printf() inmediatamente antes del fin del programa. Esta será la encargada de printear el código fuente. Hay que agregarla en la cadena también.
-
Acomodar los argumentos de printf() para que reemplace cada
%c por el carácter necesario, y %s por la cadena
que definimos.
Escribiremos su representación en ASCII para cada %c, como número decimal, para mayor facilidad. Estos son carácteres que nos complican especialmente la vida en la generación de quines, ya que se representan como secuencias de escape en las cadenas (\n, \t, \" respectivamente. Mucho lío manejar esto, porque la barra \ también necesita ser "escapada" en la representación. Mejor ASCII): - nueva línea = 10
- tabulación = 9
- comillas dobles = 34
- Escribir los argumentos del printf() en la cadena de texto, ejecutar el programa, y gozar.
Ahora vamos a ver un ejemplo práctico de esto.
Manos a la obra
Empecemos escribiendo una plantilla básica de un programa en C:
#include <stdio.h>
int main() {
return 0;
}
Uno podría hacer que este programa haga algo, pero decidimos dejarlo así para esta guía. Si se ejecuta eso, solamente retorna 0 y termina su ejecución.
Vayamos al paso 2 y agreguemos una variable para contener la cadena. Dejémosla vacía por ahora.
#include <stdio.h>
int main() {
char cadena[]="";
return 0;
}
Agreguemos la línea del printf() a continuación:
#include <stdio.h>
int main() {
char cadena[]="";
printf(/* para completar */);
return 0;
}
Y pasemos a ensuciarnos las manos. Vamos a completar la cadena con
todo el código fuente. Vayamos de a poco.
Vamos a ver cómo debería lucir esa cadena solita:
"#include <stdio.h>%cint main() {%c%cchar cadena[]=%c%s%c;%c%cprintf(/* para completar */);%c%creturn 0;%c}"
Pero, ¿quejesto?
No entrar en pánico.
Es el programa, puesto en una línea sola, y cambiando lo que dije que había que cambiar:
- Saltos de línea por %c
- Tabulaciones por %c
- Comillas dobles por %c
- La cadena en sí por %s
No te mentí. Yo te había avisado de esto. 👀
Entonces, pongamos eso en la variable que creamos.
#include <stdio.h>
int main() {
char cadena[]="#include <stdio.h&t;%cint main() {%c%cchar cadena[]=%c%s%c;%c%cprintf(/* para completar */);%c%creturn 0;%c}";
printf(/* para completar */);
return 0;
}
Completemos el printf() ahora.
Es fácil: primero, va la cadena. Después, el valor de cada elemento a reemplazar en el formato de la cadena (son los %c y el
%s).
Como vemos que el primer %c corresponde a un salto de línea, el primer número será un 10.
El segundo es otro salto de línea, otro 10.
El tercero es un tab, entonces ponemos
9. Etc.
Esa línea debería quedar así:
printf(cadena,10,10,9,34,cadena,34,10,9,10,9,10);
Vemos que para el lugar de %s pusimos simplemente el nombre de la cadena, y para las comillas, 34.
Entonces lo ponemos en el código:
#include <stdio.h>
int main() {
char cadena[]="#include <stdio.h>%cint main() {%c%cchar cadena[]=%c%s%c;%c%cprintf(/* para completar */);%c%creturn 0;%c}";
printf(cadena,10,10,9,34,cadena,34,10,9,10,9,10);
return 0;
}
Y no olvidemos de cambiar lo que dice
/* para completar */ en la cadena también:
#include <stdio.h>
int main() {
char cadena[]="#include <stdio.h>%cint main() {%c%cchar cadena[]=%c%s%c;%c%cprintf(cadena,10,10,9,34,cadena,34,10,9,10,9,10);%c%creturn 0;%c}";
printf(cadena,10,10,9,34,cadena,34,10,9,10,9,10);
return 0;
}
Y como dice el último paso, queda compilar, ejecutar el programa, y gozar. Porque ya terminamos.
Se puede compilar (usando quizás gcc programa.c), y ejecutar, para ver que efectivamente se imprime en pantalla exactamente el código del programa.
Incluso se puede ejecutar esta serie de comandos (en alguna plataforma basada en Unix) para verificar que funciona. Con un fuente hola.c:
gcc hola.c -o hola.out
touch nuevo.c
./hola.out > nuevo.c
gcc nuevo.c -o nuevo.out
./nuevo.out
Y debería printear nuevamente lo mismo.
Quiero más
Te dejo algunos links.
- Video de Lex Fridman sobre este tema (lo que me hizo conocerlo)
- Artículo de Wikipedia con ejemplos y más
- Quine Relay, lista de quines enlazados en más de 100 lenguajes. Gente demente.
Chau
Nos vemos.
0 comentarios:
¡Dejá tu comentario acá!
Para poner un comentario, escribí tu mensaje en la caja de texto y elegí en el menú desplegable como quién querés publicar el comentario. Si no tenés ninguna cuenta, seleccioná Nombre/URL o Anónimo. ¡GRACIAS!