En las últimas semanas he escrito programas que combinan C++ y Scheme. Una de las implementaciones de Scheme que mas he utilizado es GNU Guile, sin embargo no encontré muchos tutoriales que explicaran cómo integrar estos dos lenguajes en una aplicación.
En una serie de entradas de este blog, documentaré información que me resultó útil para poder integrar Guile con C++.
No tengo conocimientos intensos ni práctica matona en estos lenguajes de programación, no creo tener la capacidad de redactar buenos tutoriales y aún estoy en proceso de aprender a hackear intensamente, pero creo que estos escritos le pudieran servir a otros compatriotas ignorantes como yo.
Instalación
Los programas que utilicé para programar son los siguientes:
NOTA: Por si a alguien le interesa, aqui está mi archivo de configuración de Emacs:http://pastebin.com/ubXDaF3D
Primer programa
¿Qué se va a programar?
En este primer tutorial se va a escribir un programa que inicialice Guile utilizando el lenguaje C++, de tal manera que podamos conectarnos a este ambiente de Guile desde Emacs.
En el manual de Guile viene un programa de ejemplo (simple-guile.c) en donde muestran como correr el intérprete de guile desde un programa escrito en C. El siguiente fragmento de código corresponde a una simplificación del ejemplo:
Para compilar: gcc -o simple-guile simple-guile.c `pkg-config –cflags –libs guile-2.0`
Comenzando en la función main se llama a la función de guile scm_boot_guile la cual corre inner_main con un ambiente de guile activado, después se manda a llamar scm_shell, que es el intérprete de guile.
En el manual de guile proceden a introducir cómo llamar a funciones escritas en C desde Guile. Conforme me vi interesado en integrar a Guile con programas escritos en C++ me percaté que esta manera de correr el intérprete es intrusiva, ya que secuestra el hilo en donde está corriendo el programa.
Una manera de correr un intérprete de Guile sin secuestrar a la función inner_main es utilizando Emacs y Geiser.
El truco se encuentra en invocar un REPL server el cual permite que podamos evaluar expresiones mientras el programa se sigue ejecutando. Considere el siguiente programa.
En lugar de llamar a scm_shell se evaluan dos expresiones de scheme (use-modules (system repl server)) y (spawn-server) utilizando la función scm_c_eval_string. Después dejamos al programa esperando recibir entrada desde la consola.
Lo que ocurrirá es que Guile corre un servidor en un nuevo hilo de ejecución, al cual nos podremos conectar con Geiser (usando Emacs).
Desde Emacs corremos el comando connect-to-guile (M-x connect-to-guile (M-x es presionar la tecla alt y la tecla ‘x’)) con el Host y Port por default.
Realicé dos cambios sutiles al ejemplo:
- Al llamar a scm_boot_guile escribí NULL en lugar de 0. Resulta que lo que recibe esta función como cuarto parámetro es una variable de tipo void *. Hice este cambio porque me resulta menos confuso escribir NULL cuando quiero asignarle el valor 0 a un puntero.
- El comando para compilar es ahora g++ simple-guile.cpp `guile-config link` `guile-config compile`. En lugar del compilador de C (gcc) se usa el de C++ (g++). Además, cuando se instala guile se crea el programa guile-config el cual funciona como pkg-config pero con comandos mas fáciles de recordar.
Este segundo código es el que utilizo como plantilla al comenzar un nuevo programa que incruste Guile en C++.
Explicación de las funciones
Creo que la mejor manera de entender que es lo que hace cada una de las funciones es leyendo el manual de Guile. A continuación describo mi interpretación de las funciones:
La firma de la función es:
void scm_boot_guile (int argc , char ** argv , void (* main_func )(void * data , int argc , char ** argv ), void * data )
Lo que significa que es una función que no regresa valor y tiene 4 argumentos, el primero y el segundo son los mismos que suele recibir la función main (argc es la cantidad y argv el arreglo de cadenas correspondientes a los argumentos con los que se ejecuta el programa desde la consola). El tercer argumento es un puntero a función de tipo void que reciba tres argumentos (void*, int y char**). El último argumento aún no sé para que se utiliza, yo siempre le mando el valor NULL y en códigos ajenos que he leído hacen lo mismo.
Esta función realiza una inicialización de Guile en el hilo actual y manda a llamar a la función que es el 3er argumento con los valores del 4to argumento, 1er argumento y 2do argumento (en ese orden).
- (use-modules (system repl server))
Este es código de Guile el cual carga el módulo que tiene las funciones relacionadas con el REPL server.
Crea un REPL server en otro hilo al cual nos podemos conectar por el puerto 37146 (los numeros se parecen a la palabra GUILE al revés).
Nota colofoneadora
Me ha sido muy útil leer el mailing list de guile-user, lo recomiendo para plantear dudas y explorar dudas de otros (y si tienen la capacidad, resolver dudas).
También es de suma benevolencia buscar en github programas que utilicen Guile. Lo que usualmente hago es buscar funciones de la librería libguile.h para saber como es que son utilizadas en programas terminados.
En la siguiente entrada del blog se abordará cómo registrar funciones de C++ en Guile para menearle a una variable std::string.