lunes, 16 de marzo de 2026

ZB2SB. Paso 3.2. Lexer. Planteamiento

Transpilador ZX BASIC — Paso 1: Analizador léxico (tokens, diagrama, seudo‑código y esqueleto VB/SuperBasic)

Introducción

En una entrada previa presentamos la gramática formal del ZX BASIC del Spectrum 48K. Esa gramática define la estructura sintáctica, pero antes de poder parsear necesitamos convertir el texto en una secuencia estable y tipada de unidades mínimas: los tokens. Esta fase es el analizador léxico o lexer.

El lexer recorre el fichero carácter a carácter, identifica patrones significativos (palabras clave, variables, números, cadenas, operadores…), y los traduce en tokens autoexplicativos que el parser podrá consumir de forma determinista. 

Usaremos la estuctura de datos definida en la entrada anterior para lamcenar los Tokens de la línea y pasarlos a la siguiente fase. 


1. Analizador léxico

  • Convertir texto bruto en una secuencia de tokens bien definidos.
  • Detectar categorías: identificadores, literales, operadores, palabras clave, etc.
  • Emitir la posición exacta (línea y columna) para diagnósticos posteriores.
  • Detectar errores léxicos, continuar cuando sea posible o parar con un mensaje de error adecuado.

Todo esto debe hacerse de manera rápida y predecible, especialmente en nuestro objetivo final: ejecutarlo en un Sinclair QL real, cuyas restricciones de RAM y CPU hacen inviable tokenizar todo el fichero en memoria. Por tanto el lexer trabaja por líneas y emite los tokens de la línea directamente en una estructura compacta optimizada.


2. ¿Qué es un token?

Un token es una unidad mínima y significativa. No representa un carácter, sino un símbolo lógico del lenguaje. Tiene varios atributos:

  • Tipo: categoría (Identifier, Keyword, LineNumber, IntegerLiteral…)
  • Lexema: texto exacto que lo originó
  • Posición: línea y columna
  • Longitud: caracteres consumidos

El parser solo opera sobre tokens, nunca sobre caracteres.


3. Tokens que vamos a generar

3.1 Identificadores y variables

  • Identifier — A, B2, COUNTER
  • StringVar — A$, NAME$

3.2 Literales

  • IntegerLiteral — enteros
  • NumberLiteral — entero, decimal o exponencial
  • StringLiteral — texto entre comillas

3.3 LineNumber

  • Solo válido en columna 1
  • 1 a 4 dígitos, sin 0 inicial

3.4 Operadores

  • +, -, *, /, ^
  • =, <>, <, >, <=, >=
  • AND, OR, NOT

3.5 Separadores

  • ( ) , ; :

3.6 Palabras clave

  • LET, PRINT, INPUT, IF, THEN, FOR, …
  • PLOT, DRAW, CIRCLE, POINT, …
  • READ, DATA, RESTORE, DIM, CLS…
  • REM

3.7 Comentarios

  • Comment — tras REM hasta fin de línea

3.8 Control

  • EOL — fin de línea
  • EOFToken — fin de fichero (si se procesa completo; no usado en QL línea a línea)

4. Diagrama de bloques del analizador léxico

Diagrama de flujo del generador de Tokens
Diagrama de flujo del generador de tokens

4.3 Notas de implementación

  • Normalizar CRLF/CR a \n.
  • Keywords: case‑insensitive, se normalizan a MAYÚSCULAS.
  • LineNumber solo en columna 1.
  • REM: dos variantes — REM vacío o REM␠texto.
  • Prioridad de operadores dobles (<=, <>, >=).
  • Diagnósticos detallados y recuperación local.

5. Lexer en seudo‑código

REM ========================================================
REM  LEXER por líneas — Emite en TokenData$()
REM  Entrada: texto de la línea (linea$), número de línea (numLinea)
REM  Salida : tokens añadidos a TokenData$(), rematados con EOL
REM ========================================================

PROCEDIMIENTO Lexer.Preparar(tamInicial)
    Token.Iniciar()
    Token.Crear(tamInicial)        REM p.ej. 500
    PalabrasClave.Preparar()
FIN

FUNCIÓN Lexer.LexLine(linea$, numLinea) → (idxIni, idxFin)
    idxIni ← TokenCount + 1
    i ← 1
    L ← LEN(linea$)

    MIENTRAS i ≤ L
        REM 1) Espacios
        MIENTRAS i ≤ L Y EsEspacio(MID$(linea$, i, 1))
            i ← i + 1
        FIN MIENTRAS
        SI i > L ENTONCES ROMPER FIN SI

        c$ ← MID$(linea$, i, 1)

        REM 2) Comentario REM (resto de línea)
        SI ComienzaREM(linea$, i) ENTONCES
            lex$ ← MID$(linea$, i)
            Token.Emit(T_REM, lex$, numLinea, i)
            i ← L + 1
            SALIR MIENTRAS
        FIN SI

        REM 3) Cadena
        SI c$ = "\"" ENTONCES
            col ← i
            i ← i + 1
            ini ← i
            MIENTRAS i ≤ L Y MID$(linea$, i, 1) <> "\""
                i ← i + 1
            FIN MIENTRAS
            lex$ ← MID$(linea$, ini, i - ini)     REM sin comillas
            Token.Emit(T_STRING, lex$, numLinea, col)
            SI i ≤ L ENTONCES i ← i + 1 FIN SI
            CONTINUAR MIENTRAS
        FIN SI

        REM 4) Número
        SI EsDigito(c$) ENTONCES
            col ← i
            ini ← i
            MIENTRAS i ≤ L Y EsDigito(MID$(linea$, i, 1))
                i ← i + 1
            FIN MIENTRAS
            esReal ← FALSO
            SI i ≤ L Y MID$(linea$, i, 1) = "." ENTONCES
                esReal ← VERDADERO
                i ← i + 1
                MIENTRAS i ≤ L Y EsDigito(MID$(linea$, i, 1))
                    i ← i + 1
                FIN MIENTRAS
            FIN SI
            lex$ ← MID$(linea$, ini, i - ini)
            SI esReal ENTONCES
                Token.Emit(T_FLOAT, lex$, numLinea, col)
            SINO
                Token.Emit(T_INT,   lex$, numLinea, col)
            FIN SI
            CONTINUAR MIENTRAS
        FIN SI

        REM 5) Identificador / Palabra clave
        SI EsLetra(c$) O c$ = "_" ENTONCES
            col ← i
            ini ← i
            MIENTRAS i ≤ L Y (EsLetraNum(MID$(linea$, i, 1)) O MID$(linea$, i, 1) = "_")
                i ← i + 1
            FIN MIENTRAS
            lex$ ← MID$(linea$, ini, i - ini)
            tipo ← PalabrasClave.Tipo(UPPER$(lex$))
            SI tipo = 0 ENTONCES
                Token.Emit(T_ID, lex$, numLinea, col)
            SINO
                Token.Emit(tipo,  lex$, numLinea, col)
            FIN SI
            CONTINUAR MIENTRAS
        FIN SI

        REM 6) Operadores dobles
        SI i < L ENTONCES
            par$ ← MID$(linea$, i, 2)
            SI par$ = "<=" ENTONCES Token.Emit(T_LE, par$, numLinea, i) : i ← i + 2 : CONTINUAR MIENTRAS FIN SI
            SI par$ = ">=" ENTONCES Token.Emit(T_GE, par$, numLinea, i) : i ← i + 2 : CONTINUAR MIENTRAS FIN SI
            SI par$ = "<>" ENTONCES Token.Emit(T_NE, par$, numLinea, i) : i ← i + 2 : CONTINUAR MIENTRAS FIN SI
        FIN SI

        REM 7) Operadores simples / separadores
        SELECCIONAR c$
            CASO ":" : Token.Emit(T_COLON,  ":", numLinea, i)
            CASO "," : Token.Emit(T_COMMA,  ",", numLinea, i)
            CASO ";" : Token.Emit(T_SEMI,   ";", numLinea, i)
            CASO "(" : Token.Emit(T_LPAREN, "(", numLinea, i)
            CASO ")" : Token.Emit(T_RPAREN, ")", numLinea, i)
            CASO "+" : Token.Emit(T_PLUS,   "+", numLinea, i)
            CASO "-" : Token.Emit(T_MINUS,  "-", numLinea, i)
            CASO "*" : Token.Emit(T_MUL,    "*", numLinea, i)
            CASO "/" : Token.Emit(T_DIV,    "/", numLinea, i)
            CASO "^" : Token.Emit(T_POW,    "^", numLinea, i)
            CASO "=" : Token.Emit(T_EQ,     "=", numLinea, i)
            CASO "<" : Token.Emit(T_LT,     "<", numLinea, i)
            CASO ">" : Token.Emit(T_GT,     ">", numLinea, i)
            OTRO     : Token.Emit(T_ID, c$, numLinea, i)   REM desconocido, se emite tal cual
        FIN SELECCIONAR
        i ← i + 1
    FIN MIENTRAS

    Token.Emit(T_EOL, "", numLinea, L + 1)
    idxFin ← TokenCount
    RETORNAR (idxIni, idxFin)
FIN FUNCIÓN

6. Estructura de datos: arreglo único y registro empaquetado

CHR$(tipo) & MKI$(linea) & MKI$(columna) & CHR$(LEN(lexema$)) & lexema$ (big‑endian)
REM ========= API mínima de Tokens =========

PROCEDIMIENTO Token.Iniciar()
    Estructura_Iniciar(TokenMax, TokenCount)
FIN

PROCEDIMIENTO Token.Crear(tamInicial)
    Estructura_Crear(TokenMax, TokenCount, tamInicial, tipo.Principal)
    DIM TokenData$(TokenMax)
FIN

PROCEDIMIENTO Token.Ampliar(nuevoTamaño)
    Estructura_AmpliarArray(TokenMax, TokenCount, nuevoTamaño)
    AmpliarArray(TokenData$, TokenMax, nuevoTamaño)
    TokenMax ← nuevoTamaño
FIN

