📘 Tabla de Contenidos
- Introducción
- 1. Motivación del Director del Proceso
- 2. Pipeline completo del transpilador
- 3. Las técnicas de lexing/parsing
- 4. Flujo general del proceso (optimizado QL)
- 5. Esqueleto del Director (seudocódigo)
- 6. Integración con las siguientes fases
- 7. Próximo paso
- APÉNDICE: Director del proceso completo
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
EOLpor 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












