Archivos para Abril, 2009

ACL2::Procesador: nueva versión 1.187

Estos últimos días he estado dándole fuerte al teclado y he escrito el nuevo analizador Lisp, basado en el algoritmo del libro “Common Lisp: the Language”, tras descartar dos ramas con un analizador basado en Parse::Eyapp que producía árboles de referencias a listas Perl, y otro que producía árboles AST. Por supuesto, hay algunos cambios y simplificaciones, dado que no intento tener un entorno de ejecución de Lisp, sino simplemente de análisis del código. Por ejemplo, se preserva el espacio en blanco, y los comentarios de líneas (“; hola”) y de bloque (#| hola |#), aunque se pueden limpiar fácilmente. Es curioso saber que los comentarios de bloque en Lisp, a diferencia de los de C++, sí que se anidan.

De hecho, una vez implementado (un algoritmo recursivo descendente LL(1)), me sorprende lo flexible que es Lisp en algunos aspectos. Ya conocía la existencia de las macros de Lisp y en su momento las usé para crearme un pequeño marco de trabajo para definir reglas de un chatbot, en CLIPS, pero no sabía que había algo parecido a nivel léxico. También se conocen como macros, pero se tratan de caracteres especiales que invocan determinadas funciones, generando las estructuras apropiadas. Si uno ve el algoritmo base (sin macros), es curioso ver que sólo podríamos leer átomos (símbolos y números), aunque aún así, se pueden leer números enteros, flotantes, y racionales (fracciones exactas, como 22/9, sin pasar a coma flotante). Los famosos “(” y “)” que producen listas son, de hecho, macros, y también “;” (produce comentarios), “`” y “,” y demás. De todas formas, como el repertorio de carácteres disponibles para macros parece estar ya prefijado por el estándar Common Lisp, se suelen usar las dispatching macros para añadir extensiones, que consisten en secuencias de caracteres de la forma #nX, donde n es un número entero y X es un carácter que identifica la función a invocar..

Por ejemplo, está #|, que sirve para crear los comentarios en bloque, #=, que sirve para construir estructuras de datos en Lisp con múltiples referencias al mismo elemento (incluso referencias cíclicas), #nA (para obtener matrices multidimensionales), #( (para vectores), etc. Hay incluso macros para definir números en bases arbitrarias (incluyendo fracciones: ¿alguien quiere escribir una fracción en base 7?).

Evidentemente, no lo he implementado todo en ACL2::Lisp::Parser. El algoritmo principal está, incluso con los detalles del tratamiento de mayúsculas (el símbolo hola se entiende como HOLA, pero hol\a es HOLa, por ejemplo), pero la lectura de números en bases distintas a la decimal y la mayoría de las macros de # (excepto #|, que es muy común) no está hecha. Dicho módulo trae read_lisp_program (lee programas), read_lisp_token (lee elementos individuales) y try_read_lisp_token (intenta leer e informa de la localización en que se produce el fallo de análisis, si hay uno). Podía haberlo escrito con Parse::RecDescent, pero como vi que al final no me produciría una gran ventaja frente a escribirlo manualmente con el libro de Lisp por delante, lo descarté, para evitar añadirme una dependencia más.

En base a este código, he reescrito todas las partes que dependían de ACL2::AnalisisLisp en alguna medida, incluyendo el código que limpiaba la salida de ACL2, deshaciendo el ajuste de líneas en los párrafos de texto, y no tocando los párrafos que constituían expresiones Lisp. Antes había una expresión regular un tanto críptica que funcionaba en la mayoría de los casos, pero ahora el código de limpieza sabe cuándo algo puede ser código Lisp o no. El resto del código ha sufrido cambios parecidos. El mayor cambio externo es que ahora se incluyen los comentarios originales del código Lisp en la salida XML y se reproduce exactamente el código original, en vez de una versión “limpiada”. Como de costumbre, el cambio al analizador Lisp y la jerarquía de nodos del árbol de sintaxis abstracto ha sido un cambio más interno que externo, pero de todos modos será útil para seguir adelante y añadir nuevas funcionalidades.

Dejar un comentario

Versión 1.186 de ACL2::Procesador

Acabo de marcar en SVN y subir a la forja de Rediris la versión 1.186 de ACL2::Procesador. Aceptaron el artículo que enviamos mis antiguos directores de PFC y yo a ACL2 2009, así que es hora de darle un lavado de cara en algunos aspectos, aprovechando que estoy en vacaciones de Semana Santa :-D .

Las principales novedades que trae esta versión son básicamente:

  • Organización de órdenes en categorías: muchas órdenes repetían el mismo código una y otra vez. Este era sobre todo el caso de las órdenes que no producen prácticamente ninguna salida específica, como DEFPKG, DEFABBREV o DEFMACRO. Ahora lo que se hace es agrupar estas órdenes en categorías, para que compartan el mismo código. Con eso podré añadir más rápidamente órdenes de ACL2 que no sean tan usadas. Las órdenes que necesitan código específico se siguen usando directamente (como DEFUN, DEFTHM o ENCAPSULATE). De hecho, el orden actual para una orden X cualquiera es:
  1. Se intenta cargar el módulo ACL2::Orden::X, y analizar la orden mediante una instancia la clase contenida en dicho módulo.
  2. Si no funciona, se prueban en un orden prefijado todas las categorías, y se usa la primera que funcione. Actualmente, hay dos: DefinitionCategory (para las órdenes DEFXXX que no necesitan código especial) y FallbackCategory. La segunda categoría lo acepta todo, e imprime la salida de ACL2 como una lista de párrafos y S-expresiones, junto con el resumen debidamente procesado, si lo tenía. Su utilidad es para no fallar estrepitosamente ante órdenes de ACL2 aún no implementadas o código Lisp arbitrario (ACL2 es un entorno Lisp, al fin y al cabo). De todas formas, ACL2::Procesador sigue dando fallos fatales cuando no entiende algo en la salida de una orden para la que tiene código específico (de lo contrario, las pruebas de regresión serían inútiles).
  • Revisión del algoritmo de análisis de un fichero .lisp: ahora el grafo de dependencias es más sencillo, descartando los antiguos nodos que incluían a los ficheros .acl2 que servían para establecer el estado inicial de ACL2 antes de certificar un libro. Esto se debe a que ahora, cuando se procesa un fichero X.lisp y se encuentra un fichero X.acl2 o cert.acl2 en el mismo directorio, se usan los contenidos del primero de esos ficheros en vez del propio fichero X.lisp. Es ACL2 el que lee X.lisp y lo interpreta. De todas formas, ACL2 de hecho vuelve a volcar las fórmulas de X.lisp en la salida del evento CERTIFY-BOOK. Este nuevo esquema es más fiable de entrada, y además ahora le añadimos un prólogo y un epílogo a cada fichero .lisp procesado para asegurarnos de que funcione en más implementaciones de Lisp.

Esta versión ha sido probada con ACL2 2.9.4 bajo CLisp 2.44.1 (paquete Ubuntu 1:2.44.1-4ubuntu2), ACL2 3.0.1 bajo CMUCL (paquete Debian cmucl, versión 19a-release-20040728-9), ACL2 3.1 bajo CLisp, ACL2 3.2.1 bajo GCL (paquete Debian gcl 2.6.7-45), ACL2 3.3 bajo GCL, y ACL2 3.4 bajo GCL. Con eso tengo cubiertas varias implementaciones de Lisp y varias versiones de ACL2.

Para la siguiente versión, lo que quiero hacer es cambiar el “analizador” Lisp que tengo (me da vergüenza llamarlo así :-D ) por uno que implemente el algoritmo de la función read, tal y como viene en el libro  de “Common Lisp, the Language”, particularmente en las secciones 22.1 y 22.2. Tenía hecho una gramática LALR(1) tentativa en Parse::Eyapp (muy buen módulo para los fans de bison/flex o ANTLR) en una de mis ramas, pero va a ser que no: la sintaxis es demasiado compleja como para hacerla fácilmente :-/. Por ejemplo, ahora mismo el código no tiene forma de saber si algún trozo de código Lisp es una lista, un átomo, una cadena, y no entiende de comentarios de bloque. Puestos a parchear el código que hay, prefiero hacer algo en condiciones, que además sea capaz de reconocer correctamente programas Lisp no válidos tal y como lo hace ACL2 (o casi). Seguramente revise un poco el algoritmo, ya que a diferencia del algoritmo original, me interesa poder recuperar el código fuente original al 100%.

Dejar un comentario