PROCEDIMIENTO Token.Reset()
    SI TokenMax = 0 ENTONCES Token.Crear(50)
    TokenCount ← 0
FIN

PROCEDIMIENTO Token.Emit(tipo, lexema$, linea, columna)
    SI TokenMax = 0 ENTONCES Token.Crear(50)
    SI TokenCount >= TokenMax ENTONCES Token.Ampliar(TokenMax + 50)
    TokenCount ← TokenCount + 1
    TokenData$(TokenCount) ← CHR$(tipo) & MKI$(linea) & MKI$(columna) & CHR$(LEN(lexema$)) & lexema$
FIN

REM ==== Acceso a campos de un registro ====
FUNCIÓN Token.GetTipo(tok$) → tipo
    RETORNAR ASC(MID$(tok$,1,1))
FIN

FUNCIÓN Token.GetLinea(tok$) → lin
    RETORNAR CVI(MID$(tok$,2,2))
FIN

FUNCIÓN Token.GetColumna(tok$) → col
    RETORNAR CVI(MID$(tok$,4,2))
FIN

FUNCIÓN Token.GetLongitudLexema(tok$) → n
    RETORNAR ASC(MID$(tok$,6,1))
FIN

FUNCIÓN Token.GetLexema(tok$) → s$
    n ← ASC(MID$(tok$,6,1))
    RETORNAR MID$(tok$,7,n)
FIN

7. El programa generado en Visual Basic .NET

