Índice de entradas del conversor
Otra vuelta atrás: IR con tokens
Como ha ocurrido otras veces durante el desarrollo del proyecto, cada vez que empiezo a generar código válido aparece un problema no contemplado y me obliga a retroceder un paso más. En este caso ha sido un tema de espacios excesivos.
No sirve de nada generar código como:
LET A ( C + D ) = G ( 3 * 4 )
en lugar de:
LET A(C+D)=G(3*4)
En una máquina con recursos limitados, cada carácter cuenta. Podría parecer sencillo “eliminar espacios”, pero eso llevaría a errores como convertir:
IF A AND B THEN C = 3
en algo no válido como:
IF AANDB THEN C=3
Para usar únicamente los espacios imprescindibles es necesario identificar correctamente palabras reservadas, variables, operadores y separadores. Eso implica que los tokens deben estar disponibles en la fase correcta: el generador. Por simplificar el IR inicial, no los incluí… y ahora solo quedaban dos opciones: volver a tokenizar en el generador (lo cual es absurdo) o generar el IR conservando los tokens.
En esta entrada documento otro de los cambios arquitectónicos realizados en ZX2SB. Tras decidir no usar un AST completo por su complejidad, pasé a un IR lineal simple. Ahora el paso lógico es evolucionar hacia un IR lineal basado en tokens semánticos.
Aunque puede parecer otra “vuelta atrás”, en realidad es una decisión motivada por la naturaleza del lenguaje BASIC, por la experiencia práctica adquirida durante el desarrollo del transpilador y, sobre todo, por la necesidad de generar código SuperBASIC limpio, editable y sin reconstrucciones artificiales.
Índice
- ¿Qué es el IR en ZX2SB?
- El problema del AST completo en BASIC
- La decisión: IR lineal con tokens semánticos
- Tokens canónicos y eliminación de ambigüedad
- Espacios, formato y generación correcta
- Ventajas prácticas del nuevo IR
- Conclusión
¿Qué es el IR en ZX2SB?
En lenguajes más complejos y de estructura libre como C o Pascal, el AST es imprescindible. BASIC, en cambio, es un lenguaje más sencillo y organizado por líneas, lo que permite simplificar su representación interna.
En ZX2SB, el IR (Intermediate Representation) es la forma intermedia generada tras el análisis sintáctico y semántico del ZX BASIC original, y sirve como base para:
- Renumeración de líneas
- Reescritura de saltos
- Optimización estructural
- Generación del código SuperBASIC final
El IR no es solo texto, pero tampoco es ejecutable: describe la intención del programa sin la complejidad de manejar un árbol completo, y además se puede guardar fácilmente como texto lineal.
El problema del AST completo en BASIC
Un AST clásico es ideal para lenguajes donde la semántica está profundamente anidada y la estructura es jerárquica.
BASIC, y en particular ZX BASIC, tiene características distintas:
- Sentencias lineales
- Control de flujo basado en números de línea
- Expresiones relativamente simples
- Poca recursividad sintáctica
Mantener un AST completo obligaba a aplanar el árbol para generar código, volver a decidir espaciado y operadores, y en muchos casos reinferir información que ya se conocía.
“¿Por qué tengo que volver a tokenizar o reinterpretar lo que yo mismo ya había parseado?”
Durante un tiempo decidí continuar con el IR simple porque era “suficiente”, pero al final la realidad es que los errores no se mantienen: se refactorizan.
La decisión: IR lineal con tokens semánticos
La solución adoptada es intermedia entre un AST completo y un IR puramente textual:
Un IR lineal, basado en tokens tipados y semánticamente anotados.
Cada sentencia BASIC se representa mediante:
- Un tipo de sentencia (IF, LET, FOR, PRINT…)
- Campos estructurales explícitos
- Expresiones como listas ordenadas de tokens
No existen árboles internos complejos ni se guardan nombres textuales de sentencias, sino identificadores internos, lo que simplifica enormemente el trabajo posterior.
Tokens canónicos y eliminación de ambigüedad
Uno de los problemas recurrentes era usar nombres distintos para el mismo token en
distintos módulos. Un simple Par_Ape frente a ParenIzq puede provocar
horas de depuración absurda.
La solución fue eliminar completamente:
- Nombres textuales de tokens
- Strings mágicos entre módulos
- Dependencias implícitas entre fases
En su lugar se introdujeron TokenID canónicos:
- Identificadores numéricos estables
- Definidos en un único enum
- Usados por lexer, parser, IR y generador
Ahora todo el sistema habla exactamente el mismo idioma.
Espacios, formato y generación correcta
El nuevo IR resolvió definitivamente el problema del espaciado. La idea clave es simple:
“No eliminamos espacios. Decidimos cuándo ponerlos.”
Al conservar operadores, paréntesis y separadores como tokens reales, el generador puede emitir código compacto o legible, sin ambigüedades y sin depender de parches, y evitando usar expresiones regulares, no se soportan en SuperBASIC y el objetivo final es portar el código de ZX2SB a SuperBASIC.
Ventajas prácticas del nuevo IR
- No se recompone información ya conocida
- No se retokeniza texto generado
- El IR es imprimible y depurable
- El código SuperBASIC generado es editable
- El sistema escala sin hackear fases anteriores
“El diseño deja de luchar contra el lenguaje y empieza a trabajar con él.”
Frase muy bonita que solo quiere decir que me auto animo a seguir adelante, a pesar de las peleas que tengo contínuamente con el código, es mi primer "compilado" completo y se nota.
Conclusión
Abandonar un AST completo no significa perder rigor si el IR está bien diseñado. En ZX2SB ha supuesto mayor coherencia, menos fases ficticias y un generador más simple y fiable.
Como suele ocurrir en ingeniería de lenguajes, la solución elegante no fue añadir más estructura, sino conservar solo la estructura que importa.
ZX2SB Project (algún día será un proyecto serio; de momento es una fuente constante de problemas… y un entretenimiento mental bastante divertido)