El generador en ZX2SB: diseño, decisiones y límites conscientes
En el proyecto ZX2SB, el generador es el componente encargado de transformar la representación intermedia de un programa ZX BASIC en código SuperBASIC para el Sinclair QL. Aunque pueda parecer que su función consiste únicamente en “emitir líneas de código”, en la práctica es uno de los elementos más importantes y delicados de todo el sistema.
Este artículo describe qué hace realmente el generador, cómo está diseñado, qué problemas resuelve y, sobre todo, qué problemas decide conscientemente no resolver. Esa última decisión resulta clave para entender por qué ZX2SB va más allá de un simple transpilador mecánico .
Índice
- 1. ¿Qué es el generador?
- 2. Generación estructurada de código
- 3. Numeración, control del flujo y bloques
- 4. Instrucciones no portables
- 5. Convención FN_ y desacoplo
- 6. Inicialización e inclusión de funciones FN_
- 7. Qué se obtiene al final
- 8. El siguiente paso inmediato
- 9. Más allá del generador
1. ¿Qué es el generador?
Conviene recordar una distinción fundamental: un compilador genera un ejecutable, mientras que un transpilador genera código fuente en otro lenguaje, que puede ejecutarse directamente o servir como entrada para otro compilador.
En ZX2SB, el generador actúa como la última fase del transpilador. Recibe una representación intermedia (IR) del programa ZX BASIC, ya validada por el lexer, el parser y el análisis semántico, y produce un programa SuperBASIC funcional y estructurado.
Desde el principio se descartó una traducción línea a línea. El generador trabaja con conocimiento de contexto: estructura, bloques, flujo de control y decisiones previas. No se limita a copiar instrucciones; las reformula.
NOTA:
Qué no es ZX2SB
ZX2SB no es un emulador del ZX Spectrum ni una herramienta para ejecutar directamente programas ZX en el Sinclair QL.
Tampoco es un traductor mecánico línea a línea que intente forzar equivalencias entre dos BASIC muy distintos ignorando sus diferencias internas.
ZX2SB es un transpilador consciente: transforma programas ZX BASIC en código SuperBASIC legible, estructurado y extensible, dejando explícitas aquellas partes cuyo comportamiento no puede trasladarse correctamente sin una capa de ejecución adicional.
Su objetivo no es “que funcione como sea”, sino producir código que pueda entenderse, mantenerse y evolucionar en el entorno del QL.
En esta fase, el objetivo se cumple tal cual, en fases posteriores se puede ampliar para incluir un entorno de simulación del ZXBasic y que el programa se comporte como en un Spectrum en Basic.
2. Generación estructurada de código
Una de las primeras decisiones fue producir código SuperBASIC claramente estructurado, incluso cuando el código ZX original no lo está explícitamente.
En ZX BASIC es habitual encontrar varias sentencias en la misma línea, separadas por dos puntos (:). Por ejemplo:
PRINT A: LET B=B+1 : LET col=col+1El generador transforma este tipo de líneas en una estructura explícita y mas legible:
PRINT A LET B=B+1 LET col=col+1
Cada sentencia pasa a ocupar su propia línea. El caso más significativo es el IF.
En ZX BASIC no existen ELSE ni END IF,
y un IF puede contener múltiples sentencias en una sola línea.
El generador transforma esta construcción en un bloque explícito:
- una línea con la condición IF,
- una línea por cada sentencia interna,
- un
END IFfinal.
IF A>10: PRINT A: LET B=B+1genera:
IF A>10 THEN PRINT A LET B=B+1 END IF
Esta decisión no se tomó por comodidad, sino para garantizar:
- Claridad semántica
- Coherencia estructural
- Facilidad de mantenimiento
- Posibilidad de posteriores optimizaciones.
3. Numeración, control del flujo y bloques
Para poder descomponer líneas múltiples en sentencias independientes, el generador debe modificar la numeración de las líneas.
La estrategia empleada consiste en tomar el número de línea origianl del ZX Basic y añadir un contador de dos dígitos. Así, una línea con varias sentencias se expande de forma determinista.
Por ejemplo, a partir del código ZX:
150 LET A=3 : LET C=5 : GOTO 532
El generador produce:
15000 LET A=3 15001 LET C=5 15002 GOTO 53200
Estos números no siempre son válidos para el QL (en ZXBasic los números van del 1 al 9999, en SuperBASIC del 1 al 32525), pero esa no es responsabilidad del generador. El renumerador posterior se encargará de normalizarlos.
El generador mantiene contexto suficiente para abrir y cerrar bloques, producir saltos coherentes y dejar el programa en un estado funcional, aunque todavía no directamente cargable.
El generador sí garantiza que:
- los saltos son coherentes
- los bloques se abren y cierran correctamente
- el programa resultante es estructuralmente consistente.
Para el generador la numeración es solo una herramienta provisional. Se usa como andamio mientras se construye el programa final.
4. Instrucciones no portables
ZX BASIC y SuperBASIC difieren profundamente en áreas clave como:
- Salida de texto (PRINT)
- Gestión del cursor
- Colores y atributos
- Caracteres gráficos
- Sistema de coordenadas y gráficos
Una traducción directa de estas instrucciones produciría programas que “funcionan”, pero cuyo comportamiento se aleja mucho del ZX Spectrum original.
El generador asume explícitamente que estas instrucciones no son directamente portables y evita resolverlas de forma incorrecta.
5. Convención FN_ y desacoplo
Para manejar instrucciones sin equivalencia directa,
el generador adopta una convención clara y sistemática:
emitir llamadas a funciones o procedimientos con prefijo FN_.
El generador:
- No implementa su comportamiento
- Se limita a transformar el código
- La semántica se decide posteriormente
Por ejemplo, en lugar de generar directamente:
BIN 11001101
como el comando BIN no existe en SuperBASIC,
el generador produce:
FN_BIN(11001101)
De este modo, el generador queda completamente desacoplado de la implementación concreta de estas funciones, manteniendo el sistema limpio y extensible.
6. Inicialización e inclusión de funciones FN_
Durante la generación, el sistema mantiene una lista de todas las funciones FN_ que ha utilizado durante el proceso de conversión. Al finalizar la generación, solo se incluyen aquellas funciones que han sido realmente necesarias, evitando así añadir código innecesario al programa final.
Además, el programa resultante se completa con varias secciones fijas, que quedan organizadas de la siguiente manera:
- Un bloque de inicio del sistema, que realiza una llamada a la función FN_INIT.
- El código convertido desde ZX BASIC a SuperBASIC.
- Si es necesario, una línea STOP que marca explícitamente el final del programa ZX.
- Las funciones FN_ que han sido detectadas como necesarias durante la generación.
- La implementación de
FN_INIT
La función FN_INIT se encarga de preparar el entorno de ejecución en el QL: establece el modo de pantalla, crea una ventana de 32×24 caracteres y aplica una configuración básica que permite que el programa se ejecute de forma coherente y predecible.
El STOP final del programa ZX BASIC se añade de forma explícita porque, en BASIC un slto a una línea fuera del rango del programa es válida y provoca implícitamente la finalización del programa. Si se detecta que una línea del programa realzia un salto fuera del rango permitido, se añade esta línea de STOP, cambiando los saltos que existan para que apunten a esta nueva línea. De esta manera:
- El programa generado se vuelve más determinista y sencillo de manejar
- Se evitan posibles problemas derivados de que un salto coincida con alguna de las líneas añadidas posteriormente para las funciones FN
7. Qué se obtiene al final
El resultado del generador es un programa SuperBASIC que:
- Es sintácticamente correcto
- Preserva la estructura lógica del ZX BASIC original
- No fuerza equivalencias incorrectas
- Explicita claramente las dependencias externas
Este programa está listo para ser renumerado, indentado y ejecutado dentro de un entorno controlado.
8. El siguiente paso inmediato
El siguiente paso tras el generador es el renumerador, que ajusta definitivamente la numeración y produce un programa cargable y ejecutable en el QL. Con este proceso de renumerción se cierra la primera fase del proyecto.
9. Más allá del generador
El verdadero reto futuro no está en el generador,
sino en lo que rodea a las llamadas FN_....
Para que los programas ZX se comporten de forma reconocible, que parezca que estamos en un gomas real, es necesario proporcionar una capa de ejecución que reproduzca el entorno del Spectrum: pantalla, cursor, colores, caracteres y primitivas gráficas.
Ese componente convierte a ZX2SB en algo más que un transpilador, pero merece un análisis independiente y se abordará en otro momento.
Continuará con el renumerador…