REM Esqueleto QL de lectura por líneas
OPEN #3, "program.bas"
numLinea ← 0
REPEAT
  L$ = LINE INPUT(#3)
  numLinea ← numLinea + 1
  (i0,i1) ← Lexer.LexLine(L$, numLinea)
  REM ... pasar (i0,i1) al parser ...
UNTIL EOF(#3)
CLOSE #3

El proyecto del transpilador (hasta donde esté desarrollado) está en GitHub: ZX2SB.

Notas rápidas

  • EOL normalizado a \n.
  • Keywords en mayúsculas.
  • LineNumber solo en columna 1.
  • REM: variantes válidas documentadas.
  • Números: entero, fracción y exponente.
  • Verbose: opción para depurar mostrando los tokens en pantalla

domingo, 15 de marzo de 2026

ZB2SB. Paso 3.1. Lexer. Estructura de datos para manejo de Tokens

Transpilador ZX BASIC para Sinclair QL: Optimización del Lexer (Fase 1)

Índice de entradas del conversor



Introducción

Para ejecutar el transpilador ZX BASIC → SuperBasic en un Sinclair QL real debemos optimizar al máximo la memoria y la velocidad. Por ello, el Lexer usará un único arreglo de cadena donde cada elemento es un token en un registro empaquetado (formato binario compacto), en lugar de varios arreglos paralelos. Esto reduce la fragmentación de memoria, simplifica las ampliaciones y acelera copias.

El objetivo de esta entrada es implementar, en seudo‑código, todas las funciones necesarias para manejar la estructura de Tokens (crear, ampliar, resetear, insertar y leer) y añadir funciones de prueba que crean registros y los verifican. Esta entrada es en seudo‑código; el desarrollo real en Visual Basic se publicará en el repositorio del proyecto.


1. Diseño del registro de Token (formato binario)

Formato de registro (TokenRecord):
CHR$(tipo) & MKI$(linea) & MKI$(columna) & CHR$(LEN(lexema$)) & lexema$
  • tipo → byte (0..255).
  • línea → entero sin signo de 16‑bit (2 bytes).
  • columna → entero sin signo de 16‑bit (2 bytes).
  • len(lexema$) → byte (0..255).
  • lexema$ → cadena (longitud variable).

2. Funciones MKI$ y CVI (seudocódigo compatible QL)

Aclaración sobre endianness: Para almacenar números de más de 1 byte se usan dos sistemas diferentes, según el orden en que se guardan en memoria (o en la cadena, en nuestro caso), se denomina MSB al byte más significativo del conjunto de bytes (en un número sería la parte izquierda) y LSB al menos significativo (en un número la parte derecha):

  • Big‑endian (primero el byte más significativo, MSB): el número se mantiene en el orden “natural” de sus bytes, es decir, en binario puro con el MSB antes que el LSB. Es el formato nativo del 68008 (y toda la familia 680x0), y por tanto el que usa el QL. También lo emplearon arquitecturas como SPARC (en muchas implementaciones), PowerPC (en configuraciones clásicas de servidores/Unix) y ciertos sistemas IBM de gran porte. Es el formato que usaremos en nuestros procesos.
  • Little‑endian (primero el byte menos significativo, LSB): el número se almacena con el orden de bytes invertido. Aunque pueda parecer menos intuitivo, favorece algunas operaciones aritméticas con acarreo al comenzar por el LSB y propagar el acarreo hacia los bytes más altos. Es el formato usado en los PC con Intel y también el predeterminado en la mayoría de sistemas ARM y RISC‑V actuales, y fue el usado en los DEC PDP .
REM ==================================================================
REM  MKI$(n) — Devuelve una cadena de 2 bytes a partir de un entero
REM            de 16 bits sin signo, en formato big-endian (MSB, LSB)
REM ==================================================================
FUNCIÓN MKI$(n) → s$
    SI n < 0 ENTONCES ERROR No se soportan negativos
    SI n > 65535 ENTONCES ERROR Número demasiado grande

    hi ← (n \ 256) MOD 256
    lo ← n MOD 256

    s$ ← CHR$(hi) & CHR$(lo)
    RETORNAR s$
FIN FUNCIÓN

REM ==================================================================
REM  CVI(s$) — Convierte una cadena de 2 bytes (big-endian)
REM            en un entero de 16 bits sin signo
REM ==================================================================
FUNCIÓN CVI(s$) → n
    hi ← ASC(MID$(s$,1,1))
    lo ← ASC(MID$(s$,2,1))
    n  ← hi*256 + lo
    RETORNAR n
FIN FUNCIÓN

3. API de gestión de Tokens

REM ============================================================
REM  TOKENS — Arreglo único de cadenas con registros empaquetados
REM  TokenData$(n), TokenCount, TokenMax
REM ============================================================

PROCEDIMIENTO Token.Iniciar()
    Estructura_Iniciar(TokenMax, TokenCount)   REM Max=0, Count=0
FIN

PROCEDIMIENTO Token.Crear(tamInicial)
    Estructura_Crear(TokenMax, TokenCount, tamInicial, tipo.Principal)
    DIM TokenData$(TokenMax)
FIN

PROCEDIMIENTO Token.Ampliar(nuevoTamaño)
    Estructura_AmpliarArray(TokenMax, TokenCount, nuevoTamaño)
    AmpliarArray(TokenData$, TokenMax, nuevoTamaño)
    TokenMax ← nuevoTamaño
FIN

PROCEDIMIENTO Token.Reset()
    SI TokenMax = 0 ENTONCES Token.Crear(50)
    TokenCount ← 0
FIN

4. Empaquetado y acceso a los datos del registro

REM =========================================
REM  Empaquetado / Acceso a campos del token
REM =========================================

FUNCIÓN Token.Encode(tipo, linea, columna, lexema$) → token$
    token$ ← CHR$(tipo) & MKI$(linea) & MKI$(columna) & CHR$(LEN(lexema$)) & lexema$
    RETORNAR token$
FIN

FUNCIÓN Token.GetTipo(token$) → tipo
    tipo ← ASC(MID$(token$, 1, 1))
    RETORNAR tipo
FIN

FUNCIÓN Token.GetLinea(token$) → linea
    linea ← CVI(MID$(token$, 2, 2))
    RETORNAR linea
FIN

FUNCIÓN Token.GetColumna(token$) → col
    col ← CVI(MID$(token$, 4, 2))
    RETORNAR col
FIN

FUNCIÓN Token.GetLongitudLexema(token$) → lenLex
    lenLex ← ASC(MID$(token$, 6, 1))
    RETORNAR lenLex
FIN

FUNCIÓN Token.GetLexema(token$) → lex$
    lenLex ← ASC(MID$(token$, 6, 1))
    lex$ ← MID$(token$, 7, lenLex)
    RETORNAR lex$
FIN

5. Alta de nuevos tokens en el arreglo

REM ==========================
REM  Alta (añadir) de un token
REM ==========================
PROCEDIMIENTO Token.Emit(tipo, lexema$, linea, columna)
    SI TokenMax = 0 ENTONCES Token.Crear(50)
    SI TokenCount >= TokenMax ENTONCES Token.Ampliar(TokenMax + 50)

    TokenCount ← TokenCount + 1
    TokenData$(TokenCount) ← Token.Encode(tipo, linea, columna, lexema$)
FIN

6. Prueba: crear registros, recuperarlos y mostrarlos

REM ============================================================
REM  PRUEBAS — Director
REM   • CLS al inicio
REM   • CrearCasos una sola vez
REM   • Emite, verifica (PRUEBA 1)
REM   • Corrompe 2 tokens ya emitidos
REM   • Muestra "=== PRUEBA 2 ===" y verifica (errores)
REM ============================================================
PROCEDIMIENTO Pruebas()
    CLS

    CrearCasos()

    REM Emisión inicial desde la base
    Token.Iniciar()
    Token.Crear(MAX(NCasos, 4))
    PARA i ← 1 HASTA NCasos
        Token.Emit(BaseTipo(i), BaseLex$(i), BaseLinea(i), BaseCol(i))
    FIN PARA

    PRINT "=== PRUEBA 1 ==="
    REM PRIMERA VERIFICACIÓN (OK)
    Verificar()

    REM Corromper sin tocar la base de esperados:
    REM  i=2: lin=99
    REM  i=5: col=99
    TokenData$(2) ← Token.Encode(BaseTipo(2), 99, BaseCol(2), BaseLex$(2))
    TokenData$(5) ← Token.Encode(BaseTipo(5), BaseLinea(5), 99, BaseLex$(5))

    PRINT
    PRINT "=== PRUEBA 2 ==="

    REM SEGUNDA VERIFICACIÓN (debe mostrar 2 errores)
    Verificar()
FIN PROCEDIMIENTO


REM ============================================================
REM  CrearCasos — Define los casos base (solo se llama una vez)
REM ============================================================
PROCEDIMIENTO CrearCasos()
    DIM BaseTipo(50)
    DIM BaseLex$(50)
    DIM BaseLinea(50)
    DIM BaseCol(50)
    NCasos ← 0

    NCasos ← NCasos + 1
    BaseTipo(NCasos)  ← 10
    BaseLex$(NCasos)  ← "PRINT"
    BaseLinea(NCasos) ← 100
    BaseCol(NCasos)   ← 1

    NCasos ← NCasos + 1
    BaseTipo(NCasos)  ← 1
    BaseLex$(NCasos)  ← "A"
    BaseLinea(NCasos) ← 100
    BaseCol(NCasos)   ← 7

    NCasos ← NCasos + 1
    BaseTipo(NCasos)  ← 30
    BaseLex$(NCasos)  ← "+"
    BaseLinea(NCasos) ← 100
    BaseCol(NCasos)   ← 9

    NCasos ← NCasos + 1
    BaseTipo(NCasos)  ← 2
    BaseLex$(NCasos)  ← "42"
    BaseLinea(NCasos) ← 100
    BaseCol(NCasos)   ← 11

    NCasos ← NCasos + 1
    BaseTipo(NCasos)  ← 20
    BaseLex$(NCasos)  ← ":"
    BaseLinea(NCasos) ← 100
    BaseCol(NCasos)   ← 13

    NCasos ← NCasos + 1
    BaseTipo(NCasos)  ← 4
    BaseLex$(NCasos)  ← "HELLO"
    BaseLinea(NCasos) ← 101
    BaseCol(NCasos)   ← 1

    NCasos ← NCasos + 1
    BaseTipo(NCasos)  ← 21
    BaseLex$(NCasos)  ← ","
    BaseLinea(NCasos) ← 101
    BaseCol(NCasos)   ← 2
FIN PROCEDIMIENTO


REM ============================================================
REM  Verificar — Solo lectura y comprobación
REM  (no crea, no emite; compara almacenamiento vs. base)
REM  Salida en varias líneas por elemento para evitar desbordes
REM ============================================================
PROCEDIMIENTO Verificar()
    PRINT "=== Verificación de tokens (NCasos="; NCasos; ") ==="

    fallos ← 0
    hasta ← MIN(TokenCount, NCasos)

    PARA i ← 1 HASTA hasta
        tok$   ← TokenData$(i)
        tipo   ← Token.GetTipo(tok$)
        lin    ← Token.GetLinea(tok$)
        col    ← Token.GetColumna(tok$)
        lenLex ← Token.GetLongitudLexema(tok$)
        lex$   ← Token.GetLexema(tok$)

        expT   ← BaseTipo(i)
        expL$  ← BaseLex$(i)
        expLi  ← BaseLinea(i)
        expCo  ← BaseCol(i)

        esOK ← (tipo = expT) Y (lin = expLi) Y (col = expCo) Y (lenLex = LEN(expL$)) Y (lex$ = expL$)

        SI esOK ENTONCES
            PRINT i; ": OK  ->"
            Ver(tipo, lin, col, lenLex, lex$)
        SINO
            fallos ← fallos + 1
            PRINT i; ": ERR ->  ESP:"
            Ver(expT, expLi, expCo, LEN(expL$), expL$)
            PRINT "               OBT:"
            Ver(tipo, lin, col, lenLex, lex$)
        FIN SI
    FIN PARA

    SI fallos = 0 ENTONCES
        PRINT "Resultado: OK ( "; hasta; " registros verificados )"
    SINO
        PRINT "Resultado: ERROR ( "; fallos; " fallos de "; hasta; " )"
    FIN SI
FIN PROCEDIMIENTO

REM ============================================================
REM  Ver — Muestra los campos del registro en líneas separadas
REM ============================================================
PROCEDIMIENTO Ver(tipo, lin, col, lenLex, lex$)
    PRINT "  tipo="; tipo
    PRINT "  lin="; lin
    PRINT "  col="; col
    PRINT "  len="; lenLex
    PRINT "  lex=["; lex$; "]"
FIN PROCEDIMIENTO

7. Siguiente paso

  • Integrar estos cambios en el Lexer real (Fase 2): sustituir escrituras paralelas por Token.Emit y usar TokenData$() en el Parser con Token.Get*.
  • Publicar el módulo equivalente en VB.NET (Encode/Decode + pruebas), con BitConverter e inversión a big‑endian.

sábado, 14 de marzo de 2026

ZB2SB. Paso Auxiliar 1. Estructuras de datos

Transpilador ZX BASIC — Estructuras de datos para el transpilador (y adaptación a SuperBasic)

Índice de entradas del conversor


Introducción

El transpilador ZX BASIC → SuperBasic que estamos desarrollando debe ejecutarse finalmente en un Sinclair QL real. Esto significa trabajar con:

  • 128 KB de RAM totales (menos aún disponibles para SuperBasic),
  • sin estructuras dinámicas modernas como listas, diccionarios o árboles,
  • sin objetos ni clases,
  • E/S muy lenta (microdrives).

Por ese motivo, todas las estructuras de datos que en VB.NET son naturales (List<T>, diccionarios, árboles, AST reales…) deben redefinirse desde cero para poder implementarse en SuperBasic.

Esta entrada documenta todas las estructuras que necesitaremos en el transpilador y proporciona un seudo‑código portable al QL. Si más adelante fuera necesario añadir otras, se hará en otra entrada.

En VB.NET usamos listas y clases por comodidad y velocidad, pero en el QL se implementará todo con los recursos disponibles. Además, esta filosofía exige encapsular las estructuras, lo cual es una buena práctica de diseño. Para mantener precisión terminológica: hablaré de arreglo unidimensional (o simplemente arreglo) para lo que habitualmente se denomina array, de arreglo multidimensional (2 o más dimensiones) para lo que habitualmente se llama Matriz para 2 dimensiones, y Matriz de n dimensiones cuando hay mas de 2 (en matemáticas, un arreglo de 3 o más dimensiones se denomina tensor).


1. Por qué necesitamos estructuras propias

SuperBasic no dispone de:

  • listas dinámicas,
  • arreglos redimensionables sin pérdida,
  • tipos compuestos o registros,
  • pilas, colas o diccionarios,
  • árboles.

La única herramienta fiable son los arreglos estáticos. Por ello cada estructura del transpilador debe implementarse como arreglos más unas variables auxiliares. Los arreglos podrán crecer si es necesario.

2. Qué es un registro

En programación y en ingeniería de datos, un registro es una unidad lógica de información formada por varios campos, cada uno con un significado propio. Un registro no es un arreglo, sino un agrupador de datos relacionados que conceptualmente funcionan como una sola unidad.

En lenguajes modernos un registro sería equivalente a una “estructura” o “struct”, pero en SuperBasic del QL no existen tipos compuestos: solo disponemos de arreglos numéricos y de cadenas. Por tanto, cuando hablamos de un “registro” en este transpilador, nos referimos a un concepto abstracto que representamos usando varios arreglos paralelos o empleando un único arreglo de cadenas que contiene todos los campos empaquetados.

2.1 Campos dentro de un registro

Un registro está formado por varios valores individuales (campos). Por ejemplo, un registro para un token contiene campos como:

  • tipo del token,
  • lexema (cadena original),
  • línea donde aparece,
  • columna dentro de esa línea,
  • longitud del lexema.

Todos esos campos juntos forman un solo registro Token.

2.2 Cómo representaremos un registro

Dado que SuperBasic no permite definir estructuras complejas, podemos representar un registro de dos formas posibles:

  • Mediante varios arreglos paralelos: cada campo del registro se guarda en un arreglo independiente, y el índice n identifica el mismo registro en todos ellos.
  • Mediante un único arreglo de cadena con los datos empaquetados: cada elemento del arreglo contiene todos los campos del registro codificados en una sola cadena, usando separadores o marcas de longitud.

Ambos métodos expresan el mismo concepto: un registro es un conjunto de campos que pertenecen juntos, y que se accede siempre por un mismo índice. El formato exacto de representación dependerá de la estructura (Tokens, Diagnósticos, Tabla de líneas, etc.).

Con esta definición, cuando hablemos de “crear un registro”, “ampliar un registro” o “acceder a un registro”, nos referiremos siempre a estas unidades lógicas que agrupamos en una cadena y almacenamos mediante arreglos.

2.3 Formatos de empaquetado para registros

Para representar un registro dentro de un arreglo de cadenas en SuperBasic, existen varias técnicas de empaquetado. Cada una equilibra de forma distinta la ocupación en memoria, la velocidad de acceso y la facilidad de decodificación. En el transpilador adoptaremos tres estilos según la estructura a almacenar.

  • A) Separadores entre campos (p. ej. "3|A$|120|5|2"): legible y sencillo, adecuado para estructuras poco críticas en rendimiento.
  • B) Campos de tamaño fijo: rápido de extraer mediante MID$, aunque menos flexible y con espacio desaprovechado.
  • C) Longitud + campo (formato binario compacto):
    CHR$(tipo) & MKI$(linea) & MKI$(col) & CHR$(LEN(lex$)) & lex$
    Es el método más rápido y más eficiente para estructuras que se consultarán miles de veces.

En este artículo, para cada estructura del transpilador indicaré explícitamente el formato de registro que utilizará, detallando los nombres de los campos, su tipo y cómo se empaquetan en la cadena final.

3. Almacenamiento de cadenas en el QL real

El SuperBasic del Sinclair QL no almacena cadenas dentro del arreglo como hacía ZX BASIC (espacio fijo por elemento), ni usa arreglos de punteros al estilo C. Cada elemento de un arreglo de cadenas contiene un descriptor (puntero + longitud), y el texto vive en el heap dinámico del QDOS.

3.1 ¿Qué hay realmente en un arreglo de cadenas?

Cuando declaramos un arreglo de cadenas como:

DIM A$(9)
A$(0) = "HOLA"
A$(1) = "QL"
A$(2) = ""
A$(3) = "TRANS"

