Ejecución de programas Gacela en el navegador

Actualización: Todo lo explicado en esta entrada puede hacerse mucho mejor usando el objeto XMLHttpRequest, tal como cuento en Ejecución de programas Gacela en el navegador (rectificación). Si quieres saber cómo no hacerlo, sigue leyendo :-D

El proyecto Gacela, en el que llevo trabajando cerca de dos años y medio, está formado a su vez por tres subproyectos:

  • Gacela, la definición del lenguaje y el compilador / intérprete, que funciona localmente en el ordenador.
  • Lisp2js, el compilador de Lisp a Javascript que permite traducir programas Gacela y ejecutarlos en una página web.
  • Gacela on Wheels, un entorno web para el desarrollo de juegos con Gacela.

Estos últimos meses estoy más centrado en Lisp2js y en la posibilidad de ejecutar en el navegador programas escritos con Gacela. La idea es poder incluir en una página web un código en Javascript del estilo file2js('mi_juego.lisp'); iniciar_juego(); que traduzca el código en Lisp a código en Javascript, lo incruste en la página y lo ejecute. Pero me encontré con una dificultad que no había previsto y que me sorprendió, y es que Javascript por motivos de seguridad no tiene funciones para trabajar con ficheros.

A continuación explicaré de qué forma podemos cargar ficheros con Javascript y de paso explicaré cómo Gacela consigue añadir código Javascript a una página y ejecutarlo. Para otro día queda el funcionamiento interno de Lisp2js.

La forma de acceder a un fichero con Javascript (siempre estoy hablando de un fichero que se encuentre en el mismo dominio) es usando el elemento iframe. Hay otras formas, como usando Ajax, pero eso requiere usar algo de PHP en el servidor y la idea es que el invento pueda funcionar localmente sin necesidad de servidor web.

El código que hará funcionar todo es el siguiente:

<html>
    <head>
        <script type="text/javascript" src="lisp2js.js"></script>
        <script id="head_js" type="text/javascript"></script>
        <script type="text/javascript">
            function cargar (fichero, func) {
                var el = document.getElementById('mi_iframe');
                if (el == null) {
                    var el = document.createElement("iframe");
                    el.setAttribute('id', 'mi_iframe');
                    el.onload = function() { ejecutar_mi_codigo(func); }
                    el.style.display='none';
                    document.body.appendChild(el);
                }
                el.setAttribute('src', fichero);
            }

            function ejecutar_mi_codigo (func) {
                var el = document.getElementById('mi_iframe');
                var lisp_code = el.contentWindow.document.body.textContent || el.contentWindow.document.body.innerText;
                var js_code = string2js(lisp_code);
                document.getElementById('head_js').text = js_code;
                func();
            }
        </script>
    </head>

    <body onLoad="cargar('programa.lisp', function() { init(); });">
    </body>
</html>

Se usan dos funciones, una para crear el iframe e indicarle el fichero que debe abrir y otra para ejecutar el código que estamos cargando. Se hace así porque iframe funciona de forma asíncrona, por lo que le decimos lo que debe hacer cuando tenga todo el contenido usando la propiedad onload.

La primera función, cargar, se ocupa de crear el iframe, pero si ya existe se limita a indicarle el fichero a cargar. Aunque en este ejemplo no tiene mucho sentido, sí que lo tiene si tuvieramos que cargar varios ficheros.

La segunda función, ejecutar_mi_codigo, se lanza cuando el iframe acaba de cargar el fichero. Usando las propiedas textContent o innerText (esto depende del navegador) recogemos el código Lisp, lo traducimos a Javascript y lo insertamos en su correspondiente sección en la cabecera. Finalmente iniciamos la ejecución del código.

Así, aunque de forma más transparante, Gacela es capaz de cargar código Lisp en una página web y ejecutarlo. Es decir, que el mismo código se ejecuta de la misma forma localmente en un ordenador o de forma remota a través del navegador, que es lo que se pretende.