viernes, 2 de mayo de 2008

Creando un lenguaje funcional: XSymbol

El otro día estuve aprendiendo Lisp para realizar las prácticas de la asignatura Inteligencia Artificial de la carrera. Se trataba de mi segunda toma de contacto con un lenguaje funcional (la primera fue Caml). Mientras leía documentación sobre el lenguaje, me di cuenta de que era extremadamente sencillo realizar un intérprete para un lenguaje tipo Lisp, por una razón principalmente: los operadores prefijos. En Lisp, la operación suma por ejemplo, que en otros lenguajes de programación se escribiría a + b, se escribe + a b. Es decir, los operadores siguen la misma estructura que las funciones. Esto elimina la ambigüedad en la evaluación de expresiones, ya que el orden en que se evalúan las expresiones en una operación sólo puede ser de una determinada forma. Esto nos libra de la necesidad de utilizar los paréntesis para agrupar expresiones.

El caso es que iba leyéndome el manual de Lisp en el tren de camino a casa, y cuando llegué, abrí un fichero de texto y me puse a definir la sintaxis de este lenguaje. Quería que fuera claro, que tuviese el mínimo número de reglas y símbolos posible, que éstos fueran fácilmente reconocibles por familiaridad con otros lenguajes, y que fuera rápido a pesar de que el intérprete ejecutase directamente el código fuente (sin pasar a bytecode previamente).

Bien, el tema de la claridad implicaba hacer una serie de cambios sobre la sintaxis de Lisp. Lo de tener todas las expresiones encerradas entre paréntesis no me acaba de convencer, sobre todo porque es perfectamente posible hacer el analizador prescindiendo de ellos. Eliminándolos me quedaría un código más claro y además el intérprete tendría que leer menos cantidad de símbolos, optimizando la ejecución.

Lo de mantener las estructuras y símbolos del lenguaje en el mínimo posible facilita el aprendizaje del mismo. Básicamente un programa se agrupa en funciones. Existen los bloques IF... ELSE. Hay operadores aritméticos y lógicos estándar (cuyos símbolos se han tomado la mayoría de C), números decimales, y cadenas de texto (encerradas entre comillas como en C). No existen variables, si no que lo valores los obtenemos del retorno de las funciones a las que llamemos. Los identificadores sirven para refererirse a las funciones y al parámetro de las mismas (las funciones tienen un sólo parámetro, para pasarles varios valores podemos mandarlas una lista). Todas las expresiones devuelven un valor (un número devuelve el propio número, un string el string, las operaciones aritméticas el resultado de la operacion sobre los valores indicados, los operadores lógicos "1" si se cumple la expresión y "0" si no, los bloques IF... ELSE y las funciones el resultado de la última expresión ejecutada en ellas).

Respecto al tema de la velocidad de ejecución, gracias a los operadores prefijos se simplifica muchísimo el trabajo del evaluador de expresiones. También he hecho el lenguaje puramente simbólico. Los identificadores están limitados a definir nombres de funciones y de su parámetro. Muchos operadores son simbólicos en la mayoría de lenguajes, pero aquí otras operaciones, como la definición de una función o los bloques IF... ELSE son también símbolos (una función se define con @, el bloque IF con ?, y el ELSE con :). De esta forma, se reduce el número de caracteres a leer por el analizador.

Necesitaba un nombre para el lenguaje. Debido a su naturaleza simbólica, pensé en llamarlo Symbol, pero mi amigo Ferminho me convenció de que era poco google-friendly, y sugirió el nombre XSymbol. La idea me pareció genial, sobre todo porque en los programas de prueba que he hecho, las variables dummy de las funciones se llamaban x.

En dos días, tenía el intérprete corriendo, a excepción del soporte de listas, que acabo de añadir hace un rato (y me ha llevado apenas 5 minutos :)). Ahora me queda como primera tarea pendiente, realizar una biblioteca de funciones que permita que el lenguaje sea útil para algo (por ahora solo tiene la función print), y mejorar la gestión de los mensajes de error. Pero bueno, por ahora, ya puede ejecutar cosas como ésta:

@main x
print "Hola desde XSymbol!"
? <= 6 5 print "El primer valor es menor o igual al segundo" : print "El primer valor es mayor que el segundo"; print "Vamos a imprimir una lista:" print (-56 "Hola" (36 28) 48.7);

Como se ve, el símbolo "@" indica que vamos a definir una función. Se llama "main" (es el punto de entrada al programa, como en C), y como no vamos a utilizar su parámetro, lo hemos llamado "x" (ya he dicho que esto es una simple convención). Tiene una serie de llamadas a "print" que escriben distintos tipos de valores en pantalla, y un bloque de código condicional. La expresión comprobada es "<= 6 5", que devuelve 1 si el primer valor es menor o igual al segundo, y 0 en caso contrario. Como 6 es mayor que 5, devuelve 0 y se ejecuta la parte del ELSE, que viene a continuación del ":".

El ";" se utiliza para indicar el fin de una secuencia de expresiones, porque las funciones y los bloques IF... ELSE ejecutan un número variable de éstas. El IF se ejecuta hasta que encuentra su símbolo ":" correspondiente, que marcaría el comienzo del bloque ELSE. El ELSE se ejecuta hasta el ";" que marca su final. El último ";" marca el final de la función "main".

El resultado de la ejecución del programa es:

Hola desde XSymbol!
El primer valor es mayor que el segundo
Vamos a imprimir una lista:
( -56.000000000000000 "Hola" ( 36.000000000000000 28.000000000000000 ) 48.700000000000003 )

1 comentario:

Ferminho dijo...

¡Salgo en tu blog! weeee xD

¡Ese XSymbol!