El QL reserva 10 descriptores (índices 0 a 9), y cada descriptor contiene dos valores:

  • un puntero a la cadena real almacenada en el heap del QDOS,
  • la longitud de esa cadena.

Por tanto, tras las asignaciones, la estructura interna del arreglo es:

A$(0)  = descriptor (puntero, 4)  → "HOLA"
A$(1)  = descriptor (puntero, 2)  → "QL"
A$(2)  = descriptor (puntero, 0)  → ""
A$(3)  = descriptor (puntero, 5)  → "TRANS"
A$(4)  = descriptor (puntero, 0)  → ""
A$(5)  = descriptor (puntero, 0)  → ""
A$(6)  = descriptor (puntero, 0)  → ""
A$(7)  = descriptor (puntero, 0)  → ""
A$(8)  = descriptor (puntero, 0)  → ""
A$(9)  = descriptor (puntero, 0)  → ""

Obsérvese que ninguna cadena se guarda dentro del arreglo. Solo se almacena un descriptor por elemento; el texto vive siempre en el heap, lo que permite ampliar o copiar arreglos de cadenas de forma rápida (solo se copian punteros).


4. Comparativa de modelos de estructuras en SuperBasic

Existen varias maneras diferentes de almacenar los valores de nuestras estructuras en arreglos dentro del QL:

4.1 Varios arreglos paralelos

  • Qué es: un arreglo por cada campo de la estructura (p. ej., TokenType(), TokenLexema$()…).
  • Ventajas: acceso rápido; depuración clara.
  • Inconvenientes: hay que ampliar/copiar varios arreglos; más fragmentación con muchos campos de texto.
  • Uso: recomendable para AST y estructuras con enlaces.

4.2 Arreglo multidimensional

  • Qué es: DIM Datos(N, M) (numérico).
  • Límite: con cadenas no resuelve el problema; seguiría siendo necesario otro arreglo $.
  • Uso: no recomendado para nuestras estructuras con texto.

4.3 Un único arreglo de cadena con los datos empaquetados

  • Qué es: Datos$() donde cada elemento empaqueta campos (tipo, línea, col., lexema…).
  • Ventajas: se amplía y copia un solo arreglo; menos fragmentación.
  • Inconvenientes: hay que empaquetar/desempaquetar; ligera penalización de CPU.
  • Uso: muy adecuado para Tokens, Diagnósticos, Símbolos, Tabla de líneas y Salida.

5. Estructuras de datos del transpilador

Las estructuras necesarias son:

  • Lista de Tokens — salida del Lexer.
  • Árbol de Sintaxis Abstracta (AST) — salida del Parser.
  • Tabla de Líneas — usada por el Semántico.
  • Tabla de Símbolos — variables y arreglos.
  • Tabla de Diagnósticos — errores por fases.
  • Buffer de Salida — líneas SuperBasic generadas.

Todas deben existir en VB.NET y en SuperBasic, con forma conceptual idéntica.


6. Descripción detallada de cada estructura

En un QL real debemos optimizar al máximo memoria y tiempo de acceso. Por ello, en cada estructura emplearemos la mejor representación posible: registros empaquetados en un único arreglo de cadena cuando primen eficiencia y sencillez, y arreglos paralelos cuando la estructura sea jerárquica (AST) y requiera enlaces explícitos entre nodos.


6.1 Tokens (Lexer)

6.1.1 Representación

Un único arreglo de cadenas empaquetadas (formato binario compacto) por token:

TokenData$(n)
TokenCount
TokenMax

6.1.2 Registro: TokenRecord

Campo        : Tipo
Tipo de dato : byte (0..255)
Descripción  : Categoría del token (Identifier, Keyword, etc.)
Almacenado   : CHR$(tipo)

Campo        : Línea
Tipo de dato : entero corto (2 bytes)
Descripción  : Nº de línea física en el fichero de entrada
Almacenado   : MKI$(linea)

Campo        : Columna
Tipo de dato : entero corto (2 bytes)
Descripción  : Columna (1‑based) donde comienza el token
Almacenado   : MKI$(columna)

Campo        : LongitudLexema
Tipo de dato : byte (0..255)
Descripción  : Longitud del lexema en caracteres
Almacenado   : CHR$(LEN(lexema$))

Campo        : Lexema
Tipo de dato : cadena (longitud variable)
Descripción  : Texto literal del token
Almacenado   : lexema$

6.1.3 Empaquetado

TokenData$(n) =
  CHR$(tipo) &
  MKI$(linea) &
  MKI$(columna) &
  CHR$(LEN(lexema$)) &
  lexema$

6.2 AST por línea (Parser)

6.2.1 Representación

El AST (línea, sentencias y expresiones) es una estructura jerárquica. Para mantener los enlaces entre nodos y recorrer el “árbol” con claridad y rapidez en QL, se usarán varios arreglos paralelos.

6.2.2 Nodo de Línea

ASTLine_Number(id)     → Nº de línea ZX BASIC
ASTLine_StmtIndex(id)  → Primer índice en Stmt*
ASTLine_StmtCount(id)  → Nº de sentencias en la línea
ASTLineCount           → Nº real de nodos de línea
ASTLineMax             → Capacidad reservada

6.2.3 Sentencias

StmtType(n)            → Tipo de sentencia (LET, PRINT, IF, ...)
StmtParam1$(n)         → Parámetro textual principal (p. ej. destino en LET)
StmtParam2$(n)         → Segundo parámetro textual (si aplica)
StmtExprIndex(n)       → Índice en Expr* de la expresión asociada (0 si no hay)
StmtCount              → Nº real de sentencias
StmtMax                → Capacidad reservada

6.2.4 Expresiones

ExprType(n)            → Literal, VarRef, Unary, Binary, FuncCall, Paren...
ExprValue$(n)          → Valor textual asociado (variable, número, nombre)
ExprLeft(n)            → Índice de subexpresión izquierda (0 si no aplica)
ExprRight(n)           → Índice de subexpresión derecha (0 si no aplica)
ExprCount              → Nº real de expresiones
ExprMax                → Capacidad reservada

6.3 Tabla de Líneas (Semántico)

6.3.1 Representación

Un único arreglo de cadenas con separadores (legible y suficiente en rendimiento):

LineData$(n)   = numeroLinea$ & "|" & astIndex$
LineCount
LineMax

6.3.2 Registro: LineRecord

Campo        : NumeroLinea
Tipo de dato : entero (en texto)
Descripción  : Nº de línea ZX BASIC existente en el programa
Almacenado   : "1234"

Separador1   : "|"
Tipo de dato : carácter (longitud fija = 1)
Descripción  : Separador de campos
Almacenado   : "|"

Campo        : ASTIndex
Tipo de dato : entero (en texto)
Descripción  : Índice en las tablas ASTLine_* para esa línea
Almacenado   : "57"

6.4 Tabla de Símbolos

6.4.1 Representación

Un único arreglo de cadenas con separadores:

SymbolData$(n) = nombre$ & "|" & tipo$
SymbolCount
SymbolMax

6.4.2 Registro: SymbolRecord

Campo        : Nombre
Tipo de dato : cadena (longitud variable)
Descripción  : Identificador del símbolo (A, A$, ARR(), ...)
Almacenado   : "A$"

Separador1   : "|"
Tipo de dato : carácter (longitud fija = 1)
Descripción  : Separador de campos
Almacenado   : "|"

Campo        : Tipo
Tipo de dato : cadena corta
Descripción  : Clasificación (p. ej. "NUM", "STR", "ARR_NUM", "ARR_STR")
Almacenado   : "STR"

6.5 Diagnósticos

6.5.1 Representación

Un único arreglo de cadenas con separadores:

DiagData$(n) = mensaje$ & "|" & linea$ & "|" & columna$ & "|" & fase$
DiagCount
DiagMax

6.5.2 Registro: DiagRecord

Campo        : Mensaje
Tipo de dato : cadena (longitud variable)
Descripción  : Texto del diagnóstico (error/aviso)
Almacenado   : "Invalid token '£'"

Separador1   : "|"
Tipo de dato : carácter
Descripción  : Separador
Almacenado   : "|"

Campo        : Línea
Tipo de dato : entero (en texto)
Descripción  : Nº de línea física (0 si global)
Almacenado   : "123"

Separador2   : "|"
Tipo de dato : carácter
Descripción  : Separador
Almacenado   : "|"

Campo        : Columna
Tipo de dato : entero (en texto)
Descripción  : Columna asociada (0 si no aplica)
Almacenado   : "17"

Separador3   : "|"
Tipo de dato : carácter
Descripción  : Separador
Almacenado   : "|"

Campo        : Fase
Tipo de dato : cadena corta
Descripción  : "Lexer", "Parser", "Semántico", "Transformador", "Emisor"
Almacenado   : "Lexer"

6.6 Buffer de salida

6.6.1 Representación

Un único arreglo de cadenas donde cada elemento es la línea destino completa:

Output$(n)    = lineaSuperBasic$
OutputCount
OutputMax

6.6.2 Registro: OutputRecord

Campo        : LineaSB
Tipo de dato : cadena (longitud variable)
Descripción  : Línea completa ya generada en SuperBasic
Almacenado   : "100 PRINT \"HOLA\""

7. Seudo‑código de las estructuras

7.0 Funciones genéricas para usar arreglos como lista

Seudo‑código que expresa el comportamiento lógico (no es SuperBasic literal):

// ======================================================================
//  FUNCIONES AUXILIARES GENÉRICAS PARA TODAS LAS ESTRUCTURAS
// ======================================================================

// Reset de estructura (mantiene arreglos, Count=0)
PROCEDIMIENTO Estructura_Reset(var Count)
    Count ← 0
FIN

// Crear estructura con tamaño inicial
PROCEDIMIENTO Estructura_Crear(var Max, var Count, tamInicial, tipo)
    SI tipo = tipo.Principal ENTONCES
        Crear_Arrays(tamInicial)  // Arreglos definitivos
        Max   ← tamInicial
        Count ← 0
    SINO
        Crear_ArraysAuxiliares(tamInicial)  // Arreglos temporales
    FIN SI
