viernes, 17 de abril de 2026

ZX2SB: El generador de código SuperBASIC

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?

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: 

La “representación intermedia” es una forma neutral de describir el programa una vez entendido sintácticamente, pero antes de decidir cómo escribirlo en el lenguaje de destino. Es como pasar de una frase hablada a su significado real, antes de volver a escribirla en otro idioma.
 

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+1
El 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 IF final.
De esta manera la senténcia
IF A>10: PRINT A: LET B=B+1
genera:
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. 

Muchos programas antiguos funcionan, pero son difíciles de leer incluso para humanos. Aquí el objetivo no es solo “que funcione”, sino que el resultado sea un programa un poco más comprensible y modificable.
 


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:

  1. Un bloque de inicio del sistema, que realiza una llamada a la función FN_INIT.
  2. El código convertido desde ZX BASIC a SuperBASIC.
  3. Si es necesario, una línea STOP que marca explícitamente el final del programa ZX.
  4. Las funciones FN_ que han sido detectadas como necesarias durante la generación.
  5. 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…

No hay comentarios:

Publicar un comentario