viernes, 10 de febrero de 2017

Rehaciendo PSeInt (parte 1): motivación

Hace no mucho comenté que quería empezar a reescribir el código relacionado al parseo interno que hace el intérprete dentro de PSeInt. Todavía falta mucho para que el nuevo código llegue a "producción", pero ya hay varias cosas hechas y por ahora el cambio tiene buen color.

Antes de empezar, aclaro que en este artículo, cuando me refiero a "el intérprete", no me refiero a todo PSeInt, sino al módulo que se encarga exclusivamente de analizar un pseudocódigo de entrada y generar la ejecución (aquí más detalles de los módulos y sus relaciones).

Empecemos entonces con los problemas del código que vengo usando: 1) empezó hace 13+ años, y 2) hay funcionalidades repetidas por todas partes. El punto 1 implica que empezó cuando yo solo tenía en mente y por objetivo un 5% de lo que hace pseint hoy. Implica también que como programador estaba bastante más verde y cometía muchísimos más errores. Implica también cero experiencia con el problema, o con similares. Implica etc, etc, etc. Y entre otras cosas, el verdadero intérprete no está hecho para ser reutilizado como biblioteca, sino como programa completo a conectar por tuberías. Eso me lleva al punto 2, ya que mucha funcionalidad interna no puede ser correctamente reutilizada y se termina replicando en otras partes (en el autocompletado y el marcado de errores de la gui, en el editor de diagramas, en la traducción a otros lenguajes, etc).

 Flujo (simplificado) de la información. Verificar Sintaxis, ejecutar, exportar y el visor de diagramas,
todos hacen un parseo muy similar del código normalizado, pero con fines muy diferentes.

Por ejemplo, al momento de generar un diagrama de flujo, ejecuto a medias al intérprete, para que pase por la verificación de sintaxis y para que genere una versión "normalizada" del algoritmo, pero que se detenga justo antes de ejecutar. Si la verificación no detectó errores, la GUI principal toma la versión normalizada y se la pasa como entrada al graficador. Entonces, el graficador tiene que hacer un nuevo análisis, pero mucho más simple, ya que tiene garantías de recibir un código normalizado y sin errores. Peeero, un problema es que la normalización implica pérdida de información, mayormente formato, pero a veces más importante. Esto se nota, por dar uno de tantos ejemplos, al pasar de un pseudocódigo al diagrama de flujo, y luego volver al pseudocódigo, cuando instrucciones que no se modificaron cambian a sus formas canónicas, u operadores dentro de las mismas a sus versiones no coloquiales o alternativas.

Entonces ¿qué quiero? Quiero un parser que funcione como biblioteca y cuya interfaz a nivel de código sea lo suficientemente simple y flexible como para utilizarlo desde la interfaz para detectar errores y para mejorar el autocompletado, desde el editor de diagramas para entender lo que se escribió en el otro editor, desde el intérprete para ejecutar las instrucciones, desde el módulo que exporta a otros lenguajes para entender lo que tiene que traducir, desde la consola para poder integrar ese intérprete como parte propia y ya no como proceso independiente por tubería, etc.

Los diferentes escenarios de uso de la nueva biblioteca me obligan a hacer un diseño por etapas donde yo pueda desde el programa cliente elegir qué etapas me interesan y combinarlas como necesite. Y además, ya que voy a hacer casi todo otra vez, ahora quiero también evitar la etapa de "normalización" para que las conversiones sean lo más fieles posible. A nivel ingeniería de software, esto me representa un problema complejo e interesante. Ya probé empezar con unos 7 u 8 diseños diferentes para lograr esta separación sin sacrificar mucha eficiencia, y finalmente estoy convergiendo a algo que parece funcionar.

 Etapas del nuevo análisis, reutilizables desde todos los módulos. Algunos como
indentado y autocompletado solo requieren los primeros pasos.

Es casi como rehacer el intérprete desde cero, solo se mantiene la gestión de memoria y (por ahora) la evaluación de expresiones. Iré integrándolo de a poco en los distintos módulos a lo largo de varias releases. A cambio espero obtener una base de código muchísimo más moderna, compacta, flexible, extensible, y dejar de quejarme de mi propio código legacy de una buena vez. Y ya que casi que empiezo de cero, esta vez puedo considerar extras como que las palabras claves para cada instrucción sean configurables, para que puedan definirse a gusto arbitrariamente mediante un perfil (y "arbitrariamente" implica, por ejemplo, en otros idiomas).

En resumen, queda presentado por arriba el problema y los objetivos. En los próximos posts me iré metiendo más en lo técnico y comentando de a poco la arquitectura de esta nueva biblioteca y qué patrones y mecanismos utilizo para lograr la flexibilidad y simpleza que necesito.

No hay comentarios:

Publicar un comentario