FIN

// Ampliar estructura a un nuevo tamaño absoluto
PROCEDIMIENTO Estructura_AmpliarArray(var Max, var Count, nuevoTamaño)
    SI nuevoTamaño <= Max ENTONCES
        RETORNAR
    FIN SI

    // Crear arreglos auxiliares y copiar
    Estructura_Crear(auxMax, auxCount, nuevoTamaño, tipo.Auxiliar)
    Copiar_Arrays(array_Actual, array_Temporal)

    // Crear nuevos arreglos principales ampliados y restaurar
    Estructura_Crear(Max, Count, nuevoTamaño, tipo.Principal)
    Copiar_Arrays(array_Temporal, array_Actual)

    Max ← nuevoTamaño
FIN

7.X Firmas de funciones por estructura (sin implementación)

// TOKENS
PROCEDIMIENTO Token_Iniciar()
PROCEDIMIENTO Token_Crear(tamInicial)
PROCEDIMIENTO Token_Ampliar(nuevoTamaño)
PROCEDIMIENTO Token_Reset()
PROCEDIMIENTO Token_Add(tipo, lexema$, linea, columna, longitud)

// AST
PROCEDIMIENTO AST_Iniciar()
PROCEDIMIENTO AST_Crear(tamInicial)
PROCEDIMIENTO AST_Ampliar(nuevoTamaño)
PROCEDIMIENTO AST_Reset()
FUNCIÓN       AST_NewLine(lineNumber) → idLinea
FUNCIÓN       AST_AddStatement(idLinea, tipoStmt) → idStmt

// SENTENCIAS
PROCEDIMIENTO Stmt_Iniciar()
PROCEDIMIENTO Stmt_Crear(tamInicial)
PROCEDIMIENTO Stmt_Ampliar(nuevoTamaño)
PROCEDIMIENTO Stmt_Reset()
FUNCIÓN       Stmt_Add(tipoStmt, param1$, param2$, exprIndex) → idStmt

// EXPRESIONES
PROCEDIMIENTO Expr_Iniciar()
PROCEDIMIENTO Expr_Crear(tamInicial)
PROCEDIMIENTO Expr_Ampliar(nuevoTamaño)
PROCEDIMIENTO Expr_Reset()
FUNCIÓN       Expr_Add(tipoExpr, valor$, leftIndex, rightIndex) → idExpr

// TABLA DE LÍNEAS
PROCEDIMIENTO LineTable_Iniciar()
PROCEDIMIENTO LineTable_Crear(tamInicial)
PROCEDIMIENTO LineTable_Ampliar(nuevoTamaño)
PROCEDIMIENTO LineTable_Reset()
PROCEDIMIENTO LineTable_Insert(lineNumber, idLinea)

// SÍMBOLOS
PROCEDIMIENTO Symbol_Iniciar()
PROCEDIMIENTO Symbol_Crear(tamInicial)
PROCEDIMIENTO Symbol_Ampliar(nuevoTamaño)
PROCEDIMIENTO Symbol_Reset()
PROCEDIMIENTO Symbol_Add(nombre$, tipo)

// DIAGNÓSTICOS
PROCEDIMIENTO Diag_Iniciar()
PROCEDIMIENTO Diag_Crear(tamInicial)
PROCEDIMIENTO Diag_Ampliar(nuevoTamaño)
PROCEDIMIENTO Diag_Reset()
PROCEDIMIENTO Diag_Add(msg$, linea, columna, fase$)

// SALIDA
PROCEDIMIENTO Output_Iniciar()
PROCEDIMIENTO Output_Crear(tamInicial)
PROCEDIMIENTO Output_Ampliar(nuevoTamaño)
PROCEDIMIENTO Output_Reset()
PROCEDIMIENTO Output_Add(linea$)

7.1 Tokens (implementacióncompleta en seudo‑código como ejemplo)

PROCEDIMIENTO Token_Iniciar()
    Estructura_Iniciar(TokenMax, TokenCount)
FIN

PROCEDIMIENTO Token_Crear(tamInicial)
    Estructura_Crear(TokenMax, TokenCount, tamInicial, tipo.Principal)
    DIM TokenType(TokenMax)
    DIM TokenLexema$(TokenMax)
    DIM TokenLine(TokenMax)
    DIM TokenColumn(TokenMax)
    DIM TokenLength(TokenMax)
FIN

PROCEDIMIENTO Token_Ampliar(nuevoTamaño)
    Estructura_AmpliarArray(TokenMax, TokenCount, nuevoTamaño)
    AmpliarArray(TokenType,    TokenMax, nuevoTamaño)
    AmpliarArray(TokenLexema$, TokenMax, nuevoTamaño)
    AmpliarArray(TokenLine,    TokenMax, nuevoTamaño)
    AmpliarArray(TokenColumn,  TokenMax, nuevoTamaño)
    AmpliarArray(TokenLength,  TokenMax, nuevoTamaño)
    TokenMax ← nuevoTamaño
FIN

PROCEDIMIENTO Token_Reset()
    SI TokenMax = 0 ENTONCES
        Token_Crear(50)
    FIN SI
    TokenCount ← 0
FIN

PROCEDIMIENTO Token_Add(tipo, lexema$, linea, columna, longitud)
    SI TokenMax = 0 ENTONCES
        Token_Crear(50)
    FIN SI
    SI TokenCount >= TokenMax ENTONCES
        Token_Ampliar(TokenMax + 50)
    FIN SI
    TokenCount ← TokenCount + 1
    TokenType(TokenCount)    ← tipo
    TokenLexema$(TokenCount) ← lexema$
    TokenLine(TokenCount)    ← linea
    TokenColumn(TokenCount)  ← columna
    TokenLength(TokenCount)  ← longitud
FIN

8. Siguientes pasos

Ya tenemos la base necesaria para portar todas las fases del transpilador al QL:

  • Lexer por líneas,
  • Parser por líneas,
  • Semántico con tabla de líneas y validaciones globales,
  • Transformaciones ZX→SuperBasic,
  • Generación del programa destino.

En la próxima entrada comenzaremos con el lexer por líneas, construyendo las estructuras definidas aquí.


viernes, 13 de marzo de 2026

ZB2SB. Paso 2. Director del proyecto

Transpilador ZX BASIC — El Director del Proceso (Orquestador del Pipeline)

Índice de entradas del conversor



Introducción

Hasta ahora hemos desarrollado la gramática formal del ZX BASIC. El siguiente paso lógico es diseñar una pieza clave en cualquier compilador o transpilador: el Director del Proceso, responsable de coordinar lectura, análisis y generación. Dado que el objetivo final es ejecutar en un Sinclair QL real —un entorno con CPU más lenta y RAM muy limitada— adaptamos el diseño para que sea rápido y de mínimo consumo de memoria.


1. Motivación del Director del Proceso

Aunque cada fase del transpilador se puede ejecutar por separado, lo ideal es disponer de un módulo que:

  • Controle el orden correcto de ejecución.
  • Transmita la salida de cada fase a la siguiente.
  • Gestione errores y diagnósticos desde un único punto.
  • Permita activar/desactivar opciones (verbose, políticas del charset…).
  • Se adapte al destino: en nuestro caso, el QL, que requiere memoria mínima.

2. Pipeline completo del transpilador

FASE 01 — DEFINICIÓN PRECISA DEL LENGUAJE
FASE 02 — DIRECTOR DEL PROCESO
FASE 03 — LÉXICO (TOKENIZER)
FASE 04 — SINTÁCTICO (PARSER LL(1))
FASE 05 — ANÁLISIS SEMÁNTICO
FASE 06 — TRANSFORMACIONES (ZX BASIC → SuperBasic)
FASE 07 — GENERACIÓN DE CÓDIGO SuperBasic
FASE 08 — OPTIMIZACIÓN
FASE 09 — SALIDA + TESTS
FASE 10 — EMPAQUETADO Y HERRAMIENTAS
FASE 11 — DOCUMENTACIÓN
FASE 12 — FUTURO (ZX80/81, +2/+3…)

El Director no pertenece a ninguna de estas fases: las coordina a todas.


3. Las técnicas de lexing/parsing

Cuando procesamos el fichero de origen existen varias maneras de enfocar el compilador o transpilador. En algunos lenguajes el parser debe mirar hacia adelante o hacia atrás entre muchos tokens, lo que implica disponer de todos ellos; en otros, como ZX BASIC, cada línea constituye una unidad completa (“LineNumber → sentencias separadas por :”), lo que permite estrategias mucho más ligeras.

3.1 Tokenizar todo el fichero: LexAll + ParseProgram

  • Cómo funciona: el lexer produce la lista completa de tokens del fichero entero (incluye EOL por línea). El parser recorre esa lista y construye el AST global.
  • Ventajas: diseño simple; el parser recorre una secuencia homogénea; lookahead sencillo.
  • Inconvenientes: consume mucha RAM; listas grandes; GC costoso; hay que esperar a tener todos los tokens para empezar.

Variantes de “LexAll”

  • Tokenizar en memoria: todos los tokens en una lista → muy rápido en PC; consume mucha RAM en máquinas limitadas.
  • Tokenizar en disco: los tokens se guardan en un fichero intermedio → útil en PC; muy lento en microdrives del QL.

3.2 Tokenizar y parsear por líneas (técnica elegida)

  • Cómo funciona: el Director lee cada línea; el lexer tokeniza esa línea; el parser procesa sentencias separadas por :; libera tokens y pasa a la siguiente línea.
  • Ventajas: memoria mínima; velocidad muy alta en QL; una sola pasada; no requiere listas globales ni ficheros auxiliares.
  • Inconvenientes: el parser trabaja por línea (no por programa). Lenguajes con sentencias multilínea no podrían usar este método, pero en ZX BASIC cada línea es una unidad completa, así que encaja perfectamente.

Como el objetivo final es ejecutar en QL real, usaremos la técnica por líneas. El lexer expone LexLine(texto, numLinea) y el parser expone ParseLine(tokens, numLinea). Esta técnica maximiza la velocidad y minimiza el uso de memoria.

Comparativa rápida

Método Memoria Velocidad E/S Complejidad Cuándo usar
Tokenizar en memoria Alta Muy alta Mínima Baja PC con mucha RAM
Tokenizar en disco Muy baja Media (dos pasadas) Alta (fichero intermedio) Baja PC con disco rápido
Tokenizar por líneas Muy baja Alta Mínima Media QL o entornos con recursos limitados

4. Flujo general del proceso (optimizado QL)

Inicio
   ↓  leer fichero .BAS línea a línea
Lexer (por línea)
   ↓  tokens de esa línea (LineNumber, ... , ":" para sentencias)
Parser (por línea)
   ↓  procesa sentencias separadas por ":" y libera tokens
Transformaciones / Emisión (fases posteriores)

Esta secuencia garantiza un procesamiento ordenado, con uso mínimo de memoria y arranque inmediato desde la primera línea.


5. Esqueleto del Director (seudocódigo)

FUNCIÓN TranspilerDriver(rutaEntrada)
    Lexer.Preparar()
    Parser.Preparar()
    Semantico.Preparar()
    Transformador.Preparar()
    Emisor.Preparar()

    numeroLineaFisica ← 0
    PARA CADA lineaTexto EN leerLineas(rutaEntrada)
        numeroLineaFisica ← numeroLineaFisica + 1

        ListaTokens ← Lexer.Procesar(lineaTexto, numeroLineaFisica)
        AstLinea ← Parser.Procesar(ListaTokens, numeroLineaFisica)
        Semantico.RegistrarLinea(AstLinea)
        Semantico.VerificarReferenciaSaltos(AstLinea)
        AstTransformada ← Transformador.Aplicar(AstLinea, Politicas)
        Emisor.Emitir(AstTransformada)
    FIN PARA

    Semantico.VerificarSaltosPendientes()
    escribirConsola("Transpilación completada correctamente.")
FIN TranspilerDriver

6. Integración con las siguientes fases

  • Fase 3 (Semántico): se aplicará línea a línea o al final de un primer pase completo.
  • Fase 4 (Transformaciones ZX→SB): mapeo del charset y normalizaciones.
  • Fase 5 (Generación SuperBasic): emisión del código destino.
  • Fase 6 (Optimización): ajustes sobre el código resultante.

7. Próximo paso

En la siguiente entrada comenzaremos con el lexer (análisis léxico), usando la EBNF final y la estructura LineNumber → SimpleStatement { ":" SimpleStatement }.

En el modo verbose se mostrarán por pantalla todos los tokens emitidos por el lexer, lo que es útil para diagnósticos y muy informativo del proceso generado.


APÉNDICE: Director del proceso completo

Por completar, aquí añado el seudocódigo del director del proyecto más desarrollado, incluyendo control de errores y manejo de la opción verbose para salida de información adicional en pantalla:

// ==================================================================================
//  Director del Proceso (QL) — Seudo‑código con gestión de errores
//  • Procesamiento por líneas (memoria mínima)
//  • Manejo explícito de diagnósticos en TODAS las fases
//  • Política de error configurable: "AbortarAlPrimerError" o "AcumularYContinuar"
//  • Opción Verbose con salida en pantalla de información adicional
// ==================================================================================

TIPO PoliticaErrores = { AbortarAlPrimerError, AcumularYContinuar }

FUNCIÓN TranspilerDriver.Ejecutar(rutaEntrada, verbose, politicaErrores) → Booleano
    SI noExisteArchivo(rutaEntrada) ENTONCES
        escribirConsola("Error: no se encuentra el fichero de entrada.")
        RETORNAR FALSO
    FIN SI

    SI NO Lexer.Preparar(verbose) ENTONCES
        ReportarDiagnosticos("Lexer.Preparar", Lexer.Diagnostics)
        RETORNAR FALSO
    FIN SI
    SI NO Parser.Preparar(verbose) ENTONCES
        ReportarDiagnosticos("Parser.Preparar", Parser.Diagnostics)
        RETORNAR FALSO
    FIN SI
    SI NO Semantico.Preparar(verbose) ENTONCES
        ReportarDiagnosticos("Semantico.Preparar", Semantico.Diagnostics)
        RETORNAR FALSO
    FIN SI
    SI NO Transformador.Preparar(verbose) ENTONCES
        ReportarDiagnosticos("Transformador.Preparar", Transformador.Diagnostics)
        RETORNAR FALSO
    FIN SI
    SI NO Emisor.Preparar(verbose) ENTONCES
        ReportarDiagnosticos("Emisor.Preparar", Emisor.Diagnostics)
        RETORNAR FALSO
    FIN SI

    numeroLineaFisica ← 0
    huboErrores ← FALSO

    PARA CADA lineaTexto EN leerLineas(rutaEntrada)
        numeroLineaFisica ← numeroLineaFisica + 1

        INTENTAR
            ListaTokens ← Lexer.Procesar(lineaTexto, numeroLineaFisica)
        CAPTURAR ex
            Lexer.Diagnostics.Agregar(DiagFatal(ex, numeroLineaFisica))
        FIN

        SI noEstaVacio(Lexer.Diagnostics) ENTONCES
            ReportarDiagnosticos("Lexer (línea " + aCadena(numeroLineaFisica) + ")", Lexer.Diagnostics)
            huboErrores ← VERDADERO
            SI politicaErrores = AbortarAlPrimerError ENTONCES RETORNAR FALSO FIN SI
            CONTINUAR PARA
        FIN SI

        INTENTAR
            AstLinea ← Parser.Procesar(ListaTokens, numeroLineaFisica)
        CAPTURAR ex
            Parser.Diagnostics.Agregar(DiagFatal(ex, numeroLineaFisica))
        FIN

        SI noEstaVacio(Parser.Diagnostics) ENTONCES
            ReportarDiagnosticos("Parser (línea " + aCadena(numeroLineaFisica) + ")", Parser.Diagnostics)
            huboErrores ← VERDADERO
            SI politicaErrores = AbortarAlPrimerError ENTONCES RETORNAR FALSO FIN SI
            CONTINUAR PARA
        FIN SI

        INTENTAR
            Semantico.RegistrarLinea(AstLinea)
            Semantico.VerificarReferenciaSaltosParcial(AstLinea)
        CAPTURAR ex
            Semantico.Diagnostics.Agregar(DiagFatal(ex, numeroLineaFisica))
        FIN

        SI noEstaVacio(Semantico.Diagnostics) ENTONCES
            ReportarDiagnosticos("Semántico (línea " + aCadena(numeroLineaFisica) + ")", Semantico.Diagnostics)
            huboErrores ← VERDADERO
            SI politicaErrores = AbortarAlPrimerError ENTONCES RETORNAR FALSO FIN SI
            CONTINUAR PARA
        FIN SI

        INTENTAR
            AstTransformada ← Transformador.Aplicar(AstLinea, Politicas)
        CAPTURAR ex
            Transformador.Diagnostics.Agregar(DiagFatal(ex, numeroLineaFisica))
        FIN

        SI noEstaVacio(Transformador.Diagnostics) ENTONCES
            ReportarDiagnosticos("Transformador (línea " + aCadena(numeroLineaFisica) + ")", Transformador.Diagnostics)
            huboErrores ← VERDADERO
            SI politicaErrores = AbortarAlPrimerError ENTONCES RETORNAR FALSO FIN SI
            CONTINUAR PARA
        FIN SI

        INTENTAR
            Emisor.Emitir(AstTransformada)
        CAPTURAR ex
            Emisor.Diagnostics.Agregar(DiagFatal(ex, numeroLineaFisica))
        FIN

        SI noEstaVacio(Emisor.Diagnostics) ENTONCES
            ReportarDiagnosticos("Emisor (línea " + aCadena(numeroLineaFisica) + ")", Emisor.Diagnostics)
            huboErrores ← VERDADERO
            SI politicaErrores = AbortarAlPrimerError ENTONCES RETORNAR FALSO FIN SI
            CONTINUAR PARA
        FIN SI
    FIN PARA

    INTENTAR
        Semantico.VerificarSaltosPendientes()
        Semantico.VerificarBloquesPendientes()
    CAPTURAR ex
        Semantico.Diagnostics.Agregar(DiagFatal(ex, 0))
    FIN

    SI noEstaVacio(Semantico.Diagnostics) ENTONCES
        ReportarDiagnosticos("Semántico (verificación final)", Semantico.Diagnostics)
        huboErrores ← VERDADERO
        SI politicaErrores = AbortarAlPrimerError ENTONCES RETORNAR FALSO FIN SI
    FIN SI

    SI huboErrores ENTONCES
        escribirConsola("Transpilación finalizada con incidencias.")
        RETORNAR FALSO
    SINO
        escribirConsola("Transpilación completada correctamente.")
        RETORNAR VERDADERO
    FIN SI
FIN FUNCIÓN

PROCEDIMIENTO ReportarDiagnosticos(etapa, listaDiag)
    escribirConsola("=== Errores en " + etapa + " ===")
    PARA CADA diag EN listaDiag
        escribirConsola(diag.aTexto())
    FIN PARA
FIN PROCEDIMIENTO

FUNCIÓN DiagFatal(excepcion, linea) → Diagnostico
    diag ← nuevo Diagnostico
    diag.Linea ← linea
    diag.Columna ← 1
    diag.Mensaje ← "Fatal: " + excepcion.mensaje
    RETORNAR diag
FIN FUNCIÓN

jueves, 12 de marzo de 2026

ZB2SB. Paso 1. Definir el Lenguaje de origen

Gramática ZX BASIC Spectrum 48K — BNF completa y explicada

Índice de entradas del conversor

Modificado el 12/03/2026, ampliaciones en color rojo



Gramática del ZX BASIC del Spectrum 48K

Esta entrada ofrece un resumen estructurado de la gramática del ZX BASIC del Sinclair ZX Spectrum 48K, basado en las referencias originales del manual oficial del Spectrum y las versiones digitalizadas del manual en español. Primero se presenta una versión ligera y, después, la definición formal completa.

  • BNF/EBNF práctica, apta para implementar (recursive descent o LL(1) con pequeñas adaptaciones).
  • Las restricciones del hardware (p. ej. rango de números de línea) se indican en comentarios.
  • Se prioriza claridad en la introducción; los detalles formales aparecen en el BNF completo final.

NOTA IMPORTANTE: heredado de los ZX80/81 con solo 1 KB disponible, para ahorrar memoria todas las palabras clave se almacenan internamente como tokens de 1 byte. Por tanto, toda sentencia debe comenzar obligatoriamente por un token de palabra clave. No puede comenzar por un identificador. LET es obligatorio siempre. REM solo se reconoce si es exactamente REM.

Estos ajustes son mínimos pero críticos para definir correctamente la gramática y la generación de tokens.


1. Estructura de un programa

Un programa en ZX BASIC está compuesto por líneas numeradas:

<linea> ::= <numero> <espacio> <instruccion>
  • Los números de línea van de 1 a 9999.
  • Una línea puede contener una o varias instrucciones separadas por :.

2. Tipos de datos

2.1 Tipos básicos

  • Números: coma flotante de 40 bits.
  • Cadenas: delimitadas por comillas ("texto").
  • No existen enteros ni booleanos como tipos nativos; la lógica usa 0 para falso y <>0 para cierto.

2.2 Nombres de variables

<var_num> ::= <letra> [ <letra_o_digito> ]
<var_str> ::= <letra> [ <letra_o_digito> ] "$"
<var_array> ::= <var> "(" <lista_expresiones> ")"
  • Las variables numéricas y de cadena con el mismo nombre son distintas.
  • Las matrices pueden ser numéricas o de cadena.

3. Expresiones

3.1 Expresiones numéricas

Precedencia de operadores (de mayor a menor):

  1. Unario: -
  2. Potencia: ^
  3. Multiplicación / División: * /
  4. Suma / Resta: + -
  5. Relacionales: =, <, >, <=, >=, <>
  6. Lógicos: AND, OR, NOT

Fuentes: capítulos 3, 7 y 13 del manual.

3.2 Expresiones de cadena

  • Concatenación: "A" + "B".
  • Slicing: A$(n TO m).
  • Longitud: LEN A$.

Fuente: capítulo 8 del manual.


4. Sentencias

4.1 Asignación

<asignacion> ::= LET <var> = <expresion>

LET es obligatorio en el Spectrum 48K (no puede omitirse).

4.2 Entrada y salida

  • PRINT
  • INPUT
  • CLS
  • TAB(n), AT y,x

Fuente: capítulos 2 y 15 del manual.


5. Control de flujo

5.1 IF

IF <expresión> THEN <instrucciones>

Ejecuta todas las instrucciones (separadas por :) hasta fin de línea. No existe ELSE en el ZX Spectrum 48K.

5.2 Bucles FOR

FOR <var> = <expr> TO <expr> [STEP <expr>]
...
NEXT <var>

Fuente: capítulo 4 del manual.

5.3 Subrutinas

GOSUB número
RETURN

Fuente: capítulo 5 del manual.

5.4 Saltos

  • GO TO
  • STOP
  • CONTINUE

6. Datos

READ <lista_variables>
DATA <lista_constantes>
RESTORE [número]

Fuente: capítulo 6 del manual.


7. Comentarios

En ZX Spectrum 48K, la sentencia REM admite exactamente dos formas:

  • Comentario vacío: REM seguido inmediatamente de fin de línea.
  • Comentario con contenido: REM seguido de un ESPACIO y, a continuación, cualquier número de caracteres (incluidos espacios) hasta fin de línea.

Ejemplos válidos:

10 REM
20 REM Hola mundo
30 REM  Más espacios al inicio del comentario

Ejemplos inválidos:

40 REMHola     ; falta el espacio tras REM
50 REMHola ; tabulación inmediata tras REM (no permitido)

EBNF:

ESPACIO      = " ";
EOL          = "\n";
anyCharacter = ? cualquier carácter Unicode ? ;

remStmt =
      "REM"
    | "REM" , ESPACIO , { anyCharacter } ;

8. Funciones incorporadas

Matemáticas

ABS, INT, SGN, SIN, COS, TAN, ASN, ACS, ATN, SQR, EXP, LN, PI …

Cadenas

LEN, STR$, VAL, CHR$, CODE

Sistema

PEEK, POKE, USR, IN, OUT, RND, RANDOMIZE

Fuente: capítulos 9, 10, 11, 14 y 23 del manual.


9. Arrays

DIM A(10)
DIM N$(5)
  • Índices desde 1 hasta el tamaño declarado (arrays base 1).
  • Acceso: A(1), A(5), N$(1)…

10. Gráficos

  • PLOT x,y
  • DRAW dx,dy
  • CIRCLE x,y,r
  • POINT x,y

Fuente: capítulo 17 del manual.


11. Colores

  • INK n
  • PAPER n
  • FLASH n
  • BRIGHT n
  • INVERSE n
  • OVER n
  • BORDER n

Fuente: capítulo 16 del manual.


12. Sonido

BEEP duracion, tono

Fuente: capítulo 19 del manual.


13. Cinta y ficheros

  • SAVE
  • LOAD
  • MERGE
  • VERIFY

Fuente: capítulo 20 del manual.


14. Gramática mínima para transpiladores

<program> ::= { <line> }

<line> ::= <number> <Statement>

<Statement> ::= <SimpleStatement> { ":" <SimpleStatement> }

<SimpleStatement> ::= <assign>
                     | PRINT <print_list>
                     | INPUT <var_list>
                     | IF <expr> THEN <Statement>
                     | FOR <assign> TO <expr> ["STEP" <expr>]
                     | NEXT <var>
                     | GOSUB <number>
                     | RETURN
                     | GO TO <number>
                     | READ <var_list>
                     | DATA <const_list>
                     | DIM <dim_list>
                     | PLOT | DRAW | CIRCLE | POINT | ...

15. BNF/EBNF del ZX BASIC del Spectrum 48K (versión corregida)

1) Léxico

letter        = "A".."Z" | "a".."z"
digit         = "0".."9"
nonZeroDigit  = "1".."9"
alnum         = letter | digit

numVar        = letter , [ alnum ]
strVar        = letter , [ alnum ] , "$"
var           = numVar | strVar

intLiteral    = digit , { digit }
fractPart     = "." , { digit }
expPart       = ("E" | "e") , [ "+" | "-" ] , digit , { digit }

numLiteral    = intLiteral , [ fractPart ] , [ expPart ]
              | fractPart , [ expPart ]

ESPACIO      = " "
EOL          = "\n"
anyCharacter = ? cualquier carácter Unicode ?

strLiteral    = '"' , { anyCharacter } , '"'


; REM solo se reconoce como palabra clave si el token es exactamente "REM".
; Un identificador NO puede iniciar una sentencia.

2) Estructura del programa

program       = { line }

line          = lineNumber , Statement
lineNumber    = LineNumberToken
LineNumberToken = nonZeroDigit , [ digit ] , [ digit ] , [ digit ]
(* Rango efectivo: 1..9999; token específico generado por el lexer *)

3) Statement y SimpleStatement

Statement     = SimpleStatement , { ":" , SimpleStatement }

; SimpleStatement debe comenzar por token de palabra reservada

SimpleStatement =
                   letStmt
                 | printStmt
                 | inputStmt
                 | ifStmt
                 | forStmt
                 | nextStmt
                 | gotoStmt
                 | gosubStmt
                 | returnStmt
                 | stopStmt
                 | continueStmt
                 | readStmt
                 | dataStmt
                 | restoreStmt
                 | dimStmt
                 | clsStmt
                 | graphicStmt
                 | colorStmt
                 | soundStmt
                 | tapeStmt
                 | remStmt

4) Expresiones

expr          = logicOr
logicOr       = logicAnd , { "OR" , logicAnd }
logicAnd      = relation , { "AND" , relation }

relation      = arithExpr , [ relOp , arithExpr ]
relOp         = "=" | "<>" | "<" | ">" | "<=" | ">="

arithExpr     = term , { ("+" | "-") , term }
term          = power , { ("*" | "/") , power }
power         = unary , { "^" , unary }

unary         = [ "-" | "NOT" ] , primary

primary       = numLiteral
              | strLiteral
              | varRef
              | funcCall
              | "(" , expr , ")"

5) Variables y arrays (base 1)

varRef        = arrayRef | var

arrayRef      = ( numVar | strVar ) ,
                "(" , indexExpr , { "," , indexExpr } , ")"

indexExpr     = expr

6) Funciones

funcCall      = funcNum | funcStr | sysFunc

funcNum       = ("ABS" | "INT" | "SGN" | "SIN" | "COS" | "TAN"
                | "ASN" | "ACS" | "ATN" | "SQR" | "EXP" | "LN"
                | "VAL" | "PI")
                "(" , [ expr ] , ")"

funcStr       = "LEN" "(" , ( strVar | strLiteral ) , ")"
              | "STR$" "(" , expr , ")"
              | "CHR$" "(" , expr , ")"

sysFunc       = "PEEK" "(" , expr , ")"
              | "USR"  "(" , expr , ")"
              | "CODE" "(" , (strVar | strLiteral) , ")"

7) Sentencias (detalle)

remStmt   = "REM"
          | "REM" , ESPACIO , { anyCharacter }
 
letStmt   = "LET" , ( varRef | var ) , "=" , expr
printStmt = "PRINT" , [ printList ]
printList = printItem , { ("," | ";") , printItem }
printItem = expr
          | "TAB" "(" , expr , ")"
          | "AT" expr "," expr

inputStmt = "INPUT" , varList
varList   = varRef , { "," , varRef }

ifStmt    = "IF" , expr , "THEN" , Statement

forStmt   = "FOR" , numVar , "=" , expr , "TO" , expr , [ "STEP" , expr ]
nextStmt  = "NEXT" , [ numVar ]

gotoStmt  = ("GOTO" | "GO TO") , lineNumber
gosubStmt = ("GOSUB" | "GO SUB") , lineNumber
returnStmt= "RETURN"

stopStmt  = "STOP"
continueStmt = "CONTINUE"

readStmt  = "READ" , varList
dataStmt  = "DATA" , constList
constList = constItem , { "," , constItem }
constItem = numLiteral | strLiteral

restoreStmt = "RESTORE" , [ lineNumber ]

dimStmt   = "DIM" , dimList
dimList   = dimItem , { "," , dimItem }
dimItem   = ( numVar | strVar ) , "(" , sizeExpr , { "," , sizeExpr } , ")"
sizeExpr  = expr

clsStmt   = "CLS"

graphicStmt = plotStmt | drawStmt | circleStmt | pointStmt
plotStmt  = "PLOT" , expr , "," , expr
drawStmt  = "DRAW" , expr , "," , expr
circleStmt= "CIRCLE" , expr , "," , expr , "," , expr
pointStmt = "POINT" , expr , "," , expr

colorStmt = ("INK" | "PAPER" | "FLASH" | "BRIGHT" | "INVERSE" | "OVER" | "BORDER") , expr

soundStmt = "BEEP" , expr , "," , expr

tapeStmt  = ("SAVE" | "LOAD" | "VERIFY" | "MERGE") , strLiteral

16. Referencias

miércoles, 11 de marzo de 2026

ZB2SB. Conversor de Basic de los ZX al Super Basic del QL (Reinicio)

Índice de entradas del conversor


📝 Diseño de un Transpilador ZX Spectrum BASIC → QL SuperBASIC

Reinicio del proyecto (esta vez espero acabarlo…)

Retomo este proyecto con la intención firme de completarlo. Siempre he querido disponer de una herramienta capaz de traducir programas escritos en lenguaje ZX Basic del Spectrum al lenguaje SuperBASIC del Sinclair QL. Aunque ya he empezado varias veces, esta vez quiero estructurarlo bien para poder llegar al final. La idea es seguir una serie de pasos claros, desde la definición del alcance hasta la ejecución de pruebas reales.

Aunque ambos dialectos BASIC comparten muchos conceptos, tienen diferencias importantes que obligan a realizar transformaciones no triviales. En esta entrada describo la arquitectura general de un transpilador, enfocada a este caso concreto.


1) Definir el alcance y las variantes

✔ Lenguaje de origen

ZX BASIC (Spectrum):

  • Líneas numeradas
  • GOTO, GOSUB, RETURN
  • LET, RANDOMIZE, PLOT, INK, PAPER
  • Tokens gráficos
  • PEEK/POKE, USR
  • Comentarios con REM o '

✔ Lenguaje destino

Sinclair QL SuperBASIC:

  • No requiere números de línea
  • Bloques IF…END IF, REPeat, SELect ON
  • Procedimientos (PROCedure) y funciones (DEFine FuNction)
  • I/O con canales (OPEN, PRINT #ch)
  • Gráficos con MODE, LINE, POINT

✔ Compatibilidad y variantes

  • Compatibilidad con ZX80/81 BASIC
  • Futuro soporte para Spectrum 128/+2/+3
  • Ajuste de coordenadas y resoluciones

✔ Cobertura funcional

Cobertura mínima:

  • Asignaciones y expresiones
  • Estructuras: IF, FOR, WHILE, GOTO, GOSUB
  • Arrays
  • PRINT / INPUT
  • Matemáticas básicas

Cobertura ampliada:

  • Gráficos y sonido
  • MERGE, LOAD, SAVE
  • ON ERROR, ON x GOSUB
  • Manejo de cadenas
  • Temporización / canales I/O

No soportado:

  • PEEK, POKE, USR

2) Reunir / normalizar las gramáticas

  • Gramática del ZX BASIC: keywords, literales, tokens gráficos, comentarios.
  • Gramática del SuperBASIC usada para validar la salida.

3) Arquitectura general del transpilador

  1. Lexer: obtener tokens.
  2. Parser + AST: nodos Program, If, For, Print, etc.
  3. Análisis semántico:
    • Tabla de símbolos
    • Detección de subrutinas
    • Control de flujo (CFG)
    • Tipos y arrays
  4. Transformación del AST:
    • Eliminar números de línea
    • Reescribir saltos
    • GOSUB → PROCEDURE
    • Normalización I/O
    • Conversión gráfica
  5. Generación de código SuperBASIC
  6. Post‑procesado

4) Reglas de mapeo

4.1 Estructura y control de flujo

  • IF…THEN…ELSEIF…END IF
  • WHILE/WENDREPeat + EXIT WHEN
  • GOTO → intentar estructurar
  • GOSUBPROCedure

4.2 Variables y arrays

  • BASIC Spectrum: base 1 → QL: base 0
  • Cadenas $
  • Ajuste de rangos en DIM

4.3 Entrada/Salida

  • PRINT / PRINT #ch
  • INPUT / INPUT #ch
  • Uso de ; y ,

4.4 Funciones

  • SIN, COS, ABS, RND
  • RANDOMIZE
  • VAL, STR$, LEFT$, RIGHT$

4.5 Gráficos y sonido

  • Spectrum: PLOT, DRAW, CIRCLE
  • QL: POINT, LINE, CIRCLE
  • Atributos: sin equivalente

4.6 Memoria

  • PEEK, POKE, USR → no portables

4.7 Errores

  • ON ERROR → comportamiento diferente

5) Estrategia para convertir GOTO/GOSUB

  • Construir un CFG
  • Buscar patrones:
    • If‑then → IF…END IF
    • Saltos hacia atrás → loops
    • Subrutinas → PROCedure
  • Modo compatibilidad si no es seguro

6) Conjunto mínimo de casos de prueba

  • Asignaciones
    10 LET a=1: LET b=a+2: PRINT a+b
  • IF simple
    10 IF a>10 THEN PRINT "OK"
  • IF con ELSE
    10 IF a>10 THEN
    20 PRINT "OK"
    30 ELSE PRINT "NO"
    40 END IF
  • FOR/NEXT
    10 FOR i=1 TO 10: PRINT i: NEXT i
  • WHILE/WEND
    10 LET i=0
    20 WHILE i
  • GOSUB/RETURN
    10 LET x=5: GOSUB 90: PRINT x: STOP
    90 LET x=x*2: RETURN
  • Arrays y cadenas
    10 DIM a(10): FOR i=1 TO 10: LET a(i)=i*i: NEXT i: PRINT a(5)
    20 LET s$="HELLO": PRINT LEN s$, RIGHT$(s$,2)
  • Gráficos
    10 PLOT 10,10: DRAW 20,0: CIRCLE 30,30,10

7) Ejemplo de traducción básica

Entrada ZX Spectrum BASIC:

10 REM Calculo de suma
20 LET s=0
30 FOR i=1 TO 10
40 LET s=s+i
50 NEXT i
60 IF s>50 THEN PRINT "MAYOR"; ELSE PRINT "MENOR O IGUAL";
65 PRINT " DE 50"
70 GOSUB 90
80 PRINT "FIN": STOP
90 PRINT "TOTAL=";s: RETURN

Salida QL SuperBASIC:

REM Calculo de suma
s = 0
FOR i = 1 TO 10
  s = s + i
END FOR
IF s > 50 THEN
  PRINT "MAYOR";
ELSE
  PRINT "MENOR O IGUAL";
END IF
PRINT " DE 50"
PROC_show_total(s)
PRINT "FIN"
STOP

PROCedure PROC_show_total(total)
  PRINT "TOTAL="; total
END PROCedure

8) Ejemplo de posible mapeo de gráficos

Spectrum:

10 INK 2: PAPER 0: CLS
20 PLOT 10,10: DRAW 50,0
30 CIRCLE 40,40,10

QL:

INK 2 : PAPER 0 : CLS
POINT 10,10
LINE 10,10 TO 60,10
CIRCLE 40,40,10

9) Ejemplo de traducción (no trivial)

10 REM Serie, demo graficos y subrutina
20 LET s=0: LET x=64: LET y=64
30 FOR i=1 TO 5
40 LET s=s+i
50 NEXT i
60 IF s>10 THEN PRINT "MAYOR";: PRINT " "; s ELSE PRINT "NO";: PRINT " "; s
70 PLOT 10,10: DRAW 20,0: DRAW 0,20: DRAW -20,0: DRAW 0,-20
80 GOSUB 200
90 DATA 3,5,8
100 READ a,b,c
110 PRINT "DATA:"; a; ","; b; ","; c
120 STOP
200 REM duplica s
210 LET s=s*2
220 RETURN

Salida QL:

100 REMark Calculo de suma
110 REMark Serie, demo graficos y subrutina
120 CLS
130 s=0
140 x=64
150 y = 64
160 FOR i = 1 TO 5
170 s = s + i
180 END FOR i
190 IF s > 10 THEN
200   PRINT "MAYOR";
210   PRINT " "; s
220 ELSE
230   PRINT "NO";
240   PRINT " "; s
250 END IF

270 REMark Gráficos: PLOT y DRAW relativo
280 INK 7 : PAPER 0
290 __gx = 10 : __gy = 10
300 POINT __gx, __gy
310 LINE __gx, __gy TO __gx + 20, __gy
320 LINE __gx, __gy TO __gx, __gy + 20
330 LINE __gx, __gy TO __gx - 20, __gy
340 LINE __gx, __gy TO __gx, __gy - 20

350 PROC_duplica_s(s)

370 _DATA_INIT
390 FOR i=1 TO 3
400   PRINT _READ
410 END FOR i

450 DEFine PROCedure PROC_duplica_s(by_s)
460   s = s * 2
470 END DEFine

490 DEFine PROCedure _DATA_INIT
500   Data_Init = 0
510 END DEFine

530 DEFine FuNction _READ
540   IF Data_Init <> 1 THEN _DATA
560   __DATA(0) = __DATA(0) + 1
570   RETurn __DATA(__DATA(0))
580 END DEFine _READ

600 DEFine PROCedure _DATA
620   Data_Init = 1
630   DIM __DATA(3)
640   __DATA(0) = 0
650   __DATA(1) = 3
660   __DATA(2) = 5
670   __DATA(3) = 8
680 END